"""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: varga meanings (`divisional_charts_extended.VargaType`) and the dignity wording (`jyotish_engine.DIGNITY_LABELS`). The dignity level itself comes from `/api/varga_full` (`varga_dignity_level`, BUG-1202); the page never derives it. """ 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 test_dignity_labels_equal_engine_status_wording() -> None: # BUG-1202: divisional rows carry the engine's level and the page labels it # with the D1 status wording; the three-level page table is retired. assert TABLES["dignityLabels"] == jyotish_engine.DIGNITY_LABELS assert {"signLords", "exaltation", "debilitation", "nodeOwnSign"}.isdisjoint(TABLES) 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"] def test_d9_projected_dignity_is_the_engine_level(d9_projection: tuple[dict, dict]) -> None: golden, projected = d9_projection d9_planets = golden["varga_full"]["result"]["D9_Navamsa"]["planets"] levels = set() for row in projected["rows"]: expected = jyotish_engine.varga_dignity_level(row["name"], d9_planets) assert expected is not None assert row["dignity"] == expected == d9_planets[row["name"]]["dignity_level"] levels.add(expected) # The golden must show that friendship levels reach the page, not only # exalted / own / debilitated. assert {"OWN_SIGN", "DEBILITATED", "NEUTRAL", "ENEMY", "FRIEND"} <= levels