diff --git a/frontend/src/lib/consultation-evidence-card.ts b/frontend/src/lib/consultation-evidence-card.ts index 907ab3c5..2d0f917e 100644 --- a/frontend/src/lib/consultation-evidence-card.ts +++ b/frontend/src/lib/consultation-evidence-card.ts @@ -490,7 +490,7 @@ const DOUBLE_TRANSIT_ROW_KEYS = ["layer", "target", "strength", "jupiter_pac", " const DOUBLE_TRANSIT_ALL_KEYS = ["house", "layer", "target", "strength"] as const; const KARAKA_TARGET_KEYS = ["target", "jupiter_pac", "saturn_pac", "double_transit"] as const; const ANNUAL_KEYS = [ - "status", "varsha_year", "annual_lagna", "varshesha", "muntha", "mudda_dasha", "tajika_aspects", "boundary", + "status", "varsha_year", "annual_lagna", "varshesha", "varshesha_basis", "muntha", "mudda_dasha", "tajika_aspects", "boundary", ] as const; function nonEmpty(value: JsonRecord) { diff --git a/frontend/src/mastra/consultation-workflow.ts b/frontend/src/mastra/consultation-workflow.ts index 1c09d588..524d00bf 100644 --- a/frontend/src/mastra/consultation-workflow.ts +++ b/frontend/src/mastra/consultation-workflow.ts @@ -548,6 +548,8 @@ const nativeDoubleTransitKeys = new Set([ ]); const nativeAnnualKeys = new Set([ "status", "reason", "source", "targetyear", "age", "varshayear", "start", "end", "annuallagna", "varshesha", + // BUG-1212: how the year lord was chosen (Muntha lord for now; Panchavargiya pending). + "varsheshabasis", "muntha", "sign", "lord", "houseinannualchart", "houseinnatalchart", "muddadasha", "months", "tajikaaspects", "name", "planets", "aspect", "motion", "tajikaaspectsstatus", "producers", "solarreturn", "annualchart", "yearlord", "tajikayogas", "boundary", diff --git a/scripts/consultation_native_layers.py b/scripts/consultation_native_layers.py index 13837e20..33ca3345 100644 --- a/scripts/consultation_native_layers.py +++ b/scripts/consultation_native_layers.py @@ -71,6 +71,12 @@ YOGA_PACKET_DOMAINS = { "raman_bvr_relationship_mother_batch_closure_packet_2026_08_20.json": "parents", "raman_bvr_relationship_sibling_core_batch_closure_packet_2026_08_20.json": "siblings", } +# BUG-1212 (ruling 甲11): the year lord shown is the Muntha sign lord; the +# astrologer's rule is Panchavargiya (strongest of five office-bearers), whose +# native producer is still blocked. Shown wherever the year lord is shown. +YEAR_LORD_BASIS = "暂用 Muntha 主星;占星师口径为 Panchavargiya,待接入" +YEAR_LORD_BASIS_EN = "Muntha sign lord for now; the astrologer's rule is Panchavargiya, not yet wired" + TAJIKA_BOUNDARY = ( "parameter_sensitive: Tajika / Varshaphala is verified on the first target set only, " "not a global closure; annual structure, not an event verdict" @@ -944,6 +950,7 @@ def build_annual_tajika(birth_payload: dict, chart: dict, *, reference: datetime "varsha_year": {"start": start.strftime("%Y-%m-%d"), "end": end.strftime("%Y-%m-%d")}, "annual_lagna": annual_chart.get("asc_sign"), "varshesha": year_lord, + "varshesha_basis": YEAR_LORD_BASIS, "muntha": { "sign": muntha.get("muntha_sign"), "lord": muntha.get("muntha_lord"), diff --git a/scripts/pl9_reader_english_terms.py b/scripts/pl9_reader_english_terms.py index c0e35c21..4c86c017 100644 --- a/scripts/pl9_reader_english_terms.py +++ b/scripts/pl9_reader_english_terms.py @@ -147,6 +147,7 @@ _PHRASES: dict[str, str] = { "| 要素总数 |": "| Total elements |", # ── annual labels and values ── "Muntha 所在星座的守护星(当前本地口径)": "Ruler of the Muntha sign (current local method)", + "暂用 Muntha 主星;占星师口径为 Panchavargiya,待接入": "Muntha sign lord for now; the astrologer's rule is Panchavargiya, not yet wired", "两种计算口径给出的年主不同,暂不合并": "The two methods give different year lords; not merged", "(两者不同)": " (they differ)", "太阳返照": "Solar return", diff --git a/scripts/pl9_reader_export.py b/scripts/pl9_reader_export.py index 59867b25..38907323 100644 --- a/scripts/pl9_reader_export.py +++ b/scripts/pl9_reader_export.py @@ -580,7 +580,8 @@ def render_pl9_parity_markdown(packet: dict) -> str: if data.get('selection_basis'): return _cell(data['selection_basis']) if data.get('year_lord') and data['year_lord'] == muntha_data.get('muntha_lord'): - return 'Muntha 所在星座的守护星(当前本地口径)' + # BUG-1212 (甲11): say plainly that this is a stand-in for Panchavargiya. + return '暂用 Muntha 主星;占星师口径为 Panchavargiya,待接入' return '-' def _degree(value) -> str: @@ -2297,6 +2298,7 @@ def render_pl9_parity_markdown(packet: dict) -> str: if year_lord_data: lines.extend([ f"| Varshesha | {_cell(_annual_field_text(year_lord, 'year_lord', 'lord'))} |", + f"| Selection basis | {_cell(_annual_selection_basis(year_lord, annual_pack.get('muntha')))} |", f"| Muntha sign | {_cell(_annual_field_text(annual_pack.get('muntha'), 'muntha_sign'))} |", ]) lines.append('') diff --git a/tests/test_year_lord_basis_label.py b/tests/test_year_lord_basis_label.py new file mode 100644 index 00000000..bf1c0eed --- /dev/null +++ b/tests/test_year_lord_basis_label.py @@ -0,0 +1,68 @@ +"""Year lord is labelled as a Muntha-lord stand-in for Panchavargiya (BUG-1212). + +Astrologer rulings 2026-10-03 甲11: Muntha = natal Lagna + completed years +(kept); year lord by Panchavargiya is not implemented (native producer blocked), +so every surface that shows the year lord says so. Fictional chart. +""" + +from __future__ import annotations + +import copy +import re +from datetime import datetime +from pathlib import Path + +from scripts.consultation_native_layers import YEAR_LORD_BASIS, build_annual_tajika +from scripts.pl9_reader_export import _pl9_export_markdown_for_edition +from scripts.solar_return import SIGNS, solar_return_full_report +from tests.test_report_english_edition import CASES, _packet + +ROOT = Path(__file__).resolve().parents[1] + + +def test_report_shows_the_basis_in_both_editions() -> None: + packet = _packet(CASES["day"]) + zh = _pl9_export_markdown_for_edition(copy.deepcopy(packet), "reader_main") + english = copy.deepcopy(packet) + english["report_language"] = "en" + en = _pl9_export_markdown_for_edition(english, "reader_main") + assert zh.count("| Selection basis | 暂用 Muntha 主星;占星师口径为 Panchavargiya,待接入 |") >= 1 + assert "| Selection basis | Muntha sign lord for now; the astrologer's rule is Panchavargiya, not yet wired |" in en + assert zh.count("| Varshesha |") == zh.count("| Selection basis |") + + +def test_consultation_annual_card_carries_the_basis() -> None: + case = CASES["day"] + payload = {key: case[key] for key in ("year", "month", "day", "hour", "minute", "second", "lat", "lon", "tz", "ayanamsa", "node_mode")} + from scripts.jyotish_engine import compute_chart_data + chart, *_ = compute_chart_data(case["year"], case["month"], case["day"], case["hour"], case["minute"], + case["lat"], case["lon"], case["tz"], ayanamsa_name=case["ayanamsa"]) + annual = build_annual_tajika(payload, chart, reference=datetime(2026, 10, 3)) + assert annual["status"] == "parameter_sensitive" + assert annual["varshesha_basis"] == YEAR_LORD_BASIS + card = (ROOT / "frontend/src/lib/consultation-evidence-card.ts").read_text(encoding="utf-8") + assert '"varshesha", "varshesha_basis"' in card + workflow = (ROOT / "frontend/src/mastra/consultation-workflow.ts").read_text(encoding="utf-8") + assert '"varsheshabasis"' in workflow + + +def test_main_path_muntha_is_natal_lagna_plus_completed_years() -> None: + from scripts.jyotish_engine import compute_chart_data + + case = CASES["day"] + report = solar_return_full_report(case["year"], case["month"], case["day"], case["hour"], case["minute"], + case["lat"], case["lon"], case["tz"], 2026, ayanamsa_name="raman") + _chart, natal_asc, *_ = compute_chart_data(case["year"], case["month"], case["day"], case["hour"], case["minute"], + case["lat"], case["lon"], case["tz"], ayanamsa_name="raman") + muntha = report["muntha"] + completed = 2026 - case["year"] + assert (muntha["natal_asc_sign_idx"], muntha["age"]) == (natal_asc, completed) + assert muntha["muntha_sign_idx"] == (natal_asc + completed) % 12 + assert muntha["muntha_sign"] == SIGNS[muntha["muntha_sign_idx"]] + assert report["year_lord"]["year_lord"] == muntha["muntha_lord"] + + +def test_sun_sign_muntha_variant_is_off_the_main_path() -> None: + pattern = re.compile(r"^\s*(from\s+(scripts\.)?muntha\s+import|import\s+(scripts\.)?muntha\b)|_load_local_module\(['\"]muntha['\"]\)", re.MULTILINE) + offenders = [path.name for path in (ROOT / "scripts").glob("*.py") if path.name != "muntha.py" and pattern.search(path.read_text(encoding="utf-8"))] + assert offenders == []