feat(web): 因子研究/因子组合/股票筛选接入统一作业反馈;阶段圆点按作业类型区分

接着 /backtest 的那次改造,把其余会跑异步 Job 的页面也切到同一套
`useJobRunner` + `JobProgress`(用户原话:包括因子测试等所有测试都帮我完善用户反馈):

- **/factors**(一个因子一个 Job 的批量场景):删掉 `running/processed/current/jobId` 与
  按「已处理数 ÷ 总数」自算的 `Progress` **假百分比**;每轮把 `第 i/共 n 个` 交给反馈条,
  阶段/作业号/已用秒数全部来自后端。取消 = 用户明确意图 → 保留已出的报告、不再跑后续因子,
  不报错;单个因子失败仍继续跑其余因子,并把后端原文累积成**批级清单**(多因子时反馈条
  只能显示最后一个作业,前几个失败不能丢)。
- **/factors/compose**:删掉 `value={30}` 的假进度条;`archiveId` / 复用 `BacktestResultView` /
  「新页面放大」全部保留;failed/cancelled 交给反馈条,页面 error 只留参数与目录错误
  (同一失败不在两处各说一遍)。
- **/selection**:异步选股切反馈条;**同步**的「执行选股」保留原 loading(`POST /selections`
  没有 job_id,套上会去 `GET /jobs/{signal}` 撞 404)。
- **/signals**:`POST /api/signals` 是同步接口,**不套**作业反馈(不编作业号、不编阶段),
  改为点击即现的 `role="status"` 提示,如实写明「同步请求、请求期间不能关页、无阶段无取消、
  出错显示后端原文」。
- **阶段圆点按作业类型区分**(修掉一个真实缺陷):原来全站共用一张含 `queued/done` 的
  `STAGE_ORDER`,因子测试页实测出现过**裸英文** `factor_calculation` 且 4 个圆点全灰
  (`indexOf` = -1),还画出了因子测试根本不存在的「逐择股日选股 / 撮合与净值结算」。
  现在 `STAGE_PIPELINES = { factor_test: [加载→计算因子值→汇总], backtest: [加载→撮合→汇总],
  selection: [逐择股日选股] }`(阶段序列**不含 queued/done**,那是作业状态不是阶段),
  未知阶段显示「执行中(stage)」并保留已推进的圆点,不整排灰、不露裸枚举。

验证(真实浏览器 CDP,读数原文已记录):
- /factors:点击后 0.2s 内 `submitting`→`queued` + 作业号;+30s Pill「计算因子值」、
  圆点 `✓ 加载行情与因子数据 / ● 计算因子值 / ○ 汇总指标与曲线`(无「选股/撮合」、无英文枚举);
  成功态给「去对比 / 打开归档」。
- /selection:圆点只有 `逐择股日选股` 一段;取消 → 「已取消,没有归档」;
  另实测撞并发上限时如实显示后端原文「系统繁忙:并发研究任务已达上限」。
- /factors/compose:`submitting→queued→撮合与净值结算→success(EXP-…)`,业务结果与 5 处放大入口照旧。
- /backtest 回归:圆点由 4 个变 3 个(去掉后端**从不上报**的 selection 阶段),取消仍「已取消 + 没归档」。
- 自检产生的 12 个实验归档已全部删除(bulk-delete count:12,复查无残留)。
This commit is contained in:
Simon
2026-10-01 18:37:25 +08:00
parent c974415691
commit e58367af27
8 changed files with 228 additions and 134 deletions
+39 -3
View File
@@ -67,18 +67,54 @@ export async function waitJob<T>(
return { status: "timeout", result: null, error: "等待结果超时,请稍后在「实验」页查看归档" };
}
/** 阶段中文名(后端 stage 枚举 → 用户可读) */
/** 阶段中文名(后端 stage 枚举 → 用户可读)。
*
* 必须覆盖每种作业**上报过的全部阶段**:漏一个,JobProgress 只能把裸枚举摆给用户
* —— 实测漏过 `factor_calculation`(/factors 反馈条上直接出现过英文 `factor_calculation`)。
* 新增阶段时请同时补这里的中文名与下方 `STAGE_PIPELINES` 里对应的序列。
*/
export const STAGE_LABEL: Record<string, string> = {
queued: "排队中",
data_loading: "加载行情与因子数据",
factor_calculation: "计算因子值",
selection: "逐择股日选股",
backtesting: "撮合与净值结算",
analysis: "汇总指标与曲线",
done: "完成",
};
/** 执行阶段时间线(顺序即推进顺序,用于「阶段小圆点」) */
export const STAGE_ORDER = ["queued", "data_loading", "selection", "backtesting", "analysis", "done"];
/** 作业类型 —— **只列后端真的会异步执行的 kind**:
* - `signals` 不在其中:`POST /api/signals` 是同步接口(没有 /signals/jobs),没有作业阶段可言;
* - `combo`(回测组合)也不单列:它上报的阶段与 backtest 完全一致(证据见下),直接映射到 "backtest"。
*/
export type JobKind = "factor_test" | "backtest" | "selection";
/**
* 每种作业**真实会走**的阶段序列 —— 只放后端真的会上报的阶段,**不含 `queued` / `done`**:
* 「排队中」「已完成」是作业**状态**(由 `phase` 表达,见 JobRunState),不是后端上报的执行阶段,
* 混进圆点序列会让「尚未开始的第一个圆点」和「已结束」看起来像同一条进度。
*
* 为什么必须按作业类型分开:全站共用一条「阶段小圆点」之后,用一张全局表会把因子测试
* 根本不存在的阶段(逐择股日选股 / 撮合与净值结算)也画出来 —— 那正是要避免的
* 「看似有数据的假象」;反过来,后端新阶段如果不在表里,圆点会整排变灰。
*
* 证据(后端上报点,改动前请重新 grep 核对,勿凭印象):
* - factor_test:backend/app/quant/service.py:304 data_loading → :306 factor_calculation → :308 analysis
* - backtest :backend/app/quant/service.py:314 data_loading → :316 backtesting → :319 analysis
* - selection :backend/app/application/services/job_executor.py:156 selection(真的只有这一段)
* - combo(复用 backtest 表):backend/app/application/services/combo_service.py:68 data_loading
* → :70 backtesting → :90 analysis;组合的「逐择股日选股」是在 run_combo_backtest 内部完成的,
* 后端从未单独上报 selection 阶段,所以回测这条线不放 selection 圆点。
*/
export const STAGE_PIPELINES: Record<JobKind, string[]> = {
factor_test: ["data_loading", "factor_calculation", "analysis"],
backtest: ["data_loading", "backtesting", "analysis"],
selection: ["selection"],
};
/** @deprecated 已改为按作业类型取 `STAGE_PIPELINES[kind]`。保留导出仅为兼容旧引用,
* 其值等于回测的圆点序列(不再含 queued/done —— 它们是 phase 不是阶段)。 */
export const STAGE_ORDER = STAGE_PIPELINES.backtest;
/** 取消排队中/执行中的作业(POST /api/jobs/{id}/cancel;已结束的会返回 cancelled=false)。 */
export function cancelJob(