a68fd01f68
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
9.2 KiB
9.2 KiB
TASK · 校正候选区分阶段同一道"上大学"题问两次(2026-09-04)
- 基线:
origin/staging@a75929c1(含d4627a50采用后核对线修复;本任务书不依赖其验收结论) - 分支:
codex/rectification-quality-probe-dedupe-20260904 - 执行方:coding agent;验收:Claude
- 涉及文件:
scripts/rectification/event_probes.py、frontend/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. 事故实证(staging,2026-09-04,候选区分阶段)
用户在同一案例里连续两次看到同一张选择卡:"YYYY 年 M 月那次上大学,更接近如愿、将就调剂、发挥失常还是说不清",四个选项完全相同。用户两次都作答,第二次答完范围继续收窄。案例 dump 中可对上的事实(不写具体值):
| # | 事实 | 位置 |
|---|---|---|
| A | 该案例学业域有两条已核实事件:入学(education_start)与毕业(education_completion),相隔四年 |
案例 evidence[] |
| B | 引擎对这两条事件各发了一条 known_event_quality 探针;两条的 expected_outcomes 的 yes/no 候选分钟集合完全相同(同一 D24 星座切分),只有 semantic_key(education.<入学年>.known_event_quality / education.<毕业年>.known_event_quality)、target_evidence_id、candidate_split_hash 不同 |
scripts/rectification/event_probes.py::_quality_distinguish_probes(L901–997)、candidate_contrast.py::candidate_split_hash(hash 把 year/month 掺进去,所以同分组不同年 ≠ 同一 hash) |
| C | 第二张卡的 question_id / probe_id / 评分都指向毕业探针,但卡面题干与日期标签是入学探针的 |
choice-card.ts::pickProbe(L233–251):followup.choice_kind === "event_quality" 分支取 pool 里第一条质量探针就返回,semantic_key 匹配写在它后面,永远轮不到;periodFor(L254)与 eventQuestionPrompt(L280)都从 pickProbe 拿探针,所以日期与题干一起错 |
| D | 即便 C 修好,毕业事件的题干仍会是"YYYY 年 6 月那次上大学……"、选项仍是"发挥明显失常 / 只是将就调剂 / 基本如愿录取 / 当时说不清楚" | event_probes.py::_quality_user_meaning(L887)只按 domain == "education" 套"上大学"模板;QUALITY_DISTINGUISH_OPTIONS["education"](L92)同样不看事件 kind |
2. 根因
- 前端选探针的优先级反了:
pickProbe先按"种类"再按"键"。同域只有一条质量探针时看不出来;一旦引擎给了两条,第二次 followup 的卡面永远渲染第一条。用户第二次实际是在对着入学题、给毕业探针打分——答案被记到了错的证据上。 - 引擎对学业域所有事件一律发"高考发挥"题:质量探针的题干与选项是 BUG-390 时按"高考/录取"设计的,那时只有
education_start一种学业事件会到这里;现在采集线会产生education_completion(毕业)等其它 kind,模板没有跟着分。毕业没有"调剂/发挥失常"这种体验,这道题对毕业事件没有可回答的语义。 - 同分组质量探针没有去重:两条探针的 yes/no 分钟集合一模一样,问第二道不带来任何新信息(信息增益相同、支持/冲突集合相同),却占了
MAX_QUALITY_DISTINGUISH_PROBES = 2的全部名额和用户一轮回答。candidate_split_hash因为掺了年份而不相等,askedDiscriminatorKeys的 split-hash 去重拦不住。
3. 决策记录
- 一道题只能绑定一条探针,且必须是 followup 指定的那条:
pickProbe的匹配顺序改为probe_id→semantic_key→ 种类兜底。找不到semantic_key对应探针时不得退回"同种类第一条",改为返回null(让上游按无探针路径走,不出卡),并在进度记录里说明这种情况是否在测试中出现过。 - 质量探针只对有"体验词表"的事件种类发:学业域仅
education_start、education_change、education_interruption可发("如愿 / 调剂 / 失常"对这三类都有意义);education_completion与其它 kind 不发质量探针,而不是新造一套"毕业顺不顺"的模板。理由:产品要求"多余入口宁可删除也不修";毕业是否顺利本身对 D24 切分没有独立证据价值(与入学事件是同一切分)。 - 同域同分组只发一条:
_quality_distinguish_probes内对(domain, frozenset(yes_times), frozenset(no_times))去重,保留时间最早(或information_gain最高、并列取最早)的一条;被去掉的事件不计入MAX_QUALITY_DISTINGUISH_PROBES名额。 - 已经答过的错绑答案不追溯修正。已有案例的历史轮次不回写;只保证新的轮次不再出错。若要为受影响案例做补救,另开任务书。
- 本任务书不推翻 BUG-389/390/391/392/393 的任何防复发措施;BUG-390 的"质量探针只对学业发出、不得把高考题干套到事业/感情"仍然成立,本次是把它进一步收紧到学业内的特定 kind。
4. 硬红线
- 不得改
QUALITY_DISTINGUISH_OPTIONS四选项的文案与answer_class映射(BUG-390 / 四选项合同tests/test_probe_question_contract.py、frontend/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_id(ChoiceCardFollowup需补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 指向第二条时,buildChoiceFrame的question_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.md;CHANGELOG.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 --noEmit、npm run lint、npm test(与 origin/staging 基线失败清单逐条比对)、.venv/bin/python scripts/run_quality_gate.py --profile quick。