Files
Jyotisha/tests/test_chart_page_vedic_tables_contract.py
T
jesse-uxandClaude Code 64aa0ec20c
Independent Staging Quality Gate / publish (push) Canceled after 0s
Independent Staging Quality Gate / validate (push) Canceled after 1m27s
feat(chart): polish chart presentation and symbol explanations
Hide engine branding, gate supported divisional columns, align house cells, and add accessible symbol help. Reduce desktop chart width and stabilize the waiting slot.

Validated with 78 browser checks and 87 focused frontend tests. Full suite retains 29 identical baseline loader failures; no new failures.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 19:03:28 +08:00

134 lines
5.8 KiB
Python

"""The chart page's dignity and varga-meaning tables must be the engine's.
`frontend/src/lib/vedic-chart-tables.json` is read by the chart-page mapper so
the divisional tables can show 入旺 / 自宫 / 落陷 without a second hand-written
rule set. This test pins it to the constants `jyotish_engine._get_dignity_level`
uses and to `divisional_charts_extended.VargaType`.
"""
from __future__ import annotations
import json
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import divisional_charts_extended # noqa: E402
import jyotish_engine # noqa: E402
from consultation_native_layers import build_d9_summary # noqa: E402
TABLES = json.loads((ROOT / "frontend/src/lib/vedic-chart-tables.json").read_text(encoding="utf-8"))
PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"]
def _table_dignity(planet: str, sign: str) -> str | None:
"""Same precedence as `vargaDignity` in frontend/src/lib/chart-view-varga.ts."""
if TABLES["exaltation"].get(planet) == sign:
return "EXALTED"
if TABLES["signLords"].get(sign) == planet or TABLES["nodeOwnSign"].get(planet) == sign:
return "OWN_SIGN"
if TABLES["debilitation"].get(planet) == sign:
return "DEBILITATED"
return None
def test_tables_equal_engine_constants() -> None:
assert TABLES["signLords"] == jyotish_engine.SIGN_LORDS
assert TABLES["exaltation"] == jyotish_engine.EXALTATION
assert TABLES["debilitation"] == jyotish_engine.DEBILITATION
def test_every_planet_sign_pair_matches_engine_dignity_level() -> None:
for planet in PLANETS:
for sign in jyotish_engine.SIGN_LORDS:
# No degree and no chart context: the varga table shows the sign
# dignity only (no moolatrikona band, no neecha bhanga, no friendship).
engine = jyotish_engine._get_dignity_level(planet, sign)
expected = engine if engine in {"EXALTED", "OWN_SIGN", "DEBILITATED"} else None
if engine == "NEECHA_BHANGA":
expected = "DEBILITATED"
assert _table_dignity(planet, sign) == expected, (planet, sign, engine)
def test_varga_meanings_equal_engine_varga_types() -> None:
engine = {str(v.division): v.meaning for v in divisional_charts_extended.VargaType if v.division <= 60}
assert TABLES["vargaMeaning"] == engine
for division in ("40", "45", "60"):
assert division in TABLES["vargaMeaning"]
@pytest.fixture(scope="module")
def d9_projection() -> tuple[dict, dict]:
"""Run the real server mapper, not a Python imitation of its comparison.
The checked-in golden is a fictional chart, Raman ayanamsa / mean node.
No ephemeris is recomputed under a different process-global ayanamsa.
"""
frontend = ROOT / "frontend"
node = shutil.which("node")
tsx = frontend / "node_modules/tsx/dist/cli.mjs"
if not node or not tsx.is_file():
pytest.skip("D9 mapper/native comparison requires Node and frontend tsx dependencies")
golden = json.loads((frontend / "tests/fixtures/chart-view-golden.json").read_text(encoding="utf-8"))
assert golden["chart"]["birth"]["ayanamsa_name"] == "raman"
assert golden["chart"]["birth"]["node_mode"] == "mean"
script = """
import { readFileSync } from 'node:fs';
import { buildChartView } from './src/lib/chart-view-mapper.ts';
const golden = JSON.parse(readFileSync('./tests/fixtures/chart-view-golden.json', 'utf8'));
const birth = golden.chart.birth;
const view = buildChartView({
chart: golden.chart, varga: golden.varga_full, chara: null, western: null,
profile: {
name: 'Synthetic', date: birth.date, time: birth.time,
latitude: birth.lat, longitude: birth.lon, timezoneOffset: 8,
ayanamsa: birth.ayanamsa_name, birthTimeStatus: 'confirmed',
},
asOf: '2026-09-15',
});
process.stdout.write(JSON.stringify(view.vedic.vargas.find(varga => varga.id === 'D9')));
"""
completed = subprocess.run(
[node, str(tsx), "--eval", script],
cwd=frontend, text=True, encoding="utf-8", capture_output=True, timeout=60, check=False,
)
assert completed.returncode == 0, completed.stderr
return golden, json.loads(completed.stdout)
def test_d9_projected_planets_match_native_vargottama(d9_projection: tuple[dict, dict]) -> None:
golden, projected = d9_projection
natal = golden["chart"]["planets"]
d9 = golden["varga_full"]["result"]["D9_Navamsa"]
native = jyotish_engine._calc_vargottama(natal, {"D9_Navamsa": d9["planets"]})
assert projected["supportsDignity"] is True
assert projected["supportsVargottama"] is True
assert len(projected["rows"]) == len(PLANETS)
results = set()
for row in projected["rows"]:
expected = native[row["name"]]
assert row["sign"] == expected["d9_sign"]
assert row["natalSign"] == expected["d1_sign"]
assert row["vargottama"] is expected["is_vargottama"]
results.add(row["vargottama"])
assert results == {True, False}, "golden must exercise both valid outcomes"
def test_d9_projected_ascendant_matches_native_summary(d9_projection: tuple[dict, dict]) -> None:
# _calc_vargottama handles planets, NOT the ascendant; use the native lagna
# summary path for this separate contract instead of attributing it there.
golden, projected = d9_projection
native = build_d9_summary(
jyotish_engine, golden["chart"], {"varga_full": golden["varga_full"]["result"]},
)
assert native["status"] == "executed"
assert projected["ascendant"]["sign"] == native["lagna"]
assert projected["ascendant"]["natalSign"] == golden["chart"]["ascendant"]["sign"]
assert projected["ascendant"]["vargottama"] is native["lagna_vargottama"]