|
|
@@ -0,0 +1,105 @@
|
|
|
+# M0A 金标准 Corpus 与回放基线 Implementation Brief
|
|
|
+
|
|
|
+> 本 brief 只覆盖:固定两条 V4 金标准 corpus、用 `replay_harness` 离线复现、产出 scorecard 指纹基线快照。回归 test 的挂载与验收口径在 M0B。
|
|
|
+
|
|
|
+## 目标
|
|
|
+
|
|
|
+- 把两条 corpus(抖音三维 / 快手两维)钉成 V5 全程不可变的零变化基准。
|
|
|
+- 复用 `replay_harness.replay_case()` 离线复现(不调真实爬虫/LLM),产出可逐条比对的 scorecard 指纹。
|
|
|
+- 指纹只取确定性打分字段、去掉 run_id/时间戳等易变项,保证可重放稳定。
|
|
|
+
|
|
|
+## 现有证据(file:line)
|
|
|
+
|
|
|
+- corpus 两条已进 git:
|
|
|
+ - `tests/fixtures/cases/v4_douyin_57663/input/`(13 文件,`rule_decisions.jsonl` 110 条决策四分支全:入池20/复看37/技术18/淘汰35;scorecard 三维含 50+)。
|
|
|
+ - `tests/fixtures/cases/v4_kuaishou_57758/input/`(13 文件,`rule_decisions.jsonl` 51 条决策四分支全:入池14/复看13/淘汰20/技术4;scorecard 两维无 50+)。
|
|
|
+- 回放框架 `tests/replay_harness.py`:
|
|
|
+ - `replay_case(case_id, *, runtime_root, cases_dir=CASES_DIR, config_overrides=None, gemini_video_client=None, run_id=None) -> RunArtifacts`(`replay_harness.py:63`)——用 corpus 离线复现一次 run。
|
|
|
+ - `load_corpus(case_id, cases_dir=CASES_DIR)`(`replay_harness.py:44`)——读 corpus input 的 13 个文件。
|
|
|
+ - `RunArtifacts`(`replay_harness.py:30`),含 `.summary`(`:36`,从 `final_output.json` 取 summary)、`.decisions`(`:40`,取 `rule_decisions.jsonl`)。
|
|
|
+ - 回灌不调真实接口:`CorpusPlatformClient`(`tests/replay_clients.py:31`)、`FakeQueryVariantClient`(`replay_harness.py:22`)、`FakeGeminiVideoClient`(`replay_harness.py:21`)。
|
|
|
+- 快照工具 `tests/snapshot.py`:`assert_matches(name, actual, *, subset_keys=None)`(`snapshot.py:55`)——快照缺失时 `UPDATE_SNAPSHOTS=1`(`snapshot.py:24`)生成,偏差报错。
|
|
|
+- 现成指纹模式 `tests/test_walk_profile_degradation.py`:`_fingerprint(walk_actions)`(`:19`,`sorted([...确定性字段...])` 忽略 wa_id/run_id);`test_replay_id45_walk_actions_fingerprint_is_stable`(`:27`,replay→指纹==快照)。基线放 `tests/fixtures/snapshots/real_id45/walk_actions_fingerprint.json`。
|
|
|
+- scorecard 真实结构(`rule_decisions.jsonl` 每条):`scorecard.{schema_version, total_score, query_relevance_score, platform_performance_score, fifty_plus_score, fifty_plus_status, score_thresholds, score_missing, final_status}`;decision 顶层 `{decision_target_id, decision_action, decision_reason_code, score, search_query_effect_status}`。
|
|
|
+
|
|
|
+## 修改范围(只新增,不改现有)
|
|
|
+
|
|
|
+- 新增 `tests/fixtures/snapshots/v4_douyin_57663/scorecard_fingerprint.json` 与 `tests/fixtures/snapshots/v4_kuaishou_57758/scorecard_fingerprint.json`(由 `UPDATE_SNAPSHOTS=1` 首跑生成)。
|
|
|
+- 新增纯函数 `_scorecard_fingerprint(decisions)`(放新 test 模块 `tests/test_v5_golden_corpus_baseline.py`,不污染业务)。
|
|
|
+
|
|
|
+## 不修改范围(红线)
|
|
|
+
|
|
|
+- 不改 `tests/replay_harness.py` / `tests/replay_clients.py` / `tests/snapshot.py`(零改、纯复用)。
|
|
|
+- 不改任何 `content_agent/` 业务代码。
|
|
|
+- 不改两条 corpus 的 input 文件(不可变基准)。
|
|
|
+- 不新增 DB 表 / 不动 schema。
|
|
|
+
|
|
|
+## 涉及文件 / 函数 / 类
|
|
|
+
|
|
|
+- `tests/replay_harness.py`:`replay_case()`(`:63`)、`load_corpus()`(`:44`)、`RunArtifacts`(`:30`)——只调用。
|
|
|
+- `tests/snapshot.py`:`assert_matches()`(`:55`)——只调用。
|
|
|
+- 新增 `tests/test_v5_golden_corpus_baseline.py`:`_scorecard_fingerprint()` + 两个基线 test。
|
|
|
+- 新增快照:`tests/fixtures/snapshots/v4_{douyin_57663,kuaishou_57758}/scorecard_fingerprint.json`。
|
|
|
+
|
|
|
+## 数据合同(指纹结构)
|
|
|
+
|
|
|
+`_scorecard_fingerprint(decisions)` 返回 `sorted([...])`,每条 = 元组(按 `decision_target_id` 稳定排序):
|
|
|
+
|
|
|
+```
|
|
|
+(decision_target_id,
|
|
|
+ decision_action,
|
|
|
+ decision_reason_code,
|
|
|
+ round(score, 2) | None,
|
|
|
+ round(scorecard.query_relevance_score, 2) | None,
|
|
|
+ round(scorecard.platform_performance_score, 2) | None,
|
|
|
+ round(scorecard.fifty_plus_score, 2) | None,
|
|
|
+ scorecard.fifty_plus_status,
|
|
|
+ scorecard.final_status)
|
|
|
+```
|
|
|
+
|
|
|
+- **取确定性打分字段,去掉**:run_id、policy_run_id、时间戳、wa_id、play_url 等易变/已脱敏项。
|
|
|
+- 抖音 case 指纹含 `fifty_plus_*`(三维);快手 case 指纹 `fifty_plus_score=None`/`fifty_plus_status=None`(两维)——两条覆盖两条打分路径。
|
|
|
+
|
|
|
+## 实施步骤(顺序不可颠倒)
|
|
|
+
|
|
|
+1. 写 `_scorecard_fingerprint()`(纯函数,输入 `RunArtifacts.decisions`)。
|
|
|
+2. 写两个基线 test(`replay_case` 两 case → 取 `.decisions` → `assert_matches` 指纹 == 快照)。
|
|
|
+3. `UPDATE_SNAPSHOTS=1 uv run pytest tests/test_v5_golden_corpus_baseline.py` 首跑生成两个 `scorecard_fingerprint.json`。
|
|
|
+4. 人工核对:抖音 110 条、快手 51 条、四种 `decision_action` 都在、分数与国内库一致。
|
|
|
+5. 去掉 `UPDATE_SNAPSHOTS` 再跑一次,确认稳定通过(可重放)。
|
|
|
+
|
|
|
+## 验证命令
|
|
|
+
|
|
|
+```bash
|
|
|
+# 首次生成基线
|
|
|
+UPDATE_SNAPSHOTS=1 uv run pytest tests/test_v5_golden_corpus_baseline.py -q
|
|
|
+# 复跑确认稳定(无 UPDATE,必须绿)
|
|
|
+uv run pytest tests/test_v5_golden_corpus_baseline.py -q
|
|
|
+# 核对指纹条数(期望 110 51)
|
|
|
+python3 -c "import json;print(len(json.load(open('tests/fixtures/snapshots/v4_douyin_57663/scorecard_fingerprint.json'))),len(json.load(open('tests/fixtures/snapshots/v4_kuaishou_57758/scorecard_fingerprint.json'))))"
|
|
|
+```
|
|
|
+
|
|
|
+## Unit Test
|
|
|
+
|
|
|
+- `test_scorecard_fingerprint_is_sorted_and_deterministic` — 同一 decisions 多次调用结果一致;按 `decision_target_id` 排序。
|
|
|
+- `test_scorecard_fingerprint_drops_volatile_fields` — 指纹不含 run_id/时间戳/play_url。
|
|
|
+- `test_scorecard_fingerprint_douyin_has_fifty_plus` — 抖音指纹条目 `fifty_plus_score` 非 None。
|
|
|
+- `test_scorecard_fingerprint_kuaishou_two_dim` — 快手指纹条目 `fifty_plus_score` 为 None(两维)。
|
|
|
+
|
|
|
+## Integrated Test
|
|
|
+
|
|
|
+- `test_v5_douyin_57663_scorecard_baseline` / `test_v5_kuaishou_57758_scorecard_baseline` — `replay_case` 两 case,指纹逐条 == 基线快照(本 brief 建基线,回归门挂载在 M0B)。
|
|
|
+- 暂不要求端到端真实跑(M0 只离线回放;真实跑在 14 号 M3 验收)。
|
|
|
+
|
|
|
+## 失败归因
|
|
|
+
|
|
|
+- 指纹每次不同 → 没去干净易变字段(run_id/时间戳)或排序键不稳。
|
|
|
+- replay 报错 → corpus 文件缺失/损坏,或 `replay_case` 参数错(对照 `tests/test_case_replay.py:81`)。
|
|
|
+- 指纹条数 ≠ 110/51 → corpus 被改过,或 replay 丢了决策。
|
|
|
+- 抖音指纹无 fifty_plus → 取错字段或 corpus 不是三维 case。
|
|
|
+
|
|
|
+## sub-agent 交叉验证要点
|
|
|
+
|
|
|
+- 代码 sub-agent:确认 `_scorecard_fingerprint` 只读 `RunArtifacts`、不碰业务代码;`git diff tests/replay_harness.py tests/snapshot.py` 为空。
|
|
|
+- 数据 sub-agent:抽查指纹里几条 `total_score`/`query_relevance_score`/`platform_performance_score` 与国内库 `content_agent_rule_decisions`(对应 run_id `v1_run_57663_douyin_1782560050` / `v1_run_57758_kuaishou_1782622792`)逐字段一致。
|
|
|
+- JSON sub-agent:两个 `scorecard_fingerprint.json` 无明文 sec_uid/play_url(grep `MS4w`/`http` 应 0);条数 110/51。
|