- 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
126 lines
5.6 KiB
Python
126 lines
5.6 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: 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
|