43a26a3ccb
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
96 lines
9.3 KiB
Markdown
96 lines
9.3 KiB
Markdown
# 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")
|
||
```
|