Files
Jyotisha/tests/test_report_chart_blank_columns.py
T
Jesse_ChenandClaude Opus 5.5 ae18952e51
Independent Staging Quality Gate / validate (push) Successful in 15m27s
Independent Staging Quality Gate / publish (push) Successful in 3m42s
fix(report,chart): blank columns — drop Kranti, split Ashtakavarga house SAV, ascendant nakshatra, engine varga dignity, no duplicate charts (BUG-1199..1203)
- Report p33 table: no Kranti column (no producer; materials.kranti stays blocked).
- Report p53 table: the "SAV" column was the with-Lagna total (386). Split in the
  reader into SAV / Lagna BAV / SAV + Lagna with a totals row; ashtakavarga.py is
  untouched (frozen rectification identity, ERR-115).
- /api/chart ascendant carries nakshatra / pada / lord (_nakshatra_fields shared with
  the nine bodies); chart cache schema 4 -> 5.
- /api/varga_full bodies carry dignity_level from varga_dignity_level, the function
  the Vimsopaka path now shares (A/B identical); the chart page shows the D1 status
  wording and no longer derives a three-level dignity itself.
- Report page draws each chart once: the trailing block skips charts the body already
  fenced (same rule as the export); the birth-time caveat moves under the body's
  chart heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-10-03 09:11:15 +08:00

131 lines
6.1 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"]
for markdown, heading, header, total in (
(zh, "Ashtakavarga 完整宫位分数", ["House", "Sign", "SAV", "上升 BAV", "SAV + 上升"], "合计"),
(en, "Ashtakavarga Full House Scores", ["House", "Sign", "SAV", "Lagna BAV", "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[2:])
assert sav + lagna == full
assert totals == [total, "-", 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]) == expected_sav[row[1]]
assert int(row[3]) == ashtakavarga["bav"]["Lagna"]["bindus"][SIGNS.index(row[1])]
assert int(row[4]) == 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:]:
assert row[2:4] == ["-", "-"]
assert row[4].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)