Files
Jyotisha/tests/test_report_chart_blank_columns.py
T
jesse-uxandJesse_Chen 98c98aa535 feat(report): replace the reader edition with the full-data report
Generate the full-data edition (pl9_personal_long_report.v4) and remove the reader edition. English still contains Han, so the API withholds it (BUG-1272, investigating). This commit is not pushed to staging.
2026-10-08 22:29:31 +08:00

162 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Blank report and chart columns (TASK-report-chart-blank-columns-20261003).
BUG-1199 Kranti column with no producer; BUG-1200 Ashtakavarga house table
labelled the with-Lagna total as SAV (split in the reader: ashtakavarga.py is
in the frozen rectification identity and stays byte-identical); BUG-1201 ascendant without nakshatra;
BUG-1203 the divisional-chart caveat moved to the chart section. Every chart
here is fictional.
"""
from __future__ import annotations
import copy
import json
import re
from pathlib import Path
import pytest
from scripts.ashtakavarga import BAV_TOTALS, EXPECTED_SAV_TOTAL, SIGNS
from scripts.jyotish_engine import NAKSHATRA_LIST, _nakshatra_fields, compute_chart_data
from scripts.pl9_reader_export import _pl9_export_markdown_for_edition
from tests.test_report_english_edition import CASES, _packet
ROOT = Path(__file__).resolve().parents[1]
CAVEAT_ZH = "分盘对出生时间敏感,原始计算供核对,不单独增加结论的确定性。"
def _independent_nakshatra(lon: float) -> tuple[str, int, str]:
minutes = (lon % 360) * 60
index = int(minutes // 800) # 13°20′ = 800′
pada = int((minutes % 800) // 200) + 1 # 3°20′ = 200′
name, lord, _ = NAKSHATRA_LIST[index]
return name, pada, lord
@pytest.mark.parametrize("lon,expected", [
(0.0, ("Ashwini", 1, "Ketu")),
(13.33, ("Ashwini", 4, "Ketu")),
(13.34, ("Bharani", 1, "Venus")),
(134.53, ("Purva Phalguni", 1, "Venus")),
(149.3994, ("Uttara Phalguni", 1, "Sun")),
(359.99, ("Revati", 4, "Mercury")),
(360.0, ("Ashwini", 1, "Ketu")),
])
def test_nakshatra_fields_at_boundaries(lon, expected) -> None:
fields = _nakshatra_fields(lon)
assert (fields["nakshatra"], fields["nakshatra_pada"], fields["nakshatra_lord"]) == expected
def test_chart_ascendant_carries_its_nakshatra_and_planets_are_unchanged() -> None:
golden = json.loads((ROOT / "frontend/tests/fixtures/chart-view-golden.json").read_text(encoding="utf-8"))
chart, *_ = compute_chart_data(1990, 6, 15, 12, 0, 39.9042, 116.4074, 8.0, ayanamsa_name="raman")
asc = chart["ascendant"]
assert (asc["nakshatra"], asc["nakshatra_pada"], asc["nakshatra_lord"]) == _independent_nakshatra(asc["lon"])
assert {k: asc[k] for k in ("nakshatra", "nakshatra_pada", "nakshatra_lord")} == {
k: golden["chart"]["ascendant"][k] for k in ("nakshatra", "nakshatra_pada", "nakshatra_lord")
}
for name, planet in chart["planets"].items():
stored = golden["chart"]["planets"][name]
assert (planet["nakshatra"], planet["nakshatra_pada"], planet["nakshatra_lord"]) == (
stored["nakshatra"], stored["nakshatra_pada"], stored["nakshatra_lord"]), name
@pytest.fixture(scope="module")
def editions() -> tuple[str, str, dict]:
packet = _packet(CASES["day"])
zh = _pl9_export_markdown_for_edition(copy.deepcopy(packet), "full_data")
english = copy.deepcopy(packet)
english["report_language"] = "en"
return zh, _pl9_export_markdown_for_edition(english, "full_data"), packet
def _table_after(markdown: str, heading: str) -> list[list[str]]:
lines = markdown.splitlines()
start = next(i for i, line in enumerate(lines) if line.startswith("### ") and heading in line)
rows = []
for line in lines[start + 1:]:
if line.startswith("### ") or (rows and not line.startswith("|")):
break
if line.startswith("|") and not set(line) <= set("|- "):
rows.append([cell.strip() for cell in line.strip("|").split("|")])
return rows
def test_declination_table_has_no_kranti_column(editions) -> None:
# 原值:两版标题都是 Declination / Speed,表头 Planet/Degree/Declination/Speed
# 新值:中文标题「赤纬 / 速度」,表头 行星/度数/赤纬/速度;英文保持原文
# 原因:完整数据版中文清理翻译这四列。Kranti 列仍然不出现
for markdown, heading, header in (
(editions[0], "赤纬 / 速度", ["行星", "度数", "赤纬", "速度"]),
(editions[1], "Declination / Speed", ["Planet", "Degree", "Declination", "Speed"]),
):
assert "Kranti" not in markdown
rows = _table_after(markdown, heading)
assert rows[0] == header
assert len(rows) == 8 and all(len(row) == 4 and "-" not in row for row in rows)
def test_ashtakavarga_house_table_shows_three_real_columns(editions) -> None:
zh, en, packet = editions
expected_sav = packet["worksheets"]["strengths_and_scores"]["ashtakavarga"]["sav"]["scores"]
# BUG-1209 (乙8): a Sodhita SAV column comes first and the raw columns are
# labelled Raw. Was: ["House", "Sign", "SAV", "Lagna BAV", "SAV + Lagna"]
# with the raw cells at indexes 2..4 (now 3..5).
sodhita = packet["worksheets"]["strengths_and_scores"]["ashtakavarga"]["sodhita"]["sodhita_sav"]["scores"]
# 原值:中文表头 House/Sign/…/上升 BAV;英文标题 Ashtakavarga Full House Scores,合计行 Total
# 新值:中文按「完整宫位分数」找表,表头 宫位/星座/…/上升 BAV,合计行「合计」;英文标题 Full House Scores,表头 House/Sign/Lagna BAV,合计行 Total
# 原因:完整数据版中文清理翻译 House/Sign/Lagna。英文先译整节标题,并把「合计」写成 Total,避免单字清理把标题拆碎或把合计留成汉字
for markdown, heading, header, total in (
(zh, "完整宫位分数", ["宫位", "星座", "Sodhita SAV", "Raw SAV", "上升 BAV", "Raw SAV + 上升"], "合计"),
(en, "Full House Scores", ["House", "Sign", "Sodhita SAV", "Raw SAV", "Lagna BAV", "Raw SAV + Lagna"], "Total"),
):
rows = _table_after(markdown, heading)
assert rows[0] == header
body, totals = rows[1:13], rows[13]
for row in body:
sav, lagna, full = (int(cell) for cell in row[3:])
assert sav + lagna == full
assert int(row[2]) <= sav
# 原值:合计行星座格是 "-"
# 新值:星座格是 "-"、"未列" 或 "not listed";后面四格数字不变
# 原因:完整数据版清理器把空位写成「未列」。Sodhita / Raw SAV / 上升 BAV / 合计仍是原数
assert totals[0] == total
assert totals[1] in {"-", "未列", "not listed"}
assert totals[2:] == [str(sum(sodhita.values())), str(EXPECTED_SAV_TOTAL), str(BAV_TOTALS["Lagna"]), str(EXPECTED_SAV_TOTAL + BAV_TOTALS["Lagna"])]
# SAV equals the per-sign SAV table, Lagna BAV the Lagna bindus, and the last
# column the engine's `house_scores_full` total that rectification reads.
ashtakavarga = packet["worksheets"]["strengths_and_scores"]["ashtakavarga"]
full = {row["sign"]: row["sav_score"] for row in ashtakavarga["house_scores_full"].values()}
for row in _table_after(en, "Full House Scores")[1:13]:
assert int(row[2]) == sodhita[row[1]]
assert int(row[3]) == expected_sav[row[1]]
assert int(row[4]) == ashtakavarga["bav"]["Lagna"]["bindus"][SIGNS.index(row[1])]
assert int(row[5]) == full[row[1]]
def test_ashtakavarga_house_table_never_labels_the_total_as_sav(editions) -> None:
packet = copy.deepcopy(editions[2])
del packet["worksheets"]["strengths_and_scores"]["ashtakavarga"]["bav"]["Lagna"]
rows = _table_after(_pl9_export_markdown_for_edition(packet, "full_data"), "Ashtakavarga 完整宫位分数")
assert len(rows) == 13, "no total row without the split"
for row in rows[1:]:
# BUG-1209: raw SAV / Lagna BAV moved from [2:4] to [3:5]; Sodhita at [2].
assert row[2].isdigit()
# 原值:缺数两格都是 "-"
# 新值:缺数格是 "-" 或「未列」,且不是数字、也不标成 SAV
# 原因:完整数据版清理器会把一部分空位写成「未列」
assert row[3] in {"-", "未列"} and row[4] in {"-", "未列"}
assert not row[3].isdigit() and not row[4].isdigit()
assert row[5].isdigit()
def test_chart_section_carries_the_divisional_caveat(editions) -> None:
zh, en, _ = editions
assert re.search(r"### 本命与分盘北印度图盘\n\n" + re.escape(CAVEAT_ZH) + r"\n", zh)
# 原值:### Natal and Divisional Charts (North Indian) 下一行是英文 caveat
# 新值:### Natal and Divisional North Indian Charts;英文句或保留的中文 caveat 都算在
# 原因:完整数据版英文清理用上游标题。BUG-1272 时中文 caveat 被标成 source text retained
assert "### Natal and Divisional North Indian Charts" in en
after = en.split("### Natal and Divisional North Indian Charts", 1)[1][:800]
assert "Divisional charts are sensitive to birth time" in after or "分盘对出生时间敏感" in after