会议要求把「推断真实出生时间」与「用户认可的参考盘」分开证明。逐条核对 后:产品口径侧四条已落地(accepted≠confirmed 两条写入路径、确认门 fail-closed、采用不写 reported_birth_time、无把采用率当准确率的指标), 缺口全在评测本身。 三条实证:封存契约里三个打分哈希互不相同且 official_eval_trial_count 为 0,当前实现从未产出过有效官方盲测;全部离线评测的候选窗以真值为圆心而 生产以申报时间为圆心,真值掉出窗外这一失败模式从不可见;v3 封存但每例 仅 3 事件、v4 事件够却已被看过并用于调参,六题回放的高分是真值方向最优 答的上界。 任务分解:申报偏差敏感性 sweep、封存盲测有效重跑 + 契约对齐 + 防复发 测试、v5 采集协议(只出协议)、记录。硬红线:不改 12 个打分文件、不用 封存集调参、不得把 status 改 ready。家庭信息在拿到基线数字前不开工。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
176 lines
18 KiB
Markdown
176 lines
18 KiB
Markdown
# TASK · 生时校正验证体系补缺:申报偏差评测与封存盲测有效重跑(2026-09-20)
|
||
|
||
> 状态:待领取。**纯离线评测与契约对齐,不改任何打分逻辑、不改产品行为、不动确认门。**
|
||
> 起因:产品会议对「我们究竟在证明什么」提出边界要求(真实出生时间 vs 用户认可的参考盘必须分开证明)。Claude 2026-09-20 对代码、评测数据与线上口径做了逐条核对,结论见 §1。
|
||
> **产品口径侧的四条要求(accepted≠confirmed、不承诺精确分钟、不覆盖原始资料、不拿用户认可当验证)核对下来已经落地**,本单不碰那一层;缺口全部在评测本身。
|
||
|
||
## 0. 基线与交付
|
||
|
||
- 基线:`origin/staging` = `fcad0637`(2026-09-20 实测核对)。
|
||
- worktree `.worktrees/rectification-validation-20260920`,分支 `codex/rectification-validation-20260920`。
|
||
- 改动落点:`scripts/research/**`、`tests/**`、`references/rectification_sealed_holdout.v1.json`、`docs/**`。前三项在 `deploy/gated-paths.txt` 内,推 staging 会触发门禁并重新发布镜像。
|
||
- **前置依赖(不在本单,不阻塞开工)**:`TASK-owner-case-purge-20260919` 的三个提交 `fe9489e6` / `36c443bf` / `497798ac` 已验收通过但**尚未合入 `staging`**(已核:三者均不是 `origin/staging` 的祖先)。那条分支带着仓库级隐私守卫 `tests/test_repo_privacy_markers.py`。本单不依赖它即可执行,但**在它合入之前,任何「拿自己的案例试一下」的结果都不具备独立性**,不得当作验证证据。放行由产品负责人决定。
|
||
|
||
## 1. 事故实证
|
||
|
||
基线 `fcad0637`,行号按符号定位。
|
||
|
||
### 1.1 三个打分实现哈希互不相同,任何成绩都无法归属(BUG-978)
|
||
|
||
`references/real_case_calibration/minute_rectification_holdout_v3.json` 的 `frozen_scoring` 对 12 个打分文件做 SHA-256 封存。三处哈希实测:
|
||
|
||
| 来源 | 哈希前缀 |
|
||
| --- | --- |
|
||
| 封存值 `frozen_scoring.implementation_sha256` | `f41c298dd6cdcebe` |
|
||
| 契约文件记录的当前树 `current_tree_scorer.implementation_sha256` | `99730c84c6434f52` |
|
||
| **基线树实测**(`implementation_sha256(frozen_scoring.files)`) | `b15d9ea15227cd58` |
|
||
|
||
三者两两不等——契约文件 `references/rectification_sealed_holdout.v1.json` 里的 `current_tree_scorer` 块本身已经过期。同一文件另有两处与现实不符:`source_audit_status` 仍写 `invalidated_after_replay`,而 v3 数据集在 2026-09-14 已改为 `corrected_known_date_errors`;`evaluated_on` 仍停在 `2026-07-21`。
|
||
|
||
后果记在该文件自己的字段里:`official_eval_implementation_hash_matches: false`、`official_eval_trial_count: 0`——**当前打分实现从未在封存集上产出过一次有效的官方盲测成绩**。
|
||
|
||
- 唯一跑过的那次(2026-07-21):top-1 `0.15`、top-3 `0.25`、MAE `6.95` 分钟、确认覆盖率 `0.0`,报告状态 `invalidated_after_source_audit`(资料审计发现两处错年,已在 `docs/research/holdout_v4_build_2026_09_14.md` 更正)。
|
||
- 现在唯一能看到的较新数字(top-1 `0.45` / top-3 `0.50` / MAE `5.8`)来自 `current_tree_unfrozen_diagnostic` 块,该块自带 `is_blind_evaluation: false`、`results_already_seen: true`、`must_not_claim_as_release_metrics: true`、`must_not_use_for_tuning: true`。
|
||
|
||
预注册发布门(`minute_rectification_holdout_v3.json` 的 `release_metrics`):top-1 ≥ `0.60`、top-3 ≥ `0.85`、MAE ≤ `2.0` 分钟、误确认率 ≤ `0.05`、信息不足正确拒绝率 ≥ `0.90`。
|
||
|
||
**计算成本不是障碍**:基线树实测,20 例全量打分 ±10 档 `1.6 s`、±60 档 `8.6 s`。
|
||
|
||
### 1.2 所有离线评测的候选窗以真值为圆心,生产以申报时间为圆心(BUG-979)
|
||
|
||
- `scripts/minute_rectification_blind_eval.py` 的 `_candidate_moments()` 以 `case["birth"]["time"]`(Astro-Databank AA 记录的真实分钟)为中心,向两侧各取 `candidate_radius_minutes` 分钟。
|
||
- `scripts/minute_rectification_fact_blind_eval_v4.py` 直接 import 复用同一函数。
|
||
- `scripts/research/probe_supply_after_six.py` 的 `request_from_case()` 同样以真值为心,局部变量名就叫 `true_time`,`start_time` / `end_time` 由它 ± 半径得到;`scripts/research/minute_resolution_sweep.py` 的 `scoring_request_for()` 调的就是它。
|
||
|
||
生产不是这样:`frontend/src/lib/rectification-agentic/v9/search-window.ts` 的 `ENGINE_SEARCH_RADIUS_MINUTES = 15`,窗以用户**申报**时间为中心(`frontend/src/lib/rectification-agentic/birth-time-provenance.ts` 文件头注释明写 "Scoring still uses reportedBirthTime as the search-window centre"),可放宽到 30 / 60 / 120。
|
||
|
||
后果:离线评测里真值**永远在窗正中心、永远在候选集里**。「申报偏差大于半径 → 真值根本不在候选里」这一失败模式在现有全部评测中不可见,其发生率从未被测量。全仓检索偏移窗相关键在评测脚本内 0 命中。
|
||
|
||
### 1.3 现无「口径干净且事件数贴近真实会话」的封存集(BUG-980)
|
||
|
||
| 数据集 | 是否封存 | 每例事件 | 每例领域 | 可否当发布门 |
|
||
| --- | --- | ---: | ---: | --- |
|
||
| `minute_rectification_holdout_v3.json` | 是(`truth_hidden_from_ranker: true`) | **3** | 2–3 | 可,但事件数远低于真实会话 |
|
||
| `minute_rectification_holdout_v4.json` | **否**(`truth_hidden_from_ranker: false`) | ≥7 | ≥4 | **不可**,结果已被看过且用于 R1–R5 调参 |
|
||
|
||
`docs/research/holdout_v4_build_2026_09_14.md` 自己写明 v4「是开放评价集,用来看尺度,不是第二份封存盲测」;真实会话每例 5–18 件事。
|
||
|
||
另:`docs/research/minute_resolution_2026_09_14.md` 记载六题回放用「**真值方向最优答**」。因此 0.80 / 0.55 / 0.35(±10 / ±30 / ±60)是**上帝视角上界**,不是产品表现;其中 ±30 与 ±60 两档仍低于 `0.60` 的发布门,而生产默认半径 ±15 正落在两档之间。
|
||
|
||
### 1.4 产品口径侧已经达标(不构成 BUG,记此以免重复排查)
|
||
|
||
- `accepted` 与 `confirmed` 是两个状态、两条写入路径(`frontend/supabase/migrations/20260813010000_agentic_rectification_v9_agent_api.sql` 的 `accept_agentic_rectification_candidate_for_case` 与 confirm 函数),`selection_kind` 区分 `user_accepted` / `engine_confirmed`。
|
||
- 采用候选写 `active_birth_time` / `birth_time`,**不写 `reported_birth_time`**——原始申报保留,且仍是搜索窗圆心。
|
||
- 确认门 fail-closed:`scripts/rectification/decision_policy.py` 的 `apply_confirmation_decision()` 要求四闸同时通过,其一是 `scripts/rectification/sealed_holdout.py` 的 `holdout_passed()`。封存契约 `status: not_ready` 且 `confirmation_coverage_rate: 0.0`,因此 `confirmation_allowed` 今天在任何情况下都不可能为真。
|
||
- 交付卡自带标签 `candidate_range_not_birth_time_truth` 与「采用只会把代表性时间写入当前排盘,不是已确认的唯一出生分钟」。
|
||
- 全仓无「把采用率 / 满意度当准确率」的指标;无任何按特定生日走捷径的运行时分支(上游 `PL9_BAV_CELL_OVERRIDES`、`_known_case_regression_hint` 在本仓 0 命中)。
|
||
|
||
**关联记录:`BUG-098`**(公开候选门禁未明确独立 holdout 边界)。其防复发条写明「任何 holdout 完成声明必须先有稳定分区持久化与校准验收证据」。本单三条与之同源:那条措施**仍然有效,但从未被执行到底**,且没有任何测试守着它。
|
||
|
||
## 2. 根因
|
||
|
||
1. 封存契约文件 `references/rectification_sealed_holdout.v1.json` 是**人工维护**的(`scripts/rectification/sealed_holdout.py` 文件头注释:「A human updates ... after a valid passing evaluation」),没有任何测试断言它与数据集、与当前树哈希一致 —— 打分代码每改一次,它就更旧一次,而没有人会被提醒。
|
||
2. 评测脚本是从「给定真实出生时间,能否把它排到第一」出发写的;产品解决的是「给定用户申报时间,真实时间在哪」。两者差一个偏移量,建评测时没有人把这个差异写成一项指标。
|
||
3. v4 建立时就定性为开放评价集,此后没有人补上对应的封存孪生集,于是「事件数真实」与「口径干净」只能二选一。
|
||
|
||
## 3. 决策记录
|
||
|
||
- **2026-09-20 产品授权出本单**,覆盖核对结论中的两条行动:补申报偏差评测、重跑有效封存盲测;外加一条只出协议的封存集规划。
|
||
- **本单只产出数字与结论,不改打分逻辑、不调参、不改产品行为。** 即便重跑成绩过了发布门,执行方**不得**把 `status` 改成 `ready`、不得让确认门打开 —— 过门是产品决策,另立单。
|
||
- **家庭信息(父母职业、父母出生年份、兄弟姐妹)在本单产出基线数字之前不开工。** 核对已确认:现有七个领域全部是带日期事件,打分输入只认 `{id, domain, date, precision}` 四个字段(见 `scripts/minute_rectification_blind_eval.py` 的 `_request()`),静态家庭属性**没有输入口** —— 加它不是多问一道题,是新开一条打分通道。若产品另有决定,请在领取前更新本段。
|
||
- 上游清除分支的放行是产品动作,不在本单(§0)。
|
||
|
||
## 4. 硬红线
|
||
|
||
1. **不得修改 `frozen_scoring.files` 里那 12 个打分文件中的任何一个**,也不得修改 `scripts/rectification/decision_policy.py`、`scripts/rectification/sealed_holdout.py` 的判定逻辑。本单是量,不是改。
|
||
2. **不得把封存集 v3 用于任何调参**;不得因成绩难看而更换数据集、更换半径或剔除案例。剔除只允许发生在 `minute_rectification_holdout_validator.validate()` 判定为 `invalid_cases` 的情况下,且必须列出被剔案例与理由。
|
||
3. **不得让 `references/rectification_sealed_holdout.v1.json` 的 `status` 变成 `ready`**,不得抬高 `confirmation_coverage_rate`。
|
||
4. 新增的偏差评测必须与现有评测**并存**;不得改写 `_candidate_moments()` 与 `request_from_case()` 的现有行为(改了会让两轮历史结论不可比)。
|
||
5. 任何数字进文档必须带口径:ayanamsa / node mode / 半径 / 步长 / 数据集 / 打分实现哈希前 16 位。**不得把非盲诊断写成发布指标。**
|
||
6. 不得写入真实用户资料;案例只用已登记的公开 AA 名人;不得在任何文档里写出生分钟或坐标。
|
||
7. 不顺手升级依赖、不修不在本单内的 warning。
|
||
8. `frontend/src/app/page.tsx` 与 `scripts/jyotish_api_server.py` 均不得增长(本单本不该碰前端与主 API,碰了说明跑偏)。
|
||
|
||
## 5. 任务分解
|
||
|
||
### T1 · 申报偏差评测(BUG-979)
|
||
|
||
新增 `scripts/research/reported_offset_sweep.py`:在现有取候选方式之外,增加一个「模拟申报时间」的圆心。
|
||
|
||
- 对每个案例,把窗心从真值平移 `offset ∈ {0, ±3, ±5, ±8, ±10, ±15, ±20, ±30}` 分钟,半径取产品档位 `{15, 30, 60}`。
|
||
- 每个(案例, 偏移, 半径)组合输出四项:**真值是否落在窗内**、真值在候选中的排名、头名与真值的分钟距离、交付区间是否覆盖真值。
|
||
- 汇总成一张表:横轴偏移、纵轴半径,格子里是「真值在窗内的比例 / 头名命中 / 区间覆盖」。
|
||
- 真值仍然**排完名之后**才揭晓;并列沿用现有哈希打破方式(`_opaque_winner`),**不得按接近真值挑选**。
|
||
|
||
验收标准:
|
||
|
||
- `python3 scripts/research/reported_offset_sweep.py --json` 在基线树跑通,输出含上述四项与完整口径块(含打分实现哈希前 16 位)。
|
||
- 新增 `tests/test_reported_offset_research.py`,至少断言:(a) 偏移为 0 时窗心等于真值且「真值在窗内」为 `1.0`;(b) 偏移绝对值大于半径时「真值在窗内」为 `0`;(c) 口径块字段齐全;(d) 并列打破不依赖真值。
|
||
- 结论写进 `docs/research/reported_offset_2026_09_20.md`,**必须明确回答一句**:在半径 ±15 下,申报偏差多大时真值开始掉出窗外;以及现有放宽路径(30 / 60 / 120)能把这个比例救回多少。
|
||
- 本任务**不需要**知道真实用户的申报偏差分布;它给的是「如果偏差是 X,会怎样」的敏感性曲线。真实分布怎么拿,进 T4 的遗留项。
|
||
|
||
### T2 · 封存盲测有效重跑与契约对齐(BUG-978)
|
||
|
||
1. 冻结当前打分实现:把基线树实测哈希(`b15d9ea15227cd58…`,执行方自己重算确认)连同 12 个文件清单写进一次新的评测记录;**不改 v3 的 `frozen_scoring` 原值**(那是历史封存值,要留着做对照)。
|
||
2. 在 v3 封存集上跑一次完整盲测,产出五项预注册指标:top-1、top-3、MAE、误确认率、信息不足正确拒绝率。
|
||
3. 把 `references/rectification_sealed_holdout.v1.json` 更新到与现实一致:`source_audit_status` 取 v3 数据集当前值;`current_tree_scorer` 刷新到本次实测哈希与成绩;新增一个块记录本次重跑,注明它是**首次口径干净的官方盲测**,以及「v3 每例仅 3 件事,是下限口径,不代表真实会话」。`status` 保持 `not_ready`(§4.3)。
|
||
4. 新增 `tests/test_sealed_holdout_contract_freshness.py`:断言契约文件的 `current_tree_scorer.implementation_sha256` 等于当前树实测值、`source_audit_status` 等于 v3 数据集里的值。**这条测试就是防复发** —— 它一红就说明打分代码改了而契约没跟上。
|
||
|
||
验收标准:
|
||
|
||
- `.venv/bin/python -m pytest tests/test_sealed_holdout_contract_freshness.py tests/test_minute_rectification_holdout_validator.py tests/test_minute_rectification_fact_blind_eval_v4.py -q` 通过。
|
||
- 契约文件三处过期字段全部对齐,且 `status` 仍为 `not_ready`、`confirmation_coverage_rate` 仍为 `0.0`。
|
||
- 五项指标与发布门逐条对照写进 `docs/research/sealed_holdout_rerun_2026_09_20.md`,每项标「通过 / 未通过 / 不适用」。
|
||
- **确认门行为零变化**:跑 `frontend/tests/rectification-confirmation-gate.test.ts` 与相关 pytest,结果与基线逐条相同。
|
||
- 快速门 `python3 scripts/run_quality_gate.py --profile quick` 与基线逐条比对。
|
||
|
||
### T3 · 封存集 v5 采集协议(BUG-980,只出协议、不采集)
|
||
|
||
写 `docs/research/sealed_holdout_v5_protocol_2026_09_20.md`:
|
||
|
||
- v5 目标:**封存 + 每例 ≥7 件带年月事件 + ≥4 个领域**,即 v4 的口径、v3 的纪律。
|
||
- 人物名单必须与 v3 / v4 的 20 例**不重叠**(那 20 例成绩已被看过)。列出 ≥25 个候选公开 AA 人物作为备选池,只写人物与资料来源类型,**不写出生分钟或坐标**。
|
||
- 标注协议沿用 `holdout_v4_build_2026_09_14.md` 的四条(AA 记录、事件来自公开页且 `independent_of_birth_source=true`、工作日期须核实、三档半径写在数据集上),并补一条:**v5 建成后,在产出第一份成绩之前不得被任何人读取事件内容做调参**。
|
||
- 工作量估算:按每例 7 件事、每件需一处可核实公开来源计,给出人工小时数区间。
|
||
- 明确写出:「在 v5 就绪之前,任何对外的分钟级准确率说法都没有依据」,并指明 T2 的数字是下限口径、T1 的数字是敏感性而非准确率。
|
||
|
||
验收标准:文档含上述全部要素;候选池 ≥25 人且与现有 20 例无交集;不含任何出生分钟、坐标或真实用户资料。
|
||
|
||
### T4 · 记录
|
||
|
||
- `docs/BUG_HISTORY.md` 新增 `BUG-978` / `BUG-979` / `BUG-980`,状态按实际填(T1 / T2 完成的可写 `resolved` 并附证据;`BUG-980` 只出协议,保持 `investigating` 直到 v5 就绪)。三条都必须**关联 `BUG-098`**,并说明那条防复发措施为何没拦住。
|
||
- `BLOCKED.md` 记一条:真实用户的申报偏差分布目前拿不到(需要有独立出生记录的真实用户样本,无受控账号),T1 只能给敏感性曲线。
|
||
- `docs/research/ACTIVE_FRONTS.md` 加一节索引本单三份文档,并指回 `rectification_minute_resolution_closure_2026_09_14.md`。
|
||
- `docs/tasks/README.md` 状态板加一行;**实现合入 staging 的同一次推送里改状态**。
|
||
- `CHANGELOG.md` 不写(无用户可感知行为变化)。
|
||
|
||
## 6. 让步顺序
|
||
|
||
时间不够时按此顺序砍,砍掉的写进进度记录:
|
||
|
||
1. **最先保 T2** —— 它最便宜(±10 档全量 1.6 s),且产出的是第一份能对着发布门看的数字。
|
||
2. 其次保 T1 的 ±15 一档与 `{0, ±5, ±10, ±15, ±20}` 五个偏移;其余半径与偏移可后补。
|
||
3. T3 候选池可从 25 人降到 15 人,但**协议正文与那句「在 v5 就绪之前没有依据」不得删**。
|
||
4. **不得为赶工跳过 T2.4 那条防复发测试** —— 没有它,这三条明天会重新长出来(`BUG-098` 就是这么回来的)。
|
||
|
||
## 7. 开工前置命令
|
||
|
||
```bash
|
||
git fetch origin --prune
|
||
git worktree add -b codex/rectification-validation-20260920 \
|
||
.worktrees/rectification-validation-20260920 origin/staging
|
||
cd .worktrees/rectification-validation-20260920
|
||
git log --oneline -1 # 必须是 fcad0637 或其后代
|
||
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
|
||
```
|
||
|
||
本单属 `AGENTS.md` §9 的引擎 / 评测类任务,`pre_work_check.py` 必跑,并先读 `docs/research/pre_work_error_ledger.md`。
|
||
|
||
**另必读** `docs/research/rectification_minute_resolution_closure_2026_09_14.md` —— 它已经证伪了两条假设(加权重能拉开候选、簇合并把候选并没了),本单不得重走;它的第 6 节三条提醒(先确认指标口径、不要从 docstring 推断行为、真值覆盖率优先于区间宽度)对本单全部适用。
|
||
|
||
环境备忘:核对机上无 `.venv`,系统 `python3`(3.13)可直接 `import swisseph`;若执行方环境有 `.venv`,按 `AGENTS.md` §10 用 `.venv/bin/python`。
|
||
|
||
## 8. BUG 编号起点
|
||
|
||
`docs/BUG_HISTORY.md` 当前最大号 **975**;`TASK-consult-smalltalk-fastpath-20260920.md` 已占用 **976 / 977**。本单从 **BUG-978** 起,连续三条。开工时重新核对最大号,若已被占用则顺延并在进度记录里说明。
|