Files
Jyotisha/docs/tasks/TASK-rectification-collection-redesign-20260910.md
T

119 lines
13 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-10)
- 基线:`origin/staging` @ `3cd7a95d`(代码 `52db714b`,已部署;含 BUG-635645
- 分支:`codex/rectification-collection-redesign-20260910`,基于 `origin/staging`
- **本单取代** `TASK-rectification-exhausted-gate-exit-20260910.md`BUG-646/647)的决策 1、2、3;那份不再单独领取,其 BUG 编号并入本单。BUG-641/642 里「采集题带年龄段年份线索」的口径被本单推翻。
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-648**646/647 已占用,含在本单)
- Skill:本单改方法覆盖顺序与采集口径,**升 10.0.23**(部署后先验旧版本历史可打开,BUG-621)。
- 不改引擎计分、不改确认门(唯一分钟仍关)。
## 0. 产品口径(产品负责人 2026-09-10,原话要点)
1. Agent 一上来就按领域一个个追问,年份还是拿生日推出来的;应该**先等用户说出有价值的经历**,再从这些经历往下问。
2. 追问时**不得说「任何领域」「领域不限」**——用户不知道有哪些领域;要给具体例子。
3. 生时校正**必须走到结果**:要么直接成功,要么细节不全就问用户补全、补完拿结果。不允许流程走完告诉用户「做不了」。
4. (第二轮补充)**材料不够门槛时继续收集,不出结果**;不要固定题数,**按收集到的信息达到阈值来收口**。
## 1. 现状为什么违反这三条
| 现状 | 代码位置 | 违反 |
| --- | --- | --- |
| 开场问完第一件,立刻按 `DATED_COLLECT_ORDER`(家人→学业→财务→迁居→健康→事业→感情)逐领域轮转 | `method-followup.ts` `nextDatedCollectFollowup`(L1405 在覆盖链之前) | 口径 1:没等用户说完就开始盘问 |
| 轮转题干带「2020 年前后 / 2023 年前后」——这些年份来自 `_age_band_year`:出生年 + 该领域典型年龄段中点 | `event_probes.py` `DOMAIN_CATALOG.age_lo/age_hi`、BUG-642 的 `COLLECT_QUESTION_YEAR_CUE` | 口径 1:「干凭生日来推」。这是我今天上午写进 BUG-642 的口径,错了,本单撤回 |
| 六个领域全「没有」后,出口只写「还差 1 件带月份的经历,领域不限」,不落焦点、无按钮 | `persistExhaustionCollect`、BUG-558/627 决策 | 口径 2、3:说了「领域不限」,且流程到此为止 |
| 三件带年月经历里一件被留作 holdout,训练门要 3 件,于是要 4 件才出第一道选择题;没有一句文案说明 | `split-holdout.ts` `MIN_EVENTS_TO_RESERVE_HOLDOUT=2``MIN_ACCEPTANCE_EVENTS=3` | 口径 3:用户按要求给了 3 件,得到的是「还差 1 件」 |
| 门关时区间不可采用(`selection_allowed = acceptance_allowed` | `decision_policy.py` L681 | 口径 3:门关 = 没有结果 |
## 2. 目标流程(状态机,全部用阈值收口,没有固定题数)
```
S0 开场 ─▶ S1 收集(用户先说 → 系统按「信息价值」追问,直到 训练门开)─▶ S2 区分选择题(直到 收敛门开 或 增益见底)─▶ S3 交付
▲ │
└────────── 用户任何时候再补一件带年月的事,重算并回到 S2/S3 ◀────────────────────────────┘
```
两道门都是**信息阈值**,不是题数:
| 门 | 标准(现有常量,本单不改数值) | 达标前做什么 | 达标后做什么 |
| --- | --- | --- | --- |
| 训练门(能不能开始筛) | 带月经历 ≥ `MIN_ACCEPTANCE_EVENTS=3` 且种类 ≥ `MIN_ACCEPTANCE_DOMAINS=2``trainingScoreableGate`) | 继续收集(S1) | 进 S2 |
| 收敛门(能不能收口) | `convergence-evaluator`:领先 ≥ `CONVERGENCE_LEAD=0.2`、头部占比 ≥ `CONVERGENCE_TOP_SHARE=0.7`、稳定 `STABLE_WINNER_ROUNDS=2`;或剩余探针最高信息增益 < `HIGH_INFORMATION_GAIN=0.08`(连续 `low_information` 轮,即平台期) | 继续出选择题(S2) | 进 S3 交付 |
### S0 开场(文案基本不变)
一句说明窗口与做法,一句「最后给区间和代表分钟」,一句邀请:「先说你最容易想起的一两件,比如上大学、第一份工作、搬到别的城市、谈恋爱或结婚、家里添丁或长辈住院、生病受伤,年月大概就行。」不写年份。
### S1 收集:用户先说,系统按价值追问,问到训练门开为止
**收集问题池**由服务端按「信息价值」排序,每轮只问价值最高的一条;价值 = 这条问题能补上训练门缺口的概率 × 缺口权重。没有固定题数,停止条件只有两个:训练门开,或池空。
1. **自由邀请**(价值最高,只要还在产出就一直排第一):用户每说一批,batch 写入、复述,然后问一句「还有吗」,例子只列**还没提过的种类**、最多 4 个、用具体事物:「还有吗?比如第一份工作、搬到别的城市、谈恋爱或分手、家里添丁或长辈住院。」邀请的价值随产出衰减:上一轮邀请用户给了新事 → 价值不变;上一轮邀请用户答「没有了 / 就这些 / 记不清」→ 价值降到 0,从池里移除。
2. **锚定追问**(从用户已说的事派生,年份只来自用户):每个已记事件按 `kind` 派生 1–2 条,题干必须带那件事的年份或名字,例如「2020 年毕业后第一份工作大概哪年开始?」「2016 年上大学是搬到别的城市住吗?」「后来是分开了还是结婚了,大概哪年?」「2024 年 10 月那次之后,工作或住处有没有变?」派生规则与模板放服务端(`anchoredFollowups(evidence)`),Agent 只做口语润色,不得改年份、不得加生日推出来的年份。价值 = 派生概率(预设表,例如「毕业→第一份工作」0.8,「上大学→搬城市」0.6)× 缺口权重(补新种类 1.0,补同种类 0.5)。答「没有 / 记不清」→ 该条移除,不重问。
3. **通用补问**(价值最低,只在前两类都空了才轮到):按种类问、**不带年份**、每种只问一次、例子具体:「工作上还记得哪年入职或换过工作吗?」用户已拒答的种类不再出现。
**池空且训练门仍关**(用户确实只有一两件):不出结果卡,也不说「做不了」。写一句**精确缺口**:「现在记下的是 2016 年上大学和 2020 年毕业两件,都是上学的事。再来一件不是上学的、记得大概年月的事就能开始筛,比如第一份工作哪年开始、哪年搬到别的城市、家里哪年添丁。」缺口句里的例子必须来自未覆盖的种类;Case 保持开放,输入框占位「再说一件带年月的事」,用户补一件即自动继续。这是唯一允许的「停在收集」状态,文案禁词见 §2 末。
### S2 区分选择题:直到收敛门开或增益见底
- 现有逻辑(信息增益排序、±2/±1 计分、淘汰、平台期)不动,只把**停止条件**明确成:`converged` 为真,或剩余可问探针最高增益 < `HIGH_INFORMATION_GAIN` 且连续 `low_information` 轮 ≥ `STABLE_WINNER_ROUNDS`,或探针池空。`DEFAULT_MAX_DISCRIMINATION_ROUNDS=8` 保留为兜底上限(防模型循环),不是设计目标。
- **holdout 只在不影响筛选时保留**:带月事件 ≥4 才留 1 件作 holdout;恰好 3 件时全部参与训练,回执 `holdout.status="not_reserved_min_events"`,报告 Real Case Calibration 行写 `not reserved`。确认门不变。
- S2 中途若用户补了新事 → 重算,回到 S2 顶部。
### S3 交付结果
- 触发:收敛门开,或增益见底 / 探针池空(训练门已开是前提)。
- 内容:现有区间卡(`range_delivery` 三列)+ 一句定性 + **一句具体的「再补什么能收窄」**:从收集池里价值最高的未答条目取,写成「如果还记得毕业后第一份工作是哪年开始的,范围还能再收一截」。绝不写「任何领域」「领域不限」「做不了」「材料不够」。
- 交付后 Case 不关:用户随时再补一件带年月的事,重算并更新卡片。
- **不做「暂定采用」**(第二轮口径撤回):训练门没开就不出卡,采用门维持原样。
### 文案硬规则
- 用户可见文案禁止:领域、任何领域、领域不限、方法覆盖、分盘、探针、训练门、holdout、做不了、材料不够、还差 N 件。
- 每个「还有吗」「再补什么」「精确缺口句」都必须带 ≥2 个具体例子,例子只从未覆盖的种类里选。
- 静态词表合同(`agent-voice-copy-contract`)加以上禁词。
## 3. 决策记录
1. 撤回 BUG-642「采集题带年龄段年份线索」:`COLLECT_QUESTION_YEAR_CUE` 删除;`evidence_collection_probes` 的 age_band 年份只用于内部排序,不进任何题干。
2. 撤回 BUG-646 单决策 2(终态 = 门槛句 + 先这样按钮)与决策 3(穷尽后释放 holdout):终态改为 S3 交付卡;holdout 改为「≥4 件才留」的静态规则。
3. 撤回本单第一版的「≤2 轮邀请 / ≤4 问」固定题数与「暂定采用」:收口只看训练门与收敛门两个阈值;`DEFAULT_MAX_DISCRIMINATION_ROUNDS` 只作防循环兜底。BUG-558 的防复发(不得无限重复同一句)由「池内每条只问一次、答否即移除」保证,不再靠题数。
4. 训练门没开不出结果卡,也不出「做不了」:只出精确缺口句并保持开放。
5. Skill 10.0.23:把「先走完方法覆盖(感情 → 事业 → 家人 → 职业 → 占问),再对已覆盖领域做精度追问」改为本单 S1→S2→S3。
## 4. 硬红线
- 不得用出生年推年份写进题干(`grep -n "年前后" user-copy.ts` 只允许出现在带用户年份的锚定模板里)。
- 收集池「每条只问一次、答否即移除」必须是代码不变量并有测试;停止条件只能引用 §2 表里的门与常量,不得新增题数常量。
- 训练门关时不得写 `range_delivery`、不得出卡、不得出现禁词。
- 既有测试总数不降;被本单推翻的断言(BUG-642 年份线索、BUG-558「不落焦点」)逐条写原值 / 新值 / 原因。
- Bug 历史与测试不得含真实用户资料。
## 5. 任务分解
- **T1 收集问题池与价值排序**(BUG-648):`method-followup.ts` 新增 `collectionQuestionPool(evidence, declinedTopics, inviteHistory)` → 按价值排序的 `{kind: "invite" | "anchor" | "generic", value, prompt, key}``next_followup` 取首条;`invite_history` 与已答键落 conversation summary。验收:用户连续两轮给新事 → 邀请仍在首位;一轮答「没有了」→ 邀请移除、首条变锚定;锚定答「没有」→ 不重问;池空且门关 → 无焦点、精确缺口句含 ≥2 未覆盖种类例子、无禁词。
- **T2 锚定追问模板与派生表**(BUG-648):`anchoredFollowups(evidence)` + 概率表;焦点 id `collect:anchor:<kind>:<year>`。验收:学业两件 → 首条「2020 年毕业后第一份工作」;题干含用户年份、不含 age_band 年份。
- **T3 删年份线索,通用补问去年份**(撤回 BUG-642):删 `COLLECT_QUESTION_YEAR_CUE``USER_COLLECT_QUESTION` 只在 generic 层使用。验收:`rectification-spoken-collect.test.ts` 相关断言改写并注明。
- **T4 holdout 规则**BUG-647):训练 ≥3 后才留;回执状态;报告行。验收:3 件全训练、4 件留 1。
- **T5 S2 停止条件与 S3 交付**BUG-646):`decideRectification` 的交付触发改为收敛门 / 增益见底 / 探针池空;`persistExhaustionCollect` 在训练门关时只写精确缺口句(`preciseGapNarration(evidence, pool)`),门开时走交付卡 + 「再补什么」句;客户端对「停在收集」状态不显示「没有拿到下一个问题」,显示缺口句与占位「再说一件带年月的事」。验收:2 件学业 + 全拒答 → 缺口句、无卡、无禁词、无缺口提示;3 件 2 类 + 探针增益见底 → 交付卡 + 再补句。
- **T6 Skill 10.0.23 + 词表合同 + 记录**SKILL.md、registry、`case-status.ts`、CHANGELOG、BUG-646648 历史、`docs/testing/` 真人清单三条(只有学业路径、正常路径、中途补事路径)。
## 6. 让步顺序
T2 的概率表可先用常量 0.5 统一(写进度记录);T1 + T3 + T4 + T5 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-collection-redesign-20260910 .worktrees/rectification-collection-redesign-20260910 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | tail -1
cd frontend && npm test -- tests/rectification-spoken-collect.test.ts tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-surface-state.test.ts tests/skill-registry.test.ts
```
## 8. 验收口径
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build``/` 仍 Static、首屏 gzip ±2%。
- Python 定向 + `run_quality_gate.py --profile quick`
- 部署后先打开一个绑定 10.0.22 的历史校正,再做三条真人清单。