Files
Jyotisha/docs/tasks/TASK-rectification-narrow-before-deliver-20260911.md
T

75 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 带年月题问完不等于收敛:交付前先按剩余候选再出题、再定向补事(2026-09-11)
- 基线:`origin/staging` @ `9941c34d`(已部署;代码 `f870d3d7`,含 BUG-651/652
- 分支:`codex/rectification-narrow-before-deliver-20260911`,基于 `origin/staging`
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-653**(开工时 `grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1` 核对)
- 不改确认门、不改 `SCORE_DELTA`、不改 `MIN_ACCEPTANCE_*`;Skill 若改用户可见流程升 10.0.24
## 1. 事故实证(2026-09-11 staging 真机,代码 f870d3d7
同一序列(两件学业 + 实习入职/离职 + 六道带年月选择题 C/C/C/B/A/C)跑完后,系统按 BUG-651 直接出了三列区间卡:范围 04:48–05:07(20 分钟,5 个簇),头名 04:53 只有 29%,第二名 05:06 22%,第三名 04:50 18%。产品负责人原话:「没到出卡的时候就开始出卡了」「范围没缩短多少,这咋能算结束」。
回执事实:
| 字段 | 值 | 含义 |
| --- | --- | --- |
| `latest_result.createdAt` | 08:10:54(记完实习那一轮) | 之后六道题都是 TS 侧推理,**引擎没有再跑** |
| `inference_state.probes` | 7 道带年月(事业 3、感情 2、迁居 1、学业质量 1)+ 7 道无年月 + 月宿 | 引擎一次性生成,`MAX_PROBES=8`、每域 ≤3、每域每年 1 道 |
| 家人 / 财务 / 健康三条线 | 0 道带年月题;`oos_blind_prompts` 列出感情、家人、财务「校时还没用过」 | `("family","existence")` 先验 yes=0.85 > `DOMINANT_ANSWER_PRIOR=0.8`,家人存在题全部被 `dominant_answer_prior` 丢弃;财务无有效换运边界 |
| 剩余 5 簇的结构差异 | 04:52 D9 换升;05:00 D10 / D4 / D12 / D24 / D2 同时换升;05:06 D2405:07 D11 | 能切开 04:4804:59 与 05:0005:07 的是事业 / 迁居 / 家人 / 学业 / 财务 这些线上的**带年月**事件,不是性格题 |
| 收敛门 | lead = 16 vs 15top share 29% | 远未收敛;`rounds` 六轮全 informative,无平台期 |
## 2. 根因
1. **探针池是静态的**`event_probes.py` 的区分探针只在引擎跑(账本变化 → `rescoreStaleMinuteSnapshotIfNeeded``rescoreMinuteAfterWindowChange`)时按**初始 9 簇**生成一次;选择题答完后 TS 侧只更新候选分数,不会拿**剩余候选**去引擎再生成一批。六道题一问完池就空,BUG-651 于是交付。
2. **生成上限与先验把可用题掐掉了**`MAX_PROBES=8``MAX_PROBES_PER_DOMAIN=3``_best_probe_per_year` 每域每年只留一道;家人存在题被 0.85 的先验整体丢弃(没有任何家人题曾出现过)。
3. **交付前没有定向补事**:重设计单 S1 的锚定追问在训练门开后就停了;S2 池空直接 S3。可是回执里 `oos_blind_prompts` 已经写明感情 / 家人 / 财务三条线没用过,而这三条线正是 05:00 换升边界所需要的。
4. **卡片文案把「当前范围」写成「结束」**:「这次给出的范围 04:48–05:07 · 对照了 4 件经历」+ 三个「更像这个」按钮,没有一句「还能再收窄:…」。BUG-651 单 T5 的「再补什么」句在真机卡片上没有出现(待 T0 核实是没落还是被裁)。
## 3. 决策记录(产品负责人 2026-09-11
1. **带年月题池空 ≠ 结束**。池空后先做两步再交付:(a) 拿**剩余活跃候选**去引擎刷新一批带年月探针(保持候选集与已答题,`keepAnswers`);(b) 刷新后仍无题,则按剩余候选的换升层做**定向补事**(只问能切开剩余候选的那几条线,带具体例子,不带推算年份)。用户给了新事 → 重算 → 回到 S2;用户说「没有了 / 就这些」→ S3 交付。
2. **交付条件改为**:收敛门开,或(刷新后带年月池空 **且** 定向补事被拒/池空),或用户主动停。BUG-651 的「池空即交付」收窄为「刷新与定向补事都用尽后交付」。「永远给结果」不变:任何时候用户说停都出卡。
3. **家人存在题的先验丢弃只对无年月 / 宽窗题生效**:带具体年月的家人换运边界题(`dasha_boundary`,月级)不再因 0.85 先验被整体丢弃;先验只用于排序。
4. **卡片文案**:标题「目前范围 04:48–05:07(对照了 4 件经历)」;卡下必有一句「还能再收窄:如果记得 …(来自定向补事池的首条)」;「更像这个」按钮保留。禁用「这次给出」「结束」「最终」。
5. 不承诺分钟级:4 件月精度经历本身分辨力有限,本单目标是把「引擎已生成的 8 道题问完」变成「剩余候选能问的都问完、能补的都补完」,不是保证 5 分钟。
## 4. 硬红线
- 探针刷新不得改变 `candidate_set_id` / 已答题;重算必须走现有 `rescoreMinuteAfterWindowChange(keepAnswers: true)` 的回放路径(BUG-587 / BUG-594 防复发)。
- 刷新时 `asked_probe_keys` 必须带上全部已答键,同域同年去重(BUG-559)照旧。
- 定向补事题干不得带出生年推算年份(重设计单 §4);例子只从「能切开剩余候选的层」对应的领域取。
- 性格题仍按 BUG-651 决策 2:只作卡后可选,不进本单的刷新池。
- 不得静默返回空载体(BUG-652 不变量)。
- 既有测试总数不降;改断言写原值 / 新值 / 原因。
## 5. 任务分解
- **T0 核实**:回放 §1 序列,打印交付时 `range_delivery` 与 host 正文,确认 BUG-651 T5 的「再补什么」句是否落下;未落则并入 T4 修。
- **T1 探针刷新(BUG-653**:新增 `refreshDiscriminatorProbes(caseId)`:带年月池空且未收敛时,用 `inference_state.candidates[status=active]``candidate_times`、全部 `answered_probes``asked_probe_keys` 调引擎的探针生成(`build_discriminating_event_probes``_remaining_contexts` 分支),把新探针并入 `inference_state.probes`(不动 candidates / rounds / answered)。触发点:`persistNextInterviewIfIdle``persistNextInterviewAfterChoice``discriminatorProbe=null` 时先刷新一次再决策;每个 `candidate_set_id` 最多刷新 2 次(写进 `inference_state.refresh_count`)。验收:§1 序列第六题答完后先出现新的带年月题(预期落在家人 / 财务 / 迁居 2015–2026 的换运边界),`rounds` 继续为 informative;刷新不改 `candidate_set_id`
- **T2 引擎侧放开(BUG-653**`event_probes.py` (a) 剩余候选 ≤5 时 `MAX_PROBES_PER_DOMAIN` 提到 4、`MAX_PROBES` 提到 12(只在 `candidate_times` 给定的刷新调用生效,首轮不变);(b) `_dominant_existence_prior``source=dasha_boundary` 且带月份的家人题不丢弃,只降序。验收:`tests/test_candidate_discriminator_contract.py` 新增剩余 5 簇夹具,家人月级边界题出现在 public 里。
- **T3 定向补事(BUG-654)**:刷新后仍无带年月题 → `collection-question-pool.ts` 新增 `targetedCollectPool(remainingLayers, evidence, declined)`:按 `LAYER_DOMAIN` 把剩余候选的换升层映射到领域,排除已拒答 / 已覆盖,生成一条带 ≥2 个具体例子的口述题(例如「还有两条线能把 04:50 和 05:06 分开:家里哪年添丁或长辈住院、哪年收入明显变过。记得哪件说哪件,年月大概就行。」),焦点 `collect:targeted:<domain>`;答「没有了」→ 移除并进 S3。验收:§1 序列在刷新池空后先出这条题;答一件家人事 → 引擎重算、回到 S2;答「没有了」→ 出卡。
- **T4 交付条件与卡片文案(BUG-654)**:`decideRectification``!separation.sufficient && !probe` 分支在 `refreshExhausted && targetedCollectExhausted` 之前不得走 `completeWithRange / finish`;卡片标题改「目前范围」,卡下「还能再收窄」句取 `targetedCollectPool` 首条;`agent-voice-copy-contract` 加禁词「这次给出」「最终」。
- **T5 记录**BUG-653/654 入历史(关联 BUG-651、BUG-629、BUG-559、BUG-587);CHANGELOGPROGRESS`docs/testing/rectification-scenarios-20260907.md` 加「六道题问完必须先刷新再补事再出卡」。
## 6. 让步顺序
T2(a) 的上限放宽可先只做刷新(T1)看真机效果;T2(b)、T3、T4 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-narrow-before-deliver-20260911 .worktrees/rectification-narrow-before-deliver-20260911 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 652
cd frontend && npm test -- tests/rectification-probe-pool-exhausted-20260911.test.ts tests/rectification-replay-20260911.test.ts tests/rectification-probe-replay-loss-20260908.test.ts tests/rectification-collection-question-pool.test.ts
cd .. && .venv/bin/python -m pytest tests/test_candidate_discriminator_contract.py -q
```
## 8. 验收口径
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build``/` 仍 Static、首屏 gzip ±2%。
- Python`tests/test_candidate_discriminator_contract.py``tests/test_event_probes*.py``run_quality_gate.py --profile quick`
- 部署后真机:按 §1 序列走,第六题答完不得直接出卡;先出新的带年月题或定向补事题;说「没有了」后出卡,卡片标题为「目前范围」且带「还能再收窄」句。