edit | blame | history | raw

股票估值端到端协调层 V2 编码方案 V004

创建人员:dev.developer.project.secondary / infodev-2

开发事项:DEV-ANA-STOCK-VALUATION-PIPELINE-V2-20260801-001

当前审核员:dev.reviewer.project / inforev

方案性质:V003 的限定规范修订,只闭环 F5-R1F7-R1F8-R1

当前状态:PENDING_FOCUSED_DESIGN_REREVIEW;PASS 前仍禁止创建 V2 代码、测试、fixture,禁止修改 ana-dev,禁止真实联网 smoke。

1. 基线、继承与优先级

V004 的完整规范由以下两部分共同构成:

  1. 基线方案 dev-doc/project-doc/开发方案/CODE-DESIGN-PROJECT-INFO-STOCK-VALUATION-PIPELINE-V2-V003.md=28030 bytes / 358C37EE1FE7F5F832FAF33EBDEB1B1714DE46F315A55A1C1D53AF43D687C61F
  2. 本文件的三项替换条款。

本文件明确替换 V003 第 9、13、15、18、20 节中对应的 freshness、终态文件/stdout/receipt、task-start、长城 fixture 路径条款;其余 V003 合同逐字继承且继续有效。冲突时以本文件为准。F1—F4、F6 已在 DEV-AUDIT-PROJECT-INFO-STOCK-VALUATION-PIPELINE-V2-DESIGN-REREVIEW-20260801-001 通过,不在本版重开。

2. F5-R1:逐 data_kind freshness、状态与确定选择

2.1 共通判定输入

每个可复用字段组必须保存并参与判定:data_kind,ticker,requested_as_of,baseline_as_of,data_date,publish_date,fetched_at,expires_at,adapter_version,raw_hash,schema_status,as_of_status,semantic_status,watermark。时间均为带时区 ISO-8601;TTL 从原始请求成功完成的 fetched_at 起算,expires_at=fetched_at+TTL,判断时刻固定为本次 process_start,不随单个 provider 完成时间漂移。

共通 fresh predicate:

baseline_as_of <= requested_as_of
and publish_date <= requested_as_of(字段需要发布日时)
and data_date <= requested_as_of
and process_start <= expires_at
and adapter_version/hash/schema/as_of/semantic 全部通过
and data_kind 专属水位条件通过

任一值未知不视为 fresh。状态只允许 FRESH|STALE|MISSING:命中且全部 predicate 通过为 FRESH;候选存在但 TTL、水位、hash、版本、schema、as-of 任一失败为 STALE;无候选为 MISSING。

2.2 确定 freshness 表

data_kind TTL 与专属水位 FRESH STALE/MISSING 后动作 失败终态/停止
stock_identity 7×24h;证券代码、市场、orgId 必须完整 直接复用,不请求 在预算内请求 CNInfo stock list 取不到为核心 BLOCKED;不自由换站
announcement_index requested_as_of 距 process 本地日期<=7天时 6h,否则 30×24h;watermark=max(announcementTime<=as_of) 复用索引并继续 A1 选取 请求固定 CNInfo page,直到覆盖所需报告期或登记页数上限 核心字段无 A1 为 BLOCKED;非核心为 gap;达到登记上限即停
market_close 30×24h;data_date 必须等于交易日序列中不晚于 as-of 的最近一日,价格>0 复用,不请求 请求固定 kline 无合法收盘价为 BLOCKED;不使用陈旧值
shares_market_cap 15min;仅当 requested_as_of 等于 fetched_at 的本地日期,且股本>0、市值>0、price×shares 勾稽通过 复用,不请求 请求固定 quote;历史 as-of 只允许复用“baseline_as_of=requested_as_of 且当日采集”的候选 取不到为 BLOCKED;不得把当前股本回填旧 as-of
finance_main|income|balance|cashflow requested_as_of 距 process 本地日期<=7天时 6h,否则 30×24h;watermark=(NOTICE_DATE,REPORT_DATE);所需三期/表项齐全并有 A1 ID 复用满足的表/期间 只请求缺项或陈旧 reportName;成功项可独立提交 核心三期利润/资产负债缺失或无 A1 为 BLOCKED;非核心现金流项为 gap;不使用 STALE 核心值
forecast_summary 6h;watermark=max(REPORT_DATE|UPDATE_DATE<=as_of);机构数与年度字段完整 复用汇总 请求固定 summary 失败为一个预测 gap,然后允许请求 detail;不阻断核心
forecast_detail 6h;watermark=max(detail_report_date<=as_of);每条机构、日期、年度、数值完整 复用明细 请求固定 detail,这是第二且最后来源 失败或 4/3 差异并入唯一 W_FORECAST_COVERAGE,立即停止

STALE 原始 blob 保留为诊断,但绝不作为正式字段值;刷新失败不会把 STALE 改名为 FRESH。局部失败时只有表中明确非核心项可以 gap 降级。负 HTTP/schema/semantic 结果仍只按 V003 的 60 秒本次运行去重,不进入本表的 reusable baseline。

2.3 同 as-of 多候选确定顺序

候选先按 baseline_as_of 取不晚于 requested_as_of 的最大值;同一 baseline_as_of 再按规范化 watermark tuple 取最大值;watermark 完全相同时,按 64 位小写十六进制 data_hash 的 ASCII 字典序取最大值(max)。current.json 使用同一三元组比较;较旧/较小候选不得覆盖较新/较大指针。测试固定覆盖每个 data_kind 的 FRESH、STALE、MISSING,以及同 as-of 两个相反 data_hash 插入顺序仍选择同一最大 hash。

3. F7-R1:完整终态、精确文件与 stdout/receipt

3.1 精确正式路径

成功数据包只允许以下相对文件名:

文件 COMPLETE COMPLETE_WITH_GAPS DATA_READY_NEEDS_JUDGMENT
data_snapshot.json 必有 必有 必有
snapshot_build_report.json 必有 必有 必有
valuation_snapshot.json 必有 必有 禁止
valuation_results.json 必有 必有 禁止
report.md 必有 必有 必有,固定 16 节且判断章节为 GAP
qa_report.json 必有 必有 必有
gaps.json 必有、空数组 必有、非空 必有,至少 judgment gap
source_evidence.json 必有 必有 必有
provider_results.json 必有 必有 必有
runtime_metrics.json 必有 必有 必有
runtime.log 必有 必有 必有
manifest.json 最后写、complete=true 最后写、complete=true 最后写、complete=true、status 同名

外部提交回执固定在正式目录同级:<OUT.name>.commit-receipt.json,不位于 OUT 内,不进入 manifest。成功 stdout 中 manifest_path 指向 OUT/manifest.jsonreceipt_path 指向该同级回执。

BLOCKED/FAILED 不创建 OUT 和 receipt;失败包固定为同级 OUT.failed-<run_id>/,必有 failure.json,runtime_metrics.json,runtime.log,可有 provider_results.json,source_evidence.json,禁止 manifest.json,report.md,valuation_snapshot.json,valuation_results.json,qa_report.json。ticker 输入合同错误 exit 2 时连失败包也不创建。

3.2 stdout 完整 schema

ticker 模式每个终态都在 stdout 输出且只输出一行规范 JSON。下列字段全部出现,缺项一律 JSON null,不得省略、空串或另造字段:

{
  "schema_version": 1,
  "status": "COMPLETE|COMPLETE_WITH_GAPS|DATA_READY_NEEDS_JUDGMENT|BLOCKED|FAILED|INPUT_ERROR",
  "run_id": "string|null",
  "exit_code": 0,
  "output_dir": "absolute-string|null",
  "failed_dir": null,
  "manifest_path": "absolute-string|null",
  "manifest_sha256": "hex|null",
  "receipt_path": "absolute-string|null",
  "receipt_sha256": "hex|null",
  "receipt_bytes": 0,
  "task_start": "ISO-8601|null",
  "task_start_source": "arg|process_start|null",
  "process_start": "ISO-8601|null",
  "terminal_at": "ISO-8601|null",
  "task_wall_seconds": 0.0,
  "process_wall_seconds": 0.0,
  "gap_count": 0,
  "error_code": null,
  "error": null
}

字段值矩阵:

  • 三个成功状态:exit_code=0;run/output/manifest/receipt 及两种 hash/receipt_bytes/四个时间/双 wall/gap_count 必为非 null;failed_dir/error_code/error=null。
  • BLOCKED:exit_code=4;run_id、requested output_dir、failed_dir、task/process/terminal、双 wall、gap_count、error_code/error 必有;manifest/receipt 五字段全部 null,其中 receipt_bytes=null
  • FAILED:exit_code=5;规则同 BLOCKED。
  • ticker INPUT_ERROR:exit_code=2;status/error_code/error、process_start/terminal/process_wall 必有;run_id、output_dir、failed_dir、manifest/receipt、task_start/task_wall、gap_count 均 null。若参数已经成功解析出带时区 task_start,则 task_start/task_start_source/task_wall 允许且必须同时非 null;不得出现部分组合。

V1 --input 兼容模式不使用该 schema,继续严格遵循 V003 第 5 节的 V1 stdout/stderr 0/2/3。

3.3 receipt 的精确字段与 hash 关系

OUT.commit-receipt.json 固定包含:schema_version,run_id,status,output_path,manifest_path,manifest_sha256,manifest_bytes,commit_ready_at,commit_finished_at,task_start,task_start_source,process_start,task_wall_seconds,process_wall_seconds,wall_scope="through_commit_finished"。文件写完后计算 receipt_sha256/receipt_bytes,只写 stdout,不自引用回执本体。stdout 的 manifest/receipt 路径、hash、bytes 和双 wall 必须逐值匹配正式文件与 receipt;测试复算两个 hash。

4. F7-R1:可追溯 task-start 与真实 smoke

本事项的用户实现请求来自 Codex 任务 019fb338-fe89-7d52-9ba6-513e82de54d8 的 turn 019fbd7c-00c5-7962-8a80-3206ca5a58d9(用户消息“按照这个思路去做吧”)。Codex read_thread 返回 startedAt=1785590448 Unix 秒,规范化为 2026-08-01T21:20:48+08:00。实现证据必须保存 thread/turn/startedAt/ISO 映射;真实 smoke 两次均传同一实际 task-start:

python -m stock_valuation_pipeline_v2 --ticker 001270.SZ --as-of 2026-08-01 `
  --cache-dir dev/tmp/valuation-v2-smoke/cache --output-dir dev/tmp/valuation-v2-smoke/cold `
  --task-start 2026-08-01T21:20:48+08:00
python -m stock_valuation_pipeline_v2 --ticker 001270.SZ --as-of 2026-08-01 `
  --cache-dir dev/tmp/valuation-v2-smoke/cache --output-dir dev/tmp/valuation-v2-smoke/warm `
  --task-start 2026-08-01T21:20:48+08:00

断言:task_start<=process_start<=terminal_attask_wall_seconds>=process_wall_seconds>=0;receipt 双 wall 是到 commit_finished 的权威值,stdout 与之完全相等;包内 runtime 双 wall 是到 commit_ready,均不大于 receipt;冷/热运行使用同一 task_start,热运行 task wall 可更大,不能用 process wall 替代总任务墙钟。其他 smoke 换股条件和证据清单继续继承 V003。

5. F8-R1:长城 fixture 真实路径

V003 中不存在的 greatwall_example.json 全部作废,不得创建假同名替代文件。唯一冻结路径为:

dev/ana-dev/test/stock_valuation_pipeline/fixtures/great_wall_military_20260731.json=7142 bytes / 06AB6A7C45C2E43F71654C616F15A826B21E468C4BA72AFB0B253C9DD89FACD8

直接 V1 与 V2 bridge 的无 force、复跑、force、输入错误比较全部使用这个实际路径;命令、stdout/stderr、V1 三件套 bytes/hash 和 V2 bridge 对比证据也登记该路径。V1 测试入口和 10/10 基线继续继承 V003,不修改 ana-dev

6. 限定测试增量

V004 新增且仅新增以下方案级验收:

  1. 每个 data_kind 的 FRESH/STALE/MISSING 表驱动测试;STALE 核心值绝不进入结果;forecast 两源后停止;
  2. 同 as-of 两个候选以相反写入顺序测试,均选择 watermark 最大、继而 data_hash ASCII 最大者;
  3. 三个成功、BLOCKED、FAILED、INPUT_ERROR 六类 stdout 做 exact-key/nullability 测试;
  4. 逐项核对精确正式/失败文件集合、manifest-last、外部 receipt、manifest/receipt hash 与 stdout;
  5. 正常/data-ready/blocked/failed/force rollback 的 task/process 墙钟不等式;
  6. 长城实际 fixture 路径存在、bytes/hash 一致,直接 V1 与 bridge 命令可执行。

7. 审核门禁

V004 只提交现有 dev.reviewer.project 对 F5-R1、F7-R1、F8-R1 做限定复审。只有明确 PASS 后,才允许创建 V2 代码、测试和 fixture;本文件不回退 V003 已通过合同,也不授权修改 ana-dev 或提前联网。