Files
Jyotisha/docs/tasks/TASK-rectification-window-cluster-cap-20260909.md
T

81 lines
9.1 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 · 一小时窗口后三分之一被 12 簇上限静默丢掉,真实出生时间从一开始就不在候选里;用户中途说出更窄的时间段无人接(2026-09-09)
- 基线:`origin/staging` @ `c41afe26`
- 分支:`codex/rectification-window-cluster-cap-20260909`,基于 `origin/staging`
- 执行方:coding agent;验收:Claude
- 涉及文件:`scripts/rectification/candidate_contrast.py``MAX_PUBLIC_CLUSTERS``select_signature_representatives`)、`scripts/rectification/decision_policy.py``build_candidate_decisions` 候选字段)、`scripts/rectification/api_service.py``frontend/src/lib/rectification-agentic/v9/inference-adapter.ts` / `core/build-state.ts` / `core/credible-range.ts`(簇范围)、`frontend/src/app/api/rectification/agent/route.ts`(自由文本分支)、`frontend/src/lib/rectification-agentic/v9/turn-intent-classifier.ts``v9/answer-choice.ts`(新卡片)、`v9/tool-service.ts`、新迁移 `frontend/supabase/migrations/2026091001xxxx_rectification_set_case_window.sql``frontend/src/lib/birth-time-intake-model.ts` + `components/birth-time-intake.tsx`(自定义范围)、`skills/jyotish-birth-time-rectification/SKILL.md`(不得口头承认改窗口)
- BUG 编号起点:**BUG-623**`docs/BUG_HISTORY.md` 当前最大 BUG-622
- 优先级:**P0**(真实用户:intake 填 14:0015:00,本人知道是 14:4514:50,系统收到 14:04–14:43 并继续出题;用户说出真实时段后助手口头答应却什么都没改)
## 1. 事故实证(2026-09-09 真实用户转录 + 本机引擎复现;不写用户资料)
| 转录 | 事实 |
| --- | --- |
| 第一轮证据写完范围就是 14:02–14:40 | 一小时窗口的第一次 compare 就只给出到 14:40 的候选 |
| 用户:"我的出生时间是 14 点 45 到 14 点 50" → 助手(1 步)"明白了,出生时间以你说的 14:45–14:50 为准" | 助手没有任何工具能改搜索窗口(`rectification-v9-tools.ts` 的 15 个工具里没有);opening brief 还明写"线索仅旁白建议,不得改搜索窗口"(`agent-run.ts` L256)。这是口头答应、实际未改 |
| "继续吧" → "我不太确定这句是不是在回答上面的问题" | 当前焦点是点选题,自由文本进 `classifyRectificationTurnIntent`,枚举里没有"申报时间段"这一意图,落 `unclear` |
| 范围仍 14:04–14:43 | 真实时段从头到尾不在候选集里 |
**本机复现**(虚构盘 1997-08-08`minute_step=1`):
| 窗口 | 分钟数 | 签名簇数 | 保留簇 | 保留到的最后一分钟 | 被丢掉的簇 |
| --- | --- | --- | --- | --- | --- |
| 14:0015:00 | 61 | **17** | 12 | **14:39** | 14:4014:45、14:4614:51、14:5214:53、14:5414:57、14:5815:00 |
| 14:3015:00 | 31 | 9 | 9 | 15:00 | 无 |
| 04:4505:15 | 31 | 9 | 9 | 05:15 | 无 |
半小时窗口刚好压在 12 以内,所以前面十几次实测(都是 ±15)从未暴露;用户一选"前后半小时"(60 分钟窗)就必然丢后段。
## 2. 根因
### 2.1 BUG-623P0):候选簇按时间排序后取前 12 个,多出的整簇丢弃
`candidate_contrast.py::select_signature_representatives``cluster_contexts_by_signature` 把簇**按代表分钟时间排序**,循环里 `if len(representatives) >= MAX_PUBLIC_CLUSTERS: break``MAX_PUBLIC_CLUSTERS = 12`)。超过 12 簇时,被丢的永远是窗口尾部。这发生在任何证据评分之前,后面所有的"范围收到 …"都只在残缺候选集内进行;旁白与时间轴把 14:04–14:43 说成收敛结果,实际上 14:40 之后从未被考虑过。
### 2.2 BUG-624(P1):可信区间按代表分钟的跨度算,不按簇的实际覆盖算
引擎候选只带 `time`(簇里分数最高的那一分钟),不带簇成员;推断层 `cluster_range` 是用换升时刻在代表分钟之间重新聚的,多数是单分钟;`credible-range.ts` 取 still-valid 候选的 `cluster_range``time` 的跨度。于是即便 14:40–14:45 这一簇活着,若代表分钟是 14:40,区间右端也只写到 14:40,簇内其余五分钟被"视觉淘汰"。
### 2.3 BUG-625(P1):中途说出时段时助手口头答应改窗口,但什么都没改
没有"申报时间段"意图;没有把窗口改窄的 RPC(`widen_agentic_rectification_case_window` 要求 `v_new_width > v_old_width`);模型在采集焦点下收到这句话就自己"答应"了。intake 的"家人记得大概时间"只能选 ±15 / ±30 / ±60 / ±120,知道 14:45–14:50 这种小范围的用户没有地方填(`customDeclaredRange` 只给 `period_only`)。
## 3. 决策记录
1. **去掉按时间截断。** `select_signature_representatives` 不再 `break``MAX_PUBLIC_CLUSTERS` 改为安全上限 64(±120 窗口 241 分钟实测簇数写进度记录);若真超过 64,按**分数**合并相邻低分簇,不丢尾部。公开候选数变多(一小时 ≈17)不影响四选项合同:探针的 `candidate_ids` 本来就是分钟集合。
2. **候选带簇成员。** `build_candidate_decisions` 每个候选加 `cluster_times: [HH:MM…]`(连续分钟段可写 `cluster_start/cluster_end`);`inference-adapter.ts` 把它作为 `cluster_range` 的来源(换升聚类只作退化兜底);`credible-range.ts` 的区间 = still-valid 簇覆盖的并集跨度。时间轴的实心/空心点仍按候选(代表分钟)画,簇范围只影响区间带。
3. **中途说出时间段:不改窗口,固定回一句,不进模型。**(产品 2026-09-09 否决了"确认卡改窗口"的方案:让用户口头改范围,校正就没有意义。)`route.ts` 自由文本分支前置确定性解析(`HH:MMHH:MM`、"14 点 45 到 14 点 50"、"14:45 左右"等),命中即持久化一条固定回复:"搜索范围是开始时按你的资料定的,校正过程中不改。想按别的时间段重来,请先到资料里改出生时间,再新建一次校正。"当前焦点保持不变(点选题继续挂着)。不新增 RPC、不新增迁移。
4. **模型不得口头承认改时间。** SKILL.md §4 加一句:"用户说出出生时间或时段时,不得回答『以你说的为准』或改写搜索窗口;服务端会出确认卡。"并在 `agent-run` 的正文守卫里把"以你说的…为准"列入机器词表删除。
5. **intake 自定义范围(可选,等产品答复)。** 这是开始前的申报,不是中途改窗口;"家人记得大概时间"下方可加"我知道一个更小的范围"(两个时钟选择器 → `customDeclaredRange`,宽度 3241 分钟,仍是 `minute` 阶段)。产品若认为申报越窄校正越无意义,此条删除。
6. 不动淘汰阈值、`SCORE_DELTA`、采用/确认门、`_relative_support``minute_step`。本单**不新增迁移**(决策 3 改为固定回复后不再需要收窄 RPC)。
## 4. 任务分解
- 4.1 BUG-623:改 `select_signature_representatives``tests/test_rectification_v5_services.py` 加"14:0015:00 虚构盘 17 簇全部保留、候选含 14:46–14:51 簇的代表";用本单 §1 表格做回归夹具(虚构盘,不含真实资料)。
- 4.2 BUG-624`cluster_times` 贯通到 `credible_range`;用例——簇 14:4014:45 存活、代表 14:40 → 区间右端 14:45。
- 4.3 BUG-625:解析器 + 固定回复;用例——点选焦点下输入"我的出生时间是 14 点 45 到 14 点 50"→ 落库固定回复、无模型调用、`candidate_range` 不变、焦点不变;采集焦点下同样;"14:47 左右"同样命中;不含时间的普通句子不命中。
- 4.4 决策 4Skill 10.0.20(只加一句);`agent-voice-copy-contract` 收禁用短语。
- 4.5 决策 5:等产品答复后再做;做则 `birth-time-intake*.test.ts` 三栏。
- 4.6 记录:BUG-623P0,关联 BUG-560 校准记录:以前的"分钟级≈随机"结论是在 ±15 窗口上得出,一小时窗口还叠加了截断)、BUG-624、BUG-625`CHANGELOG.md``PROGRESS-…``docs/testing/` 加"±30 窗口下候选必须覆盖整窗;中途说出时段必须出确认卡;助手不得口头答应改时间"。
## 5. 让步顺序
4.1 当天必做并部署(一行改动止血);4.2、4.3 必做;4.4 随 4.34.5 等产品;4.6 不可省。
## 6. 给当前这位用户的临时办法(产品可直接转告)
在资料里把出生时间改成 14:47,来源选"家人记得大概时间 · 差不多准"(±15,窗口 14:3215:02,31 分钟、9 簇,不会触发截断),重新开始校正。或者选"有出生证或医院记录"填 14:47。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-window-cluster-cap-20260909 .worktrees/rectification-window-cluster-cap-20260909 origin/staging
cd .worktrees/rectification-window-cluster-cap-20260909
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
ln -s /workspace/Jyotisha/.venv .venv
.venv/bin/python -m pytest tests/test_rectification_v5_services.py -q
cd frontend && ./node_modules/.bin/tsc --noEmit; npm run lint 2>&1 | tail -1
ls tests/rectification-*.test.ts tests/birth-time-intake*.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
```