Files
Jyotisha/docs/tasks/TASK-chart-ephemeris-fixes-20260924.md
T

94 lines
8.3 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 · Chara 0 年大运崩溃 + 星历切日期旧数据 + 星盘无资料空状态(2026-09-24)
## 基线
- `origin/staging = 9c9b66d6`。分支 `codex/chart-ephemeris-fixes-20260924`。
- **串行**:T1、T2 可立即做;T3 碰 `page.tsx` / `settings-url.ts`,**排在 `TASK-home-slow-network-20260924.md` 合入之后**(或 T3 单独后合)。大运页、西洋盘 UI 重做**不在本单**(等原型定稿另出单),本单只修 bug 不改版式。
## 事故实证
**T1 · 大运右栏「这张盘算不出来,我们已经记录下来了。」**
- `frontend/src/lib/chart-view-mapper.ts` `charaTrack()` → `addWeightedSpan(start, end, index, weights)`,权重 `finite(duration_years) ?? 1`;`?? 1` 不替换 `0`。某段 Chara 大运为 0 年时 12 个子运全为 0 → `total = 0` → `0/0 = NaN` → `new Date(NaN).toISOString()` 抛 `RangeError`。
- `chart-view-load.ts` `assembleChartView` catch 非 ZodError → `engine_bad_payload` → `use-chart-page.ts` `requestLayer` `fail(...)` → `chartLayerFailureCopy` → `unavailable` 句。
- 0 来自 `scripts/jaimini.py` `_chara_dasha_duration_profile`(kn_rao):`years = count-1`,`<=0` 改 12,**之后**再按旺 +1 / 弱 -1,于是"一宫之隔且落陷"= 0。排查实测随机 2000 个生日(1960–2005)有 **7.8%** 至少一段 0 年;虚构 1985-08-03 03:59 北京可复现(Sagittarius 0 年)。
- 引擎响应形状与 mapper 读取路径一致,不是请求或超时问题。
**T2 · 星历切换日期,下方仍是前一天数据**
- `frontend/src/components/ephemeris/ephemeris-page.tsx` `changeDate`:`setDate(next); const cached = …; if (cached) setState(ready…)`,**缓存未命中无 else**,`state.payload` 保留旧日;顶部日期已是新日。
- 回归来源:`9ccb65b7`(BUG-966,09-18)删掉原 `else setState({ phase: "open" })`,因为同时加的 `if (!payload) return <SecondaryPageShell waiting…>` 会把整页(含日期条)换成等待句。
- 请求失败(429 / 非 JSON / 坏包)时 `load` 直接 `return`,旧数据**永久**留着,且不重试(effect 只随 `date` 变)。
- 挂载时 `refreshEphemerisPage()`(今天、无 date)不可中止,其回写守卫只比 `payload.date`,会在"切到未缓存日期"期间把今天的数据写回。
- `app/api/ephemeris/route.ts` 每切一天都并行重算本命 `/api/chart`(与日期无关),且不把 `request.signal` 传给引擎调用。
- 违反 `frontend/DESIGN.md` §16:「切日期立刻换五要素和行运……数据未到时写静态句「这一天的五要素还没拿到。」「这一天的行运还没拿到。」」。
- 无 BUG 记录、无测试覆盖 `EphemerisPage` 状态机(`ephemeris-page.test.tsx` 只静态渲染 `EphemerisView`)。
**T3 · 星盘页无出生资料时只有一句话**
- `chart-page-view.tsx`:任何非 ok 状态只渲染 `<section className="chart-page-message"><p>{view.message}</p></section>`;`birth_profile_incomplete` 文案「还没有可用来排盘的出生资料。把日期、时间和地点填进星盘资料后,打开就能看见盘。」但**没有按钮**;`lib/settings-url.ts` `parseSettingsQuery` 只支持 `?settings=billing`,没有跳到星盘资料的深链。
## 根因
- T1:mapper 按权重分配时间,未处理总权重为 0;引擎 kn_rao 口径可产出 0 年(合法值,不是引擎错误)。
- T2:BUG-966 为避免整页换等待句,删掉了"未命中就清空",导致日期与数据分叉;失败无出口。
- T3:失败页是纯文案设计,缺行动入口与深链。
## 决策记录(产品 2026-09-24)
- **D1** 0 年大运**照实显示**:该段标注「0 年」,不占时间轴、不展开子运(子运区写「本段时长为 0,不展开」),前后段日期连续;**不改引擎口径**(kn_rao 规则不动)。
- **D2** 星历切到未缓存日期:日期条与布局保留,五要素 / 行运区立即换成现有静态句(「这一天的五要素还没拿到。」「这一天的行运还没拿到。」),事件区写新句「换座和停滞还在取。」(不再误用「还没接上」)。**不加 spinner / 骨架**(星盘骨架例外不外溢到星历)。请求失败 → 各区显示「这一天的星历没取到,点日期再试一次。」,再点同一日期或左右箭头即重试。
- **D3** 星盘无资料:`chart-page-message` 加标题「还没有星盘资料」+ 现有文案 + 按钮「去填写出生资料」→ 打开设置弹窗的星盘资料面板(新增深链 `?settings=chart`,与 `?settings=billing` 同一 helper)。`timezone_resolver_failure` / `adopted_calculation_incomplete` / `profile_query_error` 也给同一按钮(文案各自不变)。样式:居中、标题 `--type-title-md`、按钮为主按钮,其余沿用现有 token。
- **D4** `/api/ephemeris` 本命盘按账户 + 资料指纹做服务端短缓存(复用 `chart-view-engine` 的 `cachedPostEngine` 或同等 5 分钟 TTL),并把 `request.signal` 传入引擎调用。
## 硬红线
1. 不改 `scripts/jaimini.py` 口径;不改 `HEAVY_COMPUTE_PATHS` 与并发(BUG-717)。
2. 星历不得出现骨架 / spinner / 「正在加载」(DESIGN §9 例外只限星盘)。
3. 旧日期响应不得覆盖新日期(含挂载时的今天刷新)。
4. 深链解析只在 `settings-url.ts` 一处;`/`、`/chart`、`/ephemeris` 保持 `○ Static`。
5. 既有断言三栏;测试总数不低于基线;不改 Python。
## 任务分解
### T1 · Chara 0 年(`chart-view-mapper.ts`、`chart-dasha-tab.tsx`)
- `addWeightedSpan` / `charaTrack` 处理 0 权重与 0 年大运;0 年段在 `Track` 里显示「0 年」且无子运列表。
验收:`chart-view-mapper` 测试用**真实引擎响应** golden(虚构 1985-08-03 03:59 北京,按 AGENTS §7.4 由真实 `_compute_chara_dasha` 生成并入库 fixture)→ 12 段 periods、Sagittarius 段 0 年、无 `RangeError`、前后日期连续;`assembleChartView({layers:["chara"]})` 返回 ok;UI 测试 0 年段不渲染子运。
### T2 · 星历(`ephemeris-page.tsx`、`ephemeris-view.ts`、`app/api/ephemeris/route.ts`)
- `state` 只在 `payload.date === date` 时视为 ready,否则 `EphemerisView payload={null}`(保留日期条);事件区 pending 文案;失败态与重试;挂载刷新守卫比对 `date`;路由本命缓存 + 传 signal。
验收:新增 `EphemerisPage` 生命周期测试——切到未缓存日期立即显示静态句且日期条仍在;旧响应迟到不覆盖;429 → 失败句,再点重试成功;挂载时今天刷新不覆盖正在看的其它日期。路由测试:同账户连续两天只调一次本命 `/api/chart`。
### T3 · 星盘空状态 + 深链(`chart-page-view.tsx`、`settings-url.ts`、设置弹窗打开入口;**排在 home-slow-network 之后**)
验收:四种资料类失败都渲染按钮;点按钮 → `/?settings=chart` → 设置弹窗打开到星盘资料;`?settings=billing` 行为不变;`/chart` 仍 `○`。
### T4 · 记录
- `docs/BUG_HISTORY.md`:**BUG-1023** Chara 0 年大运前端崩溃(关联 BUG-715、1017);**BUG-1024** 星历切日期旧数据(**复发自 BUG-966 的修复副作用**,写明旧测试为何没拦住);**BUG-1025** 星盘无资料无入口。
- DESIGN §15 / §16、VOICE(新增三句)、CHANGELOG、PROGRESS、`docs/testing/chart-ephemeris-fixes-20260924.md`(Slow 3G 下星历连点三天、断网切日期、0 年大运样例、无资料账号点按钮)。
## 让步顺序
1. T1 若 golden 生成需要 swisseph 而执行环境无,允许用排查时的虚构生日在有 swisseph 的机器生成后入库,注明生成命令。
2. D4 缓存若与路由结构冲突,可只做传 signal,缓存写入 BLOCKED。
3. 不得让步:D1 不改引擎、红线 2、红线 3。
## 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/chart-ephemeris-fixes-20260924 .worktrees/chart-ephemeris-fixes-20260924 origin/staging
cd .worktrees/chart-ephemeris-fixes-20260924/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5
npx tsx --test tests/ephemeris-page.test.tsx tests/ephemeris-route.test.ts tests/chart-page-view.test.tsx tests/chart-page-hook.test.ts
```
## BUG 编号
本单 **BUG-1023 / 1024 / 1025**(1021/1022 由慢网单预留),开工时核对。