Files
Jyotisha/docs/tasks/TASK-rectification-quality-probe-dedupe-20260904.md
T

9.2 KiB
Raw Blame History

TASK · 校正候选区分阶段同一道"上大学"题问两次(2026-09-04

  • 基线:origin/staging @ a75929c1(含 d4627a50 采用后核对线修复;本任务书不依赖其验收结论)
  • 分支:codex/rectification-quality-probe-dedupe-20260904
  • 执行方:coding agent;验收:Claude
  • 涉及文件:scripts/rectification/event_probes.pyfrontend/src/lib/rectification-agentic/v9/choice-card.ts;与 TASK-rectification-post-adopt-verify-20260904.md(已实现 d4627a50)无文件冲突,可并行。
  • BUG 编号起点:BUG-540(开工时用 grep -o "BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | sort -u | tail -1 复核)

1. 事故实证(staging2026-09-04,候选区分阶段)

用户在同一案例里连续两次看到同一张选择卡:"YYYY 年 M 月那次上大学,更接近如愿、将就调剂、发挥失常还是说不清",四个选项完全相同。用户两次都作答,第二次答完范围继续收窄。案例 dump 中可对上的事实(不写具体值):

# 事实 位置
A 该案例学业域有两条已核实事件:入学education_start)与毕业education_completion),相隔四年 案例 evidence[]
B 引擎对这两条事件各发了一条 known_event_quality 探针;两条的 expected_outcomes 的 yes/no 候选分钟集合完全相同(同一 D24 星座切分),只有 semantic_keyeducation.<入学年>.known_event_quality / education.<毕业年>.known_event_quality)、target_evidence_idcandidate_split_hash 不同 scripts/rectification/event_probes.py::_quality_distinguish_probesL901997)、candidate_contrast.py::candidate_split_hashhash 把 year/month 掺进去,所以同分组不同年 ≠ 同一 hash)
C 第二张卡的 question_id / probe_id / 评分都指向毕业探针,但卡面题干与日期标签是入学探针的 choice-card.ts::pickProbeL233251):followup.choice_kind === "event_quality" 分支取 pool第一条质量探针就返回,semantic_key 匹配写在它后面,永远轮不到;periodForL254)与 eventQuestionPromptL280)都从 pickProbe 拿探针,所以日期与题干一起错
D 即便 C 修好,毕业事件的题干仍会是"YYYY 年 6 月那次上大学……"、选项仍是"发挥明显失常 / 只是将就调剂 / 基本如愿录取 / 当时说不清楚" event_probes.py::_quality_user_meaningL887)只按 domain == "education" 套"上大学"模板;QUALITY_DISTINGUISH_OPTIONS["education"]L92)同样不看事件 kind

2. 根因

  1. 前端选探针的优先级反了pickProbe 先按"种类"再按"键"。同域只有一条质量探针时看不出来;一旦引擎给了两条,第二次 followup 的卡面永远渲染第一条。用户第二次实际是在对着入学题、给毕业探针打分——答案被记到了错的证据上。
  2. 引擎对学业域所有事件一律发"高考发挥"题:质量探针的题干与选项是 BUG-390 时按"高考/录取"设计的,那时只有 education_start 一种学业事件会到这里;现在采集线会产生 education_completion(毕业)等其它 kind,模板没有跟着分。毕业没有"调剂/发挥失常"这种体验,这道题对毕业事件没有可回答的语义
  3. 同分组质量探针没有去重:两条探针的 yes/no 分钟集合一模一样,问第二道不带来任何新信息(信息增益相同、支持/冲突集合相同),却占了 MAX_QUALITY_DISTINGUISH_PROBES = 2 的全部名额和用户一轮回答。candidate_split_hash 因为掺了年份而不相等,askedDiscriminatorKeys 的 split-hash 去重拦不住。

3. 决策记录

  1. 一道题只能绑定一条探针,且必须是 followup 指定的那条pickProbe 的匹配顺序改为 probe_idsemantic_key → 种类兜底。找不到 semantic_key 对应探针时不得退回"同种类第一条",改为返回 null(让上游按无探针路径走,不出卡),并在进度记录里说明这种情况是否在测试中出现过。
  2. 质量探针只对有"体验词表"的事件种类发:学业域仅 education_starteducation_changeeducation_interruption 可发("如愿 / 调剂 / 失常"对这三类都有意义);education_completion 与其它 kind 不发质量探针,而不是新造一套"毕业顺不顺"的模板。理由:产品要求"多余入口宁可删除也不修";毕业是否顺利本身对 D24 切分没有独立证据价值(与入学事件是同一切分)。
  3. 同域同分组只发一条_quality_distinguish_probes 内对 (domain, frozenset(yes_times), frozenset(no_times)) 去重,保留时间最早(或 information_gain 最高、并列取最早)的一条;被去掉的事件不计入 MAX_QUALITY_DISTINGUISH_PROBES 名额。
  4. 已经答过的错绑答案不追溯修正。已有案例的历史轮次不回写;只保证新的轮次不再出错。若要为受影响案例做补救,另开任务书。
  5. 本任务书不推翻 BUG-389/390/391/392/393 的任何防复发措施;BUG-390 的"质量探针只对学业发出、不得把高考题干套到事业/感情"仍然成立,本次是把它进一步收紧到学业内的特定 kind。

4. 硬红线

  • 不得改 QUALITY_DISTINGUISH_OPTIONS 四选项的文案与 answer_class 映射(BUG-390 / 四选项合同 tests/test_probe_question_contract.pyfrontend/tests/rectification-probe-question-contract.test.ts)。
  • 不得新增第二套"毕业体验"模板或选项。
  • 不得改 candidate_split_hash 的算法(其它模块靠它做 asked-key 去重与回执投影);去重在 _quality_distinguish_probes 内部按分组集合做。
  • pickProbe 改动不得影响 varga_style / existence 探针的现有选取(rectification-choice-card.test.ts 既有断言原样通过;如需改断言写"原值 / 新值 / 原因")。
  • 任务书、进度记录、Bug 历史、测试 fixture 一律用抽象年份与虚构事件;不得出现真实 case_id / session_id / 出生候选分钟。

5. 任务分解

5.1 pickProbe 按键选探针(前端)

  • choice-card.ts::pickProbe:顺序 followup.probe_idChoiceCardFollowup 需补 probe_id?: string,由 method-followup.ts 的 followup 透传,见其 L172 已有字段)→ followup.semantic_key → 无键时才按 choice_kind 兜底;有键但找不到 → null
  • 验收:frontend/tests/rectification-choice-card.test.ts 新增用例——同域两条 known_event_quality 探针(不同 semantic_key / target_evidence_id / display_date_label,相同 expected_outcomes),followup 指向第二条时,buildChoiceFramequestion_id、题干日期标签、user_meaning 全部来自第二条;反向再断言指向第一条时不受影响。

5.2 质量探针按事件 kind 发(引擎)

  • _quality_distinguish_probes 只对 kind ∈ {education_start, education_change, education_interruption} 的学业事件生成;education_completion 及其它 kind 跳过。事件记录里的 kind 字段名以 contracts.py::EVENT_KINDS 为准,不要凭 domain 猜。
  • 验收:tests/test_rectification_event_probes.py 新增:同域一条 education_start + 一条 education_completion → 只产出一条质量探针且 target_evidence_id 指向入学;单独一条 education_completion → 零条质量探针,且该事件仍参与普通时间探针(不得因此少问它本身)。

5.3 同域同分组去重(引擎)

  • 在 5.2 之后再做:同域两条可发事件(如 education_start + education_change)若 yes/no 集合相同,只保留一条。
  • 验收:同文件新增用例,两条 education_start 类事件、相同分组 → 一条探针;不同分组 → 两条(MAX_QUALITY_DISTINGUISH_PROBES 上限不变)。

5.4 记录

  • docs/BUG_HISTORY.md 新增 BUG-540(前端错绑)、BUG-541(引擎 kind 与去重);关联 BUG-390。
  • docs/tasks/PROGRESS-rectification-quality-probe-dedupe-20260904.mdCHANGELOG.md 一条。
  • docs/testing/rectification-quality-probe-dedupe-20260904.md:真实环境清单——新建案例录入"入学 + 毕业"两条学业事件后,候选区分阶段最多出现一次"上大学"题,且题干年份 = 入学年份。

6. 让步顺序

5.1 > 5.2 > 5.3 > 5.4。5.3 若与既有 information_gain 排序测试冲突,允许先只做 5.1 + 5.2 并把 5.3 写进 BLOCKED.md

7. 开工前置命令

git fetch origin --prune
git worktree add -b codex/rectification-quality-probe-dedupe-20260904 .worktrees/rectification-quality-probe-dedupe-20260904 origin/staging
cd .worktrees/rectification-quality-probe-dedupe-20260904
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
grep -o "BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | sort -u | tail -1
.venv/bin/python -m pytest tests/test_rectification_event_probes.py tests/test_probe_question_contract.py -q
cd frontend && npx tsx --test tests/rectification-choice-card.test.ts tests/rectification-probe-question-contract.test.ts

收尾:tsc --noEmitnpm run lintnpm test(与 origin/staging 基线失败清单逐条比对)、.venv/bin/python scripts/run_quality_gate.py --profile quick