"use client";
/**
* 归档结果的**按类型分发**视图:归档页只依赖这一个入口,不在页面里猜结果结构。
*
* 为什么必须存在(真实缺陷驱动):归档详情页原先在 `result` 非空时**无条件**渲染
* `BacktestResultView`,而 `factor_test` / `selection` 归档的结果结构与回测完全不同
* (没有 `summary` / `equity_curve` / `positions`)。实测后果:库里 4 条因子测试归档
* 与 1 条选股归档,从列表点「打开归档」**整页 500**(SSR 阶段 `result.positions.at(-1)`
* 抛 TypeError,且没有 error.tsx 兜底)——列表对每一行都给链接,所以这是必现路径。
*
* 现在按后端 `kind` 显式分发:
* - `backtest` → `BacktestResultView`(净值/回撤/个股曲线/持仓/成交/月度年度/未建模)
* - `factor_test` → IC / RankIC / ICIR / 正收益占比 / 样本日数 + **分层收益柱状图**
* - `selection` → 选股统计 + 候选明细表(代码**带名称**且可点击进个股页)
* - 其它未知类型 → 如实说明「暂不支持预览」并引导导出 JSON,**不假装能渲染**
*
* 纯派生逻辑(字段计数)不在这里,而在 `lib/archive.ts` —— Server Component 不能调用
* `"use client"` 模块导出的普通函数,放错位置会让归档页整页 500(已实测踩过)。
*
* 与 `/factors`、`/selection` 页面各自的渲染器并存是刻意的:那两个页面带交互
* (运行、直通回测、导出),归档页是**只读快照**,两类用途的文案与动作不同。
*/
import type { BacktestResult, FactorTestReport, SelectionResult } from "@/lib/types";
import type { AnyArchiveResult } from "@/lib/archive";
import { BacktestResultView } from "@/components/BacktestResultView";
import { RichText } from "@/components/RichText";
import { Card, Empty, Metric, UnimplementedNote } from "@/components/ui";
import { SymbolLink } from "@/lib/symbols";
export interface ArchiveInfo {
id: string;
created_at?: string | null;
code_version?: string | null;
data_version?: string | null;
job_id?: string | null;
}
export function ArchiveResultView({
kind,
result,
archive,
}: {
kind: string;
result: AnyArchiveResult;
archive: ArchiveInfo;
}) {
if (kind === "backtest") {
return
| # | 代码 / 名称 | 得分 | 因子值 | 入选理由 |
|---|---|---|---|---|
| {row.rank} |
|
{row.score.toFixed(4)} | {Object.entries(row.factor_values ?? {}) .map(([k, v]) => `${k}=${typeof v === "number" ? v.toFixed(4) : v}` ) .join(" ")} |
{(row.selection_reason ?? []).map((r) => (
{r.slice(0, 40)}
))}
|