docs(tasks): brief the native qizheng chart integration

Upstream branch codex/add-birth-time-rectification-skill a911c890 vendors
@4n6h4x0r/stem-branch 0.8.0 (Apache-2.0) and adds a seven-governors chart
adapter. Brief scopes the intake: vendored archival plus NOTICE, Node
runtime in the API image, vendor/** in gated-paths, a parameterised
adapter, and a thin /api/qizheng registration under the 11363-line cap.

Three defects were reproduced locally before filing: the four-pillars
hour branch is derived from the UTC wall clock (BUG-700, out of scope and
red-lined), ketuMode is pinned to the apogee school with no request
parameter (BUG-701), and the adapter boundary claims star spirits and
dignities that come back empty or unclosed (BUG-702).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
Jesse_Chen
2026-09-15 06:59:47 +00:00
co-authored by Claude Opus 5
parent 2d7698ead3
commit b3ccef4cbb
2 changed files with 241 additions and 0 deletions
+2
View File
@@ -221,6 +221,8 @@
| `TASK-rectification-house-lord-gochara-research-20260913.md` | `PROGRESS-rectification-house-lord-gochara-research-20260913.md` | 研究单:宫主触发与木星/土星过运(合冲本命宫主、罗睺紧密合、年精度、用于 block 选上升)四种放宽,20 例公开 AA 离线量 block 层与 minute 层两组指标;引擎里已有宫主/功能吉凶/受控过运,只量缺的四条 | 待验收(无收益,关闭;不立实现单) | `codex/rectification-house-lord-gochara-research-20260913` |
| `TASK-qizheng-native-chart-20260915.md` | `PROGRESS-qizheng-native-chart-20260915.md` | 接入原生七政四余排盘:vendored `stem-branch` 0.8.0Apache-2.0)归档 + API 镜像 Node runtime + 适配器(计都派别与宿度坐标系参数化、boundary 按实测重写)+ `/api/qizheng` 薄注册。实证三条:四柱时柱按 UTC 算(BUG-700,本轮不修不调用)、`ketuMode` 写死未暴露(BUG-701)、boundary 把空神煞与未闭合庙旺说成已生成(BUG-702)。前端第五个 Tab 另出单,串行在本单之后 | 待领取 | — |
## 命名与归档
- 文件名:`TASK-<kebab-主题>-<YYYYMMDD>.md`;同主题的修复单加 `-fix`;进度记录同名换前缀。
@@ -0,0 +1,239 @@
# TASK-qizheng-native-chart-20260915 · 接入原生七政四余排盘
## 基线
- 代码基线:`origin/staging` = `2d7698ea``fix(chat): keep stored rectification titles on open`)。
- 上游来源:`/workspace/yinduzhanxing`,分支 **`codex/add-birth-time-rectification-skill`**commit **`a911c890`**`feat: add native qizheng chart adapter`2026-09-15 14:14 +0800)。
- 注意:上游 `main` 仍是 `a6f47abd`2026-09-03),即我们 09-03 那轮已同步的 commit。本单要的东西**只在这条分支上**,不在 main。
- 同分支另有 `6c27aab6 fix: harden report export and validation gates``8bba9cb5 docs: record 2026-09-15 governance audit`,**不在本单范围**,不要顺手带进来。
- Skill 版本:`6.9.16`,本单**不 bump**(不新增解读口径,只新增计算与只读接口)。
- 开工前置检查已在立单时跑过:`scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` 全绿(python_runtime / fragment_scan / external_engine_adapters / remote_visibility / focused_tests 均 ok)。执行方开工时必须**重跑**。
## 上游带来了什么
`a911c890` 的实质不是继续做考据,而是**vendored 了一个开源引擎**
| 文件 | 说明 |
| --- | --- |
| `scripts/qizheng_chart_engine.py` | 168 行纯适配器,`subprocess` 调 vendored CLI |
| `vendor/stem-branch/dist/cli.cjs` | **45,963 行 / 2.68 MB** 单文件 bundle |
| `vendor/stem-branch/{LICENSE,README.md,package.json}` | `@4n6h4x0r/stem-branch` **0.8.0****Apache-2.0** |
| `scripts/jyotish_api_server.py` | +10 行,挂 `/api/qizheng` |
**许可证是本单能立的前提**Apache-2.0 是 permissive,与本仓 MIT 兼容(对照:PyJHora 是 AGPL,只能黑盒对数,见 `docs/research/open_source_integration_priority_table_2026_07_03.md`)。Apache-2.0 §4 要求保留 LICENSE 与 attribution,任务 1 里必须落实。
### 立单前的实测(在本机跑通,不是读代码推断)
用示例出生资料 `1990-04-09T13:24:00+08:00 / 31.19N 121.44E`
```
bodies: 七政(日月水金火木土)+ 四余(罗睺 计都 月孛 紫炁),共 11 曜
太阳 宿度 177.94 奎 7.44° 亥宮 陷
罗睺 宿度 112.09 女 5.09° 丑宮 平
紫炁 宿度 33.34 房 0.34° 卯宮 平
palaces: 12(地支宮 + 人事宮 + 所辖宿 + 占星)
ascendant: 星宿 · 午宮 aspects: 27 dignities: 11 曜全有
```
宿度自角宿初度起算,内部自洽(太阳 177.94 = 奎宿累计 + 7.44)。
## 事故实证
以下三条全部是实跑复现的,**不是猜测**。复现命令见每条末尾(`CLI``vendor/stem-branch/dist/cli.cjs`)。
### 实证 1 · 四柱时柱按 UTC 墙钟计算(BUG-700
vendored CLI 的 `--pillars` 读的是 UTC 墙钟,不是出生地本地时间:
| 传入 | 引擎回的时柱 | 应为 |
| --- | --- | --- |
| `1990-04-09T13:24:00+08:00` | 丁**卯**(卯时 = 0507 时) | 辛**未** |
| `1990-04-09T13:24:00`(裸) | 辛**未** | 辛未 ✓ |
| `1990-04-09T13:24:00+00:00` | 辛**未** | — |
而上游适配器 `scripts/qizheng_chart_engine.py``_iso_local()` **带偏移量**输出(`f"...{sign}{offset_hours:02d}:{offset_minutes:02d}"`)。也就是说:**一旦把 `--pillars` 接到同一个适配器上,每个中国用户的时柱都会错 8 小时**,午夜前后日柱也会跟着错。
`--seven-governors` 不受影响:天文量要的正是正确的 UTC 时刻,带偏移量是对的。
```
node CLI --date "1990-04-09T13:24:00+08:00" --lat 31.19 --lng 121.44 --pillars --json
node CLI --date "1990-04-09T13:24:00" --lat 31.19 --lng 121.44 --pillars --json
```
这条与 `docs/BUG_HISTORY.md` 的 BUG-245 / BUG-905 是同一族防复发条款(「所有星盘计算入口必须统一补算历史 offset」),必须在新记录里关联。
### 实证 2 · 计都派别被写死,且未暴露(BUG-701)
引擎回 `ketuMode: "apogee"`。实测计都落在 **亢 15.46°**,月孛 **亢 14.23°**——两者只差 1.2°,**不是罗睺的对点**(罗睺 女 5.09°,对点应在 133° 附近)。
这是真实的派别分歧,换一派整张盘变。上游适配器的 `boundary` 文案提了紫气 / 庙旺 / 限法差异,**唯独没提 ketu mode**,请求侧也没有参数可以选。
### 实证 3 · boundary 文案与实际输出不符(BUG-702)
上游 `_normalize()` 的 boundary 写:
> 「已生成完整七政四余本命结构:七政、四余、二十八宿、十二宫、命宫、相位、**神煞**和**当前庙旺**字段。」
实测:
- `starSpirits: 0` —— **神煞是空的**
- `dignities` 11 曜里,除太阳记「陷」外**全部返回「平」**。
第二条对得上上游自己的台账:`references/oracle/qizheng_runtime_truth_closure_status_2026_08_29.json``promotion_decision.may_update_runtime_dignity_table = false``runtime_promotable_count = 0``qizheng_positive_layer_runtime_gate_2026_08_30.json` 63 格**全部 blocked**;十一体 × 十二宫共 132 格的庙旺表只拿到 **9 格**直接证据。
**庙旺这一列现在没有可用的判定,只能当占位。**
## 根因
1. vendored 引擎把「天文时刻」和「历法干支」用了同一个时间入口,前者要 UTC、后者要本地民用时,混用导致时柱错位。
2. 适配器把派别选择(`ketuMode``siderealMode`)当成引擎内部细节透传,没有提升为产品参数。
3. boundary 文案是按引擎**能力清单**写的,不是按**本次实际输出**写的,于是把空字段说成已生成。
## 决策记录
产品负责人在 2026-09-15 的对话中授权:
1. **接入七政四余原生排盘**,走 vendored `stem-branch`(Apache-2.0)路线,不再自己做 132 格考据。
2. **本单只做引擎与只读接口**,前端另出一单(原因见「范围边界」)。
3. **不做八字 / 四柱**`--pillars` 存在实证 1 的缺陷,本轮不修、不调用;只把它记成红线与 BUG,避免后来者踩。
4. **计都派别与宿度起算口径必须让用户看得见**,按岁差(`ayanamsa`)既有的做法处理成显式参数,不接受引擎默认静默生效。
5. **庙旺不得进入任何结论**。它可以出现在响应里,但必须带 `unclosed` 标记;解读层、报告层、咨询层一律不得引用。
本单不推翻既有红线。特别地,AGENTS.md §6「`scripts/jyotish_api_server.py` must not grow」继续有效,见任务 3 的硬上限。
## 硬红线
1. **`--pillars` / `--luck` / `--polaris` / `--qimen` / `--liuren` 等其余子命令,本单一律不得调用。** vendored CLI 里有它们,但只有 `--seven-governors` 在本单验收范围内。
2. **绝不把带时区偏移的 ISO 字符串喂给需要本地民用时的子命令。** 若将来要做四柱,必须先解决实证 1,并在 `docs/BUG_HISTORY.md` 关联 BUG-700 / BUG-245 / BUG-905。
3. **宿度不是恒星黄经。** 引擎的 `siderealLon` 自角宿初度起算,与本仓 Raman / Lahiri 恒星黄经、与西洋回归黄道都不是同一套。响应里必须显式标注坐标系,任何界面都不得做三套之间的度数换算,也不得把两套结果叠加成「双重印证」。
4. **庙旺(dignities)不得进入结论。** 见决策记录 5。
5. `scripts/jyotish_api_server.py` **不得新增 handler 主体**,只允许薄注册,见任务 3。
6. `frontend/src/app/page.tsx``origin/staging` 上是 **1951 行**,不得再增长;本单不碰前端。
7. 不得顺手升级依赖、不得顺手修不在本单里的 warning;发现了写进 `BLOCKED.md` 或进度记录。
8. 不得把 `/workspace/yinduzhanxing` 当作运行主仓;从它那里只取 `a911c890` 的文件内容,不引用它的路径。
## 范围边界
**本单做**vendored 归档、镜像 Node runtime、适配器、只读接口、测试、Bug 记录。
**本单不做**:前端界面。原因是前端的「七政四余」是**星盘页的第五个 Tab**,而那个星盘页(P0 事实层:星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余)本身还没有立单。两件事必须串行:先有星盘页,再往上挂 Tab。前端单由 Claude 另出,依赖本单的 `/api/qizheng` 响应合同。
原型(含五个 Tab 的完整形态、中宫排盘参数卡、三套坐标系的边界文案)已经画好,前端单会引用它。
## 任务分解
### 任务 1 · vendored 归档与 API 镜像 Node runtime
**这是后续全部任务的阻塞项,必须先完成。**
- 从上游 `a911c890` 取入以下四个文件,路径保持一致:
`vendor/stem-branch/LICENSE``vendor/stem-branch/README.md``vendor/stem-branch/package.json``vendor/stem-branch/dist/cli.cjs`
- 新增仓库根 `NOTICE`(或在既有归属文件中追加):注明 `@4n6h4x0r/stem-branch` 0.8.0、Apache-2.0、上游地址与取入 commit。Apache-2.0 §4 要求保留 LICENSE 与 attribution,这一条是合规必需,不是可选。
- `deploy/railway-api.Dockerfile`:装 **Node.js 运行时**(当前基底是 `python:3.12-slim`,全文无 node / npm)。只要运行时,不要 npm、不要构建工具链;装完清理 apt 列表,与既有 `build-essential` 的 purge 写法保持一致风格。
- 同文件新增 `COPY vendor ./vendor`(现有 COPY 列表在第 17–22 行区间:`SKILL.md mcp_server.py` / `assets` / `jyotish_vedic` / `references` / `scripts` / `skills`)。
- `deploy/gated-paths.txt`:新增 `vendor/**`。**当前清单里没有它**,不加的话 vendored 引擎的任何改动都不会触发门禁、不会重建镜像。
**验收标准**
- 本地构建 API 镜像成功;容器内 `node --version` 有输出。
- 容器内 `python -c "from pathlib import Path; assert Path('/app/vendor/stem-branch/dist/cli.cjs').exists()"` 通过。
- `/api/health` 仍为 `200``swisseph_available = true`
- 镜像体积变化(MB)写进 `docs/tasks/PROGRESS-qizheng-native-chart-20260915.md`;超过 +150 MB 要说明原因。
- 无 Docker 时:把构建与容器内验证写进 `BLOCKED.md`,并给出宿主机上 `node --version` 与 CLI 实跑的替代证据,**不得写成「通过」**。
### 任务 2 · 适配器 `scripts/qizheng_chart_engine.py`
以上游 168 行版本为起点,但必须做以下修改:
- **派别参数化**`ketu_mode``sidereal_mode` 从请求体读取,缺省值在模块常量里写死并回写进响应的 `calculation` 段。响应必须能让调用方看出这次用的是哪一派。
- **boundary 文案按实际输出重写**:不得出现「神煞」(实测为空);庙旺必须标注为未闭合,附上 `references/oracle/qizheng_runtime_truth_closure_status_2026_08_29.json` 的口径(132 格只闭合 9 格、`runtime_promotable_count = 0`)。
- **坐标系标注**:响应顶层显式带 `coordinate_system: "qizheng_mansion_degrees_from_jiao"`(命名可调,但必须一眼看出不是恒星黄经、不是回归黄道)。
- **错误路径**`node` 不存在、CLI 文件缺失、超时、stdout 非 JSON、CLI 非零退出——五种都要落到结构化错误,不得 500,不得把 stderr 原文整段外泄。
- 保留 `raw_engine_output` 供审计,但确认其中不含任何本机路径或环境信息。
**验收标准**
- 新增 `tests/test_qizheng_chart_engine.py`
- fixture 来自**真实引擎响应**golden),不得手造形状(AGENTS.md §7.4)。
- 覆盖:11 曜齐全、12 宫齐全、命宫存在、`coordinate_system` 存在、`ketu_mode` 可被请求覆盖且回写、庙旺带未闭合标记、boundary 不含「神煞」。
- 覆盖五种错误路径各一条。
- `.venv/bin/python -m pytest tests/test_qizheng_chart_engine.py` fail=0。
- `.venv/bin/python scripts/run_quality_gate.py --profile quick` 通过。
### 任务 3 · `/api/qizheng` 薄注册
-`scripts/jyotish_api_server.py` 的路由分支处新增一条 `elif path == '/api/qizheng':`。参照点:`origin/staging``elif path == '/api/chart':`**3497 行**
- handler 用既有的 `_load_local_module('qizheng_chart_engine')` 模式(该函数已存在,见 137 / 413 / 1009 行的用法),函数体 **≤ 8 行**,只做加载、调用、把领域异常翻成 `BadRequest`
- **行数硬上限**`origin/staging` 上该文件是 **11313 行**,契约上限是 **11363**`tests/test_api_server_growth_contract.py`baseline 11063 + 300 余量)。**本轮对该文件的净增不得超过 12 行**,只剩 50 行总余额。
- 端点是只读计算,按既有重计算端点的口径纳入 `JYOTISH_HEAVY_COMPUTE_CONCURRENCY` 限流(默认 2,饱和 429)。CLI 子进程 20 秒超时要与限流口径对得上。
**验收标准**
- `.venv/bin/python -m pytest tests/test_api_server_growth_contract.py` 通过。
- 新增 `tests/test_qizheng_api_productization.py`(上游同名文件可参考,但断言按本仓合同重写):正常请求返回 11 曜 12 宫;缺 `lat` / `lon` 返回 400 而非 500;node 不可用时返回结构化错误。
- `curl -s -X POST http://127.0.0.1:5200/api/qizheng -d '{"year":1990,"month":4,"day":9,"hour":13,"minute":24,"lat":31.19,"lon":121.44,"tz":8}'` 的输出贴进进度记录(脱敏不需要,示例资料是虚构的)。
### 任务 4 · Bug 记录
在**同一变更**内更新 `docs/BUG_HISTORY.md`,新增连续编号(起点见下):
- **BUG-700**vendored 引擎四柱时柱按 UTC 墙钟计算。状态 `blocked`(本轮不修,不调用 `--pillars`)。必须写明三组实测对照、关联 BUG-245 / BUG-905 的「所有星盘计算入口必须统一补算历史 offset」防复发条款,并把红线 2 写进防复发栏。
- **BUG-701**|计都派别(`ketuMode: apogee`)未暴露为参数。状态 `resolved`(任务 2 参数化后)。
- **BUG-702**|适配器 boundary 文案把空的神煞与未闭合的庙旺说成已生成。状态 `resolved`(任务 2 改写后)。
不得把姓名、出生资料、邮箱、用户/案例 ID、Cookie、JWT、密钥、完整请求体或模型原文写进去(AGENTS.md §5.6)。
## 让步顺序
资源不够时按这个顺序砍,**不得自行调整**:
1. 先保证 任务 1 + 任务 2 + 任务 4。没有镜像 Node 与适配器,这一单等于没做。
2. 任务 3 的限流接入可以延后,但路由与行数上限不能延后。
3. `raw_engine_output` 的审计字段可以先不落地,改为进度记录里说明。
4. **不可让步**:许可证归档(Apache-2.0 §4)、`vendor/**` 进 gated-paths、庙旺不得进结论、`--pillars` 不得调用、api_server 行数上限。
## 开工前置命令
```bash
cd /workspace/Jyotisha
git status -sb # 确认分支,主检出常被别的会话切走
git fetch origin --prune
git worktree add -b codex/qizheng-native-chart-20260915 \
.worktrees/qizheng-native-chart-20260915 origin/staging
cd .worktrees/qizheng-native-chart-20260915
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
grep -nE "BUG-[0-9]{3}" docs/BUG_HISTORY.md | tail -5 # 核对当前最大号
wc -l scripts/jyotish_api_server.py # 应为 11313,上限 11363
```
取上游文件(只取内容,不引用上游路径作为运行时依赖):
```bash
cd /workspace/yinduzhanxing
git fetch origin codex/add-birth-time-rectification-skill
for f in vendor/stem-branch/LICENSE vendor/stem-branch/README.md \
vendor/stem-branch/package.json vendor/stem-branch/dist/cli.cjs; do
git show a911c890:"$f" # 写入目标 worktree 的同名路径
done
```
交付:`git push origin HEAD:staging`(会触发 `backend-quality-gate`,因为 `scripts/**``deploy/**``vendor/**` 都在门禁路径内),推送后必须核对远端 SHA,并确认 `/api/health``deployment.gitCommit` 等于本次提交。
## BUG 编号起点
`origin/staging``docs/BUG_HISTORY.md` 的当前最大号是 **BUG-699**。本单从 **BUG-700** 起,开工时必须重新核对一次(可能已被别的会话占用)。
## 附:本轮发现但不在本单范围
立单期间实跑我们自己的引擎时发现一条与七政无关的口径不一致,**不要在本单修**,但请在本单的 Bug 记录里补一条 `investigating` 占位,避免丢失:
- **panchanga 岁差与账户设置不一致**。`/api/panchanga_range` 的响应里 `calculation_policy.panchanga` 写着 `"SwissEph Lahiri at sunrise-relative reference time"`,而账户默认岁差自 `80102459`2026-08-20)起已是 **Raman**,且 `/api/chart` 确实按 Raman 算。同一个用户在星盘页看到的是 Raman 盘,在星历/今日路径看到的五要素却是 Lahiri 口径。
- 复现:`POST /api/panchanga_range {"start_date":"2026-09-14","end_date":"2026-09-16","lat":31.19,"lon":121.44,"tz":8,"ayanamsa":"raman"}`,读 `report.calculation_policy.panchanga`
- 处置:记 `investigating`,写明现象与复现,**不要猜根因、不要顺手改默认值**。是统一到 Raman 还是明确声明 panchanga 永远用 Lahiri,是产品决策,另出单。
## 进度与记录
- 进度记录:`docs/tasks/PROGRESS-qizheng-native-chart-20260915.md`,本单状态板一行。
- 被环境挡住的写 `BLOCKED.md`(预期缺口:无 Docker → 镜像构建与容器内 `node --version` 无法验证)。
- 用户可感知的行为变化写 `CHANGELOG.md`;本单只新增只读接口,Skill 版本不 bump,在 CHANGELOG 里写明「未 bump」。
- 索引:`docs/tasks/README.md` 追加本单。