Files
Jyotisha/tests/test_report_chart_blank_columns.py
T
Jesse_ChenandClaude Opus 5.5 ea78804b78 feat(ashtakavarga): standard Sodhita chain on reports and cards, raw values beside (T7, BUG-1209)
Astrologer ruling 乙8. New scripts/ashtakavarga_shodhana.py: per BAV row Trikona then
Ekadhipatya (the two existing ashtakavarga functions, previously only called by
tests), Sodhita SAV = sum of reduced rows; occupied signs = seven grahas. Removed
calc_sodhita_av (Sun/Mars/Saturn contribution removal labelled 'BPHS标准').
Wired into /api/ashtakavarga (result.sodhita, house_scores[*].sav_sodhita) and the
full reading. Reader: SAV table 'Sodhita SAV | Raw SAV', house table gains
Sodhita column, Sodhita BAV table before raw BAV; reference edition section
rewritten. Card: sav_sodhita before sav_score plus sav_columns label. Personal
report facts: score = Sodhita, rawScore/savRawTotal beside (optional schema
fields). Fact tables: Sodhita column and Sodhita BAV subtable. Transit scoring
unchanged (raw bindus).

Golden: report-density-fictional-engine.json leaf worksheets.strengths_and_scores
.ashtakavarga recaptured from the real engine (scripts/research/refresh_density_
fixture_ashtakavarga.py); bav/sav identical, every other leaf byte-equal.
Changed assertions (three columns in PROGRESS): BUG-1200 house-table tests,
report-fact-tables ashtakavarga columns/subtables, run_all t1/t57.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-03 12:41:26 +08:00

139 lines
6.7 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), "reader_main")
english = copy.deepcopy(packet)
english["report_language"] = "en"
return zh, _pl9_export_markdown_for_edition(english, "reader_main"), 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:
for markdown, heading in ((editions[0], "Declination / Speed"), (editions[1], "Declination / Speed")):
assert "Kranti" not in markdown
rows = _table_after(markdown, heading)
assert rows[0] == ["Planet", "Degree", "Declination", "Speed"]
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"]
for markdown, heading, header, total in (
(zh, "Ashtakavarga 完整宫位分数", ["House", "Sign", "Sodhita SAV", "Raw SAV", "上升 BAV", "Raw SAV + 上升"], "合计"),
(en, "Ashtakavarga 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
assert totals == [total, "-", 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, "Ashtakavarga 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, "reader_main"), "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()
assert row[3:5] == ["-", "-"]
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)
assert re.search(r"### Natal and Divisional Charts \(North Indian\)\n\nDivisional charts are sensitive to birth time\.", en)