Files
Jyotisha/docs/tasks/TASK-rectification-declared-uncertainty-20260907.md
T

123 lines
18 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 · 出生时间"有多确定"只问一次:intake 三档、校正读档案里的偏移、吻合率低时一键放宽并写回(2026-09-07)
- 基线:`origin/staging` @ `5c644f92`(代码头 `517df002`,含 BUG-565570
- 分支:`codex/rectification-declared-uncertainty-20260907`
- 执行方:coding agent;验收:Claude
- 涉及文件:`frontend/src/lib/birth-time-intake-model.ts``frontend/src/components/birth-time-intake.tsx``frontend/src/lib/account-profile-patch.ts``frontend/src/lib/rectification-agentic/v9/case-service.ts``decision-from-dossier.ts``core/rectification-decision.ts``method-followup.ts``answer-choice.ts``block-scan-answer.ts`(重算函数泛化)、`tool-service.ts``frontend/supabase/migrations/`(新 RPC)、`frontend/src/lib/rectification-agentic/user-copy.ts`
- 涉及引擎:`scripts/rectification/api_service.py::block_scan`(泛化为任意子段)、`scripts/rectification/contracts.py`(可选 `blocks` 字段)
- 不改:探针契约 `contracts/probe-question-v1.json`、采用/确认门、`page.tsx``jyotish_api_server.py`
- BUG 编号:**BUG-571**(声明不确定度被忽略)、**BUG-572**(吻合率低不提议放宽)、**BUG-573**(选段后直接进 4~6 小时分钟网格)(开工时 `grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1` 复核)
- 串行:在 `TASK-rectification-capability-fix-20260907.md`(已合入)之后;与其他校正单无并行
## 0. 为什么做这件事
产品负责人 2026-09-07 拍板:出生时间"有多确定"在初始化出生资料时问一次、存进档案,校正每次直接读,不再问;校正过程中只在证据说窗口选错时,由系统出一键卡提议放宽,并把新偏移写回档案。对照上游 yinduzhanxing:它保留用户给的区间,且方法 1 规定吻合率 <60% 要调 ±1~2 小时重验;我们现在两件都没做。
## 1. 现状实证
| # | 事实 | 位置 |
| --- | --- | --- |
| 1 | intake 只露出两个来源:"我知道准确出生时间"(`family_exact`,±0)和"我不确定准确时间"(`period_only` 自选起止 / `unknown`)。模型与校验里早有 `approximate`(±15/30/60)和 `hospital_record`(固定 ±2),但单选框 `birthTimeSourceOptions` 只有两项,用户选不到 | `birth-time-intake-model.ts` L9194、L96140`account-profile-patch.ts` L126145 |
| 2 | 校正开 Case:只要档案有钟点,一律 `reportedTime ± 15``FRESH_CASE_SEARCH_RADIUS_MINUTES`),注释写明"引擎执行边界,不是用户声明的不确定度";档案里的 `uncertainty_before/after` 被读进 baseline 但不参与窗口 | `case-service.ts` L131146 |
| 3 | 引擎每次重算都产出 `event_fit_rate`matched / total / percent / band:≥80 high、6080 medium、<60 low),TS 已解析,但只用在采用后的八法报告里;没有"吻合低 → 窗口可能错 → 放宽"的动作 | `refinement_packet.py::event_fit_rate``refinement-packet.ts` L761`skill-verification-report.ts` |
| 4 | 设置弹窗的星盘资料复用 `BirthTimeIntakeFields`,所以 intake 改了设置页自动同步 | `profile-fields.tsx` L3 |
| 5 | 改窗口的 RPC 只有 `advance_agentic_rectification_case_from_block_scan`,要求 `stage=block_scan`;分钟阶段没有放宽窗口的入口。`candidateRangeFingerprint` 含起止,窗口一变快照自动失配触发重算 | `20260906030000_rectification_block_scan_stage.sql` L132182`tool-service.ts` L619 |
| 6 | 既有测试把填报 05:00 → 04:4505:15 写死了三处 | `rectification-v9-case-service.test.ts` L83、L136、L428 |
## 2. 决策记录(产品负责人 2026-09-07 拍板)
1. **只在 intake 问一次**,用"你对这个时间有多确定"来问,不用"偏移 / 误差"字眼。三档:
- 「有出生证或医院记录,精确到分钟」→ `hospital_record`(校验仍固定 ±2 作无感检查)
- 「家人记得大概时间」→ `approximate` + 一排按钮「差不多准 / 前后半小时 / 前后一小时 / 前后两小时」= ±15 / 30 / 60 / 120(校验从 {15,30,60} 扩到 {15,30,60,120}
- 「只知道大概时段,或完全不知道」→ 现有 `period_only` / `unknown` 分支不动
`family_exact` 不再出现在新用户选项里,存量档案仍合法,语义等同"差不多准"(±15)。
2. **校正窗口读档案。** 有钟点时:`hospital_record` / `family_exact` → ±15(引擎边界,记录常按 5 或 15 分钟取整);`approximate` → ±max(15, 声明值),最大 ±120。**窗口宽度 > 120 分钟一律先进子段扫描(决策 7),≤ 120 分钟才进分钟网格。** `period_only` 四~六小时的时段窗口同样先切子段;`unknown` 仍先走五时段全日扫描,赢的时段再按决策 7 继续切。
3. **校正中唯一碰这个值的时机是证据说窗口错了**:训练门已开、`event_fit_rate.band = low``total ≥ 3`、代表分钟落在窗口边缘 3 分钟内、`stage=minute`、未采用、有钟点、本证据指纹下没拒过。满足即出**服务端一键卡**(不是模型问句):"按你说的经历,出生时间可能比家人记的偏得更多。放宽后再比一次?" A「放宽到前后 {下一档}」 B「放宽到前后 {再下一档}」 C「不放宽,按现在的范围继续」 D「说不好」。A/B 改窗口、重算、并把新偏移写回档案(来源改为 `approximate`);C/D 记 `declined_at_fingerprint`,同一批证据不再问。已是 ±120 的不出卡。
4. 放宽卡优先级高于区分卡:窗口都可能错的时候,先别在错窗口里出题。
5. 放宽不写证据账本、不进推断层;已答探针按 `semantic_key` 在新候选集上重放(`buildInferenceState` 现有行为),测试锁定。
6. 不动采用门、确认门、`MIN_SEPARATION_LEAD``_relative_support`;不动 `adopted_credible_range`(那是采用后的产物)。
7. **窗口 > 120 分钟先切三段,迭代到 ≤ 120 分钟再进分钟。**`block_scan` 泛化:请求可带 `blocks: [{label,start_time,end_time}]`,不带则用五个声明时段;子段 = 当前窗口等分三段(宽度不足 3 的取整到分钟),步长按窗口宽自适应(>360 分钟 10、>180 分钟 5、其余 2),支持度仍是"段内均值减窗口最低分"归一。选 A/B/C 把窗口改成该子段并再评估:仍 > 120 → 再出一张子段卡;≤ 120 → `stage=minute` 重算。D「说不好」= 再收一件经历后重比。每个 Case 最多连问 3 张子段卡,超过即按当前窗口进分钟阶段并在旁白写明"时段分不开,直接按分钟比"。
8. **事件可靠度只问一次、只问日级事件。** 用户说到精确到日的经历时,随后的采集确认里多一句服务端口述:"这个日子是查过记录,还是凭记忆?"——答"查过"写 `date_reliability=high`"凭记忆"写 `medium`,不答留空;月级、年级不问。这只影响 `date_quality` 门与精度权重,不改任何其他门。
9. **脚本化手测场景进 `docs/testing/`**:范围(家人说两点到四点)、时段(只知道傍晚)、完全未知、以及"必须停下"的反例(日期地点经历都不确定)四个脚本,每个写期望观察点,供产品负责人在 staging 照着走。
## 3. 硬红线
- `approximate` 校验扩到 120 只在 `account-profile-patch.ts``isBirthTimeDraftReady` 两处同改;`hospital_record` 固定 ±2 不改。
- 放宽只能扩大且必须包含旧窗口;新窗口 ≤ 241 分钟;只在 `stage=minute` 允许;RPC 内校验,不信客户端。
- 放宽后 `candidate_range` 改写是**合法的**(这是搜索窗口,不是采用产物);但 `adopted_credible_range` 不得被动。
- 既有 04:4505:15 断言只对 `family_exact` / `hospital_record` 保留;`approximate` 用例另写,三栏说明。
- 文案对照 `frontend/docs/VOICE.md`;不得出现"偏移 / 误差 / 置信度 / 概率";一键卡四选项契约与其他卡一致(TS 内部 `choice_kind: "widen_window"`,不进 Python 契约,比照 `block_choice`)。
- 迁移需 Docker `test:db`;无 Docker 写 `BLOCKED.md`
- 测试总数 ≥ 1521(口径同 BUG-569 验收);tsc 0 错;lint 0 error。
## 4. 任务分解
### D1 intake 三档
- `birthTimeSourceOptions` 改三项(决策 1),`approximate` 分支渲染四个按钮写 `uncertaintyBefore/After``hospital_record` 保留"无感检查前后 2 分钟"说明;`family_exact` 从选项移除、读档时映射为"差不多准"显示。
- 校验:`isBirthTimeDraftReady``account-profile-patch.ts` 的 approximate 集合 → {15,30,60,120};错误文案同步。
- 验收:`birth-time-intake.test.ts` 新增三档渲染与 120 校验;`account-profile-patch` 测试 120 合法、90 非法;设置页(`profile-fields`)源扫描仍复用 `BirthTimeIntakeFields`
### D2 校正窗口读档案(BUG-571:声明的不确定度被忽略)
- `case-service.ts::deriveRectificationOpenPlan` 增加 `uncertaintyBefore/After` 入参:`approximate``reportedTime max(15,before)` `reportedTime + max(15,after)`(各自独立,允许不对称),上限 120;其他钟点来源 ±15。
- 验收:`rectification-v9-case-service.test.ts` 新增 approximate ±60 → 04:0006:00、±120 → 03:0007:00;既有三处 04:4505:15 保留并注明来源为 `family_exact`(三栏)。
### D3 放宽窗口一键卡(BUG-572:吻合率低不提议放宽)
- 迁移:`widen_agentic_rectification_case_window(p_user_id, p_case_id, p_start_time, p_end_time)`:校验 `stage='minute'`、新窗口包含旧窗口、宽度 ≤ 241、`status ∉ terminal`、未采用;写 `candidate_range`,返回新范围;同一迁移给 `agentic_rectification_cases``widen_declined_at_fingerprint text null`
- 决策层:`decideRectification` 新入参 `windowWidenSuggested`(由 `decision-from-dossier` 按决策 3 条件计算,边缘判定用 `separation.representativeTime``case.candidateRange`),为真时返回 `nextAction="ask_window_widen"`,排在 `ask_candidate_discriminator` 之前;`sessionOutcome="widen_window"``can_adopt=false`
- 计划层:`method-followup.ts` 比照 `blockChoiceFollowup` 生成 `widen_window` 卡,选项文案服务端生成,档位从当前半径推:15 → A ±30 / B ±6030 → A ±60 / B ±12060 → A ±120 / B「先补一件带月份的经历再说」(answer_class `no`,四选项契约要求四项且不重复)。
- 应用:`answer-choice.ts` 比照 `mutateCaseForBlockChoice`:A/B → RPC 改窗口 → 按 D5 的 `stageForWindow(width)` 决定去向(新窗口 ≤ 120 分钟:泛化 `rescoreMinuteAfterBlockAdvance``rescoreMinuteAfterWindowChange` 重算;> 120 分钟:切 `block_scan` 出三子段卡) → 调用现有账户资料 patch 路径把 `uncertainty_before/after` 写为新半径、`birth_time_source``approximate``hospital_record` 例外:档案不改,只改窗口,因为其校验固定 ±2);C/D → 写 `widen_declined_at_fingerprint`
- 验收:新文件 `rectification-window-widen-20260907.test.ts`——(1) fit low + 代表分钟在边缘 → `nextAction=ask_window_widen`,卡四项齐;(2) fit low 但代表分钟在中间 → 不出卡;(3) 答 A → RPC 收到包含旧窗口的新范围、重算被触发、profile patch 收到新半径、已答探针数不变;(4) 答 C → 记指纹,同指纹下再评估不出卡,新证据到来后可再出;(5) 已 ±120 不出卡;(6) `hospital_record` 答 A 不改档案来源。`rectification-v9-database.test.ts` 加 RPC 用例(Docker)。
### D5 子段扫描(决策 7;BUG-573:选定时段后直接进 4~6 小时分钟网格)
- 引擎:`contracts.py` 接收可选 `blocks`(1~5 段,每段起止 HH:MM,必须落在请求窗口内且不重叠);`api_service.block_scan` 在有 `blocks` 时按其聚合,否则沿用五时段;步长按窗口宽自适应(决策 7),`minute_step` 显式传入时以传入为准。`jyotish_api_server.py` 不改(同一端点)。
- TS`block-scan.ts``BlockScanBlock.period` 放宽为 `label`(五时段标签或 `sub_1/sub_2/sub_3`),选项文案用起止时间("04:00—05:59");`deriveRectificationOpenPlan` 与放宽卡(D3)落地新窗口后统一走 `stageForWindow(width)`> 120 → `block_scan` + 三等分 `blocks`;≤ 120 → `minute``advance_agentic_rectification_case_from_block_scan` RPC 放开"必须是 00:0023:59"的限制,改为"新窗口必须是当前窗口的子区间";`block_scan` 列记 `rounds` 计数,≥ 3 直接切 `minute`
- 验收:`test_rectification_v5_services.py`——三段 `blocks` 支持度和 100、等分网格三段各 33.3/33.3/33.4、越界或重叠段报错;TS `rectification-block-scan-*.test.ts`——±120 approximate 开 Case 为 block_scan 三子段;period_only 傍晚(18:0022:59)先出三子段卡;选 A 后窗口 ≤ 120 切 minute;连问 3 张后强制 minute 并有旁白;unknown 五时段 → 赢段再切三段。
### D6 事件可靠度一问 + 脚本化手测场景(决策 8、9)
- `method-followup.ts` / `collect-prompt.ts`:账本新增 `datePrecision=day``date_reliability` 为空的事件时,下一轮口述题固定为服务端句"刚才那个日子是查过记录,还是凭记忆?",答案由意图分类映射 high / medium,写回该条证据(用现有证据修订链,不新造 RPC);每条事件只问一次,用户不答则跳过。
- `docs/testing/rectification-scenarios-20260907.md`:四个脚本(范围 / 时段 / 完全未知 / 必须停下),每个含开场输入、3~5 条虚构经历、期望看到的卡与旁白、不得出现的内容(如全日窗口上出现分钟或采用卡)。
- 验收:`rectification-collect-*.test.ts` 新增日级事件后下一问是可靠度句、月级不问、答"凭记忆"写 medium`docs/testing` 文件存在且四场景齐。
### D4 记录
- `docs/BUG_HISTORY.md` BUG-571、BUG-572、BUG-573`CHANGELOG.md``PROGRESS-rectification-declared-uncertainty-20260907.md``docs/testing/rectification-declared-uncertainty-20260907.md`(真实环境:intake 选"家人记得大概时间 · 前后一小时"→ 进校正后顶部范围是 2 小时;说 3 件与该窗口明显不合的经历 → 出放宽卡 → 点 A 后范围变宽且设置页里的选项跟着变);`frontend/DESIGN.md``frontend/docs/VOICE.md`
## 5. 让步顺序
D1 + D2 + D5 是一组(窗口读档案与 >120 分钟切子段互相依赖),不可拆,先做;D3 其次(若做不完,先落 RPC + 决策层 + 卡,写回档案可后置并写明);D6 最后,可拆到同分支第三次提交;D4 不可省。
## 6. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-declared-uncertainty-20260907 .worktrees/rectification-declared-uncertainty-20260907 origin/staging
cd .worktrees/rectification-declared-uncertainty-20260907
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
cd frontend && ls tests/rectification-*.test.ts tests/consultation-*.test.ts tests/report-*.test.ts tests/personal-report-*.test.ts tests/birth-time-*.test.ts tests/account-profile*.test.ts tests/agent-voice-copy-contract.test.ts 2>/dev/null | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
grep -o "^## BUG-5[0-9][0-9]" ../docs/BUG_HISTORY.md | tail -1
```
## 验收(Claude2026-09-07`origin/staging` @ `7cf7705a`,实现 `8e31680b`
| 门 | 结果 |
| --- | --- |
| tsc / lint | 0 错 / 0 error83 warning,既有类型) |
| 前端 rectification + consultation + report + personal-report + birth-time + account-api + voice(非 DB | 1899 / 0 |
| Python v5_services / event_probes / engine_convergence / growth contract / flexible engine / input_contract | 全绿 |
| `minute_step=1` 字节级不变(虚构 7 件事新旧引擎复跑) | candidate_scores / result_id / spec_hash 全等 |
| 子段扫描(18:0022:59 三等分,3 件事) | 0.8 s,步长 5,三段各 20 个候选,支持 48.7 / 41.4 / 9.9,和 100 |
| Docker `test:db` | 执行方 37/0(含 widen 扩大 / 缩小拒绝 / 权限;08:0011:59 选定后仍 block_scan08:0009:00 进 minute);本机无 Docker,取其数字 |
| 项 | 结论 |
| --- | --- |
| D1 intake 三档 | 通过。三档文案"你对这个时间有多确定"approximate 四个范围按钮,`family_exact` 读档映射"差不多准",校验集合 {15,30,60,120} 两处同改 |
| D2 窗口读档案(BUG-571 | 通过。`deriveDeclaredSearchWindow`:医院/存量准确 → ±15;approximate → 前后各自 clamp(15..120);既有 04:4505:15 断言只保留给 family_exact,三栏齐 |
| D5 子段扫描(BUG-573 | 通过。`blocks` 进请求契约(1~5 段、须落窗内、除共享端点不重叠);步长随窗宽 10/5/2;`advance_*` 改为"新窗须是当前窗子区间"span>120 且 rounds<3 留 block_scan;最多 3 轮后强制 minute 并有旁白;全零均分改 100/n(执行方偏差合理:写死 20 在三段只到 60) |
| D3 放宽卡(BUG-572) | 通过。触发条件六项齐;RPC 校验含旧窗、inclusive ≤241、stage=minute、未采用;放宽后按 `stageForClockWindow` 决定去向;档案写回 approximate + 新半径,`hospital_record` 只改窗口;C/D 记指纹 |
| D6 可靠度一问 + 脚本 | 通过。只对本回合刚写入的日级事件问一次,答案正则映射 high/medium,薄 RPC 原地写(执行方偏差合理:修订链会改指纹误伤训练门);`docs/testing/rectification-scenarios-20260907.md` 四场景齐 |
| D4 记录 | 通过;四处书面偏差都在进度记录里写明 |
| P3 | `classifyDateReliabilityUtterance` 用「记得」判 medium,用户在该焦点下改说新经历("我记得 2018 年入职")会被顺带记成凭记忆;影响只到 `date_quality` 权重,可接受但建议把"记得"从正则里去掉,只留"凭记忆 / 印象 / 大概" |
| P3 | `patchV9BirthUncertainty` 直接 `from("profiles").update`,绕开 `account-profile-patch` 的校验层,档案由此多了第二条写路径;值是常量所以当前无害,建议改走同一 patch 函数 |
| P2(沿用) | `test_block_scan_seven_events_finishes_within_fifteen_seconds` 壁钟断言仍在,门禁可能间歇红 |
部署:staging 当前 `72dd5e9d`(迁移到 `20260907010000`),本轮迁移 `20260907020000` 未应用,部署前先 Migrate Staging Database。真实环境走查按 `docs/testing/rectification-scenarios-20260907.md` 四个脚本。