# TASK · 生时校正过程解释层:每题"为什么问、答了会怎样",每轮"现在第几步、为什么、下一步"(2026-09-06) - 基线:`origin/staging` @ `b938c76a`(文档头 `211f9bb2`) - 分支:`codex/rectification-explain-layer-20260906` - 执行方:coding agent;验收:Claude - 串行:排在 `TASK-rectification-convergence-exit-fix-20260906.md` 之后、`TASK-rectification-range-reading-20260906.md` 之前(都改 `answer-choice.ts` / `rectification-agentic-chat.tsx`) - 涉及文件:`frontend/src/lib/rectification-agentic/v9/choice-card.ts`、`probe-question-contract.ts`、`choice-action.ts`、`answer-choice.ts`、`method-followup.ts`、`case-service.ts`(快照投影)、`frontend/src/lib/rectification-candidate-result.ts`、`frontend/src/components/rectification-choice-card.tsx`、`rectification-agentic-chat.tsx`、`frontend/src/lib/rectification-agentic/user-copy.ts` - 不改:Python 探针契约(`contracts/probe-question-v1.json` 字节不变)、任何门、SKILL.md - BUG 编号:本单是能力补齐,不预留;执行中发现缺陷再按当时最大号续 ## 0. 为什么做这件事 产品负责人 9-06 实测的原话是"用户也不知道怎么做,就卡在这里"。修复单堵的是出口,这一单补的是**过程可见性**:上游 yinduzhanxing 旧校时工作台的 `buildRectificationAIContext()`(当前阶段 → priority action → reason → next step)、题库里的 `why_this_question` / `answer_impact`,是它相对我们唯一明显更强的业务层。我们的数据都有,只是没投影给用户。 ## 1. 现状实证 | # | 事实 | 位置 | | --- | --- | --- | | 1 | 点选卡的 `why` = `probe.user_meaning`(引擎给模型的作题简报,如"时间范围锁定…领域锁定…语义目标是…"),经 `engineMeaningToDisplayCopy` 过滤后基本为空,用户看不到"为什么问" | `choice-card.ts::hypothesisFor` L73;`rectification-choice-card.tsx` L63 | | 2 | 答完只回固定句"已记录你的选择,并更新了候选比较",虽然 `inference_state.rounds[].score_deltas` 和 `credible_range` 前后值都在 | `choice-action.ts::composeChoiceNarration` L94–116;`build-state.ts` rounds | | 3 | 每轮只有一句"本轮对照了 D1、D9…"(技法清单),没有"现在在哪一步 / 为什么 / 下一步" | `rectification-varga-sentence.ts` L33;receipt `methods` | | 4 | 服务端其实已经知道阶段:`methods` 覆盖表、`decision.nextAction`、`precision_stage.current`、`stopReason` | `method-followup.ts` plan、`rectification-decision.ts` | | 5 | 看盘板(`rectification-board`)已经是"专业视图"(宫位表、换升时刻),不需要再做双模式开关 | `rectification-board.tsx` | ## 2. 决策记录 1. **解释文案全部服务端生成**,模型只写题干;不得让模型解释分数或候选变化(防止编造)。 2. **"为什么问这题"**:从探针数据生成一句:`{period} 这段经历能把当前 {n} 段候选分成两组({tracks 中文})`;varga_style 卡写"这题对照 {D9/D10} 的类型差异"。挂在卡片题干下方,默认折叠为"为什么问这题",点开显示。 3. **"答了会怎样"**:每个选项一句,来自 `expected_outcomes`:A/B → "会让 {supports 段落} 领先、{conflicts 段落} 落后";C → 反向;D → "不计分,换一题"。段落用可信区间口径("04:31–04:39 这段"),不用概率词。放在卡片底部一行小字,随选项高亮。 4. **答后旁白**改为三段式(服务端拼):"{已记录}。{哪段升/降,来自 score_deltas 聚合到簇}。{范围从 X 收到 Y / 范围没变}。" unsure 保持"这题先不计分,换一件事问"。 5. **步骤条**:case 快照新增 `step_state = { stage: "collect"|"discriminate"|"deliver"|"post_adopt", index: 1..4, headline, reason, next }`,服务端从 `methods` / `decision.nextAction` / `precision_stage` / `stopReason` 派生(纯函数,放 `frontend/src/lib/rectification-agentic/v9/step-state.ts`)。UI 固定显示在输入框上方、与口述态停止按钮同一行左侧;每轮重算。文案例:"第 2 步·区分候选 — 带年月的经历已经够了,现在用几道选择题分开相邻的候选 — 下一步:答完当前这题,或者按'先这样'看结果"。 6. 不做 guided/professional 双视图;看盘板就是专业视图。 ## 3. 硬红线 - BUG-390 四选项契约不变;`contracts/probe-question-v1.json` 字节不变;新增字段只在 TS 的 `RectificationChoiceFrame` / `RectificationChoiceCard`(`why_user`、`answer_impact`),Python 不改。 - 不得在用户可见文案里出现"概率 / 置信度 / 确定"字样(`agent-voice-copy-contract` 已有守卫,新文案要过它)。 - 答后旁白的段落只能来自 `score_deltas` 与 `credible_range` 前后值;范围没变时必须如实说"范围没变"。 - `page.tsx` 不增长;新逻辑进 `lib/` 或组件。 - 既有断言改动写三栏。 ## 4. 任务分解 ### B1 卡片解释字段 - `choice-card.ts::buildChoiceFrame` 新增 `why_user`(决策 2)与 `answer_impact: {A,B,C,D}`(决策 3);生成函数放 `probe-explain.ts`(纯函数,输入 probe + 活跃候选 + 簇范围)。 - `rectification-choice-card.tsx`:折叠"为什么问这题";选项 hover/选中时显示对应 impact 行。`DESIGN.md` 记录。 - 验收:`rectification-choice-card.test.ts` 新增——existence 探针生成的 `why_user` 含年月与"分成两组";`answer_impact.A` 含 supports 段落的时间;varga_style 卡 `why_user` 含分盘名;文案守卫通过。 ### B2 答后旁白 - `composeChoiceNarration` 接收 `{deltasByCluster, rangeBefore, rangeAfter}`;`persistApplied` 在应用推断后计算并传入(`scoreDeltas` + `clusterRangeFor` 已有)。 - 验收:`rectification-answer-choice.test.ts` 新增——答 A 后旁白含"领先 / 落后"与"范围从 … 收到 …";范围不变时含"范围没变";答 D 不变。 ### B3 步骤条 - `step-state.ts` 纯函数 + 单测(四个阶段各一例,含 `probe_pool_exhausted` 与 `user_uncertainty_too_high` 的 reason 文案);`case-service.ts` 快照投影加 `step_state`;`rectification-agentic-chat.tsx` 渲染;`rectification-candidate-result.ts` 解析。 - 验收:快照契约测试(`rectification-v9-contracts.test.ts`)含 `step_state`;组件源扫描断言存在 `rectification-step-state`。 ### B4 记录 - `CHANGELOG.md`;`PROGRESS-rectification-explain-layer-20260906.md`;`docs/testing/rectification-explain-layer-20260906.md`(真实环境:每张卡能展开"为什么问";答完一题看到哪段升降与范围变化;每轮顶部有第 N 步 / 原因 / 下一步);`frontend/DESIGN.md`、`frontend/docs/VOICE.md`。 ## 5. 让步顺序 B3 步骤条最便宜、最直接回应"不知道下一步",先做;B1、B2 其次;B4 不可省。 ## 6. 开工前置命令 ```bash git fetch origin --prune git worktree add -b codex/rectification-explain-layer-20260906 .worktrees/rectification-explain-layer-20260906 origin/staging cd .worktrees/rectification-explain-layer-20260906 ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules cd frontend && ls tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)" ``` ## 验收(Claude,2026-09-07,`origin/staging` @ `814c924e`) | 项 | 结论 | | --- | --- | | B1 卡片 why_user / answer_impact | 通过。服务端生成,折叠展示,选项 hover/选中显示影响句 | | B2 答后旁白 | **未通过(P1,BUG-569)**:`applyRectificationChoice` 传给 `composeChoiceNarration` 的 `rangeBefore/rangeAfter` 取的是 `InferenceState.range_start/range_end`——那是候选**搜索窗口**,答题从不改变;真正收窄的是 `credible_range`。线上每一题都会说「范围没变」,与顶部只读范围矛盾。单测之所以过,是因为直接给 `composeChoiceNarration` 喂了两个不同的范围 | | B3 步骤条 | 通过。四段派生纯函数 + 快照投影 + UI | | B4 记录 | 通过 | | 文案 | P2:why_user 里把 Vimshottari / Narayana 写成「毗湿奴多利、那罗延」,产品其他位置(`rectification-varga-sentence.ts`)一律用 Vimshottari / Narayana,见修复单 |