merge origin/staging into codex/chart-subject-table-20261021
This commit is contained in:
@@ -513,3 +513,11 @@
|
||||
| `TASK-synastry-staging-fix-20261021.md` | `PROGRESS-synastry-staging-fix-20261021.md` | #85 婚恋合盘 staging:P0 `.gte` 已在 staging(BUG-1399)。本分支补时间线起止、英文分析逻辑、删模型后旧对话改用默认模型、选人页开关文案,以及合盘创建真库测试(BUG-1400~1403) | 待验收 | `codex/synastry-staging-fix-20261021` |
|
||||
| `TASK-synastry-report-chat-fix-20261020.md` | `PROGRESS-synastry-report-chat-20261018.md` | 大运核对要认报告里的「小运」和英文 mahadasha / antardasha;预填文案只留一句 | 已验收 | `549e37b99` |
|
||||
| `TASK-other-subject-birth-time-edit-20261021.md` | — | 其他人的出生时间可以修改(去掉 `chart_profiles_guard_birth` 的填报时间不可改;推翻 people-archive P1 §验收 2 该条;active 锁不变) | 排队(等 #86 合入) | `codex/other-subject-birth-time-edit-20261021`;动表;不进循环 |
|
||||
| `TASK-chart-ascendant-slash-20261021.md` | — | #87 上升状态格「不适用」改「/」+ 悬停 / 点按说明;报告上升行同改并在表下加说明(推翻 10-15 口径) | 待领取(等 #86 合入) | `codex/chart-ascendant-slash-20261021` |
|
||||
| `TASK-report-export-dialog-polish-20261021.md` | — | #90 导出弹窗:英文不可用说明改顶部浮层(每次打开一次);选中态描边 / 圆点 / 勾选框用主题色、底色中性(推翻 DESIGN「Selected states use canvas-muted + ink」,BUG-1078 主语保留) | 待领取(可与 #89 并行) | `codex/report-export-dialog-polish-20261021` |
|
||||
| `TASK-report-english-han-leak-20261021.md` | `PROGRESS-report-english-han-leak-20261021.md` | #89 个人报告英文版约 4 成整份丢弃:漏译汉字源头补英文孪生字段、修吃空格;200 张固定种子回归 + 快速门 smoke(复发自 BUG-1272);中文逐字节不变 | 待领取(最先做;#91/#92 在后) | `codex/report-english-han-leak-20261021` |
|
||||
| `TASK-chart-dasha-yogini-20261021.md` | — | #88 星盘页大运右栏切换 Chara / Yogini,Yogini 补小运(按比例,内部核对,页面注明未经外部软件核对);Ashtottari 另立单(起运表与 PyJHora 8/12 宿不一致) | 待领取(等 #86、#87 合入) | `codex/chart-dasha-yogini-20261021` |
|
||||
| `TASK-full-data-parity-1-20261021.md` | — | #91 完整数据版补齐(一):A 段渲染(中文附录改中文源、71 小节归 6 章、导出星座名)→ B 段引擎(Yogi 点、标准大运子运、Kalachakra 简化子运、年运三年全展开、36 个月星历、解释补长);不设大小上限 | 待领取(等 #89 合入;T3 与 #90 串行) | `codex/full-data-parity-1-20261021` |
|
||||
| `TASK-goel-jaimini-strength-20261022.md` | — | #94 搬上游公开的 Goel Jaimini 强度与 Brahma / Maheshwara / Rudra(4 个上游原样文件,按 #81 决策 A 先例根目录预算 448→452;来源串改书目引用);只做计算、不改报告;冻结身份不变 | 待领取(可与 #89 并行;须在 #91 印章前合入) | `codex/goel-jaimini-strength-20261022` |
|
||||
| `TASK-chart-subject-table-fix-20261022.md` | `PROGRESS-chart-subject-table-20261021.md` | **#86 第 1 次打回**:报告所有表被强制单行(打印跑出纸面);保存他人复制了档案页代码且已分叉;点编辑框内边距会丢修改 | 待领取 | `codex/chart-subject-table-20261021` |
|
||||
| `TASK-full-data-parity-3-20261022.md` | — | #93 完整数据版补齐(三):「独立年度方法参考」正式环境调 PyJHora(子进程隔离;装上后其余输出逐字节不变,7 个换路径开关钉死);输入默认用本地时间与正确年份(D6 待产品确认) | 待领取(等 #91 合入) | `codex/full-data-parity-3-20261022` |
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# 任务书:上升的「状态」格把「不适用」改成「/」,悬停 / 点按显示说明(2026-10-21,issue #87)
|
||||
|
||||
## 基线
|
||||
|
||||
- 写单时 `origin/staging` = `bd3ce0e7b`。**本单排在 #86 之后**:#86(人名切换、出生资料就地编辑、报告行星表列宽)同样改 `chart-vedic-tab.tsx` 一带与 `scripts/pl9_reader_export.py` 的行星表。开工时 #86 必须已合入 `origin/staging`,以那时的 `origin/staging` 为基线;没合入就不开工,回报「等 #86」。
|
||||
- 执行方:Grok(Windows + Docker)。分支 `codex/chart-ascendant-slash-20261021`,工作树 `.worktrees/chart-ascendant-slash-20261021`,只推这个分支。
|
||||
|
||||
## 现状实证(按符号定位)
|
||||
|
||||
| # | 位置 | 现在写什么 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `frontend/src/components/chart-page/chart-vedic-tab.tsx` 的 `AscendantRow`(本命表、月亮盘表共用)最后一格 | `<td>不适用</td>`;函数头注释也写「the status cell says 不适用」 |
|
||||
| 2 | 同文件 `VargaAscendantRow`,`supportsDignity` 为真时的庙旺格 | `<td className="chart-page-planet-mid">不适用</td>` |
|
||||
| 3 | `scripts/pl9_reader_export.py`「#### p6 行星主星与状态」表的上升行(`asc_label` 下一行的 `dignity`) | 中文「不适用」,英文 `not applicable` |
|
||||
| 4 | `frontend/DESIGN.md` 星盘页本命表段(「上升没有尊贵,状态格写「不适用」(2026-10-15…)」)与分盘表那一行(「上升此格「不适用」」) | 现行口径 |
|
||||
|
||||
页面上已有的说明气泡:`chart-page/chart-symbol-help.tsx` 的 `ChartSymbolHelp`。它给子树里带 `data-chart-help` 的元素出气泡:鼠标悬停时显示,触屏点一下显示并停留 `TOUCH_BUBBLE_MS`。现在只包住了星盘图(`VedicLayout` 的 `chart-page-vedic-stage`),表格不在里面。
|
||||
|
||||
## 根因
|
||||
|
||||
不是缺陷,是口径变更。上升是命盘起点,不是行星,本来就不判入旺 / 落陷。2026-10-15 为了不让「—」被当成没数据,改写成「不适用」;产品现在认为「不适用」像工程内部用语。
|
||||
|
||||
## 决策记录(产品负责人 2026-10-21,issue #87)
|
||||
|
||||
1. 上升的状态格(本命表、月亮盘表、分盘表的庙旺格)写「/」。说明句一字不差:「上升点是命盘的起点,不是行星,不判断入旺或落陷」。电脑悬停显示,读屏读得到同一句,手机点一下显示。
|
||||
2. 报告的「行星主星与状态」表上升行中英文都写「/」。PDF / 导出没法悬停,所以在**这张表下面**加一行说明:中文用上面那句,英文用「The ascendant is the starting point of the chart, not a planet, so it has no dignity.」。
|
||||
3. **推翻 2026-10-15 的「不适用」口径**(DESIGN.md 两处与 BUG-1201 / 1202 相关描述)。执行方按本单改,不得以「历史决定是不适用」为由拒改。
|
||||
4. 只改上升这一格。技法审计表、合盘、兑换码、会员等其他地方的「不适用」一律不动。
|
||||
|
||||
## 硬红线
|
||||
|
||||
- tsc 0 错;`npm run lint` 0 error;`next build` 后 `/` 仍 `○ Static`;首屏 gzip ±2%。
|
||||
- 不新写第二套气泡 / tooltip:说明一律走 `ChartSymbolHelp` + `data-chart-help`(若需要给表格区也包一层 `ChartSymbolHelp`,可以;气泡位置的计算不复制)。
|
||||
- 「/」那一格要能被测试稳定找到:给它加固定 class(例如 `chart-page-asc-status`)或断言说明句。不要让测试去匹配裸的 `/`,HTML 里到处都是 `/`。
|
||||
- 改任何既有断言写「原值 / 新值 / 原因」三栏(AGENTS §7.3)。
|
||||
- 同一提交更新 `frontend/DESIGN.md`;说明句对照 `frontend/docs/VOICE.md`。
|
||||
- 不改数据库、不改 `scripts/jyotish_api_server.py`。
|
||||
|
||||
## 任务分解
|
||||
|
||||
### A1 · 星盘页三处上升格
|
||||
|
||||
- `AscendantRow`、`VargaAscendantRow` 的状态格显示「/」,带说明句(悬停气泡 + `aria-label` 或可读的隐藏文字,读屏读到的是那一句而不是「斜杠」);可聚焦或可点,触屏点一下出气泡。
|
||||
- 函数头注释改成新口径。
|
||||
- 验收:渲染测试断言本命表、月亮盘表、D9 分盘表的上升格内容是「/」、带说明句;`supportsDignity` 为假(旧缓存)时整列仍不显示。
|
||||
|
||||
### A2 · 报告上升行与表下说明
|
||||
|
||||
- `pl9_reader_export.py` 上升行 `dignity` 中英文都是 `/`;该表结束后紧跟一行说明(中 / 英各自的句子)。
|
||||
- 英文版不得出现汉字(英文版有汉字会被整份丢弃,见 issue #89 / BUG-1272)。
|
||||
- 验收:`tests/test_report_chart_blank_columns.py` 中「行星主星与状态」表上升行状态格断言改成 `/`(三栏),并新增断言:中英两版表下各有那一行说明。
|
||||
|
||||
### A3 · 按源码文本或渲染结果断言的测试(AGENTS §7.8,同轮改)
|
||||
|
||||
| 文件 | 现有断言 | 改成 |
|
||||
| --- | --- | --- |
|
||||
| `frontend/tests/chart-varga-table.test.tsx` | D1 表与基线逐字节比对时把上升行替换成 `<td>不适用</td>`;另有三处 `/>不适用</` | 替换串与断言换成新的那一格(三栏,基线 fixture 文件不改) |
|
||||
| `frontend/tests/chart-moon-transit.test.tsx` | 月亮盘上升行 `/>不适用</` | 新那一格 |
|
||||
| `frontend/tests/chart-page-hook.test.ts` | 旧缓存时 `doesNotMatch(/>庙旺<\|>同位<\|>不适用</)`,用来证明整列隐藏 | 把 `不适用` 换成新那一格的固定 class 或说明句,**整列隐藏的保护不能丢** |
|
||||
| `tests/test_report_chart_blank_columns.py` | `lords[1][5] == "不适用"` | `"/"`,并加表下说明断言 |
|
||||
|
||||
开工时再 `grep -rn "不适用" frontend/tests tests` 一遍,凡是断言上升这一格的都要改,别的不动。
|
||||
|
||||
### A4 · 文档
|
||||
|
||||
- `frontend/DESIGN.md` 两处改成「/」+ 说明句 + 2026-10-21 推翻 10-15 口径。
|
||||
- `CHANGELOG.md` 一句话。
|
||||
- 不是 Bug,不新增 BUG 编号;若开工中发现真缺陷,按 §5 从当时最大号 +1 记。
|
||||
|
||||
## 验收命令
|
||||
|
||||
| 检查 | Grok 自跑 | Claude 验收 |
|
||||
| --- | --- | --- |
|
||||
| `tsc --noEmit`、`npm run lint` | 必跑 | 复验 |
|
||||
| 上表 3 个前端测试文件 | 必跑 | 复验 |
|
||||
| `tests/test_report_chart_blank_columns.py` | 必跑(工作树 `.venv`) | 复验 |
|
||||
| 前端全量 `npm test`(与基线逐名比对)、`npm run build`(`/` Static、gzip)、Python 快速门 | 不跑 | 以 Claude 为准 |
|
||||
|
||||
真机走查清单:`docs/testing/chart-ascendant-slash-20261021.md`。
|
||||
|
||||
## 让步顺序
|
||||
|
||||
1. A1、A2 与 A3 不可省。
|
||||
2. 如果手机「点一下出气泡」在表格里和 `ChartSymbolHelp` 的定位冲突、做不好,可以先只做悬停 + 读屏,手机点按写进进度记录留待下一轮;**不得**为此另写一套气泡。
|
||||
|
||||
## 开工前置命令
|
||||
|
||||
```
|
||||
git fetch origin --prune
|
||||
git log --oneline -20 origin/staging # 确认 #86 已合入
|
||||
git worktree add -b codex/chart-ascendant-slash-20261021 .worktrees/chart-ascendant-slash-20261021 origin/staging
|
||||
grep -rn "不适用" frontend/src/components/chart-page scripts/pl9_reader_export.py frontend/tests tests frontend/DESIGN.md
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
# 任务书:星盘页大运右栏可切换,加 Yogini(含小运)(2026-10-21,issue #88)
|
||||
|
||||
> 产品 2026-10-21 已决定 **Ashtottari 不在本单**(见 P2 与「另行处理」)。
|
||||
|
||||
## 基线
|
||||
|
||||
- 写单时 `origin/staging` = `bd3ce0e7b`(此后 `308ca2a65` 只加了 #87 任务书)。**开工时以 #86、#87 合入后的 `origin/staging` 为准**;任一未合入就不开工,回报「等 #86 / #87」。
|
||||
- 执行方:**Grok(Windows + Docker)**。分支 `codex/chart-dasha-yogini-20261021`,工作树 `.worktrees/chart-dasha-yogini-20261021`,**只推这个分支**,不推 staging、不推 main。
|
||||
- 关联:BUG-1196、BUG-1262(Yogini 顺序与出生余额)、BUG-1341(`/api/dasha` 改走引擎模块)、BUG-1344(PyJHora 大运对照的来源)。
|
||||
|
||||
### 与 #86、#87 的文件重叠
|
||||
|
||||
| 文件 | #86 | #87 | 本单 |
|
||||
| --- | --- | --- | --- |
|
||||
| `frontend/src/components/chart-page/chart-page-view.tsx` | 出生资料行(页头 `birthline` 一带)、人名切换 | — | `ChartDashaTab` 的入参(等待 / 失败按层传) |
|
||||
| `frontend/src/hooks/use-chart-page.ts` | 可能(切换人物时) | — | `mergeLayer`、`missing` 判定 |
|
||||
| `frontend/tests/chart-page-hook.test.ts` | 可能 | 旧缓存时 `>不适用<` 断言改成 `/` | 新层的 429 / 失败 / 合并用例 |
|
||||
| `frontend/tests/chart-page-view.test.tsx` | 可能 | — | 大运 Tab 渲染用例 |
|
||||
| `frontend/src/app/globals.css`、`frontend/DESIGN.md` | 是 | 是 | 是(大运切换条、§大运 Tab) |
|
||||
|
||||
`chart-vedic-tab.tsx`、`scripts/pl9_reader_export.py`(#87)本单不碰。#86 写单时没有任务书在 staging 上,开工时用 `git log -p origin/staging -- <上表文件>` 看它们实际改了什么,在其上改,不得回退。
|
||||
|
||||
## 现状实证(按符号定位)
|
||||
|
||||
| # | 事实 | 位置 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `/api/dasha` 对非 Vimshottari 体系调 `dasha_registry.compute_dasha`,响应只带 `periods` 和 `algorithm_label / algorithm_note / profile`。小运放进每个大运行的 `antardasha` 字段就会原样透出,**不需要改 `jyotish_api_server.py`** | `scripts/jyotish_api_server.py` `_compute_dasha_system`;`scripts/dasha_registry.py` `compute_dasha` yogini 分支 |
|
||||
| 2 | Yogini 只有大运。行字段 `yogini / planet / full_years / years / start_date / end_date / unclipped_start_date / cycle_number`;第一段 `start_date` 已裁到出生时刻,未裁起点在 `unclipped_start_date`;年长 365.25 天;至少 3 轮(24 段) | `scripts/yogini_dasha.py` `_calculate_moon_balanced_yogini` |
|
||||
| 3 | 完整解读也调 `calculate_yogini_dasha`(`jyotish_engine.cmd_full_reading`),`test_dasha_api_engine_parity.py::test_api_major_periods_match_full_reading_modules` 断言接口 `periods` 与完整解读模块行逐字段相等 | `scripts/jyotish_engine.py`(`from yogini_dasha import calculate_yogini_dasha`);同名测试 |
|
||||
| 4 | PyJHora 4.8.7 的 Yogini 小运是 8 等分(`yogini.get_dhasa_bhukthi` 里 `parent_years / n`),与产品定的按比例算法不同,所以小运不与 PyJHora 对照(P1)。Yogini **大运**写单时实测与 PyJHora 主星顺序一致、起点误差 ≤1 天(虚构盘、Presley、Spears 等 4 张,各 24 段) | PyJHora `jhora/horoscope/dhasa/graha/yogini.py` |
|
||||
| 5 | 前端 Chara 按需层链路:`CHART_VIEW_LAYERS`(`chart-view-engine.ts`)→ `parseChartViewLayers`(`app/api/chart-view/route.ts`)→ `assembleChartView` 按层并发调引擎(`chart-view-load.ts`)→ `buildChartView` / `charaTrack`(`chart-view-mapper.ts`)→ `chartViewOkSchema.dasha` 与 `layerFailures`(`chart-view-contract.ts`,轨道 `id` 是 `z.enum(["vimshottari","chara"])`)→ `useChartPage.requestLayer` / `mergeLayer`(`hooks/use-chart-page.ts`)→ `ChartPageView.selectTab("dasha")` 请求 `chara`(`components/chart-page/chart-page-view.tsx`)→ `ChartDashaTab` 右栏写死 `view.dasha.chara`,等待态 `OrbitWaiting`(`components/chart-page/chart-dasha-tab.tsx`) | 各文件按符号 |
|
||||
| 6 | 照抄 Chara 会踩的三个坑:① 引擎缓存键 `chartViewEngineCacheKey` 只含 path 不含 body;Yogini 走 `/api/dasha`,将来其他体系也走同一 path,**不带 `cacheScope` 就会串缓存**(`chartViewEngineCachePath(path, cacheScope)`,行运层已这样做);② `mergeLayer` 对 `chara` 整个替换 `dasha` 对象(`{ dasha: incoming.dasha }`),新层这样写会把已到的 Chara 冲掉;③ `secondary-page-data.ts` 的 `refreshChartPage` 合并旧快照时只保留 `dasha.chara`,新轨道会在刷新后丢失 | `chart-view-engine.ts` `chartViewEngineCacheKey`;`use-chart-page.ts` `mergeLayer`;`secondary-page-data.ts` `refreshChartPage` |
|
||||
| 7 | DESIGN.md 现有约定:大运 Tab「≥1024 时 Vimshottari 在左、Chara 在右;更窄则上下排」(`.chart-page-dasha-columns`);按需层「失败移除等待,再点该 Tab / chip 清失败并重试」 | `frontend/DESIGN.md` §大运 Tab、§按需层 |
|
||||
|
||||
## 根因
|
||||
|
||||
不是 bug,是缺口:页面只接了 Chara 一个按需层,Yogini 引擎没有小运。
|
||||
|
||||
## 决策记录(产品负责人 2026-10-21)
|
||||
|
||||
| # | 决定 |
|
||||
| --- | --- |
|
||||
| K1 | 左栏固定 Vimshottari 不变。右栏顶部加体系切换:**Chara / Yogini**,默认 Chara |
|
||||
| K2 | Yogini 和 Chara 一样能点开大运看小运;「现在」卡显示大运、小运、小运剩余 |
|
||||
| K3 | (原「Ashtottari 只在适用时出现」)随 P2 移出本单 |
|
||||
| K4 | 数据走现有 `POST /api/dasha {dasha:"yogini"}`;Yogini 小运在 `scripts/yogini_dasha.py` 新增;不改冻结文件 `jyotish_api_server.py` |
|
||||
| K5 | 核对(按 P1 调整):Yogini **大运**与 PyJHora 对照,≥3 张公开名人盘,主星顺序一致、起止日误差 ≤1 天;Yogini **小运**做内部自洽核对(见 T3)。PyJHora 只作测试参照,不进运行代码。对不上就停下报告,不得放宽误差;对不上则 Yogini 不上页面 |
|
||||
| K6 | 前端照 Chara 按需层加 `yogini` 层;切到 Yogini 时才请求;失败只在右栏显示失败句,可重试;等待态沿用 `OrbitWaiting`,不新增加载动画 |
|
||||
| K7 | 合同测试 fixture 取自真实 `/api/dasha` 响应(golden),不得手造 |
|
||||
| K8 | 手机宽度两栏上下排,切换条横向不溢出 |
|
||||
| K9 | 改了 Python,验收跑 Python 快速门全量 |
|
||||
| K10 | 不在本单:Ashtottari、Kalachakra、Narayana、星座类体系 |
|
||||
| P1 = A | Yogini 小运**按比例**:时长 = 大运年数 × 子运年数 ÷ 36,从该大运的 Yogini 起依次轮转 8 位;首个大运按出生时已走过的比例裁掉前面的部分。小运不与 PyJHora 对照(PyJHora 是等分)。页面在 Yogini 小运处注明「小运未经外部软件核对。」 |
|
||||
| P2 = A | **Ashtottari 不在本单**。切换只有 Chara / Yogini。Ashtottari 的起运表与适用口径另立 issue(见「另行处理」) |
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. **不改 `scripts/jyotish_api_server.py`**(`git diff origin/staging -- scripts/jyotish_api_server.py` 必须为空);`tests/test_api_server_growth_contract.py` 照常通过。
|
||||
2. **不碰 Ashtottari**:`scripts/ashtottari_dasha.py`、`scripts/ashtottari_rule_profiles.py`、`dasha_registry.compute_dasha` 的 ashtottari 分支、完整解读里的 Ashtottari 段一律不改。
|
||||
3. PyJHora 只在测试里出现;`tests/test_runtime_jhora_import_scan.py` 必须通过;`scripts/**` 不得 `import jhora`。
|
||||
4. 对照测试必须**真跑**:工作树 `.venv` 按 `requirements-reference-engines.txt` 装 PyJHora 4.8.7;新对照用例 `pytest -rs` 不得出现 skip。跳过不算通过。
|
||||
5. 对照测试走**接口路径**(`_compute_dasha_system` 或 `compute_dasha`),不直接调模块。
|
||||
6. **停止条件**:Yogini 大运对照不过(主星顺序不同,或任一起止日误差 >1 天),或 T3 的内部核对不过,停下,在 PROGRESS 写清哪张盘、哪一段、差多少,回报;不得放宽误差、不得换样本凑通过、不得改 PyJHora 调用参数迎合引擎。此时 Yogini 不上页面。
|
||||
7. 不改完整解读(`cmd_full_reading`)的 Yogini 输出:小运只挂在接口返回的副本上;虚构盘完整解读逐字段不变(PROGRESS 附 diff 结论)。
|
||||
8. 前端 fixture 只来自真实 `/api/dasha` 响应,文件头写 `sourceCommand`(命令、盘、岁差、交点);不得手写形状。
|
||||
9. 改既有断言写「原值 / 新值 / 原因」三栏;测试总数不得少于开工基线。
|
||||
10. 不改数据库、不改 `.gitea/`、不升级依赖(PyJHora 只装进工作树 `.venv`,不改 `requirements*.txt`)。
|
||||
11. 隐私:fixture、测试、文档只用公开名人盘或虚构盘。
|
||||
|
||||
## 任务分解
|
||||
|
||||
| # | 内容 | 验收标准 | Grok 自跑 | Claude 验收 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T1 | `scripts/yogini_dasha.py` 新增独立函数(如 `yogini_antardashas(major_row, *, birth_dt, year_days)`):从该大运自己的 Yogini 起依次 8 位;时长 = 大运 `full_years` × 子运年数 ÷ 36;从 `unclipped_start_date` 起排,结束于出生前的小运丢掉,跨出生的那段起点裁到出生(保留 `unclipped_start_date`);最后一段收尾到大运 `end_date`。`calculate_yogini_dasha` 输出不变 | 新单测(沿用 `test_yogini_seed_profile.py` 的盘):见 T3 内部核对 | `tests/test_yogini_seed_profile.py` + 新单测 | 复验 + 快速门 |
|
||||
| T2 | `dasha_registry.compute_dasha` 的 yogini 分支:对大运行做深拷贝并挂 `antardasha`(字段 `level / planet / yogini / lord / years / start_date / end_date`,跨出生那段加 `unclipped_start_date`) | 虚构盘、Spears 接口响应有小运;完整解读 Yogini 模块与改前逐字段相同 | 同 T3 | 复验 |
|
||||
| T3 | 核对(K5、P1、红线 4–6),写进 `tests/test_dasha_api_engine_parity.py`:**① 大运对照**:≥3 张公开盘(候选 Presley、Spears、Einstein、Trump,盘数据已在 `tests/smoke_test_runner.py` / 本测试文件里),接口大运对 PyJHora `yogini.get_dhasa_bhukthi(..., dhasa_level_index=1)`,主星顺序一致,起点(用 `unclipped_start_date`)误差 ≤1 天。**② 小运内部核对**(同几张盘的全部大运):每个大运的小运时长之和与大运(裁后)时长相差 ≤1 天;小运主星从该大运自己的 Yogini 起按 Goel Table I 轮转;首个大运第一条小运起点等于大运裁后起点(出生时刻),其余大运第一条小运起点等于大运起点;相邻小运首尾相接;未裁的首个大运按比例时长与公式一致。**③ 既有断言**:`test_api_major_periods_match_full_reading_modules` 对 yogini 由 `result["periods"] == engine_rows` 改为去掉 `antardasha` 后相等(三栏) | `pytest -rs` 0 skip;全部通过或按红线 6 停止 | 必跑 | 复验(本机已装 PyJHora 4.8.7) |
|
||||
| T4 | 前端数据层:`CHART_VIEW_LAYERS` 加 `yogini`;`assembleChartView` 调 `/api/dasha`,body 为 `followUp` + `dasha: "yogini"`,**`cacheScope` 取体系名**(`"dasha:yogini"`);`chartViewDashaTrackSchema.id` 加 `yogini`,`chartViewOkSchema.dasha` 与 `layerFailures` 加 `yogini`;`chart-view-mapper.ts` 照 `charaTrack` 写 `yoginiTrack`:大运 / 小运日期直接用引擎值(`derived: false`),大运标签「<Yogini 名> / <行星中文>」(例「Pingala / 太阳」),小运同样格式;`antardashaNote` = 「小运未经外部软件核对。」(放在 `labels.ts`,在轨道列表下与其他轨道的说明句同一位置,`.chart-page-note`)。**可推广要求**:大运层的「层名 → 引擎体系键 → 缓存 scope → 轨道键」写成一张表(一处常量),加第二个 `/api/dasha` 体系时只加一行 + 一个映射函数,不改 hook 与合并逻辑 | golden 测试:Yogini 真实响应映射后过 `chartViewOkSchema`;缓存测试:同一人先取 Chara 再取 Yogini、再换一个 `/api/dasha` 体系键(测试里传参),各拿各的数据 | 改到的测试文件 | 全量 `npm test` |
|
||||
| T5 | `use-chart-page.ts`:`mergeLayer` 对大运层只替换各自那条轨道(`dasha: { ...current.dasha, [轨道]: incoming.dasha[轨道] }`,Chara 也改成这样),不再整体替换 `dasha`;`requestLayer` 中 Yogini 以 periods 为空判失败;`secondary-page-data.ts` `refreshChartPage` 按 T4 那张表保留所有已到的大运轨道,不再只写 `chara` | hook 测试:先到 Chara 再到 Yogini,Chara 仍在;反过来亦然;Yogini 429 只在 Yogini 层失败;D1 刷新后 Yogini 轨道仍在 | 改到的测试文件 | 全量 |
|
||||
| T6 | 页面:`ChartDashaTab` 右栏顶部切换条(Chara / Yogini,默认 Chara,`role="tablist"` 或分段按钮,当前项样式沿用页面 Tab 下划线语言);进大运 Tab 仍只请求 Chara;切到 Yogini 才请求 `yogini`;等待 `OrbitWaiting`;失败只在右栏显示 `chartLayerFailureCopy` 的句子,再点当前选项重试(与 DESIGN.md 按需层重试一致,不另加按钮);左栏 Vimshottari 不受影响;`chart-page-view.tsx` 把按层的等待 / 失败传进 `ChartDashaTab` | 渲染测试:默认 Chara;切到 Yogini 只发 yogini 层;右栏失败时左栏正常;再点 Yogini 重新请求;Yogini「现在」卡有大运 / 小运 / 小运剩余;Yogini 轨道下有「小运未经外部软件核对。」,Chara、Vimshottari 下没有 | 改到的测试文件 | 全量 + `next build` |
|
||||
| T7 | 样式与文档:切换条在 375px 不横向溢出(两个短标签,不用横向滚动;点击区 ≥44px);≥1024 两栏、更窄上下排沿用 `.chart-page-dasha-columns`;DESIGN.md §大运 Tab 改写(右栏可切换、Yogini 小运说明句、失败与重试、按需请求列表加 `/api/dasha` Yogini);`frontend/docs/VOICE.md` 若有大运 Tab 文案表则补这句;CHANGELOG;PROGRESS;真机清单 `docs/testing/chart-dasha-switch-20261021.md`(电脑与手机各打开大运 Tab,切两种体系、展开当前大运看小运、核对「现在」卡、手机看切换条不溢出) | 样式断言测试;文档齐 | — | 复验;`/` 仍 Static、首屏 gzip ±2% |
|
||||
|
||||
### 按源码文本断言、需要同轮跟改的测试(AGENTS §7.8,开工时再 grep 一遍)
|
||||
|
||||
| 测试 | 断言的内容 |
|
||||
| --- | --- |
|
||||
| `frontend/tests/chart-view-route.test.ts` | `loadSource` 匹配 `/api/dasha/chara`;`CHART_VIEW_LAYERS` 全层组装;`body.dasha.chara.title` |
|
||||
| `frontend/tests/chart-page-hook.test.ts` | 按层 429 循环(层名列表带三栏注释,加层再写三栏);引擎桩按 path 分派(`/api/dasha` 要按 body 的 `dasha` 分派) |
|
||||
| `frontend/tests/chart-page-view.test.tsx` | 大运 Tab 含「Chara Dasha(kn_rao 变体)」;chara 失败 / 等待;「现在」卡数量 = 2、时间条段数 = 两条轨道段数之和(默认 Chara 时应不变) |
|
||||
| `frontend/tests/chart-view-zero-year.test.ts` | `ChartDashaTab` 渲染 Chara 0 年段、段数 |
|
||||
| `frontend/tests/secondary-page-entry.test.ts` | `use-chart-page.ts` 源码文本(`useState<ChartViewResponse \| null>(() => viewMatchingSubject(subjectId))`) |
|
||||
| `frontend/tests/chart-profile-update-consistency.test.ts`、`chart-view-engine.test.ts` | 缓存键、`chartViewOkSchema` |
|
||||
| `tests/test_chart_page_vedic_tables_contract.py` | 用 golden 跑 `buildChartView`(`chara: null`,新增入参要兼容) |
|
||||
| `tests/test_dasha_api_engine_parity.py` | `periods == engine_rows`、未实现键报错 |
|
||||
| `tests/run_all.py`「Yogini Dasha 8 periods」、`tests/test_yogini_seed_profile.py`、`tests/test_full_reading_conditional_dashas.py`、`tests/test_report_longform_*` | Yogini 模块输出(完整解读不变则不应变红) |
|
||||
|
||||
## 让步顺序
|
||||
|
||||
1. 时间不够:T1–T3(Python + 核对)单独提交先推分支,前端后补;不得先上前端、后补核对。
|
||||
2. T4 的「可推广表」若与现有类型冲突难以收敛,可先只做 Yogini,但 T5 的「按轨道合并、刷新保留全部大运轨道」不让步。
|
||||
3. 核对不过:按红线 6 停,Yogini 不上页面,本单只交已完成的前端重构部分须经 Claude 确认后再推。
|
||||
|
||||
## 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git log --oneline -30 origin/staging # 确认 #86、#87 已合入;没合入就回报「等 #86 / #87」
|
||||
git worktree add -b codex/chart-dasha-yogini-20261021 .worktrees/chart-dasha-yogini-20261021 origin/staging
|
||||
cd .worktrees/chart-dasha-yogini-20261021
|
||||
.venv/Scripts/python -m pip install -r requirements-reference-engines.txt # 只装进工作树 .venv
|
||||
.venv/Scripts/python -c "import importlib.metadata as m; print(m.version('PyJHora'))" # 必须 4.8.7
|
||||
.venv/Scripts/python -m pytest tests/test_dasha_api_engine_parity.py tests/test_yogini_seed_profile.py -rs -q # 记基线
|
||||
grep -rn "chara" frontend/src/lib/chart-view-*.ts frontend/src/hooks/use-chart-page.ts frontend/src/lib/secondary-page-data.ts frontend/src/components/chart-page/
|
||||
grep -rln "chart-dasha-tab\|use-chart-page\|chart-view-load\|chart-view-contract\|chart-view-mapper\|CHART_VIEW_LAYERS\|dasha/chara" frontend/tests tests
|
||||
git log -p origin/staging -- frontend/src/components/chart-page/chart-page-view.tsx frontend/src/hooks/use-chart-page.ts | head -200 # 看 #86/#87 改了什么
|
||||
grep -o "BUG-1[0-9]\{3\}" docs/BUG_HISTORY.md | sort -u | tail -3
|
||||
```
|
||||
|
||||
验收分工:Grok 自跑 `tsc --noEmit`、`npm run lint`、改到的前端测试文件、改到的 Python 测试文件(含 T3,`-rs` 无 skip);Claude 验收时跑前端全量 `npm test`、Python 快速门 `run_quality_gate.py --profile quick`、`npm run build`(`/` 仍 Static、首屏 gzip ±2%),并复跑 T3。本单不动数据库,不要求 `test:db`。执行方不得在真机清单里写「已通过」。
|
||||
|
||||
## 另行处理(不在本单)
|
||||
|
||||
- **Ashtottari** 的起运表与「本盘是否适用」口径另立 issue,由 Claude 起草给产品负责人确认后再提。写单时已确认的问题:接口用的旧白名单起运星在 12 个月宿里有 8 个与 PyJHora 不一致、白名单索引 20 的注释错一位、三套适用口径在 10 张公开盘上结论互不相同、接口从不传规则族、现有对照测试绕过接口路径。
|
||||
- **完整解读 / 报告里的 Ashtottari 段目前可能是错的**(与接口共用同一函数、同一白名单)。本单不碰,留给上述 issue。
|
||||
|
||||
## BUG 编号
|
||||
|
||||
本单是功能缺口,原则上不新增 BUG;执行中若发现并修了 bug 才记。编号取开工时 `docs/BUG_HISTORY.md` 最大号 +1。该文件自 `400af57e8` 起整段重复了一遍,取最大号务必用 `grep -o "BUG-1[0-9]\{3\}" docs/BUG_HISTORY.md | sort -u | tail -1`,不要按行号或首次出现取。#85 占用 BUG-1404~1408。
|
||||
@@ -0,0 +1,60 @@
|
||||
# 修复单:星盘页就地编辑 / 人名切换 / 报告行星表(issue #86,第 1 次打回,2026-10-22)
|
||||
|
||||
- 被验提交:`885bd5665`(分支 `codex/chart-subject-table-20261021`)
|
||||
- 需求:issue #86 正文(无单独任务书)
|
||||
- 执行方:Grok(Windows + Docker)。在**同一分支**上继续,只推这个分支;开工前 `git merge origin/staging`(不 rebase)。
|
||||
- BUG 编号:本分支的 BUG-1404 已被 #85 占用(#85 用 1404~1408,#90 将顺延),合入前 Claude 会统一重编;新增记录从开工时 `grep -oE "^## BUG-[0-9]+" docs/BUG_HISTORY.md | sort -u | sort -t- -k2 -n | tail -1` 的最大号 +1 起(该文件自 `400af57e8` 起整段重复,必须 `sort -u`)。
|
||||
|
||||
## 验收结论(Claude,Linux / Node 22)
|
||||
|
||||
| 项 | 结论 |
|
||||
| --- | --- |
|
||||
| tsc / lint | 0 错;0 error(122 warning,同基线) |
|
||||
| 前端全量 `npm test` | 5103 条,失败 25,名单与 staging `9812f3173` 基线逐名一致;新增 7 条 |
|
||||
| build | `/` 仍 `○ Static`;首屏 gzip 644,589(基线 643,357,+0.19%) |
|
||||
| 全量 pytest | 与基线一致(多出的一条 `test_active_rectification_api` 是子进程 20 秒超时,负载抖动,与本单无关) |
|
||||
| 范围 | 没碰数据库、触发器、`page.tsx`、侧栏;没有删测试或弱化断言 |
|
||||
| 「+ 添加一个人」满 20 人才隐藏(issue 写 5) | 判断正确:线上上限是 20(含本人)。已请产品负责人在 issue 里确认 |
|
||||
|
||||
**未通过 3 项,见下。**
|
||||
|
||||
## F1 · 所有报告表格都被强制单行(必修,打印退化)
|
||||
|
||||
位置:`frontend/src/app/globals.css` 的 `.personal-report-table-wrap th, .personal-report-table-wrap td`(`white-space: nowrap; word-break: keep-all`)与 `table { width: max-content }`,以及 `@media print` 里同一组规则(`width: max-content !important`、`white-space: nowrap !important`,外层 `overflow: visible`)。
|
||||
|
||||
这些规则作用于**报告里的每一张 Markdown 表**,不只是行星表。瑜伽「作用说明」、主题「建议 / 说明」这类整句的表,屏幕上每格变成一条很长的横滑单行;打印时表格按内容撑宽、外层不裁切,宽表直接跑出 A4 纸。issue 要求「打印 / 导出的样式不能退化」,这里退化了(同类教训:BUG-153 打印测试须覆盖无滚动容器的宽表)。
|
||||
|
||||
修法:
|
||||
|
||||
1. 只让**短数值 / 名称列**不断行。推荐:`report-table-reading.ts` 识别出行星位置这类数值表(按表头,如含「速度 / Speed / 度数」等),给该表加一个类(例如 `personal-report-table-numeric`),`nowrap` 与 `max-content` 只挂在这个类上;其他表恢复原来的自动换行。
|
||||
2. 打印:所有表 `width: 100%`、单元格可换行;数值表在打印里也允许换行(纸上没有横滑)。
|
||||
3. 测试:渲染一份含「行星原始位置」表和一张整句说明表的报告,断言数值表带新类、说明表不带;CSS 合同测试断言 `nowrap` 只出现在新类的选择器下,打印块里没有 `max-content` 与 `nowrap`。
|
||||
|
||||
## F2 · 「他人」保存逻辑是复制的,已经和档案页分叉(必修)
|
||||
|
||||
位置:`frontend/src/components/chart-page/chart-birth-editor.tsx` 的保存函数,对他人直接 `fetch PUT /api/chart-profiles/:id`,与 `frontend/src/components/people/people-page.tsx` 的 `saveOther` 是两份代码。已经分叉:编辑器保存他人后清了星盘 / 星历缓存,档案页的 `saveOther` 没清——档案页改完别人,再去星盘页仍是旧盘。(本人一路已经共用 `lib/self-profile-save.ts`,是对的。)
|
||||
|
||||
修法:把「保存他人」抽成 `frontend/src/lib/` 下的一个函数(请求、409 `reported_birth_time_is_immutable` 等错误映射、保存成功后的缓存清理都在里面),编辑器与档案页**都调它**。档案页因此也补上缓存清理,在 BUG 记录里写明这是顺带修复的旧缺陷。
|
||||
|
||||
测试:两处调用同一个函数(源码断言或注入替身);档案页保存他人后 `invalidateChartPage` / `invalidateEphemerisPage` 被调用。
|
||||
|
||||
## F3 · 点编辑框内边距会关掉编辑框、丢失未保存的修改(必修)
|
||||
|
||||
位置:同文件 `<dialog onClick>` 里的 `if (event.target === event.currentTarget) onClose();`。`.chart-birth-dialog` 有 `padding: var(--space-5)`,点在内边距上、或在输入框里拖选文字松手落在内边距上,事件目标都是 dialog 本身,于是关闭并丢掉已填内容。
|
||||
|
||||
修法(二选一):内容包一层内部容器、内边距挪到容器上,dialog 自身不留可点的内边距;或按点击坐标判断是否落在 dialog 矩形之外才关闭。Esc 关闭保持现状(已正确)。
|
||||
|
||||
测试:模拟点击 dialog 内边距(目标是 dialog、坐标在矩形内)不关闭;点击遮罩(坐标在矩形外)关闭。
|
||||
|
||||
## 顺手改(小)
|
||||
|
||||
- 测试文件 `frontend/tests/issue-86-chart-subject-table.test.tsx` 改名为按内容命名(例如 `chart-birth-editor-and-report-table.test.tsx`),仓库里没有按 issue 号命名的测试文件。
|
||||
- 他人锁定出生时间时,「填报的出生时间保存后不能再改」在表单说明里与锁定提示各出现一次,留一处即可。
|
||||
|
||||
## 交回前必跑
|
||||
|
||||
`tsc --noEmit`、`npm run lint`、改到的前端测试文件(含 `chart-page-view.test.tsx`、改名后的本单测试、档案页相关测试、CSS 合同测试)、`pytest tests/test_report_chart_blank_columns.py`。全量 `npm test`、build、gzip 由 Claude 验收时跑。
|
||||
|
||||
## 让步顺序
|
||||
|
||||
F1、F2、F3 都不可让步。F1 若「按表头识别数值表」不稳,可以退一步:`nowrap` 只给数字单元格(内容全是数字 / 度数 / 符号的格),整句单元格一律可换行;但打印那一条(`width:100%`、可换行)不让。
|
||||
@@ -0,0 +1,203 @@
|
||||
# 任务书:完整数据版补齐(一)——年运、大运表与原始数据附录对齐上游交付版(agent 循环 issue #91,2026-10-21)
|
||||
|
||||
## 基线
|
||||
|
||||
- 写作基线:`origin/staging` = `bd3ce0e7b`。**开工时以 #89(英文漏译,BUG-1272 复发)合入后的 `origin/staging` 为准**:#89 改的是同一组渲染文件(`scripts/pl9_full_data_export.py`、`scripts/pl9_reader_english*.py`),并带来 200 张固定种子虚构盘的英文汉字回归测试,本单复用它。#89 未合入不得开工。#90(导出弹窗)也动 `frontend/src/lib/report-export-*`,本单 T3 改同一组文件,与 #90 串行,后开工者先 `git merge origin/staging`。
|
||||
- 分支:`codex/full-data-parity-1-20261021`;工作树:`.worktrees/full-data-parity-1-20261021`。执行方 **Grok(Windows + Docker)**,只推这个分支。
|
||||
- 本单分 **A 段(只动渲染)、B 段(动引擎)**,同一分支、按第 6 节顺序提交(见第 5 节)。
|
||||
- 上游参照(只读,放仓库外):
|
||||
- 交付版渲染:`https://github.com/732642856/yinduzhanxing.git` 分支 `codex/customer-export-frozen-20261016`(`1d549fe9`)的 `customer-export/registry/`。只读 `delivery_*.py`、`ashtakavarga_delivery.py`、`render_customer_snapshot.py` 的渲染函数。**不得复制** `subjects.json`、`customer_case.py`、`subject_guard.py`、`export_customer_bilingual.py`、`source_*` 审计文件;该分支 README 写明冻结快照含真实个案资料,冻结引擎本体未公开。
|
||||
- 计算模块与解释生成器:同仓库 `main`(`258e08e3`,10-18 起无新提交)。
|
||||
- 关联:BUG-1272、BUG-1275(真恒星口径)、BUG-1210(不回本地加减分)、BUG-1312(中文残留英文)、BUG-1393 / 1283(Niryaana Shoola)、BUG-1205(落陷取消不叫 Raja)、BUG-1282(年运卡 `sr_chart`);`TASK-report-full-data-fix-20261008`、`TASK-report-full-data-fix2-20261008`(F5 被本单推翻,见 D4);`TASK-rashi-dashas-native-20261008`;后续单 #92 `TASK-full-data-parity-2-20261021`、#93(独立年度方法参考)。
|
||||
|
||||
## 1. 现状实证
|
||||
|
||||
### 1.1 量测(Claude,Linux,屏蔽 PyJHora 模拟生产,`bd3ce0e7b`)
|
||||
|
||||
虚构盘(1985 年,北京,非真人),走 `build_professional_report_reference_packet` → `render_full_data_markdown`,与线上同一路径:
|
||||
|
||||
| 项 | 中文 | 英文 |
|
||||
| --- | --- | --- |
|
||||
| Markdown 字符数 | 411,454 | 820,922 |
|
||||
| UTF-8 字节 | 846,885 | 821,647 |
|
||||
| 渲染耗时 | 7~15 s(首次含导入) | 1~2 s |
|
||||
| 排盘 + 资料包 | 3.3~7 s | 同一份 |
|
||||
|
||||
另 8 张随机虚构盘(固定种子)只出中文,端到端每张 10.1~11.6 s。上游英文交付版(产品 10-21 上传,真实个人资料,只统计字符数,不入库)共 **4,687,328** 字符;本单范围约 0.9 M,#92 范围约 3.1 M。
|
||||
|
||||
### 1.2 小附录:74 节,不是 66
|
||||
|
||||
上游英文版在「Research High-Division Raw Data」之后,除 8 个大章外有 **74** 个二级标题、38.8 万字符(66 是把十来个大运附属节并进「大运附表」后的口径)。试跑方法:把我方资料包补上 `raw_full_reading.modules`(= 我方 full-reading 的 86 个模块),直接调用上游 registry 的渲染函数。上游读到的 35 个模块名我方全部存在。
|
||||
|
||||
| 类 | 含义 | 节数 | 本单处理 |
|
||||
| --- | --- | --- | --- |
|
||||
| A | 数据我方已算好,渲染器没出 | 60 | 做(T2) |
|
||||
| B | 数据在,字段名或形状不同 | 6 | 做(T2) |
|
||||
| C | 我方没算 | 5 | 做(T6) |
|
||||
| X | 即 BUG-1210 删掉的本地加减分(Bhava Bala Relative Score Components / Score Extremes) | 2 | **不做**(D6) |
|
||||
| E | Goel Jaimini Strength and Special-Point Evidence,源码不在归档 | 1 | **不做**(D7) |
|
||||
|
||||
另:「Alternative Divisional Calculation Profiles」「Research High-Division Raw Data」(D81 / D108 / D144 等)**移到 #92**(D11)。
|
||||
|
||||
B 类明细:
|
||||
|
||||
| 上游节 | 我方现状 |
|
||||
| --- | --- |
|
||||
| Navamsha Dasha AD Duration Contract | 我方 `navamsha_dasha.periods`,上游读 `major[].antardasha[].duration_months` |
|
||||
| Current Dasha Convergence by Domain | 我方 `dasa_convergence.domain_activations`,上游读 `domain_evaluations` |
|
||||
| Yoga Participants and Timing Links | 上游读 `advanced_systems.yoga_interpretation_pack.pages`;我方在渲染时由 `pl9_interpretation_packs.attach_full_data_interpretation` 挂上 |
|
||||
| Bhava Chalit Planet Placements | 路径不同(`d1_rasi_bhava.bhava_chalit`) |
|
||||
| Bhava Bala Three-Component Alternative | 我方正式三分量在 `modules.bhava_bala.houses`(BUG-1210 / 1385) |
|
||||
| House Components and Physical Coordinates | 三分量同上;Kranti / 速度上游读 `kranti_local` / `speed_display_local`,我方 `pl9_p33_declination_contract` 在资料包里为空,先查 BUG-1199 修好的 Kranti 列取自哪里 |
|
||||
|
||||
C 类明细:
|
||||
|
||||
| 上游节 | 缺什么 |
|
||||
| --- | --- |
|
||||
| Jaimini Yogi and Ava Yogi | **潜在缺陷**:`scripts/jyotish_engine.py` 组装 jaimini 时 `from yogi_sphuta import build_yogi_sphuta_local`,我方**没有这个文件**,`ModuleNotFoundError` 被包成 `status: blocked`,每张盘都如此(上游 main 有,195 行,不导入 jhora) |
|
||||
| Annual Return House Framework | `solar_return.annual_houses` 不产出 |
|
||||
| Tripataki Chakra Detail | 只有 `solar_return.tri_pataka` 三旗摘要;上游 `tripataki_chakra.py`(108 行)出星座格与 vedha 行 |
|
||||
| Natal / Annual Saham Formula Trace | `sahams.formula_traces` 不产出 |
|
||||
|
||||
上游 `structural_markdown` 英文在我方数据上报 `Untranslated structural field: verdict_label`:我方有上游词表没覆盖的字段,必须补全中英字段名。
|
||||
|
||||
### 1.3 主章与大章
|
||||
|
||||
| 上游章节(字符) | 我方(字符) | 根因(按符号定位) |
|
||||
| --- | --- | --- |
|
||||
| Annual Varshaphala and Tajika(131,265) | 41,654 | 我方渲染器 `pl9_reader_export` 已会读 `monthly_chart_pack`、`lifetime_annual_chart_pages`、`annual_chart_series_pack`、`pl9_annual_dasha_results`、`normalized_tables`,但我方 `scripts/annual_tajika_pack.py`(780 行)**不产出这些键**,节被静默跳过。上游同名文件 2,788 行,多 40 个函数:`_monthly_chart_pack`(月返照=月段)、`_lifetime_annual_chart_pages`(年龄段)、`_annual_chart_series_pack` / `_local_eight_year_overview`、`_pl9_annual_dasha_results`、`_normalized_tables`、`_native_patyayini_dasha`、`_native_tithi_pravesh`(依赖 `tithi_pravesh.py`,236 行,我方没有)、`_annual_visual_panel` 等。`_normalize_pyjhora_*`、`external_engine_comparison` 来自 PyJHora 回放,不搬 |
|
||||
| Annual Chart Numeric Data(189,395) | 试跑 1,075 | 依赖 `pl9_annual_visual_panels`,我方没有 |
|
||||
| Standard Dasha Tables(127,598 / 1,792 表行) | 52,410 / 894 | `dasha_master_pack.families` 15 个体系**全部只有主运**(逐体系核对,子运 0 条)。Narayana、Ashtottari、`source_bounded_child_periods` 已有子运规则,没进 master pack。Kalachakra:我方 `scripts/kalachakra_dasha.py` 是旧版,只有主运;上游有 `_build_subperiods`、`_attach_recursive_subperiods` |
|
||||
| Chara Dasha Period Durations(111,160) | 无 | A 类:`modules.jaimini.chara_dasha` 有 12 / 144 / 1,728 行,试跑 110,198 |
|
||||
| Narayana Method Comparison(28,876) | 无 | B 类:`narayana_dasha.method_comparison` 在,上游函数报 `KeyError: 'direction'` |
|
||||
| Three-Year Predictive Ephemeris(56,132) | 无 | `three_year_predictive_ephemeris_pack.months` 只有慢星,且从目标年 1 月起;`kp_monthly_report.months` 有 9 星但为 KP 口径。已有 `scripts/kp_monthly_transits.py` 可按报告口径逐月算 |
|
||||
| Saturn and KP Tables(7,687) | 2,522 | 缺 Dhayya / Laghu Kalyani / Kantaka 结果段与「KP 原版细格」;数据 `sade_sati`、`kp` 已有 |
|
||||
| Planets, Nakshatras and Houses(8,970) | 英 4,608 / 中 1,708 | `pl9_interpretation_packs.build_graha_interpretation_pack` 每星只出 6 句固定骨架(如「六维力量合计为未列出」——合计取不到);上游逐星有传统段落 |
|
||||
| Bhavesh 宫主逐宫解释 | 附录 6,352(英) | `build_bhavesh_interpretation_pack` 同样只出骨架句 |
|
||||
| Independent Annual Method Reference(16,004) | 无 | 内容是 PyJHora 回放(上游写明 `Source: PyJHora`)。**本单不做,另立 #93**(D5) |
|
||||
| Goel Jaimini Strength(48,057) | 无 | 冻结引擎专有,**不做**(D7) |
|
||||
|
||||
### 1.4 中文乱句、英文丢否定、空表与英文星座名
|
||||
|
||||
| 现象 | 位置 | 根因 |
|
||||
| --- | --- | --- |
|
||||
| 「时蛇煞: 第5宫条件7planet in」 | 附录「Dosha 与行运摘要」 | 源字段 `yogas_doshas.kaal_sarp_dosha.summary` 是中文「部分Kaal Sarp Dosha——5/7颗行星在轴线之间」。英文附录 `_render_pl9_ai_density_raw_data_markdown_en`(Dosha 表)原样印 `summary`,英文清理器删汉字后剩 `houses 5/7 condition planet in`;**中文附录不是从中文源字段出的**:`render_full_data_markdown` 中文分支把**英文附录**交给 `_sanitize_pl9_ai_density_markdown` 短词回译。原意「7 颗中 5 颗」变成「第5宫条件7」 |
|
||||
| 「火星 in 12 巨蟹座, : low」 | 同表 Mangal 行 | 同一回路。**更严重**:无火星煞的盘源文「无Mangal Dosha(Mars不在煞宫)」,英文印 `Mangal Dosha(Mars in )`,中文印「火星婚姻煞(火星 in )」,**「无」丢了,读起来像有煞**。「Detected / phase」列取 `present` / `detected` / `status`,真实键是 `has_dosha` / `is_active`,整列空 |
|
||||
| 「Raja瑜伽 … 火星 and 太阳 together in 第10」 | 附录「可用瑜伽」,及正文个别瑜伽组合句 | 同一回路;8 张随机虚构盘每张 31~46 行,265 行在「可用瑜伽」 |
|
||||
| 回译之外的现成实现没用上 | `pl9_full_data_export.py` | `_render_pl9_ai_density_raw_data_markdown_zh` **从未被调用** |
|
||||
| 「行星友敌关系矩阵」只有标题、没有表(中英都是) | 附录 | `_render_pl9_ai_density_raw_data_markdown_en` 遍历 `planetary_friendship` 的键当行星,实际结构是 `{status, method, rows: [...]}`,行全被跳过 |
|
||||
| 中文导出里「第 1 宫 · Leo:…」 | 导出 Markdown 的「D1 北印度盘」「月亮参考盘」文字列表(排在最后一章「Dosha 与行运摘要」之后,所以产品看到的是这一章) | `frontend/src/lib/report-export-blocks.ts::houseLine` 中文分支直接用 `house.sign`;`toNorthIndianChart`(`report-chart-block.ts`)给出的是英文 `SIGNS`。引擎正文本身(图盘围栏外)9 张虚构盘英文星座名为 0 |
|
||||
|
||||
## 2. 根因
|
||||
|
||||
1. 完整数据版只移植了上游 `pl9_ai_density` 主体(`23be1807`),没有移植上游交付版在主体之后追加的 registry 附录;我方年运资料包停在旧版,渲染器读的键没人产出,节被静默跳过。
|
||||
2. 中文附录用「英文附录 → 短词回译」生成,源字段本身是中文时两次转换丢字、丢否定;附录渲染对资料形状的假设与实际不符时(友敌矩阵)静默出空表。
|
||||
3. `yogi_sphuta.py` 漏搬,导入失败被包成 `blocked`,测试只看有没有这个键。
|
||||
4. 导出的图盘文字列表在中文版没有用中文星座名。
|
||||
|
||||
## 3. 决策记录(产品负责人 2026-10-21)
|
||||
|
||||
| # | 决定 |
|
||||
| --- | --- |
|
||||
| D1 | issue 列出的各章补到与上游交付版同等展开,范围以本表为准 |
|
||||
| D2 | **数值一律用我方引擎(BUG-1275 之后的真恒星口径),不搬上游视位置算法**;与上游数值不同的,在 PROGRESS 逐项列原因 |
|
||||
| D3 | 中英两版都要有,**英文汉字为 0**(复用 #89 的 200 盘扫描) |
|
||||
| D4 | **推翻 `TASK-report-full-data-fix2-20261008` F5**「Patyayini、Tripataki、月返照、八年总览、生命周期年度返照若我方无数据……不补算」,以及 `TASK-report-full-data-fix-20261008` 第 22 行「Patyayini 不补算」。本单用我方原生计算补上;PyJHora 回放部分仍不搬 |
|
||||
| D5 | 「独立年度方法参考」**本单不做**,已另立 #93(照上游调用 PyJHora,排在本单之后);本单不写占位 |
|
||||
| D6 | BUG-1210 对应的两节(Bhava Bala Relative Score Components / Score Extremes)**不做** |
|
||||
| D7 | Goel Jaimini Strength and Special-Point Evidence **不做** |
|
||||
| D8 | 年运:**当年 + 后两年,三年全部展开**,每一年都有:年盘与年主依据、Muntha、Mudda 与 Patyayini(原生)、月返照(月段)、Tithi Pravesha、Sahams(含公式轨迹)、Tajika 相位 / 瑜伽 / 力量 / Tripataki、年盘整宫框架、年度大运结果、年度图盘数值;年龄段(生命周期年返)与出生地八年总览各出一次,现居地总览只在有现居地时出 |
|
||||
| D9 | 三年逐月星历:**从报告日期所在月起 36 个月**,报告口径 9 星;另列 KP 口径表与五级 Vimshottari 月采样 |
|
||||
| D10 | 74 个小附录归为 **6 章**(第 6 节 T2 的分组表),中英文章节一一对应、顺序一致 |
|
||||
| D11 | D81 / D108 / D144 研究型分盘与「其他分盘口径的计算数据」**移到 #92**,同样标研究口径;本单不动已有的分盘数值表 |
|
||||
| D12 | 逐星解释与宫主逐宫解释补到上游篇幅,按 10-08 的写法:传统段落用「可能」口吻,叠加我方功能吉凶 v2 |
|
||||
| D13 | Kalachakra 子运明细**移入本单**,标注「简化模型」(英文 Simplified model),不改现有主运 |
|
||||
| D14 | **不设大小上限**,只记录字符数、字节数、生成时长;碰到前端等待(`ENGINE_TIMEOUT_MS` 180 s)或存储 / 接口硬上限才停下回报,不得截断 |
|
||||
| D15 | 顺带修:空的行星友敌关系矩阵;中文附录改用现成的 `_render_pl9_ai_density_raw_data_markdown_zh`,不再回译;搬 Yogi 点模块 `yogi_sphuta`;中文导出图盘列表的英文星座名改中文 |
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. **口径**:所有新经度一律经我方排盘函数取得(与 `tests/test_sidereal_nutation_frame.py` 同框架:Swiss Ephemeris 星历 + 真岁差,等价 `FLG_SIDEREAL`)。上游模块里的 `FLG_MOSEPH`、`FLG_TRUEPOS`、`longitude_profile` / `position_mode='apparent'`、交点偏移一律改走我方函数。新增测试:每个新计算模块在出生时刻取到的日、月经度与 `core_chart.planets.*.degree_raw` 差 ≤ 1e-6°。
|
||||
2. **PyJHora**:运行时不得导入 `jhora`(`tests/test_runtime_jhora_import_scan.py`);`*_pyjhora_*`、`external_engine_comparison`、`pyjhora_canonical_adapter.py`、`specialized_dasha_external_replay.py` 不搬。
|
||||
3. **增长冻结**:不改 `scripts/jyotish_api_server.py`(报告仍走 `/api/professional_report_reference`);`tests/test_api_server_growth_contract.py` 不放宽。
|
||||
4. **根目录文件数已满**:`scripts/*.py` 现为 448 个 = `tests/test_python_hygiene_budget.py` 的 `ROOT_PY_BUDGET`。新渲染代码进 `scripts/report_render/`;新计算模块(`yogi_sphuta`、`tithi_pravesh`、`tripataki_chakra` 等)进新子包(例如 `scripts/annual_native/`),引擎里的导入跟着改。不得调高预算。开工时若数字已降,仍优先放子包。
|
||||
5. **不新增静默吞错**:`SILENT_EXCEPT_BUDGET = 1` 只给 `classical_astrology.py`。失败记日志并带具体原因,不得把 `ImportError` 包成 `blocked` 了事。
|
||||
6. **引擎输出会变**:验收必须**全量 pytest 与开工基线逐条比对**;改既有断言写「原值 / 新值 / 原因」三栏。
|
||||
7. **我方报告口径全部保持**:`_apply_local_report_conventions`、BUG-1199~1229、BUG-1205、BUG-1210、BUG-1282、BUG-1312 卫生门、BUG-1393。搬来的模板逐条核:不断言死亡、寿命、疾病诊断、离婚、流产、不孕;不写「再婚 / 几段关系 / 早夭」;吉凶以我方功能吉凶 v2 为准;主题写「可能突出」,不写必然。
|
||||
8. **中英各自从源字段生成**:中文不得由英文回译,英文不得靠删汉字;每个字段名、单位、枚举值都有中英两套标签,缺标签测试失败,不得印原始键名。
|
||||
9. **对话与校正不受影响**:`read_full_data_domain` 经卡片选择后的对话卡对 3 张固定虚构盘逐字节不变;生时校正打分 golden 不变。
|
||||
10. **不截断(D14)**:没有大小上限,但碰到 180 s 前端等待或存储 / 接口报错,就停下推分支、PROGRESS 写实测,不得删行、抽样或降精度。
|
||||
11. 不动数据库结构与迁移;不改 `.gitea/`;不推 `main`。
|
||||
12. **隐私**:只用虚构盘与公开名人盘;不复制上游个案文件;PROGRESS 与 Bug 历史实测段只写「虚构盘」。
|
||||
|
||||
## 5. 分段与提交顺序(一个分支)
|
||||
|
||||
- **A 段(只动渲染与前端导出,引擎数值不变)**:T1 → T2 → T3,一条任务一个提交。做完推分支,PROGRESS 标「A 段完成」,Claude 验收后合入 staging。
|
||||
- **B 段(动引擎)**:先 `git merge origin/staging`(不 rebase 已验收的提交),再 T4 → T5 → T6 → T7 → T8 → T9 → T10,一条任务一个提交。
|
||||
|
||||
## 6. 任务分解
|
||||
|
||||
| # | 段 | 内容 | 验收标准 |
|
||||
| --- | --- | --- | --- |
|
||||
| T0 | A | 盘点:上游 82 个二级标题(74 小节 + 8 大章)及标准大运表、Saturn/KP、年运章、逐星与宫主解释的三级标题,逐一对应我方「已有 / A / B / C / X / E / 移 #92 / 移 #93」与源字段路径,核对第 1 节分类 | PROGRESS 一张表;与本任务书不同的行单列并写原因 |
|
||||
| T1 | A | D15 的渲染部分:中文附录改用 `_render_pl9_ai_density_raw_data_markdown_zh`(补齐到与英文附录同节同序),删去回译路径;Dosha 摘要、可用瑜伽表中英文都从**结构化字段**出(`has_dosha` / `type` / `rahu_house` / `ketu_house` / `is_active` / `phase` 等),没有就写「未判定 / Not determined」;正文瑜伽组合句中文取中文源;行星友敌矩阵读 `rows`(天然友 / 敌 / 中性、临时、复合)。中文卫生门加两条:图盘围栏外英文星座名为 0;汉字紧邻 `in / and / or / from / planet / lord` 以及 `, :`、`in )` 为 0 | 200 盘扫描:中文两条新门均为 0,英文汉字 0;无火星煞的盘中英都写「无 / No」;部分时蛇煞的盘写出「7 颗中 5 颗」或等价英文;友敌矩阵 9 行非空;BUG_HISTORY 新编号 |
|
||||
| T2 | A | 小附录 71 节(A 类 60 + B 类 6 + C 类 5 的渲染位,C 类数据到 T6 才有)+ Chara 时长 + Narayana 对照 + Bhrigu 推进序列,按我方字段重写渲染(`scripts/report_render/`),按 D10 归 6 章,见下方分组表。C 类在 T6 之前不出现,不写占位 | 3 张固定虚构盘中英文:6 章标题中英一一对应、顺序相同;A、B 类每节出现且非空,每节一个结构测试(表头 + 至少一行);字段标签全覆盖测试;Narayana 两种口径分列不拼接 |
|
||||
| T3 | A | D15 前端:`report-export-blocks.ts::houseLine` 中文用中文星座名(`SIGN_ZH`),英文不变;顺查导出与阅读页其他中文出口有无同类问题 | `report-export-blocks.test.ts` 加断言:中文图盘列表不含任何英文星座名,英文含;`tsc`、`lint` 过;PROGRESS 记 A 段生成时长与中英大小 |
|
||||
| — | — | **A 段交付点**:推分支,PROGRESS 标明 | — |
|
||||
| T4 | B | 搬 `yogi_sphuta` 进子包并接线(修潜在缺陷) | 虚构盘 `jaimini.yogi_avayogi_local.status` 不是 `blocked`;红线 1 经度测试;「Jaimini Yogi and Ava Yogi」中英出现;BUG_HISTORY 新编号 |
|
||||
| T5 | B | 标准大运表全量:凡我方模块已有出处子运规则的体系(Narayana、Ashtottari、`source_bounded_child_periods` 覆盖的体系)把子运写进 `dasha_master_pack` 并出表;**Kalachakra 子运与次小运**(D13)搬上游 `_build_subperiods` / `_attach_recursive_subperiods`,挂在我方现有主运上,表头与说明标「简化模型 / Simplified model」;若离不开上游新版主运就停下回报,不换主运。没有出处规则的体系写「本体系未提供子运」。Ashtottari 的起运表与适用口径和 PyJHora 不一致(#88 写单时查出,另立单处理),本单只把引擎现有子运原样出表,不改其起运与主运 | 各体系主运逐行不变;子运条数写进 PROGRESS;Vimshottari 三层不变;Kalachakra 表含「简化模型」 |
|
||||
| T6 | B | C 类:搬 `tripataki_chakra`、本命与年度 Saham `formula_traces`、年盘整宫 `annual_houses`,进子包;T2 预留的四节出表 | 红线 1 经度测试;四节中英出现 |
|
||||
| T7 | B | 年运(D4、D8):把上游 `annual_tajika_pack.py` 缺的原生部分搬进我方(月返照、生命周期年返、八年总览、年度大运结果、`normalized_tables` 原生行、原生 Patyayini、原生 Tithi Pravesha 连带 `tithi_pravesh.py`、`pl9_annual_visual_panels`);当年 + 后两年每年按 D8 清单全部展开;出「年度图盘数值数据」 | 三年每年 D8 各项中英文都出现(缺项必须在 PROGRESS 写原因);已有年运字段(年主、Muntha、Mudda、Sahams、Tajika)对 3 张虚构盘逐值不变,只多新键;对话年运卡不变 |
|
||||
| T8 | B | 三年逐月星历(D9):用 `kp_monthly_transits` 按报告口径算报告月起 36 个月 × 9 星,另列 KP 口径表与五级 Vimshottari 月采样 | 每个口径 36 × 9 行,首月 = 报告日期所在月;任取一采样点月亮经度与我方排盘函数同刻结果差 ≤ 1e-6° |
|
||||
| T9 | B | 解释补长(D12):`build_graha_interpretation_pack`、`build_bhavesh_interpretation_pack` 按上游 main 对应生成器补传统段落,「可能」口吻,叠加功能吉凶 v2;修「六维力量合计为未列出」;Saturn / KP 表补 Dhayya、Laghu Kalyani、Kantaka 结果段与 KP 细格 | 「行星、星宿与宫位解释」与宫主逐宫解释中英文字数不低于上游同盘同章(英文按上游 8,970 字符量级,中文按比例),PROGRESS 列逐章字数;红线 7 模板口径测试(中英全文) |
|
||||
| T10 | B | 量测与文档(D14):Linux(Claude 复测)与 Windows 生成时长、中英 Markdown 字符与字节、快照 JSON 大小、`.md` 导出大小、阅读页章数与最大一章;与上游数值不同的项列表并写原因(预期:真恒星 vs 视位置、Swiss vs Moshier)。BUG_HISTORY、CHANGELOG、PROGRESS、`docs/testing/full-data-parity-1-20261021.md`(真机:生成新报告、中英切换、逐章翻到底、导出中文 `.md` 检查图盘列表、旧报告仍能打开) | 文档齐;中英合计生成在 180 s 以内,否则按红线 10 停下 |
|
||||
|
||||
T2 分组表(中英一一对应,章内按此顺序):
|
||||
|
||||
| 章 | 中文标题 | 英文标题 | 包含 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 大运附表 | Dasha Appendix | Vimshottari Birth-Balance、Kalachakra Dasha(主运层)、Yogini 周期、Tribhagi Profile、Tribhagi 周期、Navamsha AD Duration、Nakshatra Dasha Context、Bhrigu Cycle、Bhrigu Approximate Progression、Conditional Dasha Applicability、Dasha Boundary Scan、Current Dasha Convergence、Chara Dasha Period Durations、Narayana Method Comparison |
|
||||
| 2 | 年运附表 | Annual Appendix | Annual Return House Framework、Annual Return Calculation Contract、Tajika Aspect and Yoga Candidates、Tripataki Chakra Detail、Annual Tajika Strength、Annual Saham Formula Trace、Annual Chart Numeric Data(T7) |
|
||||
| 3 | 力量与八分法 | Strength and Ashtakavarga | Shadbala Subcomponent、Shadbala Result、House Components and Physical Coordinates、Bhava Bala Three-Component、Vimsopaka Contribution、AV Contribution and Reduction、AV Yoga Pinda、AV Sign Assessments、Prastara Totals、Avastha Inputs、D30 States、D30 Source Input、Pushkara Basis、Lagna Vargottamamsa |
|
||||
| 4 | 关系、相位与瑜伽 | Relationships, Aspects and Yogas | Neutral and Compound Relationships、Relationship Conventions、Dispositor Terminals、Dispositor Chain、Aspect Geometry、Argala、Congregation、Bhrigu Relationship Geometry、Yoga Participants、Canonical Yoga Rows、Whole-Sign Occupants |
|
||||
| 5 | 星宿、特殊点与行运 | Nakshatras, Special Points and Transits | Nakshatra Reference、Tara-Chandra Matrix、Natal Tithi Lord、Birth Calendar、Pancha Pakshi、Natal Vivah Saham、Natal Saham Formula Basis、Natal Saham Formula Trace、Arudha / UL / A10、Jaimini Calculation Details、Graha Pada、Yogi and Ava Yogi(T4)、Karakamsha、Darakaraka、Rashi Tulya Navamsa、Moon Chart Reference、Sudarshana Points、Dated Transit、Multi-Reference Transit、Double Transit PAC、Lagna / 7th Lord Transit、三年逐月星历(T8) |
|
||||
| 6 | 计算口径 | Calculation Conventions | Natal Calculation Conventions、KP Calculation Conventions、D9 Dignity、Divisional Mapping、Bhava Chalit Planet Placements、Bhava Chalit Centres、Bhava Chalit Convention |
|
||||
|
||||
合计 71 个小节(74 − X 2 − E 1)+ Chara、Narayana 两个大章 + 两项新数据。
|
||||
|
||||
## 7. 验收分工
|
||||
|
||||
| 检查 | Grok 自跑 | Claude 验收 |
|
||||
| --- | --- | --- |
|
||||
| 改到的与新增的 Python 测试文件(至少:`test_report_english_edition`、`test_report_full_data_fix`、`test_full_data_domain_reader`、`test_python_hygiene_budget`、`test_api_server_growth_contract`、`test_runtime_jhora_import_scan`、`test_sidereal_nutation_frame`、`test_rashi_dashas_native`、`test_bhava_bala_formal`、`test_year_lord_*`)及 #89 的 200 盘扫描 | 必跑(工作树 `.venv`),屏蔽 PyJHora 再跑一次 | 复验 |
|
||||
| `tsc --noEmit`、`npm run lint`、改到的前端测试文件(`report-export-blocks.test.ts` 等) | 必跑(T3 动了前端) | 复验 |
|
||||
| Python 快速门 `run_quality_gate.py --profile quick` | 不跑 | 以 Claude 为准 |
|
||||
| **Python 全量 pytest 与开工基线逐条比对**(A、B 段各一次) | 不跑 | 以 Claude 为准 |
|
||||
| 前端全量 `npm test`、`npm run build`(`/` 仍 Static,首屏 gzip ±2%) | 不跑 | 以 Claude 为准 |
|
||||
| `test:db` | 不动数据库,不要求 | — |
|
||||
|
||||
## 8. 让步顺序
|
||||
|
||||
不让步:T1、T3、红线全部、英文汉字 0、对话与校正不变。时间不够时依次让出:T9 的 Saturn / KP 结果段 → T8 → T5 的 Kalachakra 次小运 → T7 的生命周期年返与八年总览。让出的写进 PROGRESS 留给下一单。A 段不等 B 段。
|
||||
|
||||
## 9. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git log origin/staging --oneline | grep -iE "#89|han_leak|1272" | head # 确认 #89 已合入,否则不开工
|
||||
git worktree add -b codex/full-data-parity-1-20261021 .worktrees/full-data-parity-1-20261021 origin/staging
|
||||
cd .worktrees/full-data-parity-1-20261021
|
||||
# 上游参照放仓库外,只读
|
||||
git clone --filter=blob:none https://github.com/732642856/yinduzhanxing.git ../yinduzhanxing-ref
|
||||
git -C ../yinduzhanxing-ref fetch origin codex/customer-export-frozen-20261016
|
||||
git -C ../yinduzhanxing-ref ls-tree --name-only 1d549fe9 customer-export/registry/
|
||||
ls scripts/*.py | wc -l # 448 = ROOT_PY_BUDGET
|
||||
grep -n "yogi_sphuta" scripts/jyotish_engine.py
|
||||
grep -n "_render_pl9_ai_density_raw_data_markdown_zh\|Planetary Friendship Matrix" scripts/pl9_full_data_export.py
|
||||
grep -n "function houseLine" -A4 frontend/src/lib/report-export-blocks.ts
|
||||
grep -oE "^## BUG-[0-9]+" docs/BUG_HISTORY.md | sort -u | sort -t- -k2 -n | tail -1
|
||||
```
|
||||
|
||||
## 10. BUG 编号
|
||||
|
||||
写作时 `docs/BUG_HISTORY.md` 最大号 BUG-1403。文件里有重复段落,按上面命令 `sort -u` 后取最大号,**开工时最大号 + 1 起**(#88~#90 可能先占号)。预计至少三条:中文附录乱句与英文丢否定(T1,含空友敌矩阵);中文导出图盘列表英文星座名(T3);`yogi_sphuta` 漏搬致 Yogi / Ava Yogi 恒为 blocked(T4)。
|
||||
@@ -0,0 +1,198 @@
|
||||
# 任务书:完整数据版补齐(三)——「独立年度方法参考」照上游调用 PyJHora,其余输出一字不变(agent 循环 issue #93,2026-10-22)
|
||||
|
||||
## 基线
|
||||
|
||||
- 写作基线:`origin/staging` = `f90020efd`(#91 任务书刚推上去,#91 代码未合入)。**开工以 #91 合入后的 `origin/staging` 为准**;#91 未合入不得开工。#91、#92、#93 改同一批渲染文件(`scripts/pl9_full_data_export.py`、`scripts/report_render/`):#92 若已开工,后开工的一方先 `git merge origin/staging`,在 PROGRESS 写明依赖的 staging SHA。
|
||||
- 分支:`codex/full-data-parity-3-20261022`;工作树:`.worktrees/full-data-parity-3-20261022`。执行方 **Grok(Windows + Docker)**,只推这个分支。
|
||||
- 上游参照(只读,放仓库外,同 #91):`https://github.com/732642856/yinduzhanxing.git`,分支 `codex/customer-export-frozen-20261016`(`1d549fe9`):
|
||||
- 渲染:`customer-export/registry/delivery_timing_supplement.py` 的 `_annual_observation_lines`、`_annual_anchor_label`、`annual_method_observations`;
|
||||
- 调用:`scripts/annual_pyjhora_replay.py` 的 `_isolated_annual_worker`、`_call_mudda`、`_call_mudda_with_annual_return_moon`、`_call_patyayini`、`_pyjhora_annual_return_jd`、`_probe_tajika_yogas`、`_probe_pyjhora_per_yoga_callables`、`_annual_return_marker`;
|
||||
- 整理:`scripts/annual_tajika_pack.py` 的 `_normalize_mudda_replay_rows`、`_normalize_patyayini_replay_rows`、`_normalize_pyjhora_tajika_yoga_rows`、`_pyjhora_dasha_lord_label`、`PYJHORA_DASHA_LORDS`、`_PYJHORA_TAJIKA_YOGA_NAMES`。
|
||||
- **不得复制** `subjects.json`、`customer_case.py`、`subject_guard.py`、`export_customer_bilingual.py`、`source_*` 审计文件(含真实个案资料)。
|
||||
- 关联:BUG-1273(本单推翻其「PyJHora 不进生产」约定,见 D1)、BUG-1265、BUG-1275(真恒星口径)、BUG-1272 / #89(英文汉字 0)、ERR-110(冻结身份);`TASK-full-data-parity-1-20261021`(#91,D5 把本节移到本单)、`TASK-full-data-parity-2-20261021`(#92)、`TASK-api-chart-subprocess-gate-20261013`(#64,`_jhora_importable` 短路与 `sitecustomize` 挡导入法)、`TASK-special-lagnas-native-20261008`。
|
||||
|
||||
## 1. 现状实证
|
||||
|
||||
### 1.1 上游这一节怎么来的
|
||||
|
||||
`_annual_observation_lines` 本身不调 PyJHora,只读资料包里 `annual_tajika_pack.external_engine_comparison.pyjhora`(以及 `annual_tajika_series.years` 里另外两年的同名字段)。这些字段由上游 `jyotish_engine._build_pl9_full_annual_section` → `annual_pyjhora_replay.replay_pyjhora_annual` → `_run_isolated_annual_replay` **起子进程**(`sys.executable -c "...from scripts.annual_pyjhora_replay import _isolated_annual_worker..."`,超时 30 s)产出。子进程里:
|
||||
|
||||
| 步骤 | 调用(PyJHora 4.8.7) | 参数 |
|
||||
| --- | --- | --- |
|
||||
| 设岁差 | `drik.set_ayanamsa_mode(ayanamsa.upper())`,再回读 `const._DEFAULT_AYANAMSA_MODE`,不一致整节 blocked | 资料包 `settings.ayanamsa` |
|
||||
| 地点 | `drik.Place(name, lat, lon, tz, elevation)` | 出生地经纬度、出生时区 |
|
||||
| Mudda(出生时月亮) | `mudda.varsha_vimsottari_dasha_start_date(jd, place, age)`、`mudda.mudda_dhasa_bhukthi(jd, place, age)` | `jd` = 出生 JD,`age` = 目标年 − 出生年 |
|
||||
| Mudda(年盘月亮) | `tajaka.annual_chart(jd, place, years=age)` 取回归时刻 → `utils.julian_day_number(...)` → `mudda_dhasa_bhukthi(annual_jd, place, 0)` | 同上 |
|
||||
| Patyayini | `_pyjhora_annual_return_jd`(同一个 `annual_chart`)→ `patyayini.get_dhasa_bhukthi(annual_jd, place)` | 同上;取不到回归时刻退回 `jd + age × 365.256364` |
|
||||
| Tajika 瑜伽 | `tajaka.annual_chart(jd, place, years=age)[0]` 当位置表,逐个调 `tajaka_yoga.get_ithasala / eesarpha / manahoo / kamboola / gairi_kamboola / khallasara / radda _yoga_planet_pairs`、`get_nakta / yamaya _yoga_planet_triples` | 年盘位置 |
|
||||
|
||||
整理后三张表的列:Mudda `order, main_lord, sub_lord, start_display, duration_raw`(天,无结束边界,时区未定);Patyayini `order, main_code, sub_code, start_display, end_display, duration_raw`(年;结束 = 下一段开始,末段未知);Tajika `yoga, observation_status, raw_relationships`(行星编号 JSON)。表头写 `Source: PyJHora`,并印两条实际回归锚点(Patyayini、Tajika)。
|
||||
|
||||
### 1.2 我方现状(`f90020efd`,Linux,本机装有 PyJHora 4.8.7 + pyswisseph 2.10.3.2)
|
||||
|
||||
- 我方已有旧版 `scripts/annual_pyjhora_replay.py`(1,035 行,`336286e42` 起加了 `_jhora_importable()` 短路:找不到 `jhora` 就不起子进程,直接返回与子进程失败时相同的字典,golden 在 `tests/fixtures/api-chart-subprocess-gate/annual-no-jhora.json`、`tajaka-no-jhora.json`)。它比上游旧:`_call_mudda` 没有「年盘月亮」口径,`_call_patyayini` 没有精确回归锚点,`_probe_tajika_yogas` 没有逐个瑜伽的调用。
|
||||
- **它已经接在每次全盘上**:`scripts/jyotish_engine.py::_build_pl9_full_annual_section` 每年调一次 `replay_pyjhora_annual`,`_attach_annual_tajika_pack` 对目标年 + 后两年各调一次。生产镜像没有 PyJHora,所以今天全部短路成 blocked。**一旦镜像装上 PyJHora,这条旧路会真跑,结果写进 `annual_tajika_pack.external_engine_comparison.pyjhora`,被下列现有渲染读出来**(全部按符号定位):
|
||||
- `jyotish_engine._build_pl9_professional_support_section`(`patyayini_replay`);
|
||||
- `jyotish_engine.render_pl9_markdown` 内 `_year_replay_subblocks`(替代 Patyayini 行、Sahams 值)、年运小节的 `replay_patyayini_*` / `replay_sahams*`、Yogi 行的 `pyjhora_behavior_only` 分支、Patyayini 状态行;
|
||||
- `annual_tajika_pack._external_engine_comparison` / `_audit`;
|
||||
- `pl9_reader_export` 读 `normalized_tables.pyjhora_tajika_yoga_rows` 的两处。
|
||||
这就是红线 1 的头号风险:不关掉,装包本身就会改年运章、附录和资料包。
|
||||
- 进程全局状态实测(同一进程内):先按我方 Raman 设好 `swe.set_sid_mode`,调一次 `drik.set_ayanamsa_mode("LAHIRI")` + `mudda.mudda_dhasa_bhukthi` 之后,同一 JD 的岁差从 22.207588° 变成 23.653890°,月亮恒星经度从 23.718016° 变成 22.271715°(差 1.45°)。另外 `import jhora.const` 在导入时就执行 `swe.set_ephe_path(<jhora>/data/ephe)`(该目录只有 txt 星表,没有 `.se1`);`drik.set_ayanamsa_mode` 还会改写 `const._DEFAULT_AYANAMSA_MODE`(PyJHora 默认 `TRUE_PUSHYA`);交点默认真交点(`const._use_true_nodes_for_rahu_ketu = True`,我方默认平交点)。**PyJHora 只能在子进程里跑,不得在 API 进程内导入。**
|
||||
- 子进程实测(虚构盘 1985 年北京,Raman,三年一个子进程,按上游调用次数):导入 0.3~0.6 s,每年 1.9~2.3 s,合计约 **7 s**,峰值 RSS 约 **60 MB**。生产 2 核,预计 10~14 s,由 T6 实测。
|
||||
- 上游两处输入口径与 PyJHora 文档不符(实测,见 D6):
|
||||
1. 上游传入的 `jd` 是 **UT** JD(`swe.julday(..., hour − tz)`),PyJHora 约定是**当地时** JD(`drik` 内部 `jd_utc = jd − place.timezone/24`,`annual_chart` 文档也这样写)。同一虚构盘(东八区):回归时刻 08:34:53 vs 16:35:04,正好差 8 小时;Mudda 首段起点差约 8.5 天。我方 `tajika.py` 的 PyJHora sidecar 已按当地时传(`annual_jd + tz/24`)。
|
||||
2. `years=age` 时 `annual_chart`(Patyayini、Tajika 用它)落在**目标年前一年**(请求 2026 返回 2025-06-15),Mudda 却落在 2026。上游没修,只在表头写「请求年份不等于实际返回年份」。`years=age+1` 才落在目标年。
|
||||
- PyJHora 4.8.7 自身问题:`get_radda_yoga_planet_pairs` 抛 `NameError`;`gairi_kamboola`、`khallasara` 返回 `None`。上游把它们分别印成「未获得结果」「未知」。
|
||||
|
||||
### 1.3 运行时会因「装了 PyJHora」而换路的地方(全部要保持今天的结果)
|
||||
|
||||
| # | 位置(按符号) | 今天(生产无 PyJHora) | 装上后若不改 | 处理 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| S1 | `annual_pyjhora_replay._jhora_importable` → `_run_isolated_annual_replay`、`_run_isolated_tajaka_year_lord`(经 `replay_pyjhora_annual`、`probe_pyjhora_year_lord`,由 `jyotish_engine._build_pl9_full_annual_section` 每年调用) | 短路,返回 golden | 每年起 2 个子进程、旧口径数据进年运章(见 1.2) | 产品路径一律走短路,返回与今天逐字节相同的 golden;只有研究脚本显式传参才起子进程 |
|
||||
| S2 | `unified_consultation_orchestrator._external_engine_cross_validation` → `diagnose_pyjhora_adapter.build_report()` | `find_spec` 为空,`missing_dependency` | **在 API 进程内** `import_module("jhora.utils" / ".horoscope.chart.charts" / ".panchanga.drik")`(导入即改星历路径);`dependency_available`、`missing_dependency` 变;仓库里 `benchmarks/.../run_pyjhora_compare.py` 存在时(测试环境)`status` 从 `missing_dependency` 变 `available` | 产品调用改为不探测、不导入,返回今天的「缺依赖」字典;`build_report()` 默认行为留给命令行诊断 |
|
||||
| S3 | `rectification_three_engine_packet._pyjhora_d1`(`/api/rectification…` 高严谨分支 `build_packet`) | `ModuleNotFoundError`,该引擎行为错误 | **在 API 进程内**导入 `pyjhora_rectification_d1_reference` → `jhora`,三引擎包多出 PyJHora 星座行 | 产品路径固定返回今天的错误行,不导入;不改 `jyotish_api_server.py` |
|
||||
| S4 | `tajika._external_pyjhora_panchavargiya_score`(进程内 `pyjhora_panchavargiya_reference`,失败再走 `_lookup_external_pyjhora_python` 外部解释器) | 只在 `JYOTISH_YEAR_LORD_PYJHORA_REFERENCE=1` 时由 `tajika_year_lord._select` 传入 `annual_jd`,否则不触发 | 不变(仍由环境变量控制);但镜像里 `python3` ≠ `sys.executable` 的字面路径,变量一旦被设就会真跑 | 不改代码;加测试:部署文件(`deploy/**`)不得设置该变量;变量未设时装了 PyJHora 年主结果不变 |
|
||||
| S5 | `jyotish_engine` 里 `from pyjhora_canonical_adapter import …`(Chara `pyjhora_blackbox` 口径)、`from pyjhora_yogi_replay import replay_pyjhora_yogi`(Yogi 外部回放) | 两个模块仓库里都**不存在**,走桩函数 / blocked | 不变 | 不得新增这两个模块(红线) |
|
||||
| S6 | `ephemeris_backend_probe._python_module_status("jhora")`、`diagnose_external_engine_adapters`、`scripts/_compute_one_chart.py` | 开发诊断、离线基准工具 | 输出变(显示可用) | 不在产品路径(T1 核实 `ephemeris_backend_probe` 只被 `run_quality_gate.py`、测试引用),不改;PROGRESS 记一句 |
|
||||
| S7 | 测试:`test_ashtakavarga_sodhita_chain.py::test_trikona_step_matches_pyjhora_when_installed`、`test_dasha_api_engine_parity.py` 里经 `_pyjhora_rows`(`pytest.importorskip("jhora")`)的用例 | CI `.venv` 无 PyJHora,跳过 | 门禁 `.venv` 装 `requirements.txt` 后会真跑 | 允许「跳过 → 通过」,必须通过;列进 PROGRESS |
|
||||
|
||||
`tests/test_runtime_jhora_import_scan.py`(BUG-1273,快速门内)按 AST 从全盘、报告、对话卡入口走导入图,**文件名含 `pyjhora` 的模块和 `scripts/research` 不进图**。S2、S3 不在它的入口里,所以今天没被拦。
|
||||
|
||||
### 1.4 依赖
|
||||
|
||||
| 项 | 实测 / 现状 |
|
||||
| --- | --- |
|
||||
| 上游固定版本 | `containers/pyjhora-observation/Dockerfile`:`PyJHora==4.8.7`、`pyswisseph==2.10.3.2`;`requirements-dev.txt`:`PyJHora>=4.8` |
|
||||
| 我方 | `requirements-reference-engines.txt` 已写 `PyJHora==4.8.7`、`geocoder==1.38.1`、`geopy==2.4.1`,注释写「AGPL,不得并入生产 requirements」;`requirements.txt` 里 `pyswisseph` **不带版本号** |
|
||||
| PyJHora 4.8.7 元数据 | `Requires-Dist` 为空(pip 不会因为它动 `pyswisseph`);但 `jhora.utils` 导入 `geocoder`、`geopy`、`timezonefinder`、`numpy`、`pytz`、`dateutil`、`requests`。分类器写 MIT,包内 LICENSE 是 AGPL-3.0 |
|
||||
| 安装体积 | 约 94~97 MB:`data/` 56 MB(`geonames_places_5k_IN.csv` 37 MB、`geonames_places_5k.csv` 7 MB、`data/ephe` 10 MB txt)、`images/` 15 MB、`horoscope/` 9 MB、`tests/` 5 MB、`lang/` 5 MB |
|
||||
| 镜像 | `deploy/railway-api.Dockerfile` 只 `pip install -r requirements.txt`,不需要改 Dockerfile;`requirements*.txt` 在 `deploy/gated-paths.txt` 内,推上去会触发门禁、构建新镜像 |
|
||||
| 门禁 `.venv` | `.gitea/workflows/backend-quality-gate.yml` 安装 `requirements.txt` + `requirements-dev.txt` → 合入后门禁环境也有 PyJHora(S7)。**不改 workflow** |
|
||||
|
||||
### 1.5 渲染出口的现有拦截
|
||||
|
||||
- `pl9_full_data_export._sanitize_pl9_ai_density_markdown_en` 与 `pl9_reader_english.render_pl9_user_markdown_en` 的 `drop_fragments` 含 `"PyJHora"`:**任何含 PyJHora 的行会被整行删掉**。
|
||||
- `tests/test_report_reader_main.py::LEAK_PATTERNS` 含 `PyJHora`、`\bparity\b`、`_unavailable`、`\bschema\b`、`\bsource_path\b`;`test_reader_main_hides_audit_vocabulary` 对完整数据版全文断言零命中。
|
||||
- 所以本节必须在两个清理器和 `_apply_local_report_conventions` **之后**拼接,并且不得印出任何内部失败原因、键名。
|
||||
|
||||
## 2. 根因 / 背景
|
||||
|
||||
1. 上游交付版「独立年度方法参考」是 PyJHora 子进程回放的产物;我方 BUG-1273 起约定 PyJHora 只作离线对照,生产镜像不装,所以这节在我方一直缺。
|
||||
2. 我方代码里早已散落「检测到 PyJHora 就换路」的开关(1.3 的 S1~S5),以前靠「镜像里没有」兜底。装包之后这些兜底全部失效,其中 S1~S3 会在 API 进程内导入 PyJHora 或把旧口径数据印进现有章节。
|
||||
3. PyJHora 改的是 Swiss Ephemeris 的**进程级全局状态**(岁差模式、星历路径),与我方引擎共用一个 `swisseph` 模块,进程内调用必然串味。
|
||||
|
||||
## 3. 决策记录
|
||||
|
||||
| # | 决定 | 来源 |
|
||||
| --- | --- | --- |
|
||||
| D1 | **推翻** BUG-1273 / `TASK-special-lagnas-native-20261008` 的约定「PyJHora 只作外部对照、不进生产」:正式环境安装 PyJHora,**仅**用于本节。AGPL 许可证风险由产品负责人承担,已知情。`requirements-reference-engines.txt` 与 `diagnose_pyjhora_adapter` 里「不得进生产」的文字随之过时:前者注释改写;后者属于对话输出,按红线 1 不改 | 产品 10-21 |
|
||||
| D2 | 照上游 `_annual_observation_lines` 出三张表:Mudda(各起点口径,即出生时月亮 / 年盘月亮)、Patyayini、Tajika 瑜伽;表头注明「来源:PyJHora」;与前文我方年运分开读,不替换、不合并、不比对 | 产品 10-21 |
|
||||
| D3 | 中英两版都要有;英文汉字为 0(#89 的扫描);中文用中文标签(上游中文版列名是英文,不照搬) | 产品 10-21 + 本单 |
|
||||
| D4 | 某张表调用失败(含 PyJHora 不可用、超时、岁差不支持),该表写「未获得结果 / Result unavailable」,报告照常生成;其他表照常出 | 产品 10-21 |
|
||||
| D5 | 年份范围:与上游 `annual_method_observations` 一致,目标年 + 后两年(与 #91 D8 的三年对应),每年一组三张表 | 照上游 |
|
||||
| D6 | **输入口径(待产品确认,默认如下,Grok 可按默认开工)**:上游两处与 PyJHora 文档不符(1.2)。默认:`jd` 传**当地时** JD(PyJHora 文档口径,与我方 `tajika.py` sidecar 一致);`annual_chart` 用 `years = age + 1`,让 Patyayini、Tajika 的回归锚点落在请求年,Mudda 仍按上游 `years = age`(其起点本来就落在请求年)。实际锚点照上游印出。若产品要求与上游逐字一致,改回 UT JD 与 `years = age`,其余不变。两种做法的差异(回归时刻、首段起点)在 PROGRESS 用 3 张虚构盘列表 | 本单(Claude) |
|
||||
| D7 | 岁差:按资料包 `settings.ayanamsa` 映射到 PyJHora 名称(lahiri→LAHIRI、raman→RAMAN、kp / krishnamurti→KP、fagan_bradley→FAGAN、true_citra→TRUE_CITRA、true_pushya→TRUE_PUSHYA);djwhal_khul、sassanian 等 PyJHora 不支持的写三张表「未获得结果」,**不得**让 PyJHora 静默退回它的默认 TRUE_PUSHYA。设置后回读 `const._DEFAULT_AYANAMSA_MODE` 核对 | 照上游 + 本单 |
|
||||
| D8 | 交点:上游不设(标「适配器不支持」)。本单在子进程里按资料包 `node_mode` 调 `const.set_node_mode(node_mode == "true")`,**在导入 `drik` 之前**设,并在子进程里用一颗交点经度回读核对;核对不过就不设,表头写「交点:PyJHora 默认(真交点)」 | 本单 |
|
||||
| D9 | 我方 BUG-1275 的真恒星口径不套用到 PyJHora:本节数值就是 PyJHora 自己算的,不与我方年运比对、不标一致 / 不一致 | 本单 |
|
||||
| D10 | 位置:完整数据版最后一个二级标题章(在 #91 的六章附录与「结构化资料附录 / Structured Data Appendix」之后),与上游「正文之后追加」一致;只出现在完整数据版,标准版不出 | 本单 |
|
||||
| D11 | 生时校正冻结身份、对话卡、标准版报告、星盘接口不碰本节,也不因装包变化 | 产品 10-21 红线 1 |
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. **装 / 不装逐字节一致(产品红线 1)**:除新增这一节以外,现有所有输出不得变化。验收:同一批盘「装 / 不装」两套全量 pytest 逐条一致(S7 的「跳过 → 通过」除外,必须通过);完整数据版中英 Markdown 切掉本章后逐字节相同;资料包 JSON 去掉本节新键后逐字节相同;校正冻结身份(`scripts/research/sealed_holdout_rerun.py::implementation_identity`)与封存回放(`tests/test_sealed_holdout_contract_freshness.py`、`tests/test_rectification_validation_integrity_gate.py`)逐项相同;对话卡(`read_full_data_domain`)3 张虚构盘逐字节相同。
|
||||
2. **API 进程内不得导入 `jhora`**:PyJHora 只在一个子进程 worker 里导入。生成报告、全盘、对话、校正之后,父进程 `sys.modules` 里不得有 `jhora`;`swisseph` 的岁差(`get_ayanamsa_ut`)与同刻月亮经度在调用本节前后不变。
|
||||
3. **版本固定(产品红线 2)**:`PyJHora==4.8.7`(与上游观测容器、我方 reference 文件一致);`geocoder==1.38.1`、`geopy==2.4.1` 照 `requirements-reference-engines.txt`。**不得改 `requirements.txt` 里 `pyswisseph` 那一行**,不得升级或更换 `pyswisseph`;新旧两个本地镜像里 `pip show pyswisseph` 必须相同,星位一律不变。
|
||||
4. **只有本节显式调用 PyJHora**:S1~S3 的产品路径保持不导入、不起子进程,结果与今天逐字节相同;不得新增 `pyjhora_canonical_adapter.py`、`pyjhora_yogi_replay.py`;不得把 PyJHora 数据写进 `annual_tajika_pack.*`、`normalized_tables`、`external_engine_comparison` 等现有键(现有渲染会读它们)。
|
||||
5. **增长冻结**:不改 `scripts/jyotish_api_server.py`;`tests/test_api_server_growth_contract.py` 不放宽。`scripts/*.py` 根目录已满 448 个(`tests/test_python_hygiene_budget.py::ROOT_PY_BUDGET`),新文件进 `scripts/report_render/`,不得调高预算。
|
||||
6. **不新增静默吞错**:`SILENT_EXCEPT_BUDGET = 1` 不放宽。子进程失败、超时、JSON 不合法都记日志(含原因),表里只写「未获得结果」。
|
||||
7. **报告卫生**:本节不得出现内部键名、`blocked`、`partial_verified`、`_unavailable`、`schema`、`source_path`、Python 异常名;英文汉字 0;中文不出现英文列名(行星编号、瑜伽专名、PyJHora 名称除外)。
|
||||
8. 不动数据库结构与迁移;不改 `.gitea/`、Dockerfile;不推 `main`。
|
||||
9. **冻结文件不碰**:`sealed_holdout_rerun.PRODUCTION_FILES` 所列 11 个文件(`scripts/rectification/*` 10 个 + `scripts/functional_benefics.py`)本单一行不改。
|
||||
10. **隐私**:只用虚构盘与公开名人盘;不复制上游个案文件;PROGRESS 与 Bug 历史实测段只写「虚构盘」。
|
||||
|
||||
## 5. 任务分解(一条任务一个提交,按顺序)
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| T0 | **先量风险,再动代码**:在 #91 合入后的 `origin/staging` 上,用两套环境(见第 6 节「装 / 不装」做法)对 3 张固定虚构盘跑完整数据版中英 Markdown 与资料包 JSON,`diff` 两套;全量 pytest 两套逐条比对。预期今天就有差异(S1、S2、S7),逐条归到 1.3 的 S 编号;本地构建基线镜像,记 `docker image inspect --format '{{.Size}}'` 与 `pip show pyswisseph` | PROGRESS 有一张「装 / 不装差异清单」,每条落到 S 编号;出现 S 表以外的差异,**先补进 S 表再继续**(新增条目也要在 T1 关掉);基线镜像大小与 pyswisseph 版本已记 |
|
||||
| T1 | **关掉现有开关(红线 1、4)**:S1 产品路径固定走短路(例如 `replay_pyjhora_annual` / `probe_pyjhora_year_lord` 增加只有研究脚本才传的显式参数,产品调用不传就返回现有 golden,不看 `find_spec`);S2 产品调用改为不探测、不导入,返回今天无 PyJHora 时的字典(`diagnose_pyjhora_adapter` 命令行默认行为不变);S3 产品路径固定返回今天的错误行;S4 加部署文件不设变量的测试。改动只在 `annual_pyjhora_replay.py`、`unified_consultation_orchestrator.py` / `diagnose_pyjhora_adapter.py`、`rectification_three_engine_packet.py`、`tajika_year_lord.py`(如需) | 装了 PyJHora 的环境下:`tests/fixtures/api-chart-subprocess-gate/*.json` 两个 golden 仍逐字节相等、`subprocess.run` 一次都不调;orchestrator 交叉验证段与无 PyJHora 时逐字节相同;三引擎包 PyJHora 行与无 PyJHora 时相同;父进程 `sys.modules` 无 `jhora`。T0 清单里 S1~S3 的差异全部消失。改到的既有断言写三栏(至少下表 A1) |
|
||||
| T2 | **依赖**:`requirements.txt` 加 `PyJHora==4.8.7`、`geocoder==1.38.1`、`geopy==2.4.1`,注释写明「仅供完整数据版『独立年度方法参考』子进程使用,AGPL,产品 10-21 决定」;`pyswisseph` 行不动;`requirements-reference-engines.txt` 注释改成与 D1 一致;不改 Dockerfile、workflow。本地用 `deploy/railway-api.Dockerfile` 构建新镜像 | 新镜像构建成功;`docker run --rm <新镜像> python -c "import jhora, swisseph, geocoder, geopy; ..."` 能导入;新旧镜像 `pip show pyswisseph` 相同;镜像大小增量写 PROGRESS;`pip check` 无新冲突 |
|
||||
| T3 | **子进程 worker**:新增 `scripts/report_render/pyjhora_annual_reference_worker.py`(文件名必须含 `pyjhora`,只在子进程里 `python -m` 运行,**任何产品模块不得 import 它**)与父进程侧 `scripts/report_render/annual_method_reference.py`(不导入 `jhora`、不导入 worker;用 `sys.executable -m scripts.report_render.pyjhora_annual_reference_worker`、`cwd` = 仓库根、stdin 传 JSON、stdout 收 JSON、PyJHora 的打印重定向到 stderr、超时 45 s 起步按 T6 实测调整)。一个子进程算完三年。子进程内按 1.1 表格的调用顺序、D6~D8 的口径;`annual_chart` 每年可只调一次复用结果(须在 3 张虚构盘上证明与逐次调用结果相同)。整理函数(`_normalize_mudda_replay_rows`、`_normalize_patyayini_replay_rows`、`_normalize_pyjhora_tajika_yoga_rows`、锚点、主星编号映射)搬到父进程侧,纯函数。资料包挂在**新键** `worksheets.timing_and_predictive_systems.pyjhora_annual_method_reference`(每年三表 + 实际锚点 + 实际岁差 / 交点 + 每表成功与否;失败原因只进日志)。挂载点放在报告资料包组装处(`professional_report_reference` 或 `jyotish_engine._attach_annual_tajika_pack` 之后,只加一行调用);中英两次渲染共用同一份资料包时不得算两遍,若两种语言各自组装资料包,加按请求哈希的进程内小缓存 | 3 张虚构盘:三年 × 三表都有行;Mudda 两个起点口径各一张;Tajika 9 个瑜伽各一行(`radda` 为「未获得结果」);岁差回读与请求一致;D6 两种口径的差异表进 PROGRESS;父进程 `sys.modules` 无 `jhora`;挡住 `jhora` 时三年 × 三表全部「未获得结果」、报告照常生成;超时与非零退出各有一个单元测试(替身子进程) |
|
||||
| T4 | **渲染**:照 `_annual_observation_lines` 出「独立年度方法参考 / Independent Annual Method Reference」二级标题(D10 位置),章首一句「本节与前面的原生周期分开使用……」;每年一个三级标题「年度方法补充数据(请求年份 2026)/ Supplementary Annual Method Data (requested year 2026)」;紧接「来源:PyJHora。……」/「Source: PyJHora. ……」与实际岁差、交点一行,再印 Patyayini、Tajika 两条实际回归锚点;四个四级标题:Mudda(出生时月亮起算)/ Mudda (Moon at birth)、Mudda(年盘月亮起算)/ Mudda (Moon in annual chart)、Patyayini、Tajika 瑜伽 / Tajika Yoga;每表上方照上游的单位与边界说明(中英);列名中英两套(序号 / No.、大运主星 / MD lord、小运主星 / AD lord、开始 / Start、结束 / End、时长(天)/ Duration (days)、时长(年)/ Duration (years)、瑜伽 / Yoga、结果 / Result、行星关系 / Planet relationships);主星中文用我方中文星名,`L` 写「上升 / Lagna」;Tajika 结果三态照上游(已返回组合,需核对条件 / 检测为空 / 未获得结果);某表失败只印说明行「未获得结果 / Result unavailable」,不印空表。拼接点在 `render_full_data_markdown` 中英两支的 `_apply_local_report_conventions` **之后** | 3 张虚构盘中英:本章是最后一个二级标题;三年各有四个四级标题;「来源:PyJHora / Source: PyJHora」各出现三次;英文全文汉字 0(#89 扫描);中文本章无英文列名;红线 7 卫生扫描对本章零命中;切掉本章后与 T1 完成时的输出逐字节相同 |
|
||||
| T5 | **红线测试**:①新增动态测试:装了 PyJHora 时生成一份完整数据版(中英)、跑一次全盘与一次对话卡,前后比较父进程 `swe.get_ayanamsa_ut(jd)`、同刻月亮经度、`sys.modules` 中无 `jhora`;②`tests/test_runtime_jhora_import_scan.py` **只加不减**:入口加 `scripts/unified_consultation_orchestrator.py`、`scripts/rectification_three_engine_packet.py`、`scripts/report_render/annual_method_reference.py`,并新增断言「产品导入图里没有任何模块 import `pyjhora_annual_reference_worker`」、「`annual_method_reference.py` 以 `-m` 字符串引用该 worker」;③完整数据版 golden / 快照类测试改为切掉本章再比(三栏);④批量生成报告的测试(如 #89 的 200 盘扫描)注入替身 runner,不真起 200 个子进程,本节自己的测试用真 PyJHora 跑 3 张盘 | 变异检查:在 `annual_method_reference.py` 加一行 `import jhora` 后①或②失败;把 S1 的强制短路去掉后 T1 的 golden 测试失败;快速门新增耗时写 PROGRESS |
|
||||
| T6 | **量测与文档**:①生产镜像大小(新旧 `docker image inspect` 与压缩后层大小);②单份报告生成时长增量(中、英各 5 次取中位,Linux 由 Claude 复测,Windows 照报);③内存:子进程峰值 RSS、API 容器在生成报告时的 `docker stats` 峰值;④本章中英字符数;⑤全量 pytest 耗时变化。文档:`docs/BUG_HISTORY.md`(BUG-1273 记录末尾追加「10-22 产品决定推翻『不进生产』,见 #93」,S2 / S3 进程内导入隐患按新编号记)、`CHANGELOG.md`、`deploy/README.md`(镜像含 PyJHora、仅子进程使用、AGPL 由产品决定)、PROGRESS、`docs/testing/full-data-parity-3-20261022.md`(真机:生成完整数据版、翻到最后一章、中英切换、导出 `.md`、旧报告仍能打开、标准版无本章) | 文档齐;中英合计生成仍在 180 s(`ENGINE_TIMEOUT_MS`)以内,超了停下回报 |
|
||||
|
||||
需要改的既有断言(三栏写进 PROGRESS 与测试注释):
|
||||
|
||||
| # | 测试 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| A1 | `tests/test_api_chart_subprocess_gate.py::test_jhora_available_still_uses_subprocess` | `_jhora_importable` 为真时两个隔离函数各起一次子进程(`calls == 2`) | 产品调用不传研究参数时,即使可导入也不起子进程、返回 golden;传研究参数时仍起子进程(保留 `calls == 2`) | #93 红线 1:装包后旧回放不得进年运章 |
|
||||
| A2 | `tests/test_report_reader_main.py::test_reader_main_hides_audit_vocabulary` | 完整数据版全文 `LEAK_PATTERNS`(含 `PyJHora`)零命中 | 切掉「独立年度方法参考 / Independent Annual Method Reference」一章后零命中;本章内必须含「来源:PyJHora」或「Source: PyJHora」,且本章对其余 `LEAK_PATTERNS` 零命中 | D1、D2:产品要求表头注明来源 |
|
||||
| A3 | 完整数据版 golden / 快照 / 逐字节比较类测试(T0 列出具体文件) | 全文比较 | 切掉本章后全文比较;本章另有结构测试 | 本章内容依赖是否装 PyJHora |
|
||||
| A4 | `tests/test_runtime_jhora_import_scan.py` | 现有断言 | 现有断言全部保留,只新增入口与断言(T5②) | 加严,不是放宽 |
|
||||
|
||||
## 6. 验收分工
|
||||
|
||||
「装 / 不装」两套环境的做法:
|
||||
|
||||
- **Windows(Grok)**:两个虚拟环境——`.venv`(`requirements.txt` 全装,含 PyJHora)与 `.venv-nojhora`(同一份 requirements 去掉三行新依赖后安装);两者 `pip show pyswisseph` 必须相同。
|
||||
- **Linux(Claude)**:系统 `python3` 已装 PyJHora 4.8.7 + pyswisseph 2.10.3.2;「不装」用 #64 的做法:只含下面 `sitecustomize.py` 的目录放在 `PYTHONPATH` 最前(子进程会继承,worker 里同样导入失败)。
|
||||
|
||||
```python
|
||||
# <临时目录>/nojhora/sitecustomize.py
|
||||
import sys
|
||||
class _Block:
|
||||
def find_spec(self, name, path=None, target=None):
|
||||
if name.split('.')[0] in {'jhora'}:
|
||||
raise ModuleNotFoundError(f"No module named '{name}'")
|
||||
return None
|
||||
sys.meta_path.insert(0, _Block())
|
||||
```
|
||||
|
||||
| 检查 | Grok 自跑 | Claude 验收 |
|
||||
| --- | --- | --- |
|
||||
| T0 差异清单(3 盘中英 Markdown + 资料包 + 全量 pytest,装 / 不装) | 必跑 | 复跑一次(Linux) |
|
||||
| 改到与新增的 Python 测试文件(至少 `test_api_chart_subprocess_gate`、`test_runtime_jhora_import_scan`、`test_report_reader_main`、`test_report_english_edition`、`test_report_full_data_fix`、`test_full_data_domain_reader`、`test_python_hygiene_budget`、`test_api_server_growth_contract`、`test_special_lagnas_birth_instant`、`test_year_lord_*`、`test_tajika_*`、`test_pyjhora_adapter_diagnostics`、本单新增测试)及 #89 的 200 盘扫描,**装、不装各一次** | 必跑 | 复验 |
|
||||
| 本地构建新旧 API 镜像:大小、`pip show pyswisseph`、`pip check`、容器内生成一份完整数据版并量 `docker stats` | 必跑 | 无 Docker,环境缺口,以 Grok 数据为准;部署后看门禁镜像发布日志 |
|
||||
| Python 快速门 `run_quality_gate.py --profile quick` | 不跑 | 以 Claude 为准(装、不装各一次) |
|
||||
| **Python 全量 pytest 与开工基线逐条比对,装 / 不装两套**(红线 1) | 不跑 | 以 Claude 为准 |
|
||||
| 3 盘中英完整数据版切掉本章后逐字节比对(分支装 vs 分支不装 vs 基线不装) | 必跑 | 以 Claude 为准 |
|
||||
| 封存回放与冻结身份(`test_sealed_holdout_contract_freshness`、`test_rectification_validation_integrity_gate`、`implementation_identity()` 输出),装 / 不装 | 必跑 | 复验 |
|
||||
| 生成时长(中、英各 5 次中位) | Windows 数照报 | 以 Claude 的 Linux 数判是否碰 180 s |
|
||||
| 前端 | 本单不改前端,不要求 | — |
|
||||
| `test:db` | 不动数据库,不要求 | — |
|
||||
| 部署后 | — | `/api/health` 的 `deployment.gitCommit` 等于合入 SHA;`swisseph_available = true`;真机清单留给产品 |
|
||||
|
||||
## 7. 让步顺序
|
||||
|
||||
不让步:红线全部(尤其 1~4)、T0、T1、T5、D4 的逐表失败、英文汉字 0。时间不够时依次让出:T3 的 `annual_chart` 复用优化(照上游逐次调用)→ D8 的交点设置(照上游不设,表头写 PyJHora 默认)→ T6 的 `docker stats` 内存峰值(只报子进程 RSS)。让出的写进 PROGRESS。
|
||||
|
||||
## 8. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git log origin/staging --oneline | grep -iE "#91|full-data-parity-1" | head # 确认 #91 已合入,否则不开工
|
||||
git log origin/staging --oneline | grep -iE "#92|full-data-parity-2" | head # #92 若已合入或在途,记进 PROGRESS
|
||||
git worktree add -b codex/full-data-parity-3-20261022 .worktrees/full-data-parity-3-20261022 origin/staging
|
||||
cd .worktrees/full-data-parity-3-20261022
|
||||
# 上游参照放仓库外,只读(#91 已 clone 的可复用)
|
||||
git clone --filter=blob:none https://github.com/732642856/yinduzhanxing.git ../yinduzhanxing-ref
|
||||
git -C ../yinduzhanxing-ref fetch origin codex/customer-export-frozen-20261016
|
||||
git -C ../yinduzhanxing-ref show 1d549fe9:customer-export/registry/delivery_timing_supplement.py | grep -n "_annual_observation_lines"
|
||||
git -C ../yinduzhanxing-ref show 1d549fe9:scripts/annual_pyjhora_replay.py | grep -n "_isolated_annual_worker\|_call_mudda\|_call_patyayini\|_probe_tajika_yogas"
|
||||
# 现有开关(1.3)
|
||||
grep -n "_jhora_importable\|def replay_pyjhora_annual\|def probe_pyjhora_year_lord" scripts/annual_pyjhora_replay.py
|
||||
grep -n "replay_pyjhora_annual\|pyjhora_yogi_replay\|pyjhora_canonical_adapter" scripts/jyotish_engine.py
|
||||
grep -n "build_pyjhora_adapter_report" scripts/unified_consultation_orchestrator.py
|
||||
grep -n "_pyjhora_d1" scripts/rectification_three_engine_packet.py
|
||||
grep -rn "JYOTISH_YEAR_LORD_PYJHORA_REFERENCE" deploy/ scripts/ | head
|
||||
grep -n '"PyJHora"' scripts/pl9_full_data_export.py scripts/pl9_reader_english.py
|
||||
grep -n "PyJHora" tests/test_report_reader_main.py
|
||||
grep -n "^pyswisseph\|PyJHora" requirements.txt requirements-reference-engines.txt
|
||||
ls scripts/*.py | wc -l # 448 = ROOT_PY_BUDGET
|
||||
grep -oE "^## BUG-[0-9]+" docs/BUG_HISTORY.md | sort -u | sort -t- -k2 -n | tail -1
|
||||
```
|
||||
|
||||
## 9. BUG 编号
|
||||
|
||||
写作时 `docs/BUG_HISTORY.md` 最大号 **BUG-1403**。该文件自 `400af57e8` 起有整段重复,必须用上面命令(`sort -u` 后取最大)核对,**开工时最大号 + 1 起**(#89~#92 可能先占号)。预计至少两条新编号:S2 对话交叉验证在 API 进程内导入 PyJHora 的隐患(装包即触发);S3 校正三引擎包在 API 进程内导入 PyJHora 的隐患。BUG-1273 只追加「决策推翻」一段,不改原文、不改状态。
|
||||
@@ -0,0 +1,201 @@
|
||||
# 任务书:搬上游公开的 Goel Jaimini 强度与 Brahma / Maheshwara / Rudra 计算(agent 循环 issue #94,2026-10-22)
|
||||
|
||||
## 基线
|
||||
|
||||
- 写作基线:`origin/staging` = `f90020efd`。开工以当时的 `origin/staging` 为准。
|
||||
- 分支:`codex/goel-jaimini-strength-20261022`;工作树:`.worktrees/goel-jaimini-strength-20261022`。执行方 **Grok(Windows + Docker)**,只推这个分支。
|
||||
- 顺序:**可与 #89(英文漏译)并行**,#89 只改渲染文件,不碰本单的文件。**必须在 #91 B 段印「Goel Jaimini Strength」这一章之前合入**。#91 的 T4(`yogi_sphuta`)改的是 `scripts/jyotish_engine.py` 里 full-reading 的同一段 jaimini 组装代码,与本单 T3 相邻;后开工的一方先 `git merge origin/staging`,不 rebase。
|
||||
- 上游:`https://github.com/732642856/yinduzhanxing.git` `main` 上的提交 `5fb8379c`(2026-10-21,作者应我方请求公开)。只读,放在仓库外。
|
||||
- 关联:#84 / BUG-1393、BUG-1283(Niryaana Shoola,PVR Ayur 口径);#81 决策 A(上游原样文件放根目录,预算按文件数调高);BUG-1275(真恒星口径);ERR-110、ERR-115(冻结身份);#91 `TASK-full-data-parity-1-20261021`(D7 当时定为「不做」,因为源码未公开;本单公开后由 #91 或其后续单印这一章)。
|
||||
|
||||
## 1. 现状实证(Claude,Linux,只读)
|
||||
|
||||
### 1.1 上游 `5fb8379c` 改了什么
|
||||
|
||||
共 6 个文件,+522 / −1:
|
||||
|
||||
| 文件 | 变化 | 行数 | blob 哈希(上游 `5fb8379c`) |
|
||||
| --- | --- | --- | --- |
|
||||
| `scripts/goel_jaimini_strength.py` | 新增 | 137 | `2170f781740affbad7b8ff4e048b88d87f997bcc` |
|
||||
| `tests/test_goel_jaimini_strength.py` | 新增 | 134 | `e29c47182c03c2277cff46e271a7f664c3349601` |
|
||||
| `docs/research/goel_public_calculation_module_2026_10_21.md` | 新增 | 204 | `c0bef9d9ad5f708093589ce852db20f9e2613995` |
|
||||
| `scripts/jyotish_engine.py` | +26 / −1 | — | 新增 `_goel_jaimini_evidence`;`cmd_jaimini` 在 `mode in ('all','special')` 时并入;`_cmd_full_reading` 在 `report['modules']['jaimini'] = jaimini_result` 前并入;另改 `render_pl9_markdown` 里 p89 一行说明文字 |
|
||||
| `tests/test_bphs_validation_runtime.py` | +5 | — | 在 `test_full_reading_exposes_lagna_vargottamamsa_receipt` 末尾加 Goel 四条断言 |
|
||||
| `docs/research/pre_work_error_ledger.md` | +16 | — | 上游自己的台账条目(Goel 首/七强度 ≠ PVR Niryaana) |
|
||||
|
||||
新模块依赖的上游文件(**这次提交之前就在上游,我方都没有**):
|
||||
|
||||
| 文件 | 行数 | blob 哈希(上游 `5fb8379c`) | 为什么需要 |
|
||||
| --- | --- | --- | --- |
|
||||
| `scripts/trikona_raman.py` | 233 | `1fc9283396acba98b9113bde7af0e27a80841a30` | Goel 取 `CLASSICAL`、`DIGNITY_PROFILE`、`_dignity`、`_validate_positions` |
|
||||
| `scripts/sthira_brahma_goel_short_method.py` | 112 | `6e010b54f5ba480db3e6d8d79e711a1cc2d59fbf` | Goel 取 `SIGNS`、`SIGN_LORDS`(按整数索引的表) |
|
||||
| `scripts/trikona_dasha.py` | 166 | `e6c8a27cf5e23e5c686154406ba5183226d12d79` | **issue 没列,但必需**:`trikona_raman.py` 模块顶层 `from scripts.trikona_dasha import RAMAN_AD_PROFILE, RAMAN_SOURCE, build_trikona_raman_antardasa_report`,不搬就导入失败 |
|
||||
|
||||
依赖我方已有的两个文件,逐项核对与上游相同,**不用改**:
|
||||
|
||||
| 我方文件 | 用到的符号 | 结论 |
|
||||
| --- | --- | --- |
|
||||
| `scripts/narayana_dasha.py` | `_rasi_aspects`、`SIGNS`、`SIGN_LORDS`、`_NARAYANA_DIGNITY` | 四个定义与上游逐字相同(整文件不同,不影响) |
|
||||
| `scripts/jaimini.py` | `FRIENDSHIP` | 逐字相同 |
|
||||
|
||||
### 1.2 口径与安全检查
|
||||
|
||||
| 检查 | 结果 |
|
||||
| --- | --- |
|
||||
| 位置框架 | 四个上游文件都**不算位置**:没有 `swe`、`FLG_MOSEPH`、`FLG_TRUEPOS`、视位置。只接受调用方给的恒星黄道经度(`[0,360)`,九星齐全,罗睺计都必须对冲,误差 1e-7)。位置由我方引擎提供即为真恒星口径 |
|
||||
| PyJHora | 四个文件都不导入 `jhora`;`trikona_dasha.py` 只有一句字符串提到 PyJHora,`test_runtime_jhora_import_scan` 不把字符串算导入 |
|
||||
| 隐私 | 测试只有虚构位置(`SYNTHETIC`)和 Goel 书第 15 页公开例题(只有度数,无出生资料);文档无个案资料。**不搬**上游台账条目里关于客户导出 pin 的段落 |
|
||||
| 静默吞错 | 四个文件没有宽泛 `except`;`_goel_jaimini_evidence` 捕获的是具名异常元组,不计入 `SILENT_EXCEPT_BUDGET` |
|
||||
| 来源字符串 | `goel_jaimini_strength.SOURCE` 是上游本地书稿路径(含 `Z-Library` 字样),我方仓库没有这份书稿。**这一行是唯一允许的改动**(Claude 10-22 定):公开仓库不出现盗版书库字样与他人本地路径,改成书名与作者的纯引用:`SOURCE = 'V. P. Goel, Predicting through Jaimini Astrology'`;**不得**搬入书稿 |
|
||||
| 已有同名概念 | 我方 `scripts/goel_short_strength.py` 是 Goel **第 5 章简法**(供 `rashi_dashas_native` 的 Sthira / Niryaana 等用),与本单的**第 1 章完整法**不是一套,互不替换 |
|
||||
|
||||
### 1.3 试跑(Claude,临时目录,未改仓库)
|
||||
|
||||
把四个上游文件放进临时包,`narayana_dasha.py` / `jaimini.py` / `rashi_dashas_native.py` / `goel_short_strength.py` 用我方 `f90020efd` 的版本:
|
||||
|
||||
- `tests/test_goel_jaimini_strength.py` 中**不涉及引擎**的 16 条全过(含第 15 页例题逐值、虚构盘五分量、交点两种口径、度数相同 → blocked、非法输入)。
|
||||
- 涉及引擎的 3 条需要本单接线后才能跑。其中 `test_legacy_lagna_seventh_alias_does_not_replace_pvr_second_eighth` 从 `scripts.niryaana_shoola_dasha` 导入 `select_niryaana_shoola_start_sign`;**我方这个函数在 `scripts/rashi_dashas_native.py`**(#84)。用我方函数跑虚构盘:选中 Taurus(`selected_sign_idx == 1`),`profile == 'pvr_niryaana_shoola_ayur_rasi_strength_v1'`,与上游断言一致。
|
||||
- Linux 验收机未装 swisseph,没跑真实排盘;真实盘上的结果由 T5 验证。
|
||||
|
||||
### 1.4 我方接线点(按符号定位)
|
||||
|
||||
| 位置 | 现状 |
|
||||
| --- | --- |
|
||||
| `scripts/jyotish_engine.py::cmd_jaimini` | 除上游新增那一行外与上游逐字相同。`planet_lons` 取 `pd['degree']`(**四舍五入到 4 位小数**) |
|
||||
| `scripts/jyotish_engine.py::cmd_full_reading`(Step 6: Jaimini 系统) | `planet_lons` 取 `degree_raw`(未舍入),`asc_deg` 取上升 `lon`;jaimini 组装末尾 `report['modules']['jaimini'] = jaimini_result`,整段包在 `try … except Exception` 里,任何未捕获异常会让**整个** jaimini 模块丢失 |
|
||||
| API | `/api/jaimini`(`_compute_jaimini`,mode=all)走 `cmd_jaimini`;咨询与报告走 full-reading。**不需要改 `scripts/jyotish_api_server.py`** |
|
||||
| 报告 | `pl9_reader_export.py` 按键名取 jaimini 子项,不会自动印新键;`build_pl9_style_export_packet` 会把整个 `modules.jaimini` 放进资料包 JSON(体积略增,不出正文)。`jyotish_engine.py` 里受限资料 `jaimini_special_points.reason` 仍写「Brahma/Maheshwara/Rudra still lack a same-profile structured producer」——本单不改(属报告文字,交 #91) |
|
||||
| 关键路径快照 | `tests/golden/consultation_contract_keypaths_v1.json` 只记两层(`modules.jaimini` 为止),新键在第三层,**不变** |
|
||||
| 键名冲突 | 我方没有任何代码读 `niryaana_shoola_strength`、`lagna_seventh_strength`、`goel_full_strength`、`goel_special_points`;我方 Niryaana 读的是 `dasha_families['niryaana_shoola']` 等精确键 |
|
||||
|
||||
### 1.5 冻结身份(ERR-110 / ERR-115)
|
||||
|
||||
冻结身份按**文件字节哈希**核对三份清单:`scripts/research/sealed_holdout_rerun.py` 的 `PRODUCTION_FILES`(11 个,校正与 `functional_benefics.py`)、`RESEARCH_FILES`(7 个),以及 `references/real_case_calibration/minute_rectification_holdout_v3.json` 的 `frozen_scoring.files`(12 个,**含 `jaimini.py`、`narayana_dasha.py`**)。
|
||||
|
||||
- 本单新增的四个文件、`jyotish_engine.py`、两个测试文件都**不在**清单里。
|
||||
- 本单只**导入** `jaimini.py`、`narayana_dasha.py`,不改它们。
|
||||
- 校正打分不调用 full-reading,也不读 `modules.jaimini`。
|
||||
|
||||
结论:按设计冻结身份**不会变**。若执行中发现必须改清单里的文件,或下面第 9 节的检查命令非空 / 四个身份测试转红,按红线 4 停下回报。
|
||||
|
||||
### 1.6 根目录文件预算
|
||||
|
||||
`scripts/*.py` 现为 **448** = `tests/test_python_hygiene_budget.py` 的 `ROOT_PY_BUDGET`,已满。先例:**#81 决策 A(产品 2026-10-18)**——「**上游原样文件**可以放在 `scripts/` 根目录(与上游同路径,方便以后同步),预算为这几个文件相应调高;我们自己写的文件仍不许增加」,当时 446 → 448,常量旁写了三栏。#91 把 `yogi_sphuta` 等放子包,是因为那些模块要改位置口径,不算原样文件;本单四个文件**一字不改**,正好落在 #81 A 的范围内。
|
||||
|
||||
## 2. 背景
|
||||
|
||||
Goel《Predicting through Jaimini Astrology》第 1 章(印刷页 10–17)的完整强度:行星五分量(尊贵、Karaka 名次分、从 AK 起的 Kendra 分、Kartari、Adhipati),星座五分量(固定分、所含 Karaka 分、Sthana、木星 / 水星 / 宫主三角色相位、宫主强度),再据此选 Brahma、Maheshwara、Rudra。上游交付版的「Goel Jaimini Strength and Special-Point Evidence」一章(约 4.8 万字符)就印这些数,我方此前因源码未公开没法做(#91 D7)。
|
||||
|
||||
## 3. 决策记录(产品负责人,issue #94,2026-10-21)
|
||||
|
||||
| # | 决定 |
|
||||
| --- | --- |
|
||||
| D1 | 搬 `goel_jaimini_strength.py` 及其依赖 `trikona_raman.py`、`sthira_brahma_goel_short_method.py`;`narayana_dasha._rasi_aspects` 两边已相同,不动。按 blob 哈希锁定上游版本,写进进度记录。(Claude 核实:`trikona_raman.py` 还硬依赖 `trikona_dasha.py`,一并按原样搬,见 1.1) |
|
||||
| D2 | 按上游 `jyotish_engine.py` 的改动,把 `goel_full_strength`、`goel_special_points` 接进我方 jaimini 与 full-reading 输出 |
|
||||
| D3 | 搬上游测试 `tests/test_goel_jaimini_strength.py`(书中公开数值表 + 虚构盘),必须全过;`test_bphs_validation_runtime.py` 的相应改动一并对照 |
|
||||
| D4 | 口径照上游文档:交点默认计入 Sthana(遵循正文,`include_nodes_text`);排除交点的示例表口径也保留(`worked_example_node_exclusion`);七个 Karaka 度数相同时为「无法判定」(`status: blocked`),**不得自造排序** |
|
||||
| D5 | 上游新增的 `lagna_seventh_strength` 与我方 #84 的 Niryaana Shoola(PVR Ayur)不是同一套:只搬字段本身,**不改**我方 Niryaana Shoola 的算法 |
|
||||
| D6 | 校正打分不读这两个新字段;封存回放、偏移扫描前后逐项相同。若只是因为 full-reading 多了字段导致冻结身份变化,**停下来报告,不得自行重冻** |
|
||||
| D7 | 位置一律用我方引擎口径(真恒星位置) |
|
||||
| D8 | 本单只做计算,不改报告渲染;报告里印这一章由 #91 负责 |
|
||||
| D9 | 可与 #89 并行;必须在 #91 印这一章之前合入 |
|
||||
| D10 | 根目录预算:按 **#81 决策 A** 先例,四个上游原样文件放 `scripts/` 根目录,`ROOT_PY_BUDGET` 只为这四个文件调高(448 → 452)。这是对先例的适用,不是新决定;若产品另有意见,按第 8 节让步改放子包 |
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. **上游四个文件一字不改**(唯一例外:`goel_jaimini_strength.py` 的 `SOURCE` 一行改为书目引用,见 1.2):其余三个文件落地后 `git hash-object` 必须等于 1.1 表里的 blob 哈希;`goel_jaimini_strength.py` 与上游 blob 的 `diff` 只能有 `SOURCE` 这一行(Windows 注意 `core.autocrlf=false`,LF 换行)。需要的适配一律写在调用方(`jyotish_engine.py`)或测试里。
|
||||
2. **不改我方 Niryaana Shoola 与简法强度**:`scripts/rashi_dashas_native.py`、`scripts/niryaana_shoola_dasha.py`、`scripts/goel_short_strength.py` 零改动;`tests/test_rashi_dashas_native.py`、`tests/test_niryaana_shoola_dasha.py` 不改断言且全绿。新增代码不得把 `lagna_seventh_strength` 或 `niryaana_shoola_strength` 接到任何 Niryaana 选择或大运计算上。
|
||||
3. **不改冻结清单里的文件**:`jaimini.py`、`narayana_dasha.py`、`ashtakavarga.py` 等 `frozen_scoring.files` 与 `PRODUCTION_FILES`、`RESEARCH_FILES` 中的任何文件零改动;校正代码(`scripts/rectification/**`、`minute_rectification_*`、`active_rectification_*`)不得读新字段。
|
||||
4. **冻结身份变化即停**:第 9 节检查命令输出非空,或 `test_sealed_holdout_contract_freshness`、`test_rectification_validation_integrity_gate`、`test_reported_offset_research`、`test_rectification_engine_memoization` 任一条转红——停下,推分支,PROGRESS 写清哪个文件、哪条测试,**不得重新冻结、不得改冻结记录或 golden**。
|
||||
5. **口径**:Goel 输入的经度一律取我方 `compute_chart_data` 的结果(Swiss Ephemeris + 真岁差,BUG-1275 之后),full-reading 用 `degree_raw`;不引入任何新的位置计算。
|
||||
6. **PyJHora**:运行时不得导入 `jhora`(`tests/test_runtime_jhora_import_scan.py`)。
|
||||
7. **增长冻结**:不改 `scripts/jyotish_api_server.py`;`tests/test_api_server_growth_contract.py` 不放宽。
|
||||
8. **静默吞错**:`SILENT_EXCEPT_BUDGET = 1` 不变(只给 `classical_astrology.py`)。Goel 失败必须 `logger.warning` 记具体异常类与信息,返回的 `reason` 带同样信息;不得让 Goel 失败把整个 `modules.jaimini` 带掉。
|
||||
9. **不改报告渲染**(D8):`pl9_*`、`scripts/report_render/**`、`professional_report_reference.py`、前端一概不动。3 张固定虚构盘的中英完整数据版 Markdown 改前改后逐字节相同(见 T5)。
|
||||
10. **对话不受影响**:`read_full_data_domain` 经卡片选择后的对话卡,对 3 张固定虚构盘逐字节不变。
|
||||
11. 不动数据库与迁移;不改 `.gitea/`;不推 `main`。
|
||||
12. **隐私**:只用虚构盘与书中公开例题;不搬上游书稿、客户导出文件、上游台账里的客户 pin 段落。PROGRESS 实测段只写「虚构盘」。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| T1 | **原样搬四个模块**到 `scripts/` 根目录(`goel_jaimini_strength.py`、`trikona_raman.py`、`sthira_brahma_goel_short_method.py`、`trikona_dasha.py`),用 `git -C <上游> show 5fb8379c:<路径> > <路径>` 取,不经编辑器。`tests/test_python_hygiene_budget.py`:`ROOT_PY_BUDGET` 448 → 452,常量旁写三栏——原值 448 / 新值 452 / 原因「产品 2026-10-18 #81 决策 A 先例,issue #94:上游原样文件 goel_jaimini_strength、trikona_raman、sthira_brahma_goel_short_method、trikona_dasha 按上游路径放根目录;本地新写的文件仍不得增加」。合并时若根目录计数已因别的单变化,按合并后实数重算,**只为这四个文件加名额** | 三个文件 `git hash-object` 与 1.1 表逐一相等,`goel_jaimini_strength.py` 与上游的 diff 只有 `SOURCE` 一行(贴进 PROGRESS);仓库内 `grep -rn "Z-Library" scripts tests docs` 无命中;`ls scripts/*.py \| wc -l` = 新预算;`test_python_hygiene_budget` 全绿(静默处理仍只有 `classical_astrology.py:12558`) |
|
||||
| T2 | **搬测试**:`tests/test_goel_jaimini_strength.py` 按原样取,**只改一行**:`from scripts.niryaana_shoola_dasha import select_niryaana_shoola_start_sign` → `from scripts.rashi_dashas_native import select_niryaana_shoola_start_sign`(我方 #84 的位置);PROGRESS 记上游 blob 与这一行改动。**对照** `test_bphs_validation_runtime.py`:我方这个文件没有上游的 `test_full_reading_exposes_lagna_vargottamamsa_receipt`,新增一条测试 `test_full_reading_exposes_goel_jaimini_strength`,用文件里已有的 `BASE_BIRTH_ARGS`(虚构盘)与 `_run_engine("full-reading", …)`,断言上游那四条:`goel_full_strength.status == 'executed'`、`rashi_strengths` 12 行、`goel_special_points` 键集合为 Brahma / Maheshwara / Rudra、`niryaana_shoola_strength.deprecated is True` | `tests/test_goel_jaimini_strength.py` 全部 19 条通过(含 3 条引擎接线条);新增 full-reading 测试通过;屏蔽 PyJHora 再跑一次同样通过 |
|
||||
| T3 | **接线**(`scripts/jyotish_engine.py`):照上游加 `_goel_jaimini_evidence(planet_longitudes, ascendant_longitude)`,返回 `goel_full_strength`、`goel_special_points`,以及有就带上的 `lagna_seventh_strength` 与弃用别名 `niryaana_shoola_strength`(上游模块原样产出,带 `deprecated: true`、`replacement`、`not_for` 标注,保留以便与上游测试一致)。与上游的两处差异,写进 PROGRESS:(a) 捕获异常时 `logger.warning` 记异常类与信息(红线 8),`reason` 写成 `f'{exc.__class__.__name__}: {exc}'`;(b) `cmd_jaimini` 传给 Goel 的经度用 `pd.get('degree_raw', pd['degree'])`——我方 `degree` 舍入到 4 位小数,罗睺计都对冲检查容差 1e-7,用舍入值可能误判为不对冲而整组 blocked(上游测试的模拟盘只有 `degree`,回退后仍过)。`cmd_jaimini` 在 `mode in ('all','special')` 时并入;full-reading 在 `report['modules']['jaimini'] = jaimini_result` 之前并入,**不得**放在会被外层 `except Exception` 吞掉整个模块的位置之外再抛异常 | `cmd_jaimini` 与 full-reading 对 3 张固定虚构盘都输出四个新键;`modules.jaimini` 原有各键逐值不变(改前改后 JSON 去掉四个新键后逐字节相同);`git diff` 只动 `jyotish_engine.py` 的这三处 |
|
||||
| T4 | **口径与「无法判定」**:确认 D4 的三件事在接线后的输出里成立:默认 `node_occupancy_policy == 'include_nodes_text'`;`goel_full_strength.worked_example_node_exclusion` 存在且 `node_occupancy_policy == 'exclude_nodes_worked_example'`;七星度数相同时 `status == 'blocked'`、三个特殊点全部 `blocked`,不出 AK、不出排序。另加一条测试:full-reading 里喂给 Goel 的经度与 `modules.chart.planets.*.degree_raw` 差 ≤ 1e-6° | 测试覆盖三件事与经度一致;不新增任何排序兜底 |
|
||||
| T5 | **实盘回归与不变性**:(1) 200 张固定种子随机虚构盘(可复用 #89 的生成器,未合入则自写固定种子)跑 full-reading 的 jaimini 段:`goel_full_strength.status` 全部 `executed`(若有 `blocked`,逐张列原因,只允许七星度数相同一种);`goel_full_strength.atmakaraka` 与我方 `chara_karaka_7` 的 AK 一致(不一致逐张列出并停下回报,不改任何一方);mean / true 交点各跑一遍。(2) 3 张固定虚构盘:中英完整数据版 Markdown 改前改后逐字节相同;对话卡(`read_full_data_domain`)逐字节相同。(3) 冻结身份检查(第 9 节命令)输出为空 | 三项结果写进 PROGRESS(盘数、blocked 数、AK 一致率、耗时增量);任一不满足按红线停下 |
|
||||
| T6 | **文档**:上游 `docs/research/goel_public_calculation_module_2026_10_21.md` 原样放到同路径(docs 不占根目录名额;文中唯一一处书稿本地路径同样改成书目引用,`grep -n "Z-Library"` 无命中),开头加一行我方说明:「上游 `5fb8379c` 原文;我方 Niryaana 选择器在 `scripts/rashi_dashas_native.py`;书稿未入库」。`docs/research/pre_work_error_ledger.md` 加一条我方台账(新 ERR 号):Goel 首/七强度 ≠ PVR Niryaana 二/八,弃用别名不得接入 Niryaana;Goel 完整法 ≠ `goel_short_strength` 简法。`CHANGELOG.md` 一行(引擎新增 Goel 强度与三个特殊点,报告暂不显示;Skill 版本不 bump)。PROGRESS 记:四个 blob、测试改动的那一行、接线与上游的两处差异、T5 数据、交给 #91 的事项(见下) | 文档齐;`.venv/bin/python -m pytest tests/test_repo_privacy_markers.py -q` 通过 |
|
||||
|
||||
交给 #91(写进 PROGRESS「移交」段,本单不做):
|
||||
|
||||
- 报告印「Goel Jaimini 强度与特殊点」章,中英标签齐全;`blocked` 的中文写「无法判定」、英文写「Not determinable」,并注明原因(七星度数相同 / 强度相同无唯一选择)。
|
||||
- `jyotish_engine.py` 受限资料 `jaimini_special_points.reason` 里「Brahma/Maheshwara/Rudra still lack a same-profile structured producer」随印章一并改。
|
||||
- 交点两种口径并列印出,标明默认遵循正文。
|
||||
|
||||
## 6. 验收分工
|
||||
|
||||
| 检查 | Grok 自跑 | Claude 验收 |
|
||||
| --- | --- | --- |
|
||||
| 改到与新增的 Python 测试文件:`test_goel_jaimini_strength`、`test_bphs_validation_runtime`、`test_python_hygiene_budget`、`test_rashi_dashas_native`、`test_niryaana_shoola_dasha`、`test_jaimini`、`test_runtime_jhora_import_scan`、`test_api_server_growth_contract`、`test_consultation_contract_golden`、四个冻结身份测试(红线 4)、`test_repo_privacy_markers` | 必跑(工作树 `.venv`),屏蔽 PyJHora 再跑一次 | 复验 |
|
||||
| T5 的 200 盘与 3 盘逐字节比对 | 必跑,结果写 PROGRESS | 抽 3 盘复跑 |
|
||||
| Python 快速门 `run_quality_gate.py --profile quick` | 不跑 | 以 Claude 为准 |
|
||||
| **Python 全量 pytest 与开工基线逐条比对**(引擎输出有新增键) | 不跑 | 以 Claude 为准;屏蔽 PyJHora 模拟生产 |
|
||||
| 前端 | 不动,不要求 | — |
|
||||
| `test:db` | 不动数据库,不要求 | — |
|
||||
|
||||
## 7. 提交顺序(一个分支)
|
||||
|
||||
T1 → T2 → T3 → T4 → T5 → T6,一条任务一个提交。T1、T2 的提交里不得夹带对上游文件的任何格式化。
|
||||
|
||||
## 8. 让步顺序
|
||||
|
||||
不让步:红线全部;T1 的 blob 一致;T2 上游测试全过;T3 两个输出入口;冻结身份不变。
|
||||
|
||||
时间不够时依次让出:T5 的 true 交点那一遍 → T5 的 200 盘缩到 50 盘(写明)→ T6 的上游文档原样副本(台账与 CHANGELOG 不让)。
|
||||
|
||||
若产品不同意按 #81 A 调预算:四个文件改放 `scripts/goel_jaimini/`(子包,含 `__init__.py`),把模块内 `from scripts.trikona_raman` / `scripts.sthira_brahma_goel_short_method` / `scripts.trikona_dasha` 三处导入改成子包路径,测试里的 `scripts.goel_jaimini_strength` 跟着改;PROGRESS 记上游 blob + 本地导入改动的 diff,此后上游同步需人工比对。默认不走这条。
|
||||
|
||||
## 9. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/goel-jaimini-strength-20261022 .worktrees/goel-jaimini-strength-20261022 origin/staging
|
||||
cd .worktrees/goel-jaimini-strength-20261022
|
||||
git config core.autocrlf false
|
||||
|
||||
# 上游参照放仓库外,只读
|
||||
git clone --filter=blob:none https://github.com/732642856/yinduzhanxing.git ../yinduzhanxing-ref # 已有则 git -C ../yinduzhanxing-ref fetch origin
|
||||
git -C ../yinduzhanxing-ref show --stat 5fb8379c
|
||||
for f in scripts/goel_jaimini_strength.py scripts/trikona_raman.py scripts/sthira_brahma_goel_short_method.py scripts/trikona_dasha.py tests/test_goel_jaimini_strength.py docs/research/goel_public_calculation_module_2026_10_21.md; do
|
||||
echo "$f $(git -C ../yinduzhanxing-ref rev-parse 5fb8379c:$f)"; done
|
||||
|
||||
ls scripts/*.py | wc -l # 期望 448(= ROOT_PY_BUDGET)
|
||||
grep -n "ROOT_PY_BUDGET\|SILENT_EXCEPT_BUDGET" tests/test_python_hygiene_budget.py
|
||||
grep -n "^def cmd_jaimini\|report\['modules'\]\['jaimini'\] = jaimini_result" scripts/jyotish_engine.py
|
||||
grep -n "^def select_niryaana_shoola_start_sign" scripts/rashi_dashas_native.py
|
||||
grep -oE "^## BUG-[0-9]+" docs/BUG_HISTORY.md | sort -u | sort -t- -k2 -n | tail -1
|
||||
grep -oE "^## ERR-[0-9]+" docs/research/pre_work_error_ledger.md | sort -u | sort -t- -k2 -n | tail -1
|
||||
```
|
||||
|
||||
冻结身份检查(T5 与交回前各跑一次,输出必须是 `[]`):
|
||||
|
||||
```bash
|
||||
git diff --name-only origin/staging...HEAD > /tmp/goel_changed.txt
|
||||
.venv/bin/python - <<'EOF'
|
||||
import json, re
|
||||
src = open("scripts/research/sealed_holdout_rerun.py", encoding="utf-8").read()
|
||||
listed = set(re.findall(r'"(scripts/[^"]+\.py)"', src)) # PRODUCTION_FILES + RESEARCH_FILES
|
||||
ds = json.load(open("references/real_case_calibration/minute_rectification_holdout_v3.json", encoding="utf-8"))
|
||||
listed |= set(ds["frozen_scoring"]["files"])
|
||||
changed = {l.strip() for l in open("/tmp/goel_changed.txt") if l.strip()}
|
||||
print(sorted(listed & changed))
|
||||
EOF
|
||||
.venv/bin/python -m pytest -q tests/test_sealed_holdout_contract_freshness.py tests/test_rectification_validation_integrity_gate.py tests/test_reported_offset_research.py tests/test_rectification_engine_memoization.py
|
||||
```
|
||||
|
||||
非空或转红 → 红线 4:停下、推分支、PROGRESS 写明,不重冻。
|
||||
|
||||
## 10. BUG 编号
|
||||
|
||||
本单是上游功能移植,预计**不新增** BUG 记录。若执行中确认了缺陷(例如 `cmd_jaimini` 舍入经度致对冲检查误判、Goel 失败带掉整个 jaimini 模块),按 `docs/BUG_HISTORY.md` 全文 `sort -u` 后的最大号 **+1** 起编(写作时最大号 BUG-1403;#88~#93 可能先占号,以开工时为准)。台账新 ERR 号同样开工时取最大号 +1。
|
||||
@@ -0,0 +1,137 @@
|
||||
# 任务书:个人报告英文版约 4 成被整份丢弃——漏译汉字与吃空格(issue #89,BUG-1272 复发)(2026-10-21)
|
||||
|
||||
## 基线
|
||||
|
||||
- `origin/staging` = `bd3ce0e7b`。开工前 `git fetch origin --prune`,以 `origin/staging` 为基线。
|
||||
- 执行方:**Grok(Windows + Docker)**。分支 `codex/report-english-han-leak-20261021`,工作树 `.worktrees/report-english-han-leak-20261021`,**只推这个分支**,不推 staging、不碰 main。
|
||||
- 关联:BUG-1272(本单复发自它)、BUG-1218 / BUG-1225(Rath 版 Narayana 并列展示)、BUG-1273(生产镜像无 PyJHora)、BUG-1276、BUG-1277。
|
||||
- **串行顺序**:本单最先做。#91、#92 之后改同一批渲染文件(`pl9_reader_export.py`、`pl9_full_data_export.py`),并沿用本单的 200 张盘测试。本单合入 staging 前,#91 不开工。
|
||||
|
||||
## 事故实证
|
||||
|
||||
Claude 在 `bd3ce0e7b` 上复现:两组固定种子各生成 60 张、80 张随机虚构盘,屏蔽 PyJHora(模拟生产镜像),走 `professional_report_reference._english_edition`。
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| 140 张盘中被判 `han_leak`、整份英文丢弃 | **56 张(40%)**,与 issue 的 16/40 一致 |
|
||||
| 是否与 PyJHora 有关 | 无关。同一批盘装与不装 PyJHora,泄漏完全相同 |
|
||||
| 现有测试的 3 张虚构盘(`tests/test_report_english_edition.py` 的 `CASES`) | 3 张都干净,所以测试一直是绿的 |
|
||||
| 每张盘耗时(Linux,本机) | 建资料包约 2.5 s,英文渲染约 0.9 s,`_english_edition` 全程约 1.0 s |
|
||||
|
||||
### 漏译汉字(按出现盘数)
|
||||
|
||||
| # | 残留汉字 | 140 张中出现 | 英文里的样子 | 来源(按符号定位) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| L1 | 座内 | 21 | `period start Libra(house 5 level: lord[source text retained: 座内]degreecount)` | `narayana_legacy_label.rath_summary_line_zh`:英文版也调用它,把 `STRENGTH_LEVEL_ZH[5]`「宫主星座内度数」交给末尾词表拆着译。`STRENGTH_LEVEL_EN` 已有 `lord's degree in sign`,没人用 |
|
||||
| L2 | 凶 | 21 | `\| overall nature \| [source text retained: 凶](Asubha) \|` | `muhurta.calc_panchanga` 的 `overall_quality` 只有中文(`吉(Subha)` / `中(Mixed)` / `凶(Asubha)`);`pl9_reader_export.render_pl9_parity_markdown` 的 Panchanga 小表不分语言打印标签「综合性质」和这个值 |
|
||||
| L3 | 计都、反向 | 5 | `- order: [source text retained: 计都]inperiod start: [source text retained: 反向](Table 12)` | `narayana_legacy_label.rath_report_lines_zh` 的「- 顺序:」行用 `direction_text`(中文)。`direction_text_en` 已在 `rath_narayana_view` 里存好,没人用 |
|
||||
| L4 | 计都、反向(issue 未列) | 9 | `- sub-period direction: Dashasignpresent[source text retained: 计都]: [source text retained: 反向](Table 14), retrograde(the book itself disagrees, …` | `narayana_legacy_label._ad_direction_lines_zh` 用 `rule_text`(中文)。`narayana_rath` 已给出 `rule_text_en` |
|
||||
| L5 | 观察 | 7 | `\| Punarphoo Yoga \| relationship_observation \| [source text retained: 观察] \| …` | `render_pl9_parity_markdown` 瑜伽表(表头 `\| Yoga \| Category \| Strength \|`)英文分支换了 `effects_en`、`combination_en`,**强度列仍打印 `row['strength']`**。`yoga_engine` 已为每行写 `strength_en`(Punarphoo 规则自带 `observational`,其他走 `STRENGTH_EN`)。「强 / 中 / 弱」之所以没漏,是末尾词表碰巧有单字条目 |
|
||||
| L6 | 完全(issue 未列) | 7 | `\| Kaal Sarp Dosha \| True \| … \| [source text retained: 完全]Kaal Sarp Dosha——atpresent7planetsinRahu(6)—Ketubetween the axis \|` | `yogas_doshas.calc_kaal_sarp_dosha` 的 `summary` 只有中文;`render_pl9_parity_markdown` 的 Dosha 结果表(`### p163-p171 Mangala / Sade Sati / Dosha Results`)打印 `row['summary']` |
|
||||
|
||||
### 吃空格与错译(不留汉字,但英文读不通)
|
||||
|
||||
末尾词表把中文按字词换成英文,换出来的词之间不加空格;「第」一律换成 `house `。在 140 张盘里这些不是偶发,而是**每张盘都有**:
|
||||
|
||||
| 粘连 / 错译 | 140 张中出现 | 来源线索 |
|
||||
| --- | --- | --- |
|
||||
| `scoreandtimeline`、`house seven-round decision`(「第七轮裁定」)、`house 5 level`(「第 5 级」) | 每张盘 | `RATH_LABEL_ZH` 与 `rath_summary_line_zh` 的「第 N 级」;`RATH_LABEL_EN` 已存在 |
|
||||
| `inperiod start`、`Dashasignpresent`、`aturninperiod` | 与 L3 / L4 同盘 | 同上,Rath 顺序与子运方向行 |
|
||||
| `atpresent7planetsinRahu`、`planetsinbetween`、`partialKaal Sarp Dosha——houses 6/7 condition planets…`(「6/7 颗行星」被译成 `6/7 condition planets`,意思错了) | 约 2 成 | `yogas_doshas.calc_kaal_sarp_dosha` 的 full / partial 两句 |
|
||||
| `read together withSaturnjudge from the actual position whetherinunderway` | 每张盘 | `yogas_doshas` 的 Sade Sati 框架摘要(「需结合Saturn实际位置判断是否在进行中」) |
|
||||
| `careerplanetAmK`、`careerdirection`、`marriagequality` | 每张盘 | Jaimini 星位表:「事业星AmK」等名称与 `jaimini.py` 的职责描述(如 `'Amatyakaraka': '事业方向、主要谋士、权力代理'`) |
|
||||
| `supportingstrongdegree` | 每张盘 | 标题 `### Vimsopaka / Pushkara / Vargottama …` 的中文后半 |
|
||||
| `beneficfactor count` | 每张盘 | Panchanga 小表标签「吉性要素数量」(随 T3 一并改) |
|
||||
| `comparativelystrong`、`occupiesplanetcount` | 每张盘 / 约 4 成 | Rath 子运比较行「较强」与 `STRENGTH_LEVEL_ZH[1]` |
|
||||
| `lordandstrong`、`lordstrongand`、`weakafflicted`、`partialreducedweak`、`debilitatedsign`、`planetenterssign`、`planetsdistributed`、`beneficlocated` | 2~8 成 | 瑜伽表组合列:部分规则没有 `combination_en`(或只有中文 `combo_template`),英文分支回落到中文再被拆译 |
|
||||
|
||||
`pl9_full_data_export._sanitize_pl9_ai_density_markdown_en` 的 `phrase_terms` 里有 `"在": "in"`、`"与": "and"`、`"第": "house "`、`"宫": ""` 这类单字条目,且按长度从长到短一次替换;`pl9_language_terms.normalize_en_markdown_terms` → `apply_en_residual_han` 再补一轮。长短词表分在不同轮次,先跑的短词会把后面才有的长句拆碎。这就是 L1~L6 和粘连的共同机制。
|
||||
|
||||
## 根因
|
||||
|
||||
1. **英文版不是按语言渲染出来的,而是中文渲染后再在末尾逐词替换。** `render_full_data_markdown` 的英文分支仍然调用 `render_pl9_parity_markdown`,而它对 Rath 段落、Panchanga、Dosha 摘要、瑜伽强度列等处不分语言,打印中文。英文能否干净,取决于末尾三套词表(`_sanitize_pl9_ai_density_markdown_en` 内的 `replacements` / `reverse_terms` / `phrase_terms`,以及 `pl9_language_terms` 的 `_EN_RESIDUAL_HAN`)是否恰好覆盖、替换顺序是否恰好不拆句。只要某张盘触发一条没有覆盖的句子,就会留下汉字,整份英文被撤回。
|
||||
2. **英文孪生字段早已存在却没接上。** `strength_en`、`direction_text_en`、`strength_level_text_en`、`rule_text_en`、`method_note_en`、`RATH_LABEL_EN`、`LEGACY_NOTE_EN` 都已经在资料包或常量里,渲染层没用。旧阅读版有 `pl9_reader_english._english_packet` 负责把 `strength_en` 换进去,完整数据版的 `_english_edition` 只做了 `deepcopy` + `report_language = "en"`,没有这一步。
|
||||
3. **BUG-1272 的测试为什么没拦住**:
|
||||
- 只有 3 张固定虚构盘(另 3 张公开盘只在当时手工跑过,没进测试)。泄漏都是按盘触发的:Rath 起运宫裁到第 5 级约 15%,Panchanga 综合为凶约 15%,计都在起运宫或子运方向走 Table 14 约 10%,月土合相 ≤10° 约 5%,完全 Kaal Sarp 约 5%,合起来约 40%。3 张盘恰好都没碰上。
|
||||
- `tests/test_narayana_legacy_label.py::test_reference_edition_shows_both_and_english_terms_cover_the_rath_lines` 断言 Rath 各行能被 `pl9_reader_english_terms.PHRASES_EN / REGEX_EN` 译干净。**完整数据版英文根本不走这张表**(只有旧阅读版走),所以这条测试一直通过,生产路径却漏。
|
||||
- `tests/test_report_english_edition.py` 不在快速门清单(`scripts/run_quality_gate.py` 的 `CORE_PYTEST_TARGETS`),staging 门禁不跑它,只有手动的 release 门禁全量会跑。
|
||||
- BUG-1272 的修法本身是「把拆开后剩下的碎片补进英文术语」,即继续往末尾词表加条目;遇到新的句子或新的盘况就会再漏。
|
||||
4. **BUG-1272 的防复发措施仍在**:`test_english_edition_has_no_chinese`(3 张盘汉字为 0)、`test_api_drops_english_when_chinese_would_leak`(人为写入汉字时接口撤回英文)、前端 `acceptEnglishEdition` 发现汉字时判 `han_leak`、`frontend/tests/report-english-edition.test.tsx` 的对应断言,都还在,且都通过。它们没有失效,只是覆盖面太窄。
|
||||
|
||||
## 决策记录(产品负责人,issue #89)
|
||||
|
||||
| # | 决定 |
|
||||
| --- | --- |
|
||||
| D1 | **从源头补齐英文**:英文分支直接用已有的英文孪生字段或新增的英文孪生字段渲染(例如 Punarphoo 强度走 `strength_en` / `STRENGTH_EN`)。**不准只在末尾做字符串替换**——本单不得为修这些泄漏往 `_sanitize_pl9_ai_density_markdown_en` 的三张表、`pl9_language_terms._EN_RESIDUAL_HAN`、`pl9_en_glossary`、`pl9_reader_english_terms` 新增条目 |
|
||||
| D2 | 修好吃空格(`inperiod start`、`scoreandtimeline` 等),同样在源头改 |
|
||||
| D3 | 回归测试:**200 张固定种子的随机虚构盘**,英文汉字数为 0,且不出现 `[source text retained`。时长允许放进快速门,否则放进全量测试并在 PROGRESS 写明(见 T6 的拆分) |
|
||||
| D4 | 不需要迁移。已生成的报告要重新生成才有英文版(CHANGELOG 写明) |
|
||||
| D5 | `docs/BUG_HISTORY.md` 标明**复发自 BUG-1272**,并写清当时 4 张盘的测试为什么没拦住(用上面「根因 3」) |
|
||||
| D6 | 顺序:最先做,#91 / #92 在后 |
|
||||
| — | 接口与前端「发现汉字就整份撤回英文」的判定**保留不变**(BUG-1272 既有设计);`_retain_remaining_cjk_lines` 的标记方式也保留。本单是让英文不再产生汉字,不是放宽判定 |
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. **中文版逐字节不变**。用同一组固定种子(至少 30 张,含 L1~L6 各至少一张),在 `bd3ce0e7b` 与本分支各渲染一次中文完整数据版,SHA-256 全部相同。生产者里只能**新增** `*_en` 字段,不得改中文字段的值。
|
||||
2. 不得放宽 `_english_edition` 与 `acceptEnglishEdition` 的汉字判定;不得删 `[source text retained` 标记逻辑去「凑」汉字为 0。
|
||||
3. 不往末尾词表加条目(D1)。若某一处确实只能靠词表(例如上游 PL9 原样搬来的整段说明),先停下来在 PROGRESS 列出,交 Claude 判断。
|
||||
4. Rath 版仍然只做展示:英文行函数放在 `narayana_legacy_label.py`,不得让 `narayana_rath` 被第二个文件导入(`tests/test_narayana_rath_book_tables.py::test_rath_profile_is_display_only`);不得碰冻结的打分文件(`references/rectification_sealed_holdout.v1.json` 列出的文件,ERR-110 / ERR-115)。
|
||||
5. 测试必须屏蔽 PyJHora(照 `tests/test_special_lagnas_birth_instant.py` 的 `_block_jhora` 做法;多进程时在每个子进程初始化里装同样的屏蔽),种子、`today`、`target_year` 固定,同一提交重跑结果相同。
|
||||
6. 改任何既有断言写「原值 / 新值 / 原因」三栏(AGENTS §7.3);先 grep 按源码文本断言的合同测试(§7.8),见「开工前置命令」。
|
||||
7. 不改 `.gitea/workflows/**`,不改数据库,不升级依赖。
|
||||
8. 测试与文档只用虚构盘,不写任何真实出生资料(AGENTS §8)。
|
||||
|
||||
## 任务分解
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| T1 | **Rath 段按语言渲染**(L1、L3、L4 与 Rath 段全部粘连):在 `narayana_legacy_label.py` 新增 `rath_summary_line_en` / `rath_report_lines_en` / `_ad_direction_lines_en`,用 `RATH_LABEL_EN`、`strength_level_text_en`、`direction_text_en`、`method_note_en`、`dual_lord_basis_en`、`rule_text_en`(含书内分歧那几条的 `rule_text_en`),「第 N 级」写成 `level N`,表头用英文。`render_pl9_parity_markdown` 的 Narayana 段英文分支改调英文函数,并改用 `LEGACY_NOTE_EN` 写算法行;去掉现在英文分支里拿 `dual_lord_basis` 做字符串替换的那段(被新函数取代);防重复判断(现为 `startswith('- 并列:')`)按语言各判各的 | 单测:用一个 Rath 视图分别覆盖第 1~7 级、Table 10 / 11 / 12、子运方向带书内分歧,英文行无汉字、无 `house N level`,含 `level 5: lord's degree in sign`、`Ketu in the start sign: reversed (Table 12)` 原文;中文行与改前逐字相同 |
|
||||
| T2 | **瑜伽强度列**(L5):`render_pl9_parity_markdown` 瑜伽表英文分支用 `row['strength_en']`,缺了再用 `yoga_engine.STRENGTH_EN`;两者都没有时不得回落到中文——让 200 张盘测试暴露出来,并在生产者补 `strength_en`。同一分支里,组合列中仍回落到中文的规则(`lordandstrong`、`weakafflicted`、`debilitatedsign` 等)补 `combo_template_en` / `combination_en`,改 `references/yoga_rules.json` 或对应检测器,只加英文字段 | 单测:Punarphoo 行英文强度列不含汉字;200 张盘中瑜伽表组合列无上表所列粘连词 |
|
||||
| T3 | **Panchanga 综合性质**(L2):`muhurta.calc_panchanga` 新增 `overall_quality_en`(`Auspicious (Subha)` / `Mixed` / `Inauspicious (Asubha)`,按同一分数档位);`render_pl9_parity_markdown` 的 Panchanga 小表英文分支用英文标签(`Overall quality`、`Auspicious elements`、`Total elements`)和 `overall_quality_en` | 单测:三个档位英文值正确;中文值不变。若新增字段让 `tests/golden/*keypaths*` 一类键路径快照变化,按三栏更新并说明 |
|
||||
| T4 | **Dosha 摘要**(L6 与 Kaal Sarp / Sade Sati 粘连):`yogas_doshas` 中 Mangal、Kaal Sarp、Pitra、Sade Sati 的检测函数各新增 `summary_en`(`calc_kaal_sarp_dosha` 的 full / partial / none 三句;partial 句的「N/M 颗行星」写成 `N of M planets`);Dosha 结果表与 Sade Sati 小节英文分支用 `summary_en` 并把「当前阶段 / 是否 active / 摘要」等标签按语言写 | 单测:完全与部分 Kaal Sarp 的英文摘要原文断言;200 张盘中无 `atpresent`、`planetsinbetween`、`whetherinunderway`、`withSaturn` 粘连 |
|
||||
| T5 | **其余每张盘都有的粘连**:Jaimini 星位表的名称与职责(`careerplanetAmK`、`careerdirection`、`marriagequality`,看 `jaimini.py` 的职责表与渲染处)、`### Vimsopaka / Pushkara / Vargottama` 标题后半。都按语言渲染或补英文孪生字段 | 200 张盘中这些词计数为 0 |
|
||||
| T6 | **回归测试,分两个文件**:① `tests/test_report_english_sweep.py`:200 张固定种子虚构盘(年 1940–2015、月日时分随机、纬度 −45~60、经度 −120~150、时区按经度取半小时、`ayanamsa` 在 lahiri / raman / kp 间、`node_mode` 在 mean / true 间随机;`today` / `target_year` 固定),屏蔽 PyJHora,对每张盘调 `_english_edition(packet, True)`:必须返回 `markdown_en`、不得有 `english_unavailable`;`markdown_en` 与 `reader_dasha_applicability_en` 汉字数为 0、不含 `[source text retained`;不含粘连黑名单(本任务书「吃空格」表里出现的全部词)。允许用进程池(不超过 4 个进程)。失败信息列出盘号与残留片段,不打印出生资料以外的东西也够定位。另提供 `--dump-dir`(或环境变量)把 200 份英文 Markdown 写到临时目录,供 Claude 验收时跑前端判定。② `tests/test_report_english_sweep_smoke.py`:从同一生成器中挑 12~16 张,必须覆盖 L1~L6 每一类至少一张,断言同上;**把这个文件加进 `scripts/run_quality_gate.py` 的 `CORE_PYTEST_TARGETS`**(注释写明 issue #89 / BUG 号)。旧的 `test_reference_edition_shows_both_and_english_terms_cover_the_rath_lines` 改为断言完整数据版英文路径本身(三栏:原值「Rath 中文行经 `PHRASES_EN` 可译干净」/ 新值「完整数据版英文渲染的 Rath 段无汉字且用英文函数」/ 原因「完整数据版不走 `PHRASES_EN`,旧断言测的不是生产路径」) | 两个文件在本分支全绿;把 T1 的英文函数临时改回中文时,smoke 文件会红(PROGRESS 写这次变异的结果);PROGRESS 记录 Windows 上两个文件的耗时(串行与进程池各一次) |
|
||||
| T7 | **文档**:`docs/BUG_HISTORY.md` 新增两条(见「BUG 编号」);`CHANGELOG.md` 写「英文版不再因个别漏译整份消失;已生成的报告需重新生成才有英文版」;`docs/tasks/PROGRESS-report-english-han-leak-20261021.md` 写清改了哪些源头、各粘连词前后计数、两份测试耗时、中文逐字节对比结果、真机复测步骤(登录 staging → 新建一份个人报告 → 揭幕后切到 English → 全文搜不到汉字、L1~L6 对应段落读得通 → 导出英文版) | 文档齐;BUG 记录写明复发自 BUG-1272 与根因 3 的四点;不含任何真实资料(推送前跑 `python -m pytest tests/test_repo_privacy_markers.py -q`) |
|
||||
|
||||
### 自跑与验收分工
|
||||
|
||||
| 检查 | Grok(Windows + Docker)自跑 | Claude(Linux)验收 |
|
||||
| --- | --- | --- |
|
||||
| 改到的 Python 测试文件(含 T6 两个新文件、`test_report_english_edition.py`、`test_narayana_legacy_label.py`、`test_narayana_rath_book_tables.py`、`test_year_lord_blocked_no_fallback.py`、`test_year_lord_basis_label.py`、`test_report_full_data_fix.py`、`test_report_chart_blank_columns.py`、`test_runtime_jhora_import_scan.py`,以及改到的 muhurta / yoga 相关测试) | 必跑(工作树 `.venv`) | 复验 |
|
||||
| 中文逐字节对比(红线 1) | 必跑,结果写 PROGRESS | 复验(独立重算哈希) |
|
||||
| `tsc --noEmit`、`npm run lint`、改到的前端测试单文件 | 只在动了 `frontend/` 时跑 | 复验 |
|
||||
| Python 快速门 `scripts/run_quality_gate.py --profile quick` | 不跑 | **以 Claude 为准** |
|
||||
| Python 全量 pytest,失败名单与 `bd3ce0e7b` 逐名比对 | 不跑 | **以 Claude 为准**(本单改的是引擎输出文字,必须全量) |
|
||||
| 前端全量 `npm test` | 不跑 | 以 Claude 为准 |
|
||||
| 200 份英文 Markdown 过前端判定:`acceptEnglishEdition`(含 `ordinaryOutputLeaks(…, "en")` 与 `assembleReportFactTables(…, "en")` 的汉字检查) | 不跑 | Claude 用 T6 的 `--dump-dir` 输出跑一次;有 `public_leak` / `fact_tables_han` 时另开修复单 |
|
||||
| `npm run test:db` | 不涉及数据库,不跑 | — |
|
||||
|
||||
## 让步顺序
|
||||
|
||||
1. 不可让步:英文汉字为 0 与无 `[source text retained`(200 张)、中文逐字节不变、不往末尾词表加条目、Rath 只做展示。
|
||||
2. 先交 T1~T4(issue 点名的泄漏与粘连)+ T6 + T7,作为第一个提交;T5 作为第二个提交。
|
||||
3. 若 T5 中某一处源头改动牵涉过大(例如要改上游原样搬来的整段说明),可以把该处从 T6 粘连黑名单中拿出,在 PROGRESS 列出词、出现盘数和源头位置,留给 #91 一并处理;汉字为 0 不在可让步之列。
|
||||
4. 若 200 张盘测试在 Windows 上即便用进程池也超过 10 分钟,在 PROGRESS 写明实测时间,Claude 决定是否请产品负责人改为只在验收时跑;不得自行减少盘数。
|
||||
|
||||
## 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/report-english-han-leak-20261021 .worktrees/report-english-han-leak-20261021 origin/staging
|
||||
cd .worktrees/report-english-han-leak-20261021
|
||||
git status -sb
|
||||
# 按源码文本断言、或直接 import 这些符号的测试(§7.8)
|
||||
grep -rln "rath_report_lines_zh\|rath_summary_line_zh\|_ad_direction_lines_zh\|STRENGTH_LEVEL_\|DIRECTION_ZH\|RATH_LABEL_" tests frontend/tests
|
||||
grep -rln "overall_quality\|calc_panchanga\|calc_kaal_sarp_dosha\|summary_en\|strength_en\|STRENGTH_EN" tests frontend/tests
|
||||
grep -rln "source text retained\|han_leak\|PHRASES_EN\|REGEX_EN" tests frontend/tests
|
||||
grep -n "CORE_PYTEST_TARGETS" -A5 scripts/run_quality_gate.py
|
||||
grep -rln "CORE_PYTEST_TARGETS\|test_report_english" tests
|
||||
# BUG 编号:取整个文件的最大号(文件里有整段重复,见下)
|
||||
grep -o "BUG-1[0-9]\{3\}" docs/BUG_HISTORY.md | sort -u | tail -3
|
||||
```
|
||||
|
||||
## BUG 编号
|
||||
|
||||
- 当前 `docs/BUG_HISTORY.md` 最大号 BUG-1403;#85 已预留 1404 起的若干号。**开工时取全文件最大号 + 1**,预计在 BUG-1410 前后。
|
||||
- 建议两条:① 英文版漏译致整份丢弃(复发自 BUG-1272;写根因 3 的四点);② 英文版吃空格与「第 N 级」错译成 house(关联 BUG-1272、BUG-1218)。
|
||||
- 注意:`docs/BUG_HISTORY.md` 自 `400af57e8`(2026-10-14 合并)起整份内容重复了一遍(同一编号出现两次)。本单**只在文件末尾追加**,不顺手去重(另行处理)。
|
||||
@@ -0,0 +1,113 @@
|
||||
# 任务书:导出弹窗——英文不可用提示改顶部浮层,选中态改用主题色(2026-10-21)
|
||||
|
||||
## 基线
|
||||
|
||||
- `origin/staging` = `bd3ce0e7b`。开工前 `git fetch origin --prune`,以届时的 `origin/staging` 为准。
|
||||
- 执行方:**Grok(Windows + Docker)**;分支 `codex/report-export-dialog-polish-20261021`,工作树 `.worktrees/report-export-dialog-polish-20261021`,**只推这个分支**。
|
||||
- 关联:issue #90;BUG-1078(导出面板选中态深红褐底)、BUG-1079(全站实心按钮配色)、BUG-1094(导出显示真实大小)、BUG-1121(结果提示只经 `notify.ts`);`TASK-report-reader-polish-and-english-20260929`、导出面板 2026-09-30 重设计。
|
||||
- 纯前端,不涉及数据库、Python、`scripts/`。
|
||||
|
||||
## 现状实证(按符号定位)
|
||||
|
||||
| # | 现象 | 位置 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 英文版不可用时,弹窗里多出一行灰字占位置 | `frontend/src/components/personal-report/report-export-drawer.tsx` 的 `ReportExportDrawer`:`hasEnglish` 为假时渲染 `<p className="report-export-caption">`,两句固定文案——`englishEdition === "unavailable"` →「这份报告的英文版没有生成成功,只能导出中文版。」;否则(`"legacy"`)→「这份报告生成于英文版上线前,重新生成即可获得中英两版。」 |
|
||||
| 2 | 英文是否可用在弹窗打开时就已知,不需要再请求 | 同组件的 `hasEnglish = englishEdition === "ready" && Boolean(editions.en?.length)`。`englishEdition` 来自 `report-actions.tsx` 的 `ReportActions` 属性,再上游是 `personal-report-page.tsx` 的 `readEnglishEdition`(接口 `englishEdition` + 前端汉字复核)。弹窗由 `ReportActions` 的 `exportOpen` 状态控制,**每次打开都重新挂载**,挂载时 `useEffect(…, [])` 调 `showModal()`。注意两个组件的 `englishEdition` 默认值都是 `"legacy"` |
|
||||
| 3 | 英文可用时语言分段下方还有一行建议「拿去问 ChatGPT、Claude 等 AI,建议选 English,术语与原典一致。」,同样是 `.report-export-caption` | issue 只点名「英文不可用」与 legacy 两句,见决策 D1 |
|
||||
| 4 | 顶部浮层 API 已有 | `frontend/src/lib/notify.ts`:`notifySuccess` / `notifyInfo` / `notifyError(message, { id?, duration? })`,info 3 秒;同 `id` 后一条替换前一条;每次调用先 `liftToastsAboveModal()`——**只有当页面上已有 `dialog:modal` 时**才把浮层提到弹窗之上。弹窗现有用法:`download()` 里 `notifySuccess("已导出")`、`notifyError(…)`。其它处用 `id` 去重的例子:`personal-report-center.tsx` 的 `{ id: "reports-load-more" }`、`email-otp-login.tsx` 的 `AUTH_NOTICE` |
|
||||
| 5 | 选中态是灰底+黑字 | `frontend/src/app/globals.css`(按选择器找):`.report-export-segment button[aria-checked="true"]`(`--color-canvas` 底 + `--color-ink` + 阴影,轨道 `.report-export-segment` 是 `--color-canvas-muted`);`.report-export-mode[aria-checked="true"]`(边 `--color-ink-secondary`、底 `--color-canvas-muted`);`.report-export-mode[aria-checked="true"] .report-export-radio`(`border: 5px solid var(--color-ink)`);`.report-export-row:is([aria-checked="true"], [aria-checked="mixed"]) .report-export-box`(边与底 `--color-ink`,勾为 `--color-canvas`) |
|
||||
| 6 | 要推翻的那句设计规范 | `frontend/DESIGN.md`「导出报告 sheet (redesigned 2026-09-30 …)」条目末尾:「Selected states use `--color-canvas-muted` + `--color-ink`, never `--color-action-soft` (BUG-1078).」同一条目里还有「otherwise one caption saying why there is no English」;`frontend/docs/VOICE.md`「导出面板(2026-09-30 重设计)」条目末尾写了两句无英文说明 |
|
||||
| 7 | 主题色 token | DESIGN.md 色表:`--color-action-strong` 是「fill rank:rings, carets, **checked radios**」,`--color-action` 是文字/图标档。值:`--color-action-strong` 浅 `#cc785c`、深 `#d78064`;`--color-action` 浅 `#a9583e`、深 `#d78064`(`globals.css` `:root`、`@media (prefers-color-scheme: dark) :root:not([data-theme="light"])`、`:root[data-theme="dark"]` 三处)。BUG-1078 的深红褐是 **`--color-action-soft` 深色 `#3a2620`**;按钮专用 `--color-action-fill` 深色 `#8f4a33` 也偏深红褐。实测对比度:`#cc785c` 对浅底 `#fbfaf7` 3.14:1、`#d78064` 对深底 `#262624` 5.17:1(非文字 3:1 线都过);`#a9583e` 对浅底 4.85:1 |
|
||||
| 8 | 页面顶栏还有一个「中文 / English」胶囊 `.report-language-switch`(不在弹窗内),选中态也是 `--color-canvas-muted` + ink(DESIGN.md「中文 / English (2026-09-29)」条) | 见决策 D3 |
|
||||
|
||||
## 根因
|
||||
|
||||
1. 导出面板 2026-09-30 重设计时,把「为什么没有英文」当成面板内容写成常驻说明行;按 DESIGN.md「提示与报错:toast」口径,这是一次性告知,应走顶部浮层,不占布局。
|
||||
2. BUG-1078 当时为了躲开深色模式下 `--color-action-soft` 的深红褐色块,把选中态全部改成中性灰 + 黑,导致选中与未选中区分弱、不带品牌色。正确做法是**只把描边、圆点、勾选框换成主题色,底色保持中性**,既不出现大块深色底,也能一眼看出选中。
|
||||
|
||||
## 决策记录(产品负责人,issue #90)
|
||||
|
||||
| # | 决定 |
|
||||
| --- | --- |
|
||||
| D0 | 打开导出弹窗时若英文版不可用(`unavailable` 与 `legacy` 两种),出**一次**顶部浮层提示(复用 `notify.ts`),删除弹窗内对应的那行说明。文案沿用原两句,不改字 |
|
||||
| D0' | 「整篇报告 / 自选章节」选中态:描边与单选圆点用主题色,底色中性;深色模式不得出现 BUG-1078 那种深红褐底。勾选框、弹窗内中英切换的选中态同样处理 |
|
||||
| D0'' | **推翻 DESIGN.md「Selected states use `--color-canvas-muted` + `--color-ink`, never `--color-action-soft` (BUG-1078).」这一句**,同一提交更新 DESIGN.md。BUG-1078 的主语「导出面板选中态不得用 `--color-action-soft` 深色块」保留(不推翻) |
|
||||
| D1 | (Claude 按 issue 字面定,产品可否决)英文**可用**时那句「拿去问 ChatGPT…建议选 English」保留在语言分段下方,不改成浮层——它是选项说明,不是事件提示。`.report-export-caption` 类保留给它用 |
|
||||
| D2 | (Claude 定)主题色用 DESIGN 色表里的 fill 档 `--color-action-strong` 画描边/圆点/勾选框底;文字若需着色用文字档 `--color-action`。**不用** `--color-action-soft`、`--color-action-fill`、任何新色值 |
|
||||
| D3 | 页面顶栏的 `.report-language-switch` 胶囊**不在本单范围**(issue 写的是弹窗内);若产品要一并改,另开 issue |
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. `./node_modules/.bin/tsc --noEmit` 0 错;`npm run lint` 0 error。
|
||||
2. 前端全量 `npm test` 失败清单与开工时 `origin/staging` 基线逐条一致(`# fail`、`# cancelled` 与退出码一起看);测试总数不低于基线。
|
||||
3. `next build` 后 `/` 仍 `○ Static`;首屏 gzip 变化 ±2% 内(导出弹窗不在首屏,预期 0 变化)。
|
||||
4. 改 UI 的同一提交更新 `frontend/DESIGN.md`(含推翻那一句)与 `frontend/docs/VOICE.md`。
|
||||
5. 浮层**每次打开弹窗只出一次**:只在挂载时的 effect 里、`showModal()` **之后**调用(否则 `liftToastsAboveModal` 找不到 `dialog:modal`,浮层落在遮罩下);切换整篇/自选、勾选、切换语言等重渲染不得再出;必须带固定 `id`(如 `"report-export-english"`),React 严格模式开发环境 effect 双跑时也只剩一条。不得在 render 里调用 notify。
|
||||
6. 不新增任何加载动画、spinner、骨架、额外请求(`report-export-drawer.test.tsx` 现有 `doesNotMatch(/InlineSpinner|navigator\.clipboard|fetch\(/)` 保持)。
|
||||
7. 选中态不得出现 `--color-action-soft` / `--color-action-fill`,不得新造色值,不得改任何主题 token 值(`tests/test_frontend_theme_contract.py`、`dark-theme-contract`、`design-token-contract` 不动)。
|
||||
8. 改既有断言必须写「原值 / 新值 / 原因」三栏;不得静默弱化。不改 `.gitea/`、不升依赖、不顺手修范围外 warning。
|
||||
9. 公开仓库:测试与文档只用虚构 / 已有 golden fixture,不写任何真实个人资料。
|
||||
|
||||
## 必须同轮更新的源码文本合同测试(AGENTS §7.8)
|
||||
|
||||
| 文件 · 用例 | 现断言 | 本单后必须改成 |
|
||||
| --- | --- | --- |
|
||||
| `frontend/tests/report-buttons-scope-20260928.test.ts`「the BUG-1078 report-only override is gone; the selected export preset stays neutral」 | 对 `.report-export-mode[aria-checked="true"]`、`.report-export-segment button[aria-checked="true"]` 断言 `doesNotMatch(/--color-action-soft\|--color-action\b/)` 且 `match(/--color-ink\|--color-canvas/)`。**注意 `--color-action\b` 会命中 `--color-action-strong`**,改完必红 | 三栏:保留「不得用 `--color-action-soft` / `--color-action-fill`」;新增「选中描边 / 圆点用 `--color-action-strong`」;底色只许中性 token(`transparent` / `--color-canvas*`)。原因:issue #90 推翻 DESIGN 那一句,BUG-1078 主语不变 |
|
||||
| `frontend/tests/report-english-edition.test.tsx`「the export drawer suggests English for AI only when it can switch」 | legacy 时断言 `h.container.text.includes("重新生成即可获得中英两版")` | 三栏:改为断言弹窗内**不含**该句、`toast.info` 被调用一次且文案为该句;保留「没有 English 选项」与「建议选 English」两条断言 |
|
||||
| `frontend/tests/report-export-drawer.test.tsx`「drawer contract uses native dialog …」 | 只读 CSS 片段与源码 | 不需改既有断言;新增用例写在本文件(见 T1、T2) |
|
||||
|
||||
改前再按 §7.8 grep 一次:`grep -rn "report-export-\|英文版没有生成成功\|英文版上线前\|canvas-muted\` + \`--color-ink" frontend/tests tests`,以实际命中为准补进上表。
|
||||
|
||||
## 任务分解
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| T1 | `report-export-drawer.tsx`:删掉 `hasEnglish` 为假时的 `<p className="report-export-caption">` 分支;在挂载 effect 中 `showModal()` 之后,若英文不可用则 `notifyInfo(<原两句之一>, { id: "report-export-english" })`。判断条件与原分支完全一致(`!hasEnglish`;`englishEdition === "unavailable"` 用第一句,其余用 legacy 句)。英文可用时的 ChatGPT 建议句不动(D1) | 新增单测(`report-export-drawer.test.tsx`,桩 `toast.info`):① `legacy` 打开 → info 恰好 1 次、文案为 legacy 句、带 `id`;② `unavailable` 打开 → 1 次、文案为失败句;③ `ready` 打开 → 0 次;④ 打开后切「自选章节」、勾选 / 取消、展开分组 → 次数不变;⑤ 关闭(`render(null)`)再打开 → 再出 1 次;⑥ 弹窗 DOM 里不再有这两句。`report-english-edition.test.tsx` 按上表三栏改 |
|
||||
| T2 | `globals.css` 选中态(只改 `.report-export-*` 下列选择器,其余不动):`.report-export-mode[aria-checked="true"]` → `border-color: var(--color-action-strong)`,底色保持中性(`transparent`,与未选一致);`.report-export-mode[aria-checked="true"] .report-export-radio` → `border: 5px solid var(--color-action-strong)`;`.report-export-row:is([aria-checked="true"], [aria-checked="mixed"]) .report-export-box` → 边与底 `--color-action-strong`,勾 / 横线保持 `--color-canvas`;`.report-export-segment button[aria-checked="true"]` → 保留 `--color-canvas` 底与阴影,文字改 `--color-action`、加 `box-shadow` 内描边 1px `--color-action-strong`(或等价写法),字重 600 不变 | 按上表改 `report-buttons-scope-20260928.test.ts`;新增断言:四条选中规则都不含 `--color-action-soft` / `--color-action-fill`、背景值只在 `transparent` / `--color-canvas*` / `--color-action-strong`(仅勾选框 18px 小方块)之内;三处主题块 `--color-action-strong` 值仍为 `#cc785c` / `#d78064` / `#d78064` |
|
||||
| T3 | `frontend/DESIGN.md`「导出报告 sheet」条:删除「Selected states use `--color-canvas-muted` + `--color-ink`, never `--color-action-soft` (BUG-1078).」,换成「Selected states (issue #90, 2026-10-21, overrides the 2026-09-30 neutral rule): the scope card's border and radio dot, checked / mixed boxes, and the selected language segment use `--color-action-strong` (text `--color-action`); backgrounds stay neutral; never `--color-action-soft` or `--color-action-fill` (BUG-1078: no dark red-brown block in dark mode).」;「otherwise one caption saying why there is no English」改为「otherwise no caption: opening the sheet raises one info toast (id `report-export-english`) saying why」。`frontend/docs/VOICE.md` 导出面板条末句改为「没有英文版时弹窗内不写字,打开时顶部浮层提示一次:……(两句原文)」 | 文档 diff 与实现一致;同一提交 |
|
||||
| T4 | 记录:`docs/BUG_HISTORY.md` 新编号一条(现象:英文不可用说明占弹窗布局、选中态不带主题色;关联 BUG-1078 / 1079 / 1121;写明推翻 DESIGN 那一句、BUG-1078 主语保留);`CHANGELOG.md` 一行;`docs/tasks/PROGRESS-report-export-dialog-polish-20261021.md`(三栏、测试数字、Grok 自跑结果);`docs/tasks/README.md` 状态板改「待验收」;真机清单 `docs/testing/report-export-dialog-20261021.md`(Claude 已写好,执行方按实际偏差修订) | 文档齐;隐私守卫 `.venv/bin/python -m pytest tests/test_repo_privacy_markers.py -q` 通过 |
|
||||
|
||||
### 验收分工(按 `TASK-agent-loop-pilot-20261013.md`「T1 结果与定稿分工」)
|
||||
|
||||
| 检查 | Grok 自跑 | Claude 验收 |
|
||||
| --- | --- | --- |
|
||||
| `tsc --noEmit`、`npm run lint` | 必跑 | 复验 |
|
||||
| `report-export-drawer.test.tsx`、`report-english-edition.test.tsx`、`report-buttons-scope-20260928.test.ts`、`class-name-definition-contract.test.ts`、`dark-theme-contract.test.ts`、`design-token-contract.test.ts` | 必跑 | 复验 |
|
||||
| 前端全量 `npm test`(与基线逐条比对) | 不跑 | **以 Claude 为准** |
|
||||
| `npm run build`:`/` Static、首屏 gzip ±2% | 不跑 | **以 Claude 为准** |
|
||||
| `test:db` | 不需要(不动数据库访问层) | — |
|
||||
| 浅色 / 深色 / 手机宽度真机 | 不做 | 环境缺口,交产品按清单走 |
|
||||
|
||||
## 与 #89 的并行关系
|
||||
|
||||
#89(英文版漏译)预计改 `scripts/professional_report_reference.py`、`scripts/pl9_reader_english_terms.py`、`scripts/yoga_engine.py`、`tests/test_report_english_edition.py`,可能改 `frontend/src/lib/personal-report-english-edition.ts`(`acceptEnglishEdition`)。**业务代码无重叠,可并行**。可能撞车的只有:
|
||||
|
||||
- `frontend/tests/report-english-edition.test.tsx`:#89 若在此加 `acceptEnglishEdition` 用例会与本单 T1 改同一文件(不同用例,通常可自动合并)。本单只改上表那一个用例,新用例一律写进 `report-export-drawer.test.tsx`。
|
||||
- `docs/BUG_HISTORY.md`、`CHANGELOG.md`、`docs/tasks/README.md`:都是追加,后合入的一方 rebase 时手工合并,**BUG 编号以合入时最大号 +1 重新核对**。
|
||||
|
||||
## 让步顺序
|
||||
|
||||
时间不够时:T1(浮层 + 删说明行)> T2(选中态)> T3(文档,但 T2 落地则 T3 必须同提交)> T4。T1 与 T2 可分两个提交,但 T2 与 T3 不得拆开。
|
||||
|
||||
## 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/report-export-dialog-polish-20261021 .worktrees/report-export-dialog-polish-20261021 origin/staging
|
||||
cd .worktrees/report-export-dialog-polish-20261021
|
||||
git status -sb
|
||||
grep -n "report-export-caption\|notifyInfo\|showModal" frontend/src/components/personal-report/report-export-drawer.tsx
|
||||
grep -n 'report-export-\(mode\|radio\|box\|segment\)' frontend/src/app/globals.css
|
||||
grep -n "Selected states use" frontend/DESIGN.md
|
||||
grep -rn "report-export-\|英文版没有生成成功\|英文版上线前" frontend/tests tests
|
||||
grep -n "^## BUG-1[0-9][0-9][0-9]" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -3
|
||||
```
|
||||
|
||||
基线测试:开工时在 `origin/staging` 上先跑一次上表单文件测试,记数字进 PROGRESS。
|
||||
|
||||
## BUG 编号
|
||||
|
||||
当前最大 **BUG-1403**;**BUG-1404 起预留给 #85**。本单开工时核对 `docs/BUG_HISTORY.md` 最大号与 #85 / #89 已占用号,取当时最大号 +1,在 PROGRESS 写明。
|
||||
|
||||
> 备注(不在本单范围):`docs/BUG_HISTORY.md` 当前整份内容重复了一遍(`# Bug History` 出现在第 1 行和约第 18099 行,`## BUG-` 标题 2337 个、去重后 1199 个)。本单新增记录只追加在文件末尾,不要处理重复;已报给 Claude 另行处理。
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# 真机走查:上升状态格「/」与说明(issue #87)
|
||||
|
||||
用 staging,登录自己的账号,选一个已有星盘的人。
|
||||
|
||||
| # | 操作 | 应该看到 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 电脑打开星盘页,本命表第一行「上升」的「状态」格 | 写「/」,不是「不适用」 |
|
||||
| 2 | 鼠标移到这个「/」上 | 弹出一句:上升点是命盘的起点,不是行星,不判断入旺或落陷 |
|
||||
| 3 | 切到月亮盘、D9 分盘 | 上升行同样是「/」,悬停有同一句 |
|
||||
| 4 | 手机打开同一页,点一下「/」 | 出现同一句,几秒后自动消失;页面不被撑宽 |
|
||||
| 5 | 其他行(太阳、月亮等)的状态格 | 照旧,如「入旺」「落陷」,没有变化 |
|
||||
| 6 | 打开一份**新生成**的个人报告,找「行星主星与状态」表 | 上升行状态写「/」;表下面有一行同样的说明 |
|
||||
| 7 | 把这份报告切到英文 / 导出 PDF | 上升行写「/」,表下是英文说明;PDF 里也能看到这行说明 |
|
||||
| 8 | 深色模式下重复 1、2 | 「/」与气泡都看得清 |
|
||||
|
||||
旧报告要重新生成才会变;旧报告里仍是「不适用」属正常。
|
||||
@@ -0,0 +1,17 @@
|
||||
# 真机走查:导出弹窗提示与选中态(issue #90)
|
||||
|
||||
环境:`https://staging.jyotisha.chat`,用受控测试账号。需要三类报告各一份:**有英文版**的新报告、**英文版没生成成功**的报告(如有)、**英文版上线前**生成的旧报告。没有某一类就写「无样本」,不算通过。每步在**浅色**、**深色**各走一遍;第 8 步用手机(或桌面浏览器窄到约 390px)。
|
||||
|
||||
| # | 操作 | 期望 | 浅色 | 深色 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 打开旧报告(英文版上线前),点「导出」 | 屏幕**顶部中间**出浮层「这份报告生成于英文版上线前,重新生成即可获得中英两版。」,约 3 秒自动消失;浮层在弹窗之上、没被遮罩盖住;弹窗里**没有**这句灰字,也没有中文 / English 选项 | | |
|
||||
| 2 | 同一弹窗里点「自选章节」、勾掉几项、展开「分盘」、再点回「整篇报告」 | 浮层**不再出现** | | |
|
||||
| 3 | 关掉弹窗再点「导出」 | 浮层再出一次,只一条不叠 | | |
|
||||
| 4 | 打开英文版没生成成功的报告,点「导出」 | 浮层「这份报告的英文版没有生成成功,只能导出中文版。」一次;弹窗内无此句 | | |
|
||||
| 5 | 打开有英文版的报告,点「导出」 | **没有**浮层;语言选项下仍有「拿去问 ChatGPT、Claude 等 AI,建议选 English,术语与原典一致。」 | | |
|
||||
| 6 | 第 5 步弹窗里看选中样式 | 「整篇报告」卡片描边和圆点是主题珊瑚色,卡片底色与未选中一样(深色下**不是深红褐色块**);切「English」后选中那半边文字变珊瑚色、有一圈细描边,底色仍中性 | | |
|
||||
| 7 | 点「自选章节」 | 勾上的方框珊瑚色底 + 勾;部分勾选的分组框同色 + 横线;未勾是灰框 | | |
|
||||
| 8 | 手机宽度重复 1、5、6、7 | 弹窗从底部升起;浮层左右各留约 16px、不压住弹窗按钮;选中样式同上 | | |
|
||||
| 9 | 勾好后点「导出 …」 | 正常下载;顶部出「已导出」 | | |
|
||||
|
||||
发现问题截图(浅 / 深各一张),注明第几步。
|
||||
Reference in New Issue
Block a user