Files
Jyotisha/docs/tasks/TASK-chart-vedastro-decouple-20260915.md
T
Jesse_ChenandClaude Fable 5 ce1939b074 docs(tasks): 星盘页与 VedAstro 外网调用脱钩 + 运行期真相两单
星盘页首屏那一发 /api/chart 没传 skip_vedastro_main_entry_overview,
本机实测冷算 0.40–0.66 秒里约 0.36 秒是 VedAstro 空转(连 endpoint 都
没配的情况下);带标志的同一调用是 5 毫秒。生产 env 开着 network 与
fanout,等于首屏同步等 24 个外部请求加 3 次领域扫描,而前端 mapper 与
contract 根本不读这份证据。BUG-718,复发自 BUG-161。

运行期单记录两条新发现:官方 vedastro==1.23.25 其实是 REST 客户端,
且 import 时请求 pypi 并 pip install --upgrade 自升级(实测 pin 装完
一 import 即变 1.23.26);无 key 时免费层排队是同步 sleep 加进程级
全局锁,24 个请求约 4.8 分钟堵住前台线程。台账补 ERR-107 / ERR-108,
生产 env 核对清单交产品负责人执行。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-09-15 15:47:32 +00:00

147 lines
12 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 · 星盘页首屏与 VedAstro 外网调用脱钩(2026-09-15
## 基线
- 基线 commit`origin/staging` = `69ede436``fix(ci): migrate staging automatically before deploy`
- 分支:`codex/chart-vedastro-decouple-20260915`
- 串行依赖:本单改 `frontend/src/lib/chart-view-load.ts`,与 `TASK-chart-page-blocking-open-20260915`BUG-715/716/717,代码已在 staging)同文件。该单的代码已合入基线,本单在其之上改;**不得回退它的 layer / 缓存 / 日志分档三项修复**。
- BUG 编号起点:**BUG-718**(开工时复核 `docs/BUG_HISTORY.md` 当前最大号 717`BUG-905` 是他单误称,本仓无该条目)
## 事故实证
以下数字是 2026-09-15 在本机对基线代码实测得到的(引擎跑在 `.venv`swisseph 可用,`JYOTISH_API_CHART_CACHE_TTL_SECONDS=0` 关缓存冷算)。
### 一、星盘页首屏那一发 `/api/chart` 有 99% 的时间花在外网
| 调用 | 页面 | 冷算耗时 | 响应体积 |
| --- | --- | --- | --- |
| `/api/chart`(星盘页原样,不带 skip | `/chart` | **0.400.66 s** | 163 KB |
| `/api/chart`(带 `skip_vedastro_main_entry_overview` | `/ephemeris` | **5 ms** | 58 KB |
| `/api/varga_full`17 张分盘) | `/chart` | 5 ms | 22 KB |
| `/api/dasha/chara` | `/chart` | 3 ms | 177 KB |
| `/api/western` | `/chart` | 2 ms | 8 KB |
| `/api/qizheng` | `/chart` | 160 ms | 15 KB |
| 任意一条缓存命中 | — | 5 ms | — |
同一台机器上单独计时 `vedastro_evidence_orchestrator.orchestrate_vedastro_evidence(route='overview')`:**0.36 s / 次,连调两次都是 0.36 s(无进程内记忆)**,返回 `status=service_endpoint_not_configured`——**这还是本机没有配 endpoint、一个外部请求都没发出去的情况下的空转成本**。
`TASK-chart-page-blocking-open-20260915` 把这 0.6 秒记成「引擎五个调用合计 0.75 秒」并据此设了 10 秒超时。那份实测没错,但 0.6 秒里几乎全部不是 Swiss Ephemeris,是 VedAstro 分支。
### 二、生产配置下这一发会变成外网请求乘法
- `frontend/src/lib/chart-view-load.ts``birthPayload()`(基线第 4060 行)构造 `/api/chart` 请求体,**没有 `skip_vedastro_main_entry_overview`**;同文件 `assembleChartView()` 用它发第一发引擎调用。
- `frontend/src/app/api/ephemeris/route.ts` 两处(基线第 93、185 行)都显式传了 `skip_vedastro_main_entry_overview: true`。**同一个引擎端点,两个页面策略相反。**
- `scripts/jyotish_api_server.py` `_compute_chart_sync()``if not body.get('skip_vedastro_main_entry_overview'):``_attach_vedastro_main_entry_overview(...)`(基线第 70907091 行)。
- 该 attach 走 `vedastro_evidence_orchestrator.orchestrate_vedastro_evidence(route='overview')`:先 `run_official_full_snapshot_for_case`,再对 `career / marriage / wealth` 三个领域各跑一次 `run_range_scan_for_case`
- full snapshot 的请求清单(实跑 `_official_full_snapshot_manifest` 数出来的):`events_overview` 1 + `dasha_all` 1 + `chart_core` 10 + `house_core` 12 = **24 个外部 HTTP 请求**fanout 开关 `VEDASTRO_FULL_SNAPSHOT_FANOUT_ENABLED` 默认值就是 `"1"``scripts/vedastro_service_adapter.py:2520`)。
- `deploy/README.md` 的生产 env 段写着 `VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api``VEDASTRO_ENABLE_NETWORK=1``VEDASTRO_RANGE_SCAN_NETWORK_ENABLED=1``VEDASTRO_TIMEOUT_SECONDS=20`**没有关 fanout**。
也就是说:生产上每一次缓存未命中的星盘页打开,首屏那一发要等 24 个外网请求加 3 次领域扫描。前端这条链路只等 10 秒(`chart-view-engine.ts``CHART_VIEW_ENGINE_TIMEOUT_MS`),VedAstro 侧超时 20 秒。
### 三、这份外部证据星盘页根本不用
- `frontend/src/lib/chart-view-mapper.ts` 全文不出现 `vedastro`、也不读 `modules`
- `frontend/src/lib/chart-view-contract.ts` 全文不出现 `vedastro`
`/api/chart` 响应里那份 `modules.vedastro_range_scan_result` 从 Python 传到 Next,再被 mapper 整段丢弃。它不展示、不进合同、不给任何下游消费。
### 四、`docs/engine/vedastro-gateway.md` 自己不推荐这种用法
> 完整模式会增加外部请求数量和首包等待时间,更适合后台任务、预计算或非实时专业解盘,**不建议直接放在聊天首轮的同步关键路径**。
## 根因
`/api/chart` 的默认行为是「顺带取一份 VedAstro 外部证据」,跳过它要显式传标志。这个标志是 BUG-161 修复时引入的(`defer_optional_external_evidence``scripts/jyotish_api_server.py:2183/2292/2334`),当时只给 `/api/consult` 这一条前台路径装上了。
**BUG-161 的防复发原文:「用户前台请求不得同步串联多个可选外部证据调用;快速排盘与完整证据排盘必须使用不同缓存键。」**
2026-09-15 新建的 `/chart` 是一条新的前台路径。星历页作者按老规矩传了标志,星盘页作者没传。防复发条款只写在 Bug 历史里,代码里没有任何东西拦住新页面漏传——这是 BUG-161 的复发面,不是新问题。
顺带说明:缓存键那一半是好的。`_build_api_chart_cache_payload` 已经把 `skip_vedastro_main_entry_overview` 放进 key(基线第 2634 行附近),快慢两种排盘天然不共享缓存,本单不需要动它。
## 决策记录
- 2026-09-15,产品负责人在会话中看过上述实证后授权本单,口径是:**星盘页首屏不挂 VedAstro**。
- **不做「按需加载的 VedAstro 层」**。理由是上面第三条:当前没有任何消费方。按产品既有偏好(多余入口宁可删除也不修),先把它从这条路径上摘干净;将来星盘页真要展示外部交叉验证,另立任务书连同 UI 一起设计。
- 本单**不推翻**任何既有红线。它是把 BUG-161 的防复发从「聊天入口」扩大到「所有前台页面路径」,并第一次用测试把它钉住。
- 本单**不动** `HEAVY_COMPUTE_PATHS`、不动 `JYOTISH_HEAVY_COMPUTE_CONCURRENCY`、不动前端 10 秒超时、不放宽任何限流——BUG-717 的防复发继续有效。
- 生产 env 与镜像层面的两件事(API key 是否配置、SDK 自动联网升级)**不在本单**,见 `TASK-vedastro-runtime-ops-20260915.md`。两单可并行,文件不重叠。
## 硬红线
1. 只改前台请求体与测试。**不得修改** `scripts/jyotish_api_server.py``scripts/vedastro_service_adapter.py``scripts/vedastro_evidence_orchestrator.py`
2. 不得把 `_attach_vedastro_main_entry_overview` 的默认行为改成「默认不取」——`/api/consult``/api/professional_reading`、高严谨工作流、报告链仍然依赖它,改默认值会静默削掉解读链的外部证据(AGENTS Part B / MEVG 是硬约束)。本单只在调用方显式传标志。
3. 不得回退 BUG-715/716/717 的三项修复(layer 按需、5 分钟引擎结果缓存、失败原因分档日志与文案)。
4. 不得给星盘页加 spinner / 骨架 / 「正在加载」(AGENTS §6)。
5. 测试总数不得低于开工时 `origin/staging` 实测;改任何既有断言要写「原值 / 新值 / 原因」三栏。
6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
## 任务分解
### 1. 星盘页首屏请求显式跳过 VedAstro(P0
`frontend/src/lib/chart-view-load.ts``birthPayload()` 返回体里加 `skip_vedastro_main_entry_overview: true`,与 `frontend/src/app/api/ephemeris/route.ts` 的既有写法一致。
注意 `assembleChartView()``followUp``{...payload, planets, ascendant, houses}`,所以四个后续 layer 调用会自动带上这个字段——`/api/varga_full``/api/dasha/chara` 不读它,`/api/western``/api/qizheng` 也不读,无副作用。确认一遍即可,不要为此拆两个 payload。
**验收标准**
- `assembleChartView` 发往 `/api/chart` 的 body 里 `skip_vedastro_main_entry_overview === true`
- 进度记录里给出改前 / 改后同一份出生资料的实测对比:冷算耗时、响应字节数。改后主盘应在 10 ms 量级(本机基线 5 ms)。
- 星盘页五个 Tab 的渲染结果与改前逐项一致(mapper 不读 `modules`,预期零差异)。任一 Tab 出现差异即停手上报,不要自行调 mapper。
### 2. 用合同测试钉住「前台页面必须显式声明外部证据策略」(P0)
新增合同测试(建议放 `frontend/tests/chart-view-route.test.ts` 或同目录新文件),至少锁住两条:
- 无 layers 时,`assembleChartView``/api/chart` 的请求体带 `skip_vedastro_main_entry_overview: true`
- `/api/ephemeris` 路由的两处 natal / transit 请求体同样带该字段(现状已满足,本轮只是补上锁)。
第二条可以用源码合同的方式写(读文件断言),与既有 `chart/page.tsx` 禁止 `force-dynamic` 的源码合同同一路数;能用真实桩跑通就优先跑桩。
**验收标准**
-`birthPayload()` 里那一行删掉,测试必须红;恢复后绿。执行方要在进度记录里写明自己做过这个反向验证(防止写出恒真断言)。
- `npx tsx --test frontend/tests/chart-view-route.test.ts`(或新文件)通过。
### 3. Bug 历史(P0
新增 **BUG-718**,字段按 `docs/BUG_HISTORY.md` 既有格式写全,其中:
- `复发自`**BUG-161**(必填,不得写「无」)。说明旧防复发为什么没拦住:它以 `/api/consult``defer_optional_external_evidence` 形式落在一条路径上,没有任何代码级约束能作用于 2026-09-15 新建的 `/chart`
- `相关记录`BUG-161、BUG-065、BUG-715、BUG-716、BUG-717、ERR-107、ERR-108、TASK-chart-page-blocking-open-20260915。
- `验证`:贴任务 1 的耗时 / 字节数对比与任务 2 的反向验证结论。
- `防复发`:写成可执行的一句——任何前台页面路由调 `/api/chart` 必须显式声明外部证据策略,并有合同测试覆盖;新增页面路由时按此检查。
BUG-065VedAstro 事件扫描请求乘法导致校正数分钟无响应,288 次同步请求)是同族根因的更早一次,在 `相关记录` 里带上即可,不必标成复发自它。
### 4. 文档(P1
- `CHANGELOG.md`:一句话——打开星盘页不再等外部占星服务,本地直接出盘。用户可感知的是「快」,不要写内部端点名。
- 不改 `frontend/DESIGN.md`(本单不动视觉)。
- `docs/testing/chart-page-20260915.md` 追加一条真人走查项:登录后打开 `/chart`,主盘应几乎即时出现;断网或外部服务异常时,星盘页仍应正常出盘(因为它已经不依赖外网)。
## 让步顺序
资源不够时按此顺序保:任务 1 → 任务 3 → 任务 2 → 任务 4。任务 1 单独交付也是净收益;任务 2 缺席则必须在进度记录里明确写「防复发未落测试」,不得沉默。
## 开工前置命令
```bash
cd /workspace/Jyotisha
git fetch origin --prune
git worktree add -b codex/chart-vedastro-decouple-20260915 \
.worktrees/chart-vedastro-decouple-20260915 origin/staging
cd .worktrees/chart-vedastro-decouple-20260915/frontend && npm ci
```
开工前必读:`docs/BUG_HISTORY.md` 的 BUG-161、BUG-065、BUG-715/716/717`docs/research/pre_work_error_ledger.md` 的 ERR-107、ERR-108。
本单只改前端请求体与测试,不碰引擎与适配器,`scripts/pre_work_check.py` 可不跑(AGENTS §9 纯前端例外)。
## 验收口径
- `./node_modules/.bin/tsc --noEmit` 0 错;`npm run lint` 0 error。
- 前端相关测试套件 fail=0,总数不低于开工基线。
- `next build``/``○ Static`;本单不碰首屏包,gzip 应无变化,有变化要解释。
- 部署后 `https://staging.jyotisha.chat/api/health``deployment.gitCommit` 等于本轮 staging 提交。
- 真人走查(无登录态 / 无 Chrome 的环境缺口)写进 `docs/testing/chart-page-20260915.md`,不得写成「通过」。