diff --git a/CHANGELOG.md b/CHANGELOG.md index cac04799..bdd1512c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 印度占星 Skill 更新日志 +## 2026-10-15 — 星盘页上升行补上星宿,表头对齐(待合入 staging) + +- 本命盘和月亮盘的上升行现在有星宿、宿主和 pada。状态写「不适用」,不再是空横杠。分盘等表里居中的那几列,表头也居中。当前大运不再画左侧那条线,浅色底还在。已经打开过的星盘会重新计算。Skill 版本不变(BUG-1352、BUG-1353)。 + ## 2026-10-14 — 生产可以在测试环境后面只多了文档时照常发布(待合入 staging) - 生产发布用最近一次测过的代码版本。测试环境后面如果只多了文档,不再挡住发布。生产环境拒绝测试用验证码,网址末尾多一个斜杠也拒绝。迁移演练在主机上不再依赖本机 Node;演练库删不掉会明确失败。只多了文档时,回退不再被拒绝。Skill 版本不变(BUG-1348、BUG-1349)。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index deb92593..90582d0f 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -16174,6 +16174,7 @@ - 根因:`compute_chart_data` 给九曜按 `nak_span` 算了 `nakshatra / nakshatra_pada / nakshatra_lord`,`result["ascendant"]` 漏了;前端按「引擎不给就不推」留空。 - 修复:抽 `_nakshatra_fields(sidereal_lon)`,九曜、计都、上升共用;`/api/chart` 缓存 `cache_schema_version` 4 → 5(缓存 TTL 900 s,改版本只影响查找键;指纹 / 绑定只用出生资料,不受影响)。前端只改注释。 - 验证:`tests/test_report_chart_blank_columns.py`(边界 0°、13°20′ 两侧、360°;golden 盘上升 Uttara Phalguni 1 足、九曜与 golden 逐星一致);`frontend/tests/chart-varga-table.test.tsx` 上升行断言;旧缓存无字段仍渲染「—」。golden `chart-view-golden.json` 用 `_nakshatra_fields` 重录(命令写在 `sourceCommand`)。 +- 2026-10-15 更正:上面的验证只打到引擎 `compute_chart_data`,前端 golden 的星宿三项是事后补上去的,没有经过 `/api/chart` 重建上升的那一层。接口仍把三项丢掉,星盘页继续显示「—」。见 BUG-1352。 - 防复发:同一个量(星宿)只有一个计算函数,新增盘点直接复用。 - 相关记录:BUG-1076(D1 表逐字节基线:本次放开上升三格,见进度记录三栏)。 - 复发自:无 @@ -34241,6 +34242,7 @@ - 根因:`compute_chart_data` 给九曜按 `nak_span` 算了 `nakshatra / nakshatra_pada / nakshatra_lord`,`result["ascendant"]` 漏了;前端按「引擎不给就不推」留空。 - 修复:抽 `_nakshatra_fields(sidereal_lon)`,九曜、计都、上升共用;`/api/chart` 缓存 `cache_schema_version` 4 → 5(缓存 TTL 900 s,改版本只影响查找键;指纹 / 绑定只用出生资料,不受影响)。前端只改注释。 - 验证:`tests/test_report_chart_blank_columns.py`(边界 0°、13°20′ 两侧、360°;golden 盘上升 Uttara Phalguni 1 足、九曜与 golden 逐星一致);`frontend/tests/chart-varga-table.test.tsx` 上升行断言;旧缓存无字段仍渲染「—」。golden `chart-view-golden.json` 用 `_nakshatra_fields` 重录(命令写在 `sourceCommand`)。 +- 2026-10-15 更正:上面的验证只打到引擎 `compute_chart_data`,前端 golden 的星宿三项是事后补上去的,没有经过 `/api/chart` 重建上升的那一层。接口仍把三项丢掉,星盘页继续显示「—」。见 BUG-1352。 - 防复发:同一个量(星宿)只有一个计算函数,新增盘点直接复用。 - 相关记录:BUG-1076(D1 表逐字节基线:本次放开上升三格,见进度记录三栏)。 - 复发自:无 @@ -36131,3 +36133,36 @@ - 相关记录:BUG-203(锁文件,不是这一条)、BUG-1063(人物表列级读取权)、BUG-1348。 - 复发自:无。 - 修复版本:分支 `codex/release-path-production-20261014`。编号接在 BUG-1349 之后。 + +## BUG-1352 | 星盘页上升行的星宿三项被排盘接口丢掉 + +- 状态:resolved +- 首次发现:2026-10-03 +- 最近更新:2026-10-15 +- 影响面:星盘页本命盘和月亮盘的上升行(星宿、宿主、pada) +- 用户现象:这两行的星宿、宿主、pada、状态都是「—」,像没算出来。九曜那几行是有的。 +- 触发条件:打开星盘页的本命盘或月亮盘。上升经度其实已经算好。 +- 根因:引擎给上升写了星宿三项,排盘接口在重组结果时只留了星座和度数,把这三项丢了。月亮盘用的是同一份上升。上次修复只测了引擎,前端样例又是事后补上这三项的,所以接口这一层一直没被发现。 +- 修复:接口重组上升时原样带上这三项;引擎没给时按同一套星宿算法补上。排盘缓存版本 6 改为 7,避免继续用没有这三项的旧结果。 +- 验证:虚构盘(1990-06-15 12:00 北京)走排盘处理函数,上升和 `modules.chart.ascendant` 的三项与星宿算法一致,为 Uttara Phalguni、1、Sun。本命表和月亮盘表都渲染出这三项。 +- 防复发:回归测试必须经过排盘处理函数,不能只测引擎,也不能手工往样例里补字段。 +- 相关记录:BUG-1201、BUG-1314 +- 复发自:BUG-1201(修复没有打通接口层) +- 修复版本:分支 `codex/chart-ascendant-row-20261015`。任务书写明 BUG-1351 已被另一单占用,本单从 BUG-1352 起。 + +## BUG-1353 | 星盘表居中列的表头靠左,当前大运卡片左边多一条线 + +- 状态:resolved +- 首次发现:2026-10-15 +- 最近更新:2026-10-15 +- 影响面:星盘页行星表的表头,以及大运列表里当前这一段 +- 用户现象:分盘「庙旺」的表头靠左、格子里的字居中,对不齐。当前大运卡片左边有一条竖线。 +- 触发条件:打开星盘页的分盘表或大运列表。 +- 根因:居中只加在内容格子上,表头没有。八列表的写法是冻结的,不能改标记。大运当前段用内阴影画了左侧强调线,设计说明里没写,也没有测试盯着。 +- 修复:表头与内容一起居中。本命和月亮盘的八列表不改标记,用列选择器把速度、宿主、pada 的表头居中,宫位仍靠左。分盘的庙旺和同位、行运的速度、Bhava 的换宫,表头加上与内容相同的居中标记。当前大运去掉左侧那条线,浅底和细边框留着。 +- 验证:样式合同锁定八列表对应表头居中、宫位列仍靠左,以及当前大运规则里没有 box-shadow。分盘、行运、Bhava 的渲染测试锁定表头带居中标记。 +- 防复发:上述样式合同和渲染测试。 +- 相关记录:BUG-1076、BUG-1352 +- 复发自:无 +- 修复版本:分支 `codex/chart-ascendant-row-20261015`。 + diff --git a/docs/tasks/PROGRESS-chart-ascendant-row-20261015.md b/docs/tasks/PROGRESS-chart-ascendant-row-20261015.md new file mode 100644 index 00000000..54bf0568 --- /dev/null +++ b/docs/tasks/PROGRESS-chart-ascendant-row-20261015.md @@ -0,0 +1,33 @@ +# PROGRESS · 星盘页上升行(issue #53 第二轮) + +- 分支:`codex/chart-ascendant-row-20261015` +- 基线:`origin/staging` `2b7112a6010acbcc0fec809b6b27e2375293dd5c` +- 编号:本文件最大号是 BUG-1350。任务书说 BUG-1351 已被 #65 的分支占用,本单用 BUG-1352(上升星宿被接口丢掉,复发自 BUG-1201)和 BUG-1353(表头错位与大运左边线)。 + +## 做了什么 + +- `/api/chart` 重组上升时带上 `nakshatra`、`nakshatra_pada`、`nakshatra_lord`。引擎已经有就原样带上;没有才按同一套星宿算法补。`cache_schema_version` 6 → 7。 +- 上升行状态从「—」改为「不适用」。本命、月亮盘共用这一行。 +- 八列表的标记没改。速度、宿主、pada 的表头用列选择器居中,宫位仍靠左。分盘的庙旺 / 同位、行运的速度、Bhava 的换宫,表头加上与内容相同的居中标记。 +- 当前大运去掉 `box-shadow`,浅底和细边框还在。 + +## 样例 + +`chart-view-golden.json` 里上升的三项本来就是 Uttara Phalguni、1、Sun,和这次接口真实返回一致,数值没有手改。`sourceCommand` 不再写「用 `_nakshatra_fields` 事后补上」,改为写明来自 `_compute_chart_sync`。 + +## 断言改动 + +| 位置 | 原值 | 新值 | 原因 | +| --- | --- | --- | --- | +| `chart-varga-table.test.tsx` 上升行状态格 | `—` | `不适用` | 产品把横杠看成没数据 | +| `test_api_scratch_retention.py` 缓存版本 | `6` | `7` | 旧缓存没有星宿三项,必须换键 | +| 基线 html | 仍是改前的整张表 | 不改文件 | 测试用替换得到新表,原值留在基线里 | + +## 自测 + +- `pytest tests/test_api_chart_ascendant_nakshatra.py tests/test_report_chart_blank_columns.py tests/test_api_server_growth_contract.py tests/test_api_scratch_retention.py -q`:34 通过,退出码 0。没有新增 `JyotishAPIHandler.__new__`。 +- 前端:`chart-varga-table`、`chart-moon-transit`、`chart-bhava`、`chart-page-mobile-width-20260929`、`runtime-sources-no-test-imports`。月亮盘那条第一次用整页去配「上升」,配到了图上的标记;改成只看月亮盘表格后通过。 +- `tsc --noEmit -p .`:0 个错误,退出码 0。 +- `npm run lint`:0 个错误、123 条原有警告,退出码 0。 +- `chart-page-view` 与 `chart-page-hook`:46 通过,退出码 0。 +- 真机清单沿用任务书里已有的 `docs/testing/chart-ascendant-row-20261015.md`。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 6a6c77eb..bd829596 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -213,7 +213,7 @@ | --- | --- | --- | --- | --- | | `TASK-rectification-step0-entry-20261013.md` | — | **明说要校正时第一步就能打开(agent 循环 issue #54,BUG-1306 复发)**:第 0 步只放排盘工具、规则正向句埋在禁止句里、「纠正」撞词,模型没带 explicitRequest 调工具(BUG-1317)。产品选 B:第 0 步放出校正工具 + 规则重排;推翻 BUG-923 第 0 步限制,保险用现有 runtime-contract-retry。执行方 Grok | 待领取 | `codex/rectification-step0-entry-20261013` | | `TASK-report-focus-card-20261013.md` | — | **报告开篇「重点」做成摘要卡(agent 循环 issue #55)**:10-08 加的开篇重点从未设计——三重「重点」、标题无样式、MD/AD/PD 行话、数据句堆砌(BUG-1316)。产品选 A 摘要卡 + 名字与日期 + 保留提醒句;引擎出 `jyotish-focus` 围栏,前端一个组件渲染。执行方 Grok | 待领取 | `codex/report-focus-card-20261013` | -| `TASK-chart-ascendant-row-20261015.md` | — | **星盘页上升行无星宿 / 表头错位 / 大运卡左边线(issue #53 第二轮)**:`/api/chart` 重建上升时丢了星宿三字段,BUG-1201 从未打通(golden 取自引擎内部);状态改「不适用」(推翻 DESIGN 原口径);居中列表头没居中;当前大运 inset 左线去掉。执行方 Grok | 待领取 | `codex/chart-ascendant-row-20261015` | +| `TASK-chart-ascendant-row-20261015.md` | `PROGRESS-chart-ascendant-row-20261015.md` | **星盘页上升行无星宿 / 表头错位 / 大运卡左边线(issue #53 第二轮)**:`/api/chart` 重建上升时丢了星宿三字段(BUG-1352,复发自 BUG-1201);状态改「不适用」;居中列表头对齐;当前大运去掉左边线(BUG-1353) | 待验收 | `codex/chart-ascendant-row-20261015` | | `TASK-report-translation-gaps-20261013.md` | — | **个人报告中英混杂、正文图盘空白、上升行、同位列(agent 循环 issue #53)**:中文完整数据版靠短词替换英文,正文残留 218 处英文词与 14 处中英粘连,卫生检查只有黑名单(BUG-1312);完整数据版图盘节只有空标题、无围栏,BUG-1203 的回归(BUG-1313);报告本命表无上升行(BUG-1314);D9 同位列「—」读如没数据,改「是/否」(BUG-1315)。分盘核对段随 T1 自动消失。执行方 Grok | 待领取 | `codex/report-translation-gaps-20261013` | | `TASK-report-translation-gaps-fix-20261013.md` | — | **#53 第 1 次打回**:`a295afa4` 实现四项都验过,但分支未基于最新 staging,且全量 pytest 新增 1 条失败(报告 Jaimini 星位名改成「灵魂星(AK)」后 `test_chara_karaka_8_bphs_order` 未跟改)。要求 rebase + 按三栏改测试,不改 `KARAKA_CN` | 待领取 | `codex/report-translation-gaps-20261013` | | `TASK-report-full-data-edition-20261007.md` | `PROGRESS-report-full-data-edition-20261007.md` | **报告改为完整数据版、删除阅读版**:网站阅读版中文 7.6 万字符 vs 上游 `pl9_ai_density` 97 万;移植原始数据附录 / 清理器 / 时间补充,中英文;我方报告口径(BUG-1199~1229)全部保持;旧报告仍可打开;排在 sync6 之后 | 已验收(Claude 补修 BUG-1282 后合入 staging) | `codex/report-full-data-edition-20261007`(未推 staging) | diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index ba81ed57..74d2c6f0 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -1178,7 +1178,7 @@ D40 / D45 / D60 的表格上方,出生时间不是「已校正」时多一句 | pada | 1–4,居中 | | 状态 | 尊贵 / 庙旺等,原样保留,只是挪到最后一列 | -第一行是上升。星宿 / 宿主 / pada 用引擎按上升经度算出的值,和九曜同一个算法(2026-10-03,BUG-1201;之前引擎漏算,这三格一直是「—」);旧缓存里没有这三项时仍写「—」,页面不自己推算。上升没有尊贵,状态格写「—」。数字列 `tabular-nums`,符号列与宿主列居中。 +第一行是上升。星宿 / 宿主 / pada 用引擎按上升经度算出的值,和九曜同一个算法(2026-10-03,BUG-1201;之前引擎漏算,这三格一直是「—」);旧缓存里没有这三项时仍写「—」,页面不自己推算。上升没有尊贵,状态格写「不适用」(2026-10-15,与分盘表、报告同一口径;不再写「—」)。数字列 `tabular-nums`,符号列与宿主列居中。 本轮不新增第二层页签:参照里「宫位 / 大运 / 星力 / 瑜伽」等页签的内容后续另立单。 @@ -1192,7 +1192,7 @@ D40 / D45 / D60 的表格上方,出生时间不是「已校正」时多一句 | Bhava | 行星 · 黄道经度 · 星座宫 · Bhava 宫 · 换宫;另一张「宫 · 宫头 · 起点(sandhi)」12 行 | Sripati。换宫的行用 `--color-selected` 浅底。拿不到真实 MC 时这一栏走失败句,不画等宫(BUG-1077) | | 行运 | 行星 · 今天的位置 · 速度 · 落本命 · 本命同星 | 表头写计算时刻「行运 · YYYY-MM-DD 12:00(时区)」,上方一句「以本命上升为第 1 宫,放的是这一天中午的行星位置。岁差和交点与本命相同。」 | -说明句 + 表格包在 `.chart-page-varga-table-block` 里,作为一个 grid 项在 ≥1024 时跨两列。宫位表头与对应数据格统一左对齐、相同 padding;包括本命/月亮、分盘、Bhava、行运。宿主、pada、速度、换宫等原本居中列不随之变化。本命/月亮八列 markup 冻结,通过限定八列表头的第四列 CSS 修正宫位,不能全局覆盖 `chart-page-planet-mid`。 +说明句 + 表格包在 `.chart-page-varga-table-block` 里,作为一个 grid 项在 ≥1024 时跨两列。宫位表头与对应数据格统一左对齐、相同 padding;包括本命/月亮、分盘、Bhava、行运。宿主、pada、速度、换宫等原本居中列不随之变化:表头与内容同为居中。本命/月亮八列 markup 冻结,速度、宿主、pada 的表头用列选择器居中,不改 markup,也不把宫位那一列居中。分盘的庙旺 / 同位、行运的速度、Bhava 的换宫,表头带 `chart-page-planet-mid`。不能全局覆盖 `chart-page-planet-mid` 去改宫位列。 ### 三套坐标系 @@ -1214,7 +1214,7 @@ D40 / D45 / D60 的表格上方,出生时间不是「已校正」时多一句 时间条只画时长大于 0 的段,宽度按时长。当前段用主文字色内描边,一条竖线标 `asOf`。两端是 `YYYY.MM`。0 年段不进这条轴。段色轮换现有的浅底 token(`--color-action-soft`、`--color-success-muted`、`--color-canvas-strong`、`--color-danger-muted`、`--color-selected`、`--color-canvas-muted`),不新造一套色。 -列表用原生 `
` / ``。只有当前段带 `open`。子运一行是「名称 · YYYY.MM – YYYY.MM」,日期 `tabular-nums` 靠右,当前子运用主文字色。`summary` 与子运行高 44px。未展开的子运列表用 `details:not([open]) > ul { display: none }` 收起。0 年段不是 `
`,文字用 `--color-ink-tertiary`,只写「0 年」和「本段时长为 0,不展开」。 +列表用原生 `
` / ``。只有当前段带 `open`。子运一行是「名称 · YYYY.MM – YYYY.MM」,日期 `tabular-nums` 靠右,当前子运用主文字色。`summary` 与子运行高 44px。未展开的子运列表用 `details:not([open]) > ul { display: none }` 收起。0 年段不是 `
`,文字用 `--color-ink-tertiary`,只写「0 年」和「本段时长为 0,不展开」。当前大运用浅底和细边框标出,不画左侧强调线。 ### 西洋盘 Tab(2026-09-25) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 17afe73f..36fcfcb4 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -5655,11 +5655,18 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class .chart-page-planet-glyph.is-node, .ephemeris-transit-glyph.is-node { color: var(--color-planet-node); } .chart-page-planet-glyph.is-ascendant { color: var(--color-planet-ascendant); font-weight: 700; } +.chart-page-planet-table th.chart-page-planet-mid, .chart-page-planet-table td.chart-page-planet-mid { text-align: center; } -/* The natal/Moon eight-column markup stays frozen; only its house column changes. */ +/* The natal/Moon eight-column markup stays frozen; only its house column changes. + Speed, nakshatra lord and pada stay centered, including their headers. */ .chart-page-planet-table:has(> thead > tr > th:nth-child(8)) > tbody > tr > td:nth-child(4) { text-align: left; } +.chart-page-planet-table:has(> thead > tr > th:nth-child(8)) > thead > tr > th:nth-child(3), +.chart-page-planet-table:has(> thead > tr > th:nth-child(8)) > thead > tr > th:nth-child(6), +.chart-page-planet-table:has(> thead > tr > th:nth-child(8)) > thead > tr > th:nth-child(7) { + text-align: center; +} /* Figures line up column-wise instead of drifting with glyph width. */ .chart-page-planet-num { font-variant-numeric: tabular-nums; } .chart-page-boundary { @@ -5774,7 +5781,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class .chart-page-dasha-period.is-current { background: var(--color-selected); border-color: var(--color-border-strong); - box-shadow: inset 2px 0 0 var(--color-focus); } .chart-page-dasha-period.is-zero { color: var(--color-ink-tertiary); } .chart-page-dasha-period.is-zero span { margin-left: auto; } diff --git a/frontend/src/components/chart-page/chart-vedic-tab.tsx b/frontend/src/components/chart-page/chart-vedic-tab.tsx index 3a768db5..596bbdb9 100644 --- a/frontend/src/components/chart-page/chart-vedic-tab.tsx +++ b/frontend/src/components/chart-page/chart-vedic-tab.tsx @@ -79,7 +79,7 @@ function PlanetRow({ row }: { row: ChartViewPlanetRow }) { /** * The ascendant is a chart point: nakshatra, lord and pada come from the - * engine (BUG-1201); it has no dignity, so the status cell stays empty. + * engine (BUG-1201); it has no dignity, so the status cell says 不适用. * `house` is 1 except on the Moon chart, which counts from the Moon. */ function AscendantRow({ row, house = row.house }: { row: ChartViewAscendantRow; house?: number }) { @@ -96,7 +96,7 @@ function AscendantRow({ row, house = row.house }: { row: ChartViewAscendantRow; {row.nakshatraLord ? : "—"} {row.pada ?? "—"} - — + 不适用 ); } @@ -192,8 +192,8 @@ export function VargaTable({ varga }: { varga: ChartViewVarga }) { 分盘星座 宫位 本命星座 - {columns.supportsDignity && 庙旺} - {columns.supportsVargottama && 同位} + {columns.supportsDignity && 庙旺} + {columns.supportsVargottama && 同位} @@ -219,7 +219,7 @@ function TransitTable({ transit }: { transit: ChartViewTransitOk }) { 行星 今天的位置 - 速度 + 速度 落本命 本命同星 @@ -257,7 +257,7 @@ function BhavaTables({ bhava }: { bhava: ChartViewBhavaOk }) { 黄道经度 星座宫 Bhava 宫 - 换宫 + 换宫 diff --git a/frontend/tests/chart-bhava.test.tsx b/frontend/tests/chart-bhava.test.tsx index c90b870d..13cf1b05 100644 --- a/frontend/tests/chart-bhava.test.tsx +++ b/frontend/tests/chart-bhava.test.tsx @@ -90,6 +90,7 @@ test("Sripati rows, cusps and the drawn houses follow the engine's Bhava houses" assert.match(markup, /
交点<\/dt>/); assert.doesNotMatch(markup, /Swiss Ephemeris|>引擎Bhava · Sripati<\/caption>/); + assert.match(markup, /换宫<\/th>/); assert.match(markup, /宫头与宫界<\/caption>/); assert.equal((markup.match(/class="is-shifted"/g) ?? []).length, shifted.length); assert.equal((markup.match(/是<\/td>/g) ?? []).length, shifted.length); diff --git a/frontend/tests/chart-moon-transit.test.tsx b/frontend/tests/chart-moon-transit.test.tsx index 79237cb0..283b9014 100644 --- a/frontend/tests/chart-moon-transit.test.tsx +++ b/frontend/tests/chart-moon-transit.test.tsx @@ -87,6 +87,12 @@ test("the Moon chart is the D1 counted from the Moon's sign, projected without a const markup = renderToStaticMarkup(React.createElement(ChartVedicTab, { view, vargaId: "MOON", onSelectVarga: () => {} })); assert.match(markup, /月亮盘 · 以月亮所在星座([^)]+)为第 1 宫<\/caption>/); assert.deepEqual(ascendantMarkHouses(markup), [moon.ascendantHouse], "the lagna mark sits in the natal ascendant's house"); + const table = markup.slice(markup.indexOf("月亮盘")); + const ascendantRow = table.match(/[\s\S]*?上升[\s\S]*?<\/tr>/)?.[0] ?? ""; + assert.match(ascendantRow, /Uttara Phalguni<\/td>/); + assert.match(ascendantRow, /is-sun/); + assert.match(ascendantRow, />1不适用 { @@ -125,7 +131,8 @@ test("the transit layer posts today's noon with the natal ayanamsa and node mode const markup = renderToStaticMarkup(React.createElement(ChartVedicTab, { view, vargaId: "TRANSIT", onSelectVarga: () => {} })); assert.match(markup, /行运 · 2026-09-15 12:00(Asia\/Shanghai)<\/caption>/); - for (const column of ["行星", "今天的位置", "速度", "落本命", "本命同星"]) assert.match(markup, new RegExp(`${column}`)); + for (const column of ["行星", "今天的位置", "落本命", "本命同星"]) assert.match(markup, new RegExp(`${column}`)); + assert.match(markup, /速度<\/th>/); assert.ok(markup.includes(CHART_VIEW_COPY.transitNote)); assert.match(markup, /
/); assert.match(markup, /
岁差<\/dt>/); diff --git a/frontend/tests/chart-page-mobile-width-20260929.test.ts b/frontend/tests/chart-page-mobile-width-20260929.test.ts index 24b4742b..3ea5335d 100644 --- a/frontend/tests/chart-page-mobile-width-20260929.test.ts +++ b/frontend/tests/chart-page-mobile-width-20260929.test.ts @@ -48,7 +48,21 @@ test("the Vedic waiting slot reserves the result legend band without changing th test("only the house column in frozen natal and Moon tables overrides centering", () => { assert.match(cssDeclarations(".chart-page-planet-table td.chart-page-planet-mid", globalStyles), /text-align:\s*center/); + assert.match(cssDeclarations(".chart-page-planet-table th.chart-page-planet-mid", globalStyles), /text-align:\s*center/); assert.match(cssDeclarations(".chart-page-planet-table:has(> thead > tr > th:nth-child(8)) > tbody > tr > td:nth-child(4)", globalStyles), /text-align:\s*left/); + for (const child of [3, 6, 7]) { + assert.match( + cssDeclarations(`.chart-page-planet-table:has(> thead > tr > th:nth-child(8)) > thead > tr > th:nth-child(${child})`, globalStyles), + /text-align:\s*center/, + ); + } +}); + +test("the current dasha card keeps its tint and drops the left accent line", () => { + const current = cssDeclarations(".chart-page-dasha-period.is-current", globalStyles); + assert.match(current, /background:\s*var\(--color-selected\)/); + assert.match(current, /border-color:\s*var\(--color-border-strong\)/); + assert.doesNotMatch(current, /box-shadow/); }); test("wide chart-page content still scrolls inside its own box instead of widening the page", () => { diff --git a/frontend/tests/chart-varga-table.test.tsx b/frontend/tests/chart-varga-table.test.tsx index 12451038..cefabcf5 100644 --- a/frontend/tests/chart-varga-table.test.tsx +++ b/frontend/tests/chart-varga-table.test.tsx @@ -99,8 +99,11 @@ test("the D1 table is byte-for-byte the table the page drew before BUG-1076, asc // (Leo 29°23′ = Uttara Phalguni pada 1, lord Sun), where the baseline had dashes. const baseline = readFileSync(new URL("./fixtures/chart-d1-table-baseline.html", import.meta.url), "utf8"); const dashes = '————'; + // 原值:上升行状态格 — + // 新值:不适用 + // 原因:产品把「—」看成没数据。上升没有尊贵,与分盘表、报告同一口径写「不适用」。基线文件仍是改前的表。 const filled = 'Uttara Phalguni☉' - + '1—'; + + '1不适用'; assert.equal(baseline.split(dashes).length, 2, "the baseline's ascendant row is the only all-dash row"); assert.equal(table, baseline.replace(dashes, filled)); }); @@ -113,6 +116,8 @@ test("the D1 ascendant row shows the engine's nakshatra, lord and pada", async ( const ascendant = row(render(view, "D1"), "上升"); assert.match(ascendant, /Uttara Phalguni<\/td>/); assert.match(ascendant, /is-sun/, "Uttara Phalguni's lord is the Sun"); + assert.match(ascendant, />1不适用)[key]; @@ -136,6 +141,8 @@ test("divisional rows carry engine dignity and vargottama, and drop nakshatra co const markup = render(view, "D9"); const header = markup.match(/([\s\S]*?)<\/thead>/)?.[1] ?? ""; for (const column of ["行星", "分盘星座", "宫位", "本命星座", "庙旺", "同位"]) assert.match(header, new RegExp(`>${column}<`)); + assert.match(header, /庙旺<\/th>/); + assert.match(header, /同位<\/th>/); assert.doesNotMatch(header, /星宿|宿主|pada/); // Same wording as the D1 status column (BUG-1202), friendship levels included. // 原值:同位行星单元格是「同位」,非同位是「—」 diff --git a/frontend/tests/fixtures/chart-view-golden.json b/frontend/tests/fixtures/chart-view-golden.json index 8d47e425..a1dfc734 100644 --- a/frontend/tests/fixtures/chart-view-golden.json +++ b/frontend/tests/fixtures/chart-view-golden.json @@ -1,5 +1,5 @@ { - "sourceCommand": "anaconda python: domain_calculation_service.compute_chart + compute_vimshottari_timeline; divisional_charts_extended.DivisionalChartsCalculator; jaimini.calc_chara_dasha_with_antardasha (pratyantar_dashas stripped; unused by the page); western_chart_engine.build_tropical_natal_chart. Fictional 1990-06-15 12:00 Beijing 39.9042N 116.4074E tz+8 Raman mean node. D40/D45/D60 appended 2026-09-28 from the same chart.planets[*].lon and ascendant.lon via DivisionalChartsCalculator._calculate_single_varga (recompute of D9/D10/D30 from those inputs equals the stored ones). 2026-10-03 (BUG-1201/1202): chart.ascendant nakshatra fields from jyotish_engine._nakshatra_fields(ascendant.lon) and varga_full.result[*].planets[*].dignity_level from scripts/varga_dignity.annotate_varga_dignity on the stored positions (the same calls /api/chart and /api/varga_full now make).", + "sourceCommand": "anaconda python: domain_calculation_service.compute_chart + compute_vimshottari_timeline; divisional_charts_extended.DivisionalChartsCalculator; jaimini.calc_chara_dasha_with_antardasha (pratyantar_dashas stripped; unused by the page); western_chart_engine.build_tropical_natal_chart. Fictional 1990-06-15 12:00 Beijing 39.9042N 116.4074E tz+8 Raman mean node. D40/D45/D60 appended 2026-09-28 from the same chart.planets[*].lon and ascendant.lon via DivisionalChartsCalculator._calculate_single_varga (recompute of D9/D10/D30 from those inputs equals the stored ones). 2026-10-03 (BUG-1202): varga_full.result[*].planets[*].dignity_level from scripts/varga_dignity.annotate_varga_dignity on the stored positions. 2026-10-15 (BUG-1352): chart.ascendant nakshatra, nakshatra_pada and nakshatra_lord are the fields returned by JyotishAPIHandler._compute_chart_sync for this birth (tests/test_api_chart_ascendant_nakshatra.py); they are not filled in by hand.", "chart": { "success": true, "birth": { diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index ca07c6cd..432467e5 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -2589,12 +2589,24 @@ def _vedastro_runtime_fingerprint() -> dict: } +def _chart_ascendant_nakshatra(ascendant_data: dict, lon: float) -> dict: + """Keep the engine's nakshatra fields when /api/chart rebuilds the ascendant.""" + fields = { + key: ascendant_data[key] + for key in ('nakshatra', 'nakshatra_pada', 'nakshatra_lord') + if ascendant_data.get(key) not in (None, '') + } + if 'nakshatra' in fields: + return fields + return _load_local_module('jyotish_engine')._nakshatra_fields(lon) + + def _build_api_chart_cache_payload(body: dict) -> dict: import api_scratch_retention as retention today = body.get('today') or body.get('current_date') return { - 'cache_schema_version': 6, + 'cache_schema_version': 7, 'birth': retention.normalize_chart_cache_birth(body), 'calculation': { 'ayanamsa': _request_ayanamsa(body), @@ -6663,6 +6675,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM 'degree': round(asc_lon % 30, 2), 'degree_in_sign': round(asc_lon % 30, 2), 'lon': round(asc_lon, 4), + **_chart_ascendant_nakshatra(ascendant_data, asc_lon), }, 'planets': planets_data, 'houses': houses, 'shadbala': shadbala_summary, 'dasha': { diff --git a/tests/test_api_chart_ascendant_nakshatra.py b/tests/test_api_chart_ascendant_nakshatra.py new file mode 100644 index 00000000..8232dc87 --- /dev/null +++ b/tests/test_api_chart_ascendant_nakshatra.py @@ -0,0 +1,56 @@ +"""The chart API must keep the ascendant nakshatra the engine already computed.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +SCRIPTS = ROOT / "scripts" +TESTS = ROOT / "tests" +for path in (SCRIPTS, TESTS): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +import jyotish_api_server # noqa: E402 +from test_api_server_security import _handler # noqa: E402 + +BEIJING = { + "year": 1990, + "month": 6, + "day": 15, + "hour": 12, + "minute": 0, + "second": 0, + "lat": 39.9042, + "lon": 116.4074, + "tz": 8, + "ayanamsa": "raman", + "node_mode": "mean", + "transit_date": "2026-10-15", + "skip_vedastro_main_entry_overview": True, +} + + +def test_api_chart_keeps_ascendant_nakshatra_from_the_engine(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0") + monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0") + monkeypatch.setattr( + jyotish_api_server, + "_attach_vedastro_main_entry_overview", + lambda result, _birth: result, + ) + engine = jyotish_api_server._load_local_module("jyotish_engine") + result = _handler()._compute_chart_sync(BEIJING) + lon = result["ascendant"]["lon"] + expected = engine._nakshatra_fields(lon) + + for packet in (result["ascendant"], result["modules"]["chart"]["ascendant"]): + assert packet["nakshatra"] == expected["nakshatra"] + assert packet["nakshatra_pada"] == expected["nakshatra_pada"] + assert packet["nakshatra_lord"] == expected["nakshatra_lord"] + assert result["ascendant"]["nakshatra"] == "Uttara Phalguni" + assert result["ascendant"]["nakshatra_pada"] == 1 + assert result["ascendant"]["nakshatra_lord"] == "Sun" diff --git a/tests/test_api_scratch_retention.py b/tests/test_api_scratch_retention.py index 65dab310..9c401d36 100644 --- a/tests/test_api_scratch_retention.py +++ b/tests/test_api_scratch_retention.py @@ -139,7 +139,10 @@ def test_numeric_birth_spellings_share_one_chart_cache_key(): assert _chart_key(dict(base, hour=12, tz="Asia/Kolkata")) not in keys skipped = _chart_key(dict(base, hour=12, skip_vedastro_main_entry_overview=True)) assert skipped not in keys - assert api._build_api_chart_cache_payload(base)["cache_schema_version"] == 6 + # 原值:cache_schema_version == 6 + # 新值:7 + # 原因:/api/chart 的上升补上星宿三项,旧缓存里没有这三项,必须换键。 + assert api._build_api_chart_cache_payload(base)["cache_schema_version"] == 7 def test_audit_artifact_default_off_and_compact_when_on(monkeypatch, tmp_path):