"""Structured chart fences next to each engine SVG in longform Markdown.""" from __future__ import annotations import json from functools import lru_cache from types import SimpleNamespace from report_chart_block import SIGNS, extract_chart_blocks from scripts.jyotish_engine import ( SIGNS as ENGINE_SIGNS, _compute_chart_from_args, build_professional_report_reference_packet, cmd_varga_full, render_pl9_markdown, ) PLACE = "FictionalHarborTownship" PLANET_ORDER = ( "Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu", ) def _fictional_args() -> SimpleNamespace: return SimpleNamespace( year=1990, month=6, day=15, hour=12, minute=0, second=0, lat=39.9042, lon=116.4074, tz=8.0, ayanamsa="raman", node_mode="mean", divisions=None, custom=None, composite=None, variant=None, today="2026-09-09", target_year=2026, age=36, visual_chart_observations=None, startrack_language_bridge=False, birth_time_accuracy="confirmed", position_mode="legacy", pack=[], packs=None, ) def _moon_chart(planets: dict) -> dict: longitudes: dict[str, float] = {} for name, row in planets.items(): if not isinstance(row, dict): continue value = row.get("degree_raw", row.get("degree")) if isinstance(value, (int, float)): longitudes[name] = float(value) moon_lon = longitudes["Moon"] % 360 moon_sign_idx = int(moon_lon // 30) % 12 return { "status": "computed", "raw": { "Ascendant": { "sign": ENGINE_SIGNS[moon_sign_idx], "longitude": round(moon_lon, 6), "reference": "Moon", }, "planets": { name: { "longitude": round(lon % 360, 6), "sign": ENGINE_SIGNS[int(lon % 360 // 30) % 12], "house_from_moon": ((int(lon % 360 // 30) - moon_sign_idx) % 12) + 1, } for name, lon in longitudes.items() }, }, } @lru_cache(maxsize=1) def _golden() -> dict: args = _fictional_args() chart, _asc_idx, _jd, _ayanamsa = _compute_chart_from_args(args) assert isinstance(chart, dict) varga_full = cmd_varga_full(args) full_reading = { "version": "4.4.0-full-reading", "birth_info": { "date": "1990-06-15", "time": "12:00:00", "hour": 12, "minute": 0, "second": 0, "lat": args.lat, "lon": args.lon, "tz": "UTC+8.0", "place": PLACE, }, "ascendant": chart.get("ascendant"), "planets": chart.get("planets"), "houses": chart.get("houses"), "chart": chart, "modules": { "varga_full": varga_full, "moon_chart": _moon_chart(chart.get("planets") or {}), }, "errors": [], "warnings": [], } packet = build_professional_report_reference_packet(full_reading, args) markdown = render_pl9_markdown(packet) return {"packet": packet, "markdown": markdown} def _svg_after_heading(markdown: str, heading: str) -> str: rest = markdown[markdown.index(heading):] start = rest.index("") + len("") return rest[start:end] def test_fence_count_matches_svg_and_is_twenty_two() -> None: markdown = _golden()["markdown"] assert markdown.count(" None: blocks = extract_chart_blocks(_golden()["markdown"]) assert len(blocks) == 22 for block in blocks: assert block["version"] == 1 assert block["layout"] == "north" assert block["id"] == "MOON" or block["id"].startswith("D") assert block["ascendant"]["sign"] in SIGNS assert 0 <= block["ascendant"]["degree"] < 30 assert len(block["planets"]) <= 9 for planet in block["planets"]: assert planet["name"] in PLANET_ORDER assert planet["sign"] in SIGNS assert 0 <= planet["degree"] < 30 assert isinstance(planet["retrograde"], bool) def test_d1_ascendant_matches_core_chart() -> None: golden = _golden() core = golden["packet"]["core_chart"] d1 = next(block for block in extract_chart_blocks(golden["markdown"]) if block["id"] == "D1") assert d1["ascendant"]["sign"] == core["ascendant"]["sign"] natal = core["planets"] for planet in d1["planets"]: row = natal.get(planet["name"]) or {} assert planet["retrograde"] is bool(row.get("retrograde")) def test_every_block_uses_natal_retrograde() -> None: golden = _golden() natal = golden["packet"]["core_chart"]["planets"] for block in extract_chart_blocks(golden["markdown"]): for planet in block["planets"]: row = natal.get(planet["name"]) or {} assert planet["retrograde"] is bool(row.get("retrograde")) def test_chart_blocks_omit_birthplace_label() -> None: for block in extract_chart_blocks(_golden()["markdown"]): dumped = json.dumps(block, ensure_ascii=False) assert PLACE not in dumped assert PLACE not in block["title"] def test_d9_svg_center_label_follows_title() -> None: markdown = _golden()["markdown"] d9_svg = _svg_after_heading(markdown, "#### D9 — Navamsa(婚盘)") assert "(D9)" in d9_svg assert "(D1)" not in d9_svg d1_svg = _svg_after_heading(markdown, "#### D1 — Rashi Chart(本命盘)") assert "(D1)" in d1_svg assert "Rasi Chart" in d1_svg def test_chart_block_suite_is_on_the_quick_quality_gate() -> None: from scripts.run_quality_gate import CORE_PYTEST_TARGETS assert "tests/test_report_chart_block.py" in CORE_PYTEST_TARGETS