docs(tasks): rectification capability briefs vs upstream (explain layer, range reading BUG-568, unknown-time route)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
@@ -63,6 +63,9 @@
|
||||
| `TASK-rectification-collect-vs-offer-consistency-20260905.md` | `PROGRESS-rectification-collect-vs-offer-consistency-20260905.md` | 带年份采集没问完就出采用卡 + 报告,同一轮又被搬家采集题把卡挤掉:决策层判 `adopt_representative` 而计划层仍有 dated 采集(BUG-546 只修了一半);改为剩余采集未完保持 `collect_evidence`,出牌轮才出卡写报告 | 已验收通过 `ca4e2408`(2026-09-05;staging 部署仍停在 `afd14948`,`deploy-staging` 自 `c295b853` 起连续失败,先解决 `bab07187` 的待迁移) | `codex/rectification-collect-vs-offer-consistency-20260905`(BUG-550) |
|
||||
| `TASK-rectification-convergence-exit-20260906.md` | `PROGRESS-rectification-convergence-exit-20260906.md` | 采集问完落到「也可以再说一件事」无出口(非收敛出牌/非终止修复兜底 `other`)、同年月多领域拆题与同域同年重复问、`relative_support` 正比例归一把引擎证据压平成 7–9 使 lead 8 不可达;改为穷尽即交付 + 口述态停止按钮、同年月合并为「发生了什么」四选卡 + 同域同年去重 + 性格题须锚点、prior 按 (分−窗口最低分) 归一并过 20 例公开 holdout 校准门 | 已验收(部分完成:B2 合并卡未做、C 校准未过门未启用;1 P1 + 2 P2 见修复单) | `150d7ef1` / `3a9ae736` / `62afa521`(BUG-558~560,已合入 staging)|
|
||||
| `TASK-rectification-convergence-exit-fix-20260906.md` | `PROGRESS-rectification-convergence-exit-fix-20260906.md` | BUG-558 修复单:门槛翻译句每回合写 2~3 条相同助手消息(`persistExhaustionCollect` 自己写 turn + 非终止修复再写一次)、穷尽分支排在问题持久化前吞掉可渲染区分卡/holdout 题、范围小字变按钮但文案仍是状态句;并把校准结论(引擎原始分分钟级区分力≈随机)写进 BUG-560 与 BLOCKED | 待领取 | `codex/rectification-convergence-exit-fix-20260906`(BUG-565~567) |
|
||||
| `TASK-rectification-explain-layer-20260906.md` | `PROGRESS-rectification-explain-layer-20260906.md` | 过程解释层(对照上游 yinduzhanxing 旧工作台唯一领先的业务层):每张卡服务端生成「为什么问这题」与 A/B/C/D「答了会怎样」、答后旁白改成「哪段升降 + 范围从 X 收到 Y」、每轮步骤条「第 N 步 / 为什么 / 下一步」;不做双视图 | 待领取(串行:在 exit-fix 之后、range-reading 之前) | `codex/rectification-explain-layer-20260906` |
|
||||
| `TASK-rectification-range-reading-20260906.md` | `PROGRESS-rectification-range-reading-20260906.md` | 可信区间成为一等公民:采用时落库 `adopted_credible_range`,报告 `read_report_candidate_range` 与聊天 `verified_chart(accepted)` 都改读它并接同一份 `birth_time_sensitivity`(现在报告读的是开工窗口,BUG-568);引擎 >15 分钟只取 3 样本改为 ≤31 逐分钟;采用旁白加「稳定 / 随分钟变」两句;`declared_birth_window` 复用 | 待领取(串行:在 explain-layer 之后) | `codex/rectification-range-reading-20260906`(BUG-568) |
|
||||
| `TASK-rectification-unknown-time-20260906.md` | `PROGRESS-rectification-unknown-time-20260906.md` | 完全不知道出生时间的两段式路线:`stage=block_scan` 以 10 分钟步长扫 24 小时只做事件计分、出五时段四选卡(不写账本不采用),选定后进现有分钟流程;引擎加 `minute_step`;开场读 `birth_time_clue`;删 intake 劝退文案 | 待领取(串行:在 range-reading 之后) | `codex/rectification-unknown-time-20260906` |
|
||||
| `TASK-api-not-configured-mislabel-20260904.md` | `PROGRESS-api-not-configured-mislabel-20260904.md` | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 已验收 | `5483649b`(BUG-542);2 条子进程测试留 CI Node 22 复核 |
|
||||
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) |
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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)"
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
# TASK · 生时校正:可信区间成为一等公民,报告与对话按"稳定层 / 分钟敏感层"读盘(2026-09-06)
|
||||
|
||||
- 基线:`origin/staging` @ `b938c76a`(文档头 `211f9bb2`)
|
||||
- 分支:`codex/rectification-range-reading-20260906`
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- 串行:排在 `TASK-rectification-convergence-exit-fix-20260906.md`(BUG-565~567)与 `TASK-rectification-explain-layer-20260906.md` 之后,三者都改 `adopt-narration.ts` / `rectification-agentic-chat.tsx`
|
||||
- 涉及文件:`frontend/supabase/migrations/`(新迁移)、`frontend/src/lib/report-candidate-range.ts`、`frontend/src/lib/personal-report-route-core.ts`、`frontend/src/lib/consultation-route-service.ts`、`frontend/src/lib/consultation-birth-time-mode.ts`、`frontend/src/mastra/consultation-workflow.ts`、`frontend/src/lib/rectification-agentic/v9/tool-service.ts`、`adopt-narration.ts`、`scripts/jyotish_engine.py::_candidate_minutes`、`scripts/rectification/api_service.py`(新函数)
|
||||
- 不改:`scripts/jyotish_api_server.py` 主体(只允许一行薄注册)、`frontend/src/app/page.tsx`、任何采用/确认门
|
||||
- BUG 编号起点:**BUG-568**(开工时复核;565~567 已被修复单预留)
|
||||
|
||||
## 0. 为什么做这件事
|
||||
|
||||
昨天的 20 例公开 holdout 校准说明引擎原始分在分钟级几乎没有区分力(见父单 `TASK-rectification-convergence-exit-20260906.md` 验收段与 BUG-560)。既然大多数校正最后都停在一个 10~30 分钟的可信区间,产品最诚实、也最有价值的交付就是:**告诉用户这段区间里哪些结论稳定、哪些结论随分钟变**,而不是把代表分钟当成精确时间去排盘。上游 yinduzhanxing 的 `FlexibleBirthTimeProfile` 合同(稳定证据 / 分钟敏感证据分开、多分钟区间不建单分钟盘)就是这个思路;它的四个脚本 9-03 已经同步进本仓,引擎 `_build_birth_time_sensitivity` 也已经在报告链上跑,但接的是错的窗口。
|
||||
|
||||
## 1. 现状实证(代码定位,非猜测)
|
||||
|
||||
| # | 事实 | 位置 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 报告的 `birth_time_sensitivity` 窗口来自 RPC `read_report_candidate_range`,它返回 `agentic_rectification_cases.candidate_range`——即校正**开工时**的搜索窗口(填报时间 ±15 分钟,或时段 4~6 小时),不是 `latest_result.credible_range` | `frontend/supabase/migrations/20260904010000_read_report_candidate_range.sql` L50–68;`personal-report-route-core.ts::resolveReportBirthTimeSensitivityInput` |
|
||||
| 2 | 采用(accept)RPC 只写 `p_result_id / p_candidate_id`,可信区间不落库;profile 上只有 `active_birth_time` + `birth_time_status=accepted`,`uncertainty_before/after` 仍是填报值 | `tool-service.ts` L1726 `accept_agentic_rectification_candidate_for_case_v2` |
|
||||
| 3 | 引擎窗口 >15 分钟时只取 3 个样本(起点 / 代表 / 终点)做稳定-敏感分类,27 分钟区间等于只看 3 个分钟 | `jyotish_engine.py::_candidate_minutes` L2191 |
|
||||
| 4 | 聊天:`birth_time_status=accepted` → `verified_chart` 模式,按 `active_birth_time` 当精确分钟排盘;`birth_time_accuracy` / `candidate_range` 只在报告路径传,聊天不传 | `consultation-route-service.ts` L254、L515;`consultation-workflow.ts` L22–23 |
|
||||
| 5 | 校正采用轮只交付代表分钟 + 八法报告;没有"这段区间里哪些主题稳定"的话 | `adopt-narration.ts`、SKILL.md §9 |
|
||||
| 6 | `declared_birth_window` 聊天模式已存在(未校正的时段用户),只做输出守卫降级,不做稳定/敏感分层 | `consultation-birth-time-mode.ts` L84–95 |
|
||||
|
||||
## 2. 决策记录(PM 代为落地,可否决)
|
||||
|
||||
1. **采用即落库可信区间。** `accept_agentic_rectification_candidate_for_case_v2` 同时写入 `adopted_credible_range jsonb`(`{start_time,end_time,representative_time,width_minutes,source:"inference_credible_range"}`),来源 = 采用那一刻决策层的 `credibleRange`(与 UI 上"目前范围"同源)。用户改选候选时重写。
|
||||
2. **报告与聊天优先读它。** `read_report_candidate_range` 先返回 `adopted_credible_range`,没有再退回 `candidate_range`;聊天 `verified_chart` 且 `status=accepted` 时把 `birth_time_accuracy="provisional"` + `candidate_range` 传进 workflow,与报告同一份 `birth_time_sensitivity`。`status=confirmed` 仍是 `confirmed`,不变。
|
||||
3. **采样规则。** 窗口 ≤ 31 分钟逐分钟;> 31 分钟等距取 31 个样本(含起点、代表、终点),并在 `window` 里标 `sampled: true`。`flexible_birth_time_profile` 的 2~31 上限不改。
|
||||
4. **校正采用轮多一段话。** 采用旁白在八法报告之后加"稳定 / 随分钟变"两句(服务端从 `theme_sensitivity` 生成,模型不得自编),例:"这 27 分钟里,事业方向、性格底色的判断是稳定的;婚恋(D9)和学业(D24)会随分钟变,看盘时按范围读。"
|
||||
5. **`declared_birth_window` 模式也用同一分层**:时段用户没做校正也能拿到"这个时段里稳定的是什么",但窗口 > 31 分钟按 3 采样,措辞里必须写明"只是粗看"。
|
||||
6. 不动任何门:采用门、确认门、`MIN_SEPARATION_LEAD`、报告的 `birthTimePolicy` 枚举都不改。
|
||||
|
||||
## 3. 硬红线
|
||||
|
||||
- 迁移只加列 + 改一个 RPC;必须真跑 `npm run test:db --prefix frontend`(无 Docker 时写 `BLOCKED.md`,不得标通过)。
|
||||
- `adopted_credible_range` 不得反向改写 `candidate_range`(那是开工基线,重算指纹依赖它)。
|
||||
- 稳定 / 敏感句只允许来自 `theme_sensitivity`,不得由模型补写;文案对照 `frontend/docs/VOICE.md`,保留"不是已确认的唯一出生分钟"边界句。
|
||||
- `scripts/jyotish_api_server.py` 只允许薄注册一行;新逻辑进 `scripts/rectification/api_service.py`。
|
||||
- 测试总数 ≥ 开工实测;`tsc` 0 错;lint 0 error;`run_quality_gate.py --profile quick`。
|
||||
|
||||
## 4. 任务分解
|
||||
|
||||
### A1 落库(BUG-568:报告敏感度窗口用的是开工窗口)
|
||||
- 迁移 `agentic_rectification_cases.adopted_credible_range jsonb null`;`accept_agentic_rectification_candidate_for_case_v2` 新增可选参数 `p_credible_range jsonb`;`tool-service.ts` 采用时从 `decision.credibleRange` + `representativeTime` 组装传入;改选时覆盖。
|
||||
- `read_report_candidate_range`:优先 `adopted_credible_range`,否则原逻辑。
|
||||
- 验收:`rectification-v9-database.test.ts` 新增采用后读回区间用例(Docker);无 Docker 时 SQL 单元断言写进 `BLOCKED.md` 并附手工 psql 验证步骤。
|
||||
|
||||
### A2 引擎采样
|
||||
- `_candidate_minutes(start, representative, end)`:≤31 全取;>31 等距 31 点;返回值不变(`list[str]`)。
|
||||
- 验收:`tests/test_jyotish_engine_birth_time_sensitivity*.py`(若无则新建)——27 分钟窗口返回 27 个样本;60 分钟返回 31 个且含起点/代表/终点。
|
||||
|
||||
### A3 聊天接同一份敏感度
|
||||
- `consultation-route-service.ts`:`verified_chart` 且 `birth_time_status=accepted` 时加载 `adopted_credible_range`(复用 `loadReportCandidateRange`),把 `birth_time_accuracy="provisional"` 与 `candidate_range` 放进 workflow 输入;`consultation-workflow.ts` 把 `birth_time_sensitivity.theme_sensitivity` 映射到 `consumer_context.answer_policy.minute_sensitive_themes: string[]`;系统提示块加一句固定说明(服务端文案),敏感主题的应期结论走 `guardPreciseTimingOutput` 同级降级。
|
||||
- 验收:`consultation-*.test.ts` 新增——accepted + 有区间 → workflow 输入含 `candidate_range`,`answer_policy.minute_sensitive_themes` 非空;confirmed → 不含。
|
||||
|
||||
### A4 校正采用轮的"范围读盘"
|
||||
- `scripts/rectification/api_service.py` 新增 `range_reading(request)`:输入出生资料 + 区间 + 代表分钟,内部调 `_build_birth_time_sensitivity`,返回 `{window, stable_themes[], sensitive_themes[], claim_boundary}`;`jyotish_api_server.py` 只加一行注册 `/api/rectification/v5/range_reading`。
|
||||
- `engine-client.ts` 新增 `runV9RangeReading`;`adopt-narration.ts` 在采用旁白追加两句(决策 4);`rectification-offer-candidates` 工具返回里带 `range_reading`,SKILL 不改(措辞由服务端旁白承载)。
|
||||
- 验收:`rectification-adopt-narration*.test.ts` 新增——给定 theme_sensitivity fixture,旁白含"稳定"与"随分钟变"两句且含边界句;引擎不可用时旁白不带这两句、不报错。
|
||||
|
||||
### A5 `declared_birth_window` 复用(决策 5)
|
||||
- 同 A3 路径,`birth_time_accuracy="approximate"`,`candidate_range` = 声明时段;措辞加"只是粗看"。
|
||||
- 验收:一条 consultation 测试。
|
||||
|
||||
### A6 记录
|
||||
- `docs/BUG_HISTORY.md` BUG-568;`CHANGELOG.md`;`docs/tasks/PROGRESS-rectification-range-reading-20260906.md`;`docs/testing/rectification-range-reading-20260906.md`(真实环境:采用后新建对话,回答里对婚恋/学业类问题出现范围口径;报告出生时间敏感度节的窗口等于采用时的"目前范围");`frontend/DESIGN.md` 若采用旁白布局有变。
|
||||
|
||||
## 5. 让步顺序
|
||||
|
||||
A1 + A2 + A4 是核心,不可拆;A3 必做;A5 可后置到同分支第二次提交;A6 不可省。
|
||||
|
||||
## 6. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-range-reading-20260906 .worktrees/rectification-range-reading-20260906 origin/staging
|
||||
cd .worktrees/rectification-range-reading-20260906
|
||||
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
|
||||
ln -s /workspace/Jyotisha/.venv .venv
|
||||
cd frontend && ls tests/rectification-*.test.ts tests/consultation-*.test.ts tests/agent-voice-copy-contract.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"; cd ..
|
||||
.venv/bin/python -m pytest tests/test_rectification_v5_services.py -q
|
||||
grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1
|
||||
```
|
||||
@@ -0,0 +1,95 @@
|
||||
# TASK · 生时校正"完全不知道出生时间"路线:先比时段,再进分钟(2026-09-06)
|
||||
|
||||
- 基线:`origin/staging` @ `b938c76a`(文档头 `211f9bb2`)
|
||||
- 分支:`codex/rectification-unknown-time-20260906`
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- 串行:排在 `TASK-rectification-range-reading-20260906.md` 之后(都改 `case-service.ts` / 采用旁白)
|
||||
- 涉及文件:`scripts/rectification/contracts.py`、`scripts/rectification/scoring_service.py`(`calculation_spec`)、`scripts/active_rectification_event_engine.py::_candidate_datetimes`、`scripts/rectification/api_service.py`、`frontend/src/lib/rectification-agentic/v9/case-service.ts`、`engine-client.ts`、`method-followup.ts`、`choice-card.ts`、`frontend/src/app/api/rectification/cases/open/route.ts`、`frontend/src/components/birth-time-intake.tsx`、`frontend/src/lib/declared-birth-window.ts`
|
||||
- 不改:`scripts/jyotish_api_server.py` 主体、`page.tsx`、采用/确认门
|
||||
- BUG 编号:能力补齐,不预留
|
||||
|
||||
## 0. 为什么做这件事
|
||||
|
||||
上游 yinduzhanxing 便携 skill 的 `unknown` 路线是:先用高信号领域比 `morning / afternoon / evening / night` 四个时段,赢的时段下一轮再切三段。我们的 intake 有 `birth_time_source=unknown`,但之后要么劝退("生时校正以后需要时再做"),要么按代码路径开一个 00:00–23:59 的 1440 分钟 Case——分钟级流程在 24 小时上既慢又没意义。另一方面,昨天的校准说明引擎在**分钟级**近乎随机,但上升星座每 2 小时换一次、Dasha 宫位命中在**时段级**的差异是真实存在的:时段比较恰恰是这套引擎最有把握的用法。
|
||||
|
||||
## 1. 现状实证
|
||||
|
||||
| # | 事实 | 位置 |
|
||||
| --- | --- | --- |
|
||||
| 1 | intake 选"完全不清楚"后,文案是"已跳过具体出生时间…生时校正以后需要时再做",并提供"我可以选一段时间范围"回退到 `period_only` | `birth-time-intake.tsx` L222–232 |
|
||||
| 2 | `declaredClockRange(source="unknown")` 返回 `00:00–23:59`;`deriveCandidateRange` 据此给 Case 一个 1440 分钟窗口;引擎 `_candidate_datetimes` 允许 ≤1440 | `declared-birth-window.ts` L31、L146;`case-service.ts` L123;`event_engine.py` L96 |
|
||||
| 3 | 旧 journey 链 `scanInput()` 对 unknown 返回 null(那条链已不是 v9 主链) | `birth-time-journey-assessment.ts` L13 |
|
||||
| 4 | intake 已收集 `birth_time_clue`(家人线索文本)并落 profile,但校正没有用它 | `birth-time-intake-model.ts` L379;`birth-time-journey-dynamic-case.ts` L58 |
|
||||
| 5 | 引擎耗时实测(虚构资料,3 件事):61 分钟窗口 0.9 s,241 分钟 3.2 s;线性外推 1440 分钟 ≈ 20 s,7 件事约 ×2 | 本单附录脚本 |
|
||||
| 6 | 时段模型:`DECLARED_PERIOD_RANGES` 五段(04–08 / 08–12 / 12–18 / 18–23 / 23–04) | `declared-birth-window.ts` L19 |
|
||||
|
||||
## 2. 决策记录
|
||||
|
||||
1. **两段式。** Stage 0"时段比较":Case 以 `stage="block_scan"` 打开,候选窗口 00:00–23:59,引擎以 **10 分钟步长**(144 个候选)只做事件计分,不生成探针、不出候选卡;输出五个时段各自的相对支持(按时段内候选分求和归一)与"哪些事件在拉开差距"。Stage 1:用户在时段卡上选定一段(或系统领先段被用户认可)→ Case 的 `candidate_range` 改为该时段(≤6 小时),`stage="minute"`,进入现有分钟流程。
|
||||
2. **时段卡是四选项**:A/B/C 取相对支持最高的三段(标出支持度),D "说不好 / 都不像"。选 A/B/C = 进入该段;选 D = 继续采集经历再比一次(同分钟流程的采集轮)。不写账本、不进推断层(时段选择是窗口决策,不是证据)。
|
||||
3. **家人线索先用。** 开场先把 `birth_time_clue` 读给模型:"家人记得天快亮 / 吃晚饭时"之类,模型只能把它映射成一个**建议时段**放在旁白里,不能改窗口;窗口只由用户点选或事件计分改。
|
||||
4. **门槛。** 时段比较前至少 3 件带年月经历、2 个领域(复用 `MIN_ACCEPTANCE_*`);不够就先采集。`block_scan` 阶段 `acceptance_allowed / selection_allowed` 恒 false,不得采用任何分钟。
|
||||
5. **引擎契约**:请求新增可选 `minute_step`(1~15,默认 1);`calculation_spec.minuteStep` 随之;`minute_step>1` 时 `discriminating_event_probes` 为空、`precision_stage` 为 `block_scan`;指纹包含 `minute_step`(步长不同结果不同)。
|
||||
6. 时段划分沿用 `DECLARED_PERIOD_RANGES` 五段,不引入上游的 dawn/noon 七段。
|
||||
|
||||
## 3. 硬红线
|
||||
|
||||
- `block_scan` 阶段不得出现候选卡、采用按钮、代表分钟;旁白不得说"更像 X 点"。
|
||||
- 分钟流程(stage=minute)的全部既有用例原样通过;`minute_step=1` 时引擎输出与改前逐字节一致(`test_rectification_v5_services.py` 已有指纹用例加一条)。
|
||||
- 24 小时 × 1 分钟不再允许开 Case(`deriveCandidateRange` 对 unknown 只走 block_scan)。
|
||||
- 引擎耗时:`block_scan` 单次 ≤ 15 s(7 件事),超出要在进度记录里写实测并给出步长/事件上限方案。
|
||||
- 真实用户资料不进测试。
|
||||
|
||||
## 4. 任务分解
|
||||
|
||||
### C1 引擎步长
|
||||
- `contracts.py` 接收 `minute_step`;`_candidate_datetimes` 用它;`calculation_spec` 带 `minuteStep`;`scoring_service.build_event_contribution_matrix` 的网格一致性检查沿用;`api_service.score_candidates` 在 `minute_step>1` 时跳过 `build_refinement_packet` 的探针部分,`precision_stage={"current":"block_scan"}`。新增 `api_service.block_scan(request)`:返回 `blocks: [{period, start_time, end_time, relative_support, top_events[]}]`(按五段聚合)。`jyotish_api_server.py` 只加一行注册。
|
||||
- 验收:`tests/test_rectification_v5_services.py`——`minute_step=1` 指纹/输出不变;`minute_step=10` 候选数 = 144;`block_scan` 五段支持度和为 100;耗时断言(≤15 s,7 件事虚构资料)。
|
||||
|
||||
### C2 Case 阶段
|
||||
- `agentic_rectification_cases` 加 `stage text default 'minute'`(迁移,需 `test:db`);`case-service.ts::deriveCandidateRange` 对 `unknown` 返回 00:00–23:59 且 `stage=block_scan`;`open` 路由透传。
|
||||
- 验收:`rectification-v9-database.test.ts`(Docker)或 `BLOCKED.md`;`case-service` 单测:unknown → block_scan。
|
||||
|
||||
### C3 采集与时段卡
|
||||
- `method-followup.ts`:`stage=block_scan` 时不走 `rankRenderableDiscriminators`,只走采集链;训练门开后由 `decideRectification` 新分支 `ask_block_choice`(不复用 `discriminate`);`choice-card.ts` 新 `choice_kind: "block_choice"`(TS 内部,不进 Python 探针契约);答题写 `case.candidate_range` + `stage=minute`,并触发一次正常重算。
|
||||
- `engine-client.ts` 新增 `runV9BlockScan`;`rectification-v9-tools.ts` 的 compare 在 block_scan 阶段调它。
|
||||
- 开场:`openingBrief` 加入 `birth_time_clue`(决策 3);intake 的 unknown 文案改为"可以直接开始生时校正:先从你记得的经历比出大致时段",删掉劝退句。
|
||||
- 验收:`rectification-block-scan-20260906.test.ts`——3 件事 2 域后 next_action 为 `ask_block_choice`;选 B 后 candidate_range = 该段、stage=minute、`snapshotCurrent=false`;选 D 后回到采集;block_scan 阶段 `can_adopt=false`、无候选卡。
|
||||
|
||||
### C4 记录
|
||||
- `CHANGELOG.md`;`PROGRESS-rectification-unknown-time-20260906.md`;`docs/testing/rectification-unknown-time-20260906.md`(真实环境:intake 选"完全不清楚"→ 开校正 → 说 3 件事 → 出时段卡 → 选一段 → 进入分钟流程);`frontend/DESIGN.md`、`frontend/docs/VOICE.md`;SKILL.md 若需要新增 `block_scan` 状态一行则 bump 到 10.0.15 并在 CHANGELOG 写明。
|
||||
|
||||
## 5. 让步顺序
|
||||
|
||||
C1 + C2 + C3 不可拆(缺一个就没有产品价值);C4 不可省。若耗时红线过不了,先把步长提到 15 分钟并写明。
|
||||
|
||||
## 6. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-unknown-time-20260906 .worktrees/rectification-unknown-time-20260906 origin/staging
|
||||
cd .worktrees/rectification-unknown-time-20260906
|
||||
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 tests/test_active_rectification_api.py -q
|
||||
cd frontend && ls tests/rectification-*.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
|
||||
```
|
||||
|
||||
## 附录 · 耗时复现(虚构资料)
|
||||
|
||||
```python
|
||||
# .venv/bin/python,在仓库根目录
|
||||
import time, uuid, sys
|
||||
sys.path.insert(0, "."); sys.path.insert(0, "scripts")
|
||||
from scripts.rectification.contracts import normalize_rectification_request
|
||||
from scripts.rectification.api_service import score_candidates
|
||||
EV = [("education","education_start","2016-09-01","2016-09-30","month"),
|
||||
("relationship","relationship_end","2024-08-08","2024-08-08","day"),
|
||||
("relocation","relocation","2023-07-01","2023-07-31","month")]
|
||||
for start, end in (("04:30","05:30"), ("13:00","17:00")):
|
||||
req = normalize_rectification_request({"birth_date":"1998-03-15","start_time":start,"end_time":end,
|
||||
"lat":39.9042,"lon":116.4074,"tz":8.0,"events":[{"id":str(uuid.uuid4()),"domain":d,"event_kind":k,
|
||||
"date_start":s,"date_end":e,"precision":p,"summary":k} for d,k,s,e,p in EV]})
|
||||
t = time.time(); out = score_candidates(req)
|
||||
print(start, end, len(out["candidate_scores"]), "minutes", round(time.time()-t, 1), "s")
|
||||
```
|
||||
Reference in New Issue
Block a user