"""Year lord: no stand-in when Panchadhikari is blocked (BUG-1223). Product owner 2026-10-04 (batch 5 T5), aligned with upstream yinduzhanxing 03bea6ed (`docs/research/tajika_shared_entrypoint_repair_2026_10_03.md` item 3): when the year lord cannot be chosen (annual chart incomplete) the producers return `status: "blocked"` with the reason and no planet; the Muntha sign lord is no longer put in its place. The card and the report show "年主暂无法确定(原因)". Mudda is independent of the year lord (BUG-1216). Fictional chart. """ from __future__ import annotations import copy import re import types from datetime import datetime import pytest import tajika_year_lord as year_lord_module # the name the producers import from scripts.pl9_reader_english_terms import PHRASES_EN BLOCKED = {"status": "blocked", "reason": "sun_or_moon_missing_for_panchadhikari"} FICTIONAL = {"birth": {"date": "1990-01-01", "time": "12:00:00", "latitude": 39.9, "longitude": 116.4, "utc_offset": "+08:00"}, "settings": {"ayanamsa": "raman"}} @pytest.fixture def blocked_selector(monkeypatch): monkeypatch.delenv("JYOTISH_YEAR_LORD_PYJHORA_REFERENCE", raising=False) monkeypatch.setattr(year_lord_module, "calc_panchadhikari_year_lord", lambda *a, **k: dict(BLOCKED)) def test_selector_returns_blocked_without_a_stand_in_planet(blocked_selector) -> None: sr = {"asc_sign_idx": 0, "planet_lons": {}, "solar_return": {}, "annual_location": {}} data = year_lord_module.select_annual_year_lord( sr, natal_asc_sign_idx=4, completed_years=30, ayanamsa_name="raman", birth_lat=39.9, birth_lon=116.4, birth_tz=8.0) assert data["status"] == "blocked" assert data["reason"] == "sun_or_moon_missing_for_panchadhikari" assert data["year_lord"] is None assert "year_lord_sign" not in data and "fallback_reason" not in data assert data["selection_basis"] == year_lord_module.BLOCKED_BASIS assert data["selection_basis_text"] == "年主暂无法确定(年盘缺太阳或月亮位置)" assert data["selection_basis_text_en"].startswith("Year lord cannot be determined yet (") assert "Muntha" not in data["selection_basis_text"] def test_solar_return_producer_blocks_the_year_lord_and_tri_pataka_keeps_mudda(blocked_selector) -> None: from scripts.solar_return import solar_return_full_report report = solar_return_full_report(1990, 1, 1, 12, 0, 39.9, 116.4, 8.0, 2026, ayanamsa_name="raman") assert report["year_lord"]["status"] == "blocked" assert report["year_lord"]["year_lord"] is None assert report["tri_pataka"] == {"status": "blocked", "reason": "year_lord_blocked"} assert report["mudda_dasha"]["status"] == "partial_verified" assert report["mudda_dasha"]["independent_of_year_lord"] is True def test_cmd_tajika_without_an_annual_chart_is_blocked_not_muntha_lord(monkeypatch) -> None: import solar_return as solar_return_module from scripts.jyotish_engine import cmd_tajika monkeypatch.setattr(solar_return_module, "calc_solar_return_chart", lambda *a, **k: {"error": "fictional failure"}) args = types.SimpleNamespace(year=1990, month=1, day=1, hour=12, minute=0, lat=39.9, lon=116.4, tz=8.0, age=36, mode="all", ayanamsa="raman") result = cmd_tajika(args) result = result.get("data", result) if isinstance(result.get("data"), dict) else result year_lord = result["year_lord"] assert year_lord["status"] == "blocked" assert year_lord["reason"] == "solar_return_chart_unavailable" assert year_lord["year_lord"] is None assert year_lord["selection_basis_text"] == "年主暂无法确定(年盘无法计算)" assert result["tri_pataka"] == {"status": "blocked", "reason": "year_lord_blocked"} def test_pack_and_card_show_the_blocked_sentence(blocked_selector) -> None: from scripts.annual_tajika_pack import build_annual_tajika_pack from scripts.consultation_native_layers import build_annual_tajika from scripts.jyotish_engine import compute_chart_data pack = build_annual_tajika_pack({**FICTIONAL, "target_year": 2026}) field = pack["year_lord"] assert field["status"] == "blocked" assert field["reason"] == "sun_or_moon_missing_for_panchadhikari" assert field["blocked_detail"]["year_lord"] is None assert pack["mudda_dasha"]["status"] == "partial_verified" payload = {"year": 1990, "month": 1, "day": 1, "hour": 12, "minute": 0, "second": 0, "lat": 39.9, "lon": 116.4, "tz": 8.0, "ayanamsa": "raman", "node_mode": "mean"} chart, *_ = compute_chart_data(1990, 1, 1, 12, 0, 39.9, 116.4, 8.0, ayanamsa_name="raman") annual = build_annual_tajika(payload, chart, reference=datetime(2026, 10, 3)) assert annual["status"] == "parameter_sensitive" # the card is not blocked by the year lord alone assert annual["varshesha"] is None assert annual["varshesha_basis"] == "年主暂无法确定(年盘缺太阳或月亮位置)" assert annual["mudda_dasha"] def test_report_shows_the_blocked_sentence_in_both_editions(blocked_selector) -> None: from scripts.annual_tajika_pack import build_annual_tajika_pack from scripts.pl9_reader_export import _pl9_export_markdown_for_edition from tests.test_report_english_edition import CASES, _packet blocked_field = build_annual_tajika_pack({**FICTIONAL, "target_year": 2026})["year_lord"] packet = _packet(CASES["day"]) replaced = 0 def walk(node): nonlocal replaced if isinstance(node, dict): for key, value in list(node.items()): if key == "year_lord" and isinstance(value, dict) and "producers_checked" in value: node[key] = copy.deepcopy(blocked_field) replaced += 1 else: walk(value) elif isinstance(node, list): for value in node: walk(value) walk(packet) assert replaced 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") zh_rows = [line for line in zh.splitlines() if line.startswith("| Selection basis |")] en_rows = [line for line in en.splitlines() if line.startswith("| Selection basis |")] assert zh_rows and all("年主暂无法确定(年盘缺太阳或月亮位置)" in row for row in zh_rows) assert en_rows and all("Year lord cannot be determined yet" in row for row in en_rows) assert not re.search(r"[一-鿿]", "\n".join(en_rows)) assert all("| Varshesha | - |" in zh for _ in zh_rows) def test_every_blocked_sentence_has_its_english_twin() -> None: phrases = dict(PHRASES_EN) for reason in [*year_lord_module.BLOCKED_REASON_TEXT, "unknown_reason"]: zh, en = year_lord_module.blocked_text(reason) assert phrases.get(zh) == en def test_no_producer_falls_back_to_the_muntha_lord() -> None: from pathlib import Path root = Path(__file__).resolve().parents[1] for name in ("tajika_year_lord.py", "solar_return.py", "jyotish_engine.py"): text = (root / "scripts" / name).read_text(encoding="utf-8") assert "fallback_muntha_sign_lord_after_panchadhikari_block" not in text