@@ -0,0 +1,154 @@
# 任务书:完整数据版补齐(二)——研究型推运附录、星座大运第三层与研究型分盘,标注研究口径(agent 循环 issue #92, 2026-10-21)
## 基线
- 写作基线:初稿 `bd3ce0e7b` ( 10-21),定稿 `origin/staging` = `d03a8bc86` ( 10-23)。**开工时以 #91 ( `TASK-full-data-parity-1-20261021` ) A、B 两段及其修复单 `TASK-full-data-parity-1-b-fix-20261023` 都合入后的 `origin/staging` 为准;#91 未合入不得开工。** 两单改同一批渲染文件(`scripts/pl9_full_data_export.py` 、`scripts/report_render/` 、`scripts/rashi_dasha_report.py` )。#91 B 段已建好 `scripts/annual_native/` 子包(含 `tithi_pravesh.py` 、`dasha_children.py` 、`report_ephemeris.py` )与 `report_render/parity_chapters.py` 的中英字段标签表,本单接着用,不另起一套。
- 分支:`codex/full-data-parity-2-20261021` ;工作树:`.worktrees/full-data-parity-2-20261021` 。执行方 **Grok( Windows + Docker) ** ,只推这个分支。
- 上游参照(只读,放仓库外):`https://github.com/732642856/yinduzhanxing.git` `main` ( `258e08e3` )的计算模块;交付版分支 `codex/customer-export-frozen-20261016` ( `1d549fe9` )的 `customer-export/registry/delivery_timing_supplement.py` 与 `delivery_evidence_appendix.py::auxiliary_dasha_markdown` 。不得复制该分支的个案文件(见 #91 任务书「基线」)。
- 关联:BUG-1275(真恒星口径)、BUG-1393 / 1283( Niryaana Shoola 不编子运)、`TASK-rashi-dashas-native-20261008` (星座大运出处表)、`TASK-report-full-data-fix-20261008` 红线 4( `customer_timing_supplement` 不搬)。
## 1. 现状实证(Claude, Linux, `bd3ce0e7b`,屏蔽 PyJHora)
### 1.1 上游两章的体量
产品 10-21 上传的上游英文交付版(真实个人资料,只统计字符数,不入库):
| 上游章节 | 字符 | 表行 | 占全文 |
| --- | --- | --- | --- |
| Additional Native Timing Calculations(补充推运计算数据) | 2,424,694 | 36,752 | 52% |
| Auxiliary Dasha Subperiods(辅助大运次小运数据) | 713,444 | 9,402 | 15% |
### 1.2 「补充推运计算数据」:计算我方全部没有
上游由 `scripts/customer_timing_supplement.py::build_customer_timing_supplement` ( main)产出,交付版的 `delivery_timing_supplement.py::timing_supplement_markdown` 渲染。资料包键 `worksheets.timing_and_predictive_systems.customer_timing_supplement` , **我方资料包里没有**。
| 方法族 | 上游计算模块(行数) | 我方 |
| --- | --- | --- |
| Tithi Ashtottari 本命,顺 / 逆两口径 | `tithi_ashtottari.py` ( 296) | 无 |
| Tithi Ashtottari 年度,三年内每对 300~400 天回归 × 顺逆 × 角度 / 时间余额 | 同上 + `annual_pravesha.py` ( 540) | 无 |
| Yoga Vimshottari 本命 | `yoga_annual_dasha.py` ( 176) | 无 |
| Saptavarga 宫主关系(Sripati 输入) | `aayu_sripati_periods.py` ( 834) | 无 |
| Buddhi Gati | `buddhi_gati.py` ( 185) | 无 |
| Kaala | `kaala_dasha.py` ( 172) | 无 |
| Sudasa(另有一条「已确认起点」固定为 blocked) | `sudasa.py` ( 585) | 无 |
| Varnada JSS(两个备选起点分开) | `varnada_dasha.py` ( 349) | 无 |
| Yoga / Nakshatra Pravesha:参考年内全部回归 + 每个回归的后继分支 + 360 Tithi 年 | `annual_pravesha.py` 、`pravesha_return_year.py` ( 417) | 无 |
| Karana 年度三种(Julene Louis 主星表 / PVR 角宫主星 / PVR 360 Tithi 年) | `karana_annual_dasha.py` ( 535)、`tithi_pravesh.py` ( 236, #91 会先搬) | 无 |
| 当前周期定位 | 以上结果查询 | 无 |
连带依赖:`conditional_dashas.py` ( 193)、`nakshatra_annual_dasha.py` ( 96)、`satabdika_annual.py` ( 90)、`transit_search.py` ( 210)、`pravesha_year_basis_diagnostic.py` ( 91)。原生部分合计约 **5,000 行 ** 。依赖链里的 `pyjhora_canonical_adapter.py` ( 356)、`specialized_dasha_external_replay.py` ( 1,545)是 PyJHora 对照,**不搬**。「三年逐月星历」在 #91 已做。
**口径问题 ** :这些模块取经度用 `FLG_MOSEPH` (Moshier 星历,不是我方的 Swiss Ephemeris 星历文件),参数 `longitude_profile='apparent'` ,可选 `'true_position'` ( `FLG_TRUEPOS` ,几何位置);`customer_timing_supplement` 全部传 `'apparent'` ; Karana 一路还写死 `position_mode='apparent'` 。三者都不是我方 BUG-1275 之后的口径(Swiss Ephemeris + 真岁差,等价 `FLG_SIDEREAL` )。
### 1.3 「辅助大运次小运数据」:我方只到第二层
| 体系 | 我方现状(`scripts/rashi_dashas_native.py::calculate_rashi_dashas` ,虚构盘实测) | 上游 |
| --- | --- | --- |
| Sthira | 主运 12 / 子运 144 / 次小运 0 | 次小运 1,728 |
| Drig | 12 / 144 / 0 | 同上 |
| Shoola | 12 / 144 / 0 | 同上 |
| Navamsha | 12 / 144 / 0 | 同上,带次小运规则说明 |
| Niryaana Shoola | 只出主运(`pvr_niryaana_shoola_v1` );三层平手时本轮不上表(这张虚构盘即如此) | 子运 + 次小运 |
星座大运在渲染时才算(`rashi_dasha_report.py::render_rashi_dasha_chapter` ),不在资料包 `worksheets` 里;对话卡读的 `read_full_data_domain` 只拿 `worksheets` ,所以现在不会进对话卡。
### 1.4 研究型分盘(10-21 产品从 #91 移入)
上游交付版「Alternative Divisional Calculation Profiles」(3,569 字符)与「Research High-Division Raw Data」(941 字符,D81 / D108 / D144 等)由 `delivery_research_vargas.py::research_varga_markdown` 渲染。上游函数直接跑在我方数据上能出 4,643 字符(英文),数据来自我方 `varga_research_high` 、`varga_extended` 、`varga_full` ,**只缺渲染**。我方现有报告结构化附录里已有少量 D81 / D108 / D144 行(英文 3 处),本单统一到研究口径章节下。
### 1.5 体量估算与现有上限
| 项 | 数 |
| --- | --- |
| #91 B 段实测(0 号虚构盘,Linux,屏蔽 PyJHora) | 英文 1,263,805 字符、中文 753,562 字符;资料包 JSON 14.1 MB;先英文后中文合计约 24 s。#91 修复单还会加解释补长、后两年全套与 72 年返照表,开工时以合入后的实测为准 |
| 本单再加约 | 3.1 M 字符 / 每种语言 |
| 预计每种语言 | 英文约 4.5 M 字符以上,中文按比例;生成时长必须先按 T1 实测外推 |
| 快照(中文 + 英文 + 事实表,存一行) | 约 10~15 MB |
代码里的上限(全部按符号定位):
| 上限 | 位置 | 数值 |
| --- | --- | --- |
| 前端等引擎出稿 | `frontend/src/lib/personal-report-longform-generate.ts` `ENGINE_TIMEOUT_MS` | 180 s(中英两份一起算) |
| 快照存储 | `personal_report_sections.payload` ( `jsonb` ,迁移 `20260830020000_personal_report_sections.sql` ) | 应用层无上限;PostgreSQL 单值约 255 MB |
| 阅读页 | `personal-report-markdown-view.tsx` `FULL_DATA_CHAPTER_PAGE_CHARS` | 全文超 20 万字符按二级标题一章一页;**单章没有上限**,240 万字符的一章会整页渲染;打印时全部章节一起渲染 |
| 每次打开报告 | `app/api/reports/[reportId]/route.ts` → `readLongformSnapshot` | 读整行快照(中英两份 + 事实表),看中文也会把英文一起读出来 |
| 请求体 | `jyotish_api_server.py` `MAX_REQUEST_BYTES` | 2 MiB,只限请求,不限响应 |
| `REPORT_DOCUMENT_MAX_BYTES` 1.5 MiB | `personal-report-contract.ts` | 只管封面文档,不含 Markdown,不受影响 |
## 2. 根因
上游交付版的这两章依赖上游的原生推运模块,我方从未移植(Kalachakra 子运已在 #91 B 段以「简化模型」补到第二层);研究型分盘只差渲染;`customer_timing_supplement` 当时因视位置被红线挡住(`TASK-report-full-data-fix-20261008` 红线 4),挡住的是**口径**,不是这些方法本身。
## 3. 决策记录
| # | 决定 | 来源 |
| --- | --- | --- |
| E1 | 补上游「补充推运计算数据」(各方法变体)与「辅助大运次小运数据」(四种星座大运第三层,Niryaana Shoola 见 E7, Kalachakra 见 E8) | 产品,issue #92 ; 10-21 定稿 |
| E1b | D81 / D108 / D144 研究型分盘与「其他分盘口径的计算数据」从 #91 移入本单,同样标研究口径 | 产品 2026-10-21 |
| E2 | 三章(补充推运、辅助次小运、研究型分盘)开头写明「研究口径:依赖所选方法,未经外部软件核对」,英文「Research basis: method-dependent; not checked against external software.」;已知与 PyJHora 不一致的体系逐个标出 | 产品,issue #92 |
| E3 | **这些数据不进对话卡、不进校正打分 ** | 产品,issue #92 |
| E4 | **位置用我方引擎口径;上游 `customer_timing_supplement` 用的视位置设置不得照搬 ** 。本条改写 `TASK-report-full-data-fix-20261008` 红线 4「`customer_timing_supplement` 仍不搬」为:方法可以搬,口径换成我方 | 产品,issue #92 |
| E5 | **大小不设上限 ** ,只记录字符数 / 字节数 / 生成时长;碰到前端等待(`ENGINE_TIMEOUT_MS` 180 s)或存储 / 接口硬上限就停下回报,不得截断 | 产品 2026-10-21 |
| E6 | 阅读页没有单章上限:240 万字符的一章会整页渲染。渲染时按方法族拆成多个二级标题(不是体量上限,是为了能翻页),拆法写进 PROGRESS | Claude(执行细节) |
| E7 | **Niryaana Shoola 子运与第三层:只在有书面出处时做 ** (书名 + 页码)。照上游实现核对其出处;找不到就不上这一层,并在报告该体系标题下与 PROGRESS 写明原因,不得自编规则(BUG-1393、#84 )。Claude 10-23 先查过:上游 `main` 的 `scripts/niryaana_shoola_dasha.py` 只产主运,原文写着「Its child-period rule is not independently specified」;上游交付版里那几行子运来自未公开的冻结引擎,**不能当出处**。所以除非 T0 找到书面规则,默认结果是不做 | 产品 10-23(issue #92 scope-final) |
| E8 | **Kalachakra 补到第三层,不改现有结果 ** : #91 B 段已出 Kalachakra 主运与子运(标「简化模型」);本单只加第三层边界,同样标「简化模型 / Simplified model」。现有主运、子运对 3 张虚构盘逐项不变;第三层与上游规则对不上就停下回报,不自行调整 | 产品 10-23(issue #92 scope-final) |
## 4. 硬红线
1. **口径 ** :新模块取经度一律经我方排盘函数(Swiss Ephemeris + 真岁差,与 `tests/test_sidereal_nutation_frame.py` 同框架)。删去 `FLG_MOSEPH` 、`FLG_TRUEPOS` 、`'apparent'` 参数与交点偏移;回归点求根也用我方函数。新增测试:每个方法族在出生时刻用到的日、月经度与 `core_chart.planets.*.degree_raw` 差 ≤ 1e-6°;任取一个 Tithi 回归时刻,日月角距与目标 Tithi 角差 ≤ 1e-6°。
2. **隔离(E3) ** :本单产出**不写进资料包 `worksheets` **,放在单独的键(例如 `packet["research_timing"]` )或像星座大运一样在渲染时计算。测试:(a) `read_full_data_domain` 对 3 张虚构盘的输出不含本单任何键,对话卡逐字节不变;(b) 用语法树断言 `scripts/rectification*` 、`scripts/birth_time*` 、校正打分入口不导入本单新模块;(c) 校正打分 golden 不变。
3. **PyJHora ** :运行时不得导入 `jhora` 。「与 PyJHora 不一致」的标注来自**离线对照**:脚本放 `scripts/research/` ,只在执行方本机跑,结果存成 JSON 提交,pytest 不导入 jhora( BUG-1275 的做法)。
4. **增长冻结 ** :不改 `scripts/jyotish_api_server.py` ; `scripts/*.py` 根目录 452 个已到 `ROOT_PY_BUDGET = 452` ,新模块进子包(例如 `scripts/research_timing/` ,或并入 #91 的 `scripts/annual_native/` ),不得调高预算;`SILENT_EXCEPT_BUDGET` 不增加,每个方法族失败要记日志并给出具体原因,不得把导入失败包成 `blocked` 了事。
5. **引擎输出变化 ** :验收必须全量 pytest 与开工基线逐条比对;四种星座大运的主运与子运、#91 已出的 Kalachakra 主运与子运对 3 张虚构盘逐行不变。
6. **不编周期 ** :上游注明「不提供次小运」「不推造小运」的方法照样不提供;blocked 的分支(如 Sudasa 已确认起点)只写原因,不上空表。
7. **不截断(E5) ** :不设大小上限;只有中英合计生成超过 180 s 前端等待,或存储 / 接口报错(写入失败、请求或响应被拒),才停止接线、推分支、在 PROGRESS 写清实测数和卡在哪一步,等产品决定;不得删行、抽样或降精度来压体量。
8. 中英文各自从源字段生成,英文汉字 0(#89 的 200 盘测试照跑);中文卫生门(#91 新加的两条)照跑。
9. 不动数据库结构;不改 `.gitea/` ;不推 `main` ;只用虚构盘与公开名人盘。
## 5. 任务分解
| # | 内容 | 验收标准 |
| --- | --- | --- |
| T0 | 盘点:逐个方法族列出上游函数、输入、出处、口径参数,以及要改成我方函数的地方;Niryaana Shoola 子运出处(E7);四种星座大运次小运规则的出处(接 `TASK-rashi-dashas-native-20261008` T0 的规则表) | PROGRESS 一张表,每格有出处或 `blocked` |
| T1 | **先量再接 ** :搬两个最重的方法族(Tithi Ashtottari 年度、Yoga / Nakshatra Pravesha 后继分支),先只算不渲染,对 3 张虚构盘量耗时和输出行数,按上游行数比例外推整章大小与时长;外推超过 180 s 就停(红线 7) | PROGRESS 有实测与外推;未碰硬上限才继续 |
| T2 | 其余方法族搬进子包,改口径(红线 1),每族一个测试:固定虚构盘的周期边界 golden 来自我方引擎实跑,不手造 | 各族测试通过;红线 1 两条口径测试通过 |
| T3 | 渲染「补充推运计算数据」:章首 E2 文字;按方法族分二级标题(E6);当前周期定位表;中英双语字段标签 | 3 张虚构盘中英文出现;英文汉字 0 |
| T4 | 「辅助大运次小运数据」:Sthira / Drig / Shoola / Navamsha 次小运(按 T0 出处);Niryaana Shoola 按 E7; Kalachakra 第三层按 E8;章首 E2 文字,每个体系写所用规则 | 主运、子运逐行不变(含 Kalachakra);各体系次小运条数写进 PROGRESS;Niryaana 若不做,标题下有原因句 |
| T4b | 研究型分盘(E1b):渲染「其他分盘口径的计算数据」与 D81 / D108 / D144 原始数据,中英双语,章首 E2 文字;现有结构化附录里的零散 D81 / D108 / D144 行并入该章,不重复 | 3 张虚构盘中英文出现;全文 D81 / D108 / D144 只在研究口径章节出现 |
| T5 | PyJHora 离线对照(红线 3):10 张以上虚构盘,逐体系逐层比对,不一致的在报告该体系标题下标「与 PyJHora 不一致:差在起点 / 方向 / 年数 / 子运划分」 | 对照 JSON 与汇总表进 PROGRESS;报告标注与 JSON 一致 |
| T6 | 隔离测试(红线 2 的 a、b、c) | 全部通过 |
| T7 | 量测与文档:Linux( Claude 复测)与 Windows 生成时长;中英 Markdown 字符与字节;快照 JSON 大小;打开报告接口的响应大小与耗时;阅读页章数与最大一章。BUG_HISTORY(若有缺陷)、CHANGELOG、PROGRESS、`docs/testing/full-data-parity-2-20261021.md` (真机:生成、翻到两章、中英切换、打印预览、导出) | 实测数齐全;未碰硬上限,或已按红线 7 停下 |
## 6. 验收分工
| 检查 | Grok 自跑 | Claude 验收 |
| --- | --- | --- |
| 改到的与新增的 Python 测试文件,含 `test_sidereal_nutation_frame` 、`test_rashi_dashas_native` 、`test_full_data_domain_reader` 、`test_report_english_edition` 、`test_python_hygiene_budget` 、`test_api_server_growth_contract` 、`test_runtime_jhora_import_scan` | 必跑(工作树 `.venv` ),屏蔽 PyJHora 再跑一次 | 复验 |
| 前端 `tsc` 、`lint` 、改到的测试文件 | 动了前端才跑 | 复验 |
| Python 快速门 | 不跑 | 以 Claude 为准 |
| **Python 全量 pytest 与基线逐条比对 ** | 不跑 | 以 Claude 为准 |
| 前端全量 `npm test` 、`npm run build` | 不跑 | 以 Claude 为准 |
| 生成时长与体量(Linux,屏蔽 PyJHora) | Windows 数照报 | 以 Claude 的 Linux 数判是否碰 180 s |
## 7. 让步顺序
不让步:红线 1、2、7,E2 章首说明,英文汉字 0。时间不够时依次让出:T5 的覆盖盘数(不少于 5 张)→ Karana 三种中的 PVR 360 Tithi 年 → Tithi Ashtottari 年度的「时间余额」分支。让出的写进 PROGRESS。
## 8. 开工前置命令
``` bash
git fetch origin --prune
git log origin/staging --oneline | grep -i "full-data-parity-1" | head # 确认 #91 两段都已合入
git worktree add -b codex/full-data-parity-2-20261021 .worktrees/full-data-parity-2-20261021 origin/staging
cd .worktrees/full-data-parity-2-20261021
git -C ../yinduzhanxing-ref fetch origin main codex/customer-export-frozen-20261016
git -C ../yinduzhanxing-ref show 258e08e3:scripts/customer_timing_supplement.py | head -60
grep -n "FLG_MOSEPH\|FLG_TRUEPOS\|apparent" ../yinduzhanxing-ref/scripts/{ annual_pravesha,pravesha_return_year,karana_annual_dasha,tithi_ashtottari} .py
ls scripts/*.py | wc -l # 452 = ROOT_PY_BUDGET,新文件不得放根目录
grep -oE "^## BUG-[0-9]+" docs/BUG_HISTORY.md | sort -u | sort -t- -k2 -n | tail -1
```
## 9. BUG 编号
定稿时 staging 最大号 BUG-1423, #91 修复单合入时还会占号,**开工时核对最大号 + 1 起**。本单以补功能为主,只有发现缺陷才占号。