diff --git a/scripts/annual_tajika_pack.py b/scripts/annual_tajika_pack.py index 3c00fcbd..4c3a2c5f 100755 --- a/scripts/annual_tajika_pack.py +++ b/scripts/annual_tajika_pack.py @@ -277,11 +277,13 @@ def _field_value_is_blocked(value: Any) -> bool: def _field_with_conflict_gate(key: str, solar_report: dict[str, Any], tajika_report: dict[str, Any]) -> dict[str, Any]: values = [] + blocked = [] for producer, report in (("solar_return", solar_report), ("tajika", tajika_report)): if not isinstance(report, dict) or report.get(key) is None or report.get("error"): continue value = report[key] if _field_value_is_blocked(value): + blocked.append({"producer": producer, "value": value}) continue values.append({"producer": producer, "value": value}) comparable_values = [_annual_comparable_value(key, item["value"]) for item in values] @@ -299,12 +301,20 @@ def _field_with_conflict_gate(key: str, solar_report: dict[str, Any], tajika_rep "data": values[0]["value"], "producers_checked": values, } - return { + field = { "status": "blocked", "field": key, "reason": f"{key}_missing", "producers_checked": values, } + if blocked: + # BUG-1223: keep the producer's own blocked record (reason and the + # "年主暂无法确定(原因)" sentence) instead of a bare "_missing". + first = blocked[0]["value"] + field["reason"] = first.get("reason") or field["reason"] + field["blocked_detail"] = first + field["producers_blocked"] = blocked + return field def _dasha_field(label: str, solar_report: dict[str, Any], tajika_report: dict[str, Any], key: str) -> dict[str, Any]: @@ -643,6 +653,10 @@ def _field_brief(label: str, field: dict[str, Any] | None) -> str: return "Tajika Yogas: annual candidate structures visible" if label == "Sahams": return "Sahams: annual sensitive points visible" + if status == "blocked" and label == "Year Lord": + detail = field.get("blocked_detail") if isinstance(field.get("blocked_detail"), dict) else {} + if detail.get("selection_basis_text"): # BUG-1223: 「年主暂无法确定(原因)」 + return f"{label}: {detail['selection_basis_text']}" return f"{label}: {status}" diff --git a/scripts/consultation_native_layers.py b/scripts/consultation_native_layers.py index 367e6bb4..dc3920a4 100644 --- a/scripts/consultation_native_layers.py +++ b/scripts/consultation_native_layers.py @@ -924,7 +924,12 @@ def build_annual_tajika(birth_payload: dict, chart: dict, *, reference: datetime producers = {key: field.get("status") for key, field in fields.items()} periods = fields["mudda_dasha"].get("periods") required = ("solar_return", "annual_chart", "muntha", "year_lord", "mudda_dasha") - unreliable = [key for key in required if producers.get(key) != "partial_verified"] + # BUG-1223: a year lord the producers blocked (annual chart incomplete, + # no stand-in planet) does not block the card; it shows + # "年主暂无法确定(原因)". Mudda does not depend on the year lord. + year_lord_blocked = _record(fields["year_lord"].get("blocked_detail")) if producers.get("year_lord") == "blocked" else {} + unreliable = [key for key in required + if producers.get(key) != "partial_verified" and not (key == "year_lord" and year_lord_blocked)] if start is None or end is None or end <= start: unreliable.append("solar_return_dates") if not isinstance(periods, list) or not periods: @@ -939,7 +944,7 @@ def build_annual_tajika(birth_payload: dict, chart: dict, *, reference: datetime } annual_chart = _record(fields["annual_chart"].get("data")) muntha = _record(fields["muntha"].get("data")) - year_lord_data = _record(fields["year_lord"].get("data")) + year_lord_data = year_lord_blocked or _record(fields["year_lord"].get("data")) year_lord = year_lord_data.get("year_lord") annual_lagna_idx = _sign_index(annual_chart.get("asc_sign")) focus = {"Sun", "Moon", year_lord} diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 116e85eb..8bc1d54e 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -14627,7 +14627,7 @@ def _tajika_annual_core(args, planet_lons, asc_si, age, mode='all'): """ try: sys.path.insert(0, SCRIPT_DIR) - from tajika import calc_muntha, calc_year_lord, calc_tri_pataka + from tajika import calc_muntha, calc_tri_pataka from tajika_mudda import annual_mudda_dasha except ImportError as e: return {"error": f"tajika模块导入失败: {e}"} @@ -14652,15 +14652,13 @@ def _tajika_annual_core(args, planet_lons, asc_si, age, mode='all'): def _year_lord() -> Dict: # Panchadhikari on the solar-return chart, same helper as - # solar_return_full_report (BUG-1214); Muntha lord only when the - # annual chart cannot be computed (recorded as fallback, not hidden). - from tajika_year_lord import select_annual_year_lord + # solar_return_full_report (BUG-1214). No annual chart -> blocked, no + # stand-in Muntha lord (BUG-1223, aligned with upstream 03bea6ed). + from tajika_year_lord import blocked_year_lord, select_annual_year_lord sr = _solar_return() if not _sr_usable(sr): - fallback = calc_year_lord(asc_si, age) - fallback['selection_basis'] = 'fallback_muntha_sign_lord_after_panchadhikari_block' - fallback['fallback_reason'] = (sr or {}).get('error', 'solar_return_chart_unavailable') if isinstance(sr, dict) else 'solar_return_chart_unavailable' - return fallback + detail = sr.get('error') if isinstance(sr, dict) else None + return blocked_year_lord('solar_return_chart_unavailable', detail) return select_annual_year_lord( sr, natal_asc_sign_idx=sr['birth_asc_sign_idx'], completed_years=int(age), ayanamsa_name=sr_cache['ayanamsa'], birth_lat=float(args.lat), birth_lon=float(args.lon), birth_tz=float(args.tz), @@ -14685,9 +14683,11 @@ def _tajika_annual_core(args, planet_lons, asc_si, age, mode='all'): if mode in ('all', 'mudda'): result['mudda_dasha'] = _mudda() if mode in ('all', 'tripataka'): - varsha_lord = year_lord.get('year_lord', 'Jupiter') + varsha_lord = year_lord.get('year_lord') muntha_si = (asc_si + age) % 12 - result['tri_pataka'] = calc_tri_pataka(planet_lons, varsha_lord, muntha_si) + # BUG-1223: no year lord -> no Tri-Pataka (no stand-in planet). + result['tri_pataka'] = (calc_tri_pataka(planet_lons, varsha_lord, muntha_si) if varsha_lord + else {'status': 'blocked', 'reason': 'year_lord_blocked'}) return result diff --git a/scripts/pl9_reader_english_terms.py b/scripts/pl9_reader_english_terms.py index 479777c5..c500943d 100644 --- a/scripts/pl9_reader_english_terms.py +++ b/scripts/pl9_reader_english_terms.py @@ -177,6 +177,13 @@ _PHRASES: dict[str, str] = { ",12 等分": ", twelve equal parts", # BUG-1214: tajika_year_lord.selection_basis_text (zh) → its en twin. "年盘不全,暂用 Muntha 主星": "Annual chart incomplete; Muntha sign lord used for now", + # BUG-1223: tajika_year_lord.blocked_text (no stand-in year lord). + "年主暂无法确定(年盘缺太阳或月亮位置)": "Year lord cannot be determined yet (the annual chart lacks the Sun or Moon position)", + "年主暂无法确定(年盘缺行星位置,五分力量无法计算)": "Year lord cannot be determined yet (planet positions are missing from the annual chart, so the five-fold strength cannot be computed)", + "年主暂无法确定(五类候选星都不在年盘上)": "Year lord cannot be determined yet (none of the five candidates is in the annual chart)", + "年主暂无法确定(年盘无法计算)": "Year lord cannot be determined yet (the annual chart could not be computed)", + "年主暂无法确定(缺少 Muntha 星座)": "Year lord cannot be determined yet (the Muntha sign is missing)", + "年主暂无法确定(年盘数据不全)": "Year lord cannot be determined yet (the annual chart data are incomplete)", "(Panchavargiya 为本地近似计算)": " (Panchavargiya is a local approximation)", # BUG-1217: tajika_year_lord.ENGINE_NOTE["tajika_native_five_fold"] "(按 Tajika 五分力量)": " (by Tajika five-fold strength)", diff --git a/scripts/pl9_reader_export.py b/scripts/pl9_reader_export.py index 4119de3a..13fe9681 100644 --- a/scripts/pl9_reader_export.py +++ b/scripts/pl9_reader_export.py @@ -577,6 +577,10 @@ def render_pl9_parity_markdown(packet: dict) -> str: def _annual_selection_basis(year_lord, muntha) -> str: year_lord = _dict(year_lord) + blocked_detail = _dict(year_lord.get('blocked_detail')) + if year_lord.get('status') == 'blocked' and blocked_detail.get('selection_basis_text'): + # BUG-1223: no stand-in year lord; 「年主暂无法确定(原因)」. + return _cell(blocked_detail['selection_basis_text']) if year_lord.get('status') == 'conflict': return '两种计算口径给出的年主不同,暂不合并' data = _dict(year_lord.get('data') or year_lord) diff --git a/scripts/solar_return.py b/scripts/solar_return.py index 9d83b4fc..cd25d148 100644 --- a/scripts/solar_return.py +++ b/scripts/solar_return.py @@ -559,18 +559,15 @@ def solar_return_full_report( result['muntha'] = {'error': str(e)} # 3. Year Lord:Panchadhikari 选法(占星师第四轮问 3,BUG-1214)。 - # 与 cmd_tajika 共用 tajika_year_lord.select_annual_year_lord;年盘不全 - # (降级模式或 Muntha 缺失)时才用 Muntha 主星,并写明 fallback。 + # 与 cmd_tajika 共用 tajika_year_lord.select_annual_year_lord;年盘不全时 + # 返回 blocked 与原因,不用 Muntha 主星顶替(BUG-1223)。 try: muntha_data = result.get('muntha', {}) muntha_sign_val = muntha_data.get('muntha_sign_idx') if isinstance(muntha_data, dict) else None + from tajika_year_lord import blocked_year_lord, select_annual_year_lord if not isinstance(muntha_sign_val, int) or isinstance(muntha_sign_val, bool): - result['year_lord'] = { - 'status': 'blocked', - 'reason': 'muntha_sign_required_for_year_lord', - } + result['year_lord'] = blocked_year_lord('muntha_sign_required_for_year_lord') else: - from tajika_year_lord import select_annual_year_lord result['year_lord'] = select_annual_year_lord( sr, natal_asc_sign_idx=sr['birth_asc_sign_idx'], @@ -624,11 +621,13 @@ def solar_return_full_report( try: from tajika import calc_tri_pataka yl_data = result.get('year_lord', {}) - yl = yl_data.get('year_lord', '') + yl = yl_data.get('year_lord') if isinstance(yl_data, dict) else None muntha_data = result.get('muntha', {}) ms = muntha_data.get('muntha_sign_idx', 0) - tri = calc_tri_pataka(planet_lons, yl, ms) - result['tri_pataka'] = tri + if yl: + result['tri_pataka'] = calc_tri_pataka(planet_lons, yl, ms) + else: # BUG-1223: no year lord -> no Tri-Pataka (no stand-in planet). + result['tri_pataka'] = {'status': 'blocked', 'reason': 'year_lord_blocked'} except Exception as e: result['tri_pataka'] = {'error': str(e)} @@ -708,8 +707,10 @@ def _make_sr_summary(result: Dict) -> str: lines.append(f" → {muntha.get('interpretation','')[:60]}") yl = result.get('year_lord', {}) - if 'year_lord' in yl: + if yl.get('year_lord'): lines.append(f" Year Lord:{yl['year_lord']}") + elif yl.get('selection_basis_text'): + lines.append(f" Year Lord:{yl['selection_basis_text']}") tp = result.get('tri_pataka', {}) if 'verdict' in tp: diff --git a/scripts/tajika_year_lord.py b/scripts/tajika_year_lord.py index ec9f0ac4..a8baafc8 100644 --- a/scripts/tajika_year_lord.py +++ b/scripts/tajika_year_lord.py @@ -26,6 +26,13 @@ Drekkana, Navamsa; raw out of 80, score = raw / 4 out of 20; `engine: tajika_native_five_fold`); the dignity proxy no longer selects a year lord. Set JYOTISH_YEAR_LORD_PYJHORA_REFERENCE=1 to pass the annual JD and let the upstream PyJHora lookup run (research comparison only). + +No stand-in year lord (BUG-1223, product owner 2026-10-04, aligned with +upstream 03bea6ed `docs/research/tajika_shared_entrypoint_repair_2026_10_03.md` +item 3): when Panchadhikari is blocked (annual chart incomplete) the result is +`status: "blocked"` with the reason, `year_lord: None`, and the sentence +"年主暂无法确定(原因)"; the Muntha sign lord is no longer put in its place. +Mudda is independent of the year lord (BUG-1216) and is unaffected. """ from __future__ import annotations @@ -36,14 +43,23 @@ import os from typing import Any, Dict, List, Optional, Tuple try: # scripts/ on sys.path - from tajika import SIGNS, SIGN_LORDS, calc_panchadhikari_year_lord + from tajika import calc_panchadhikari_year_lord except ImportError: # pragma: no cover - package import - from scripts.tajika import SIGNS, SIGN_LORDS, calc_panchadhikari_year_lord + from scripts.tajika import calc_panchadhikari_year_lord PYJHORA_REFERENCE_ENV = "JYOTISH_YEAR_LORD_PYJHORA_REFERENCE" SOURCE = "tajika.calc_panchadhikari_year_lord (upstream yinduzhanxing c7117f92)" MUNTHA_BASIS = "natal_ascendant_plus_completed_years" -FALLBACK_BASIS = "fallback_muntha_sign_lord_after_panchadhikari_block" +BLOCKED_BASIS = "panchadhikari_blocked_no_year_lord" +# Reason token -> (zh, en) for "年主暂无法确定(原因)". +BLOCKED_REASON_TEXT = { + "sun_or_moon_missing_for_panchadhikari": ("年盘缺太阳或月亮位置", "the annual chart lacks the Sun or Moon position"), + "tajika_five_fold_panchavargiya_unavailable": ("年盘缺行星位置,五分力量无法计算", "planet positions are missing from the annual chart, so the five-fold strength cannot be computed"), + "no_panchadhikari_candidates_available": ("五类候选星都不在年盘上", "none of the five candidates is in the annual chart"), + "solar_return_chart_unavailable": ("年盘无法计算", "the annual chart could not be computed"), + "muntha_sign_required_for_year_lord": ("缺少 Muntha 星座", "the Muntha sign is missing"), +} +BLOCKED_REASON_DEFAULT = ("年盘数据不全", "the annual chart data are incomplete") ENGINE_NOTE = { "tajika_native_five_fold": ("按 Tajika 五分力量", "by Tajika five-fold strength"), @@ -61,8 +77,8 @@ def selection_basis_text(data: Dict[str, Any]) -> Tuple[str, str]: """One sentence (zh, en) saying why this planet is the year lord.""" basis = data.get("selection_basis") lord = data.get("year_lord") - if basis == FALLBACK_BASIS: - return ("年盘不全,暂用 Muntha 主星", "Annual chart incomplete; Muntha sign lord used for now") + if basis == BLOCKED_BASIS or data.get("status") == "blocked": + return blocked_text(data.get("reason")) candidates = [row for row in data.get("candidates") or [] if isinstance(row, dict)] benefic = [row for row in candidates if row.get("benefic_aspects_asc")] malefic = [row for row in candidates if row.get("malefic_aspects_asc")] @@ -91,6 +107,29 @@ def selection_basis_text(data: Dict[str, Any]) -> Tuple[str, str]: return zh, en +def blocked_text(reason: Any) -> Tuple[str, str]: + """「年主暂无法确定(原因)」 and its English twin.""" + zh, en = BLOCKED_REASON_TEXT.get(str(reason), BLOCKED_REASON_DEFAULT) + return f"年主暂无法确定({zh})", f"Year lord cannot be determined yet ({en})" + + +def blocked_year_lord(reason: Any, detail: Any = None) -> Dict[str, Any]: + """Blocked year lord: no planet, reason and sentence only (BUG-1223).""" + data: Dict[str, Any] = { + "status": "blocked", + "reason": str(reason or "panchadhikari_blocked"), + "year_lord": None, + "selection_basis": BLOCKED_BASIS, + "muntha_basis": MUNTHA_BASIS, + "candidate_list": [], + "source": SOURCE, + } + if detail is not None: + data["detail"] = detail + data["selection_basis_text"], data["selection_basis_text_en"] = blocked_text(data["reason"]) + return data + + def _candidate_list(candidates: List[Dict[str, Any]], winner: Optional[str]) -> List[Dict[str, Any]]: return [{ "source": row.get("source"), @@ -142,14 +181,7 @@ def select_annual_year_lord( data = _select(sr, natal_asc_sign_idx, completed_years, muntha_sign_idx, ayanamsa_name, birth_lat, birth_lon, birth_tz) if data.get("status") != "partial_verified": - lord = SIGN_LORDS[SIGNS[muntha_sign_idx]] - data = { - "year_lord": lord, - "year_lord_sign_idx": muntha_sign_idx, - "year_lord_sign": SIGNS[muntha_sign_idx], - "selection_basis": FALLBACK_BASIS, - "fallback_reason": data.get("reason", "panchadhikari_blocked"), - } + return blocked_year_lord(data.get("reason", "panchadhikari_blocked"), data.get("detail")) data["muntha_basis"] = MUNTHA_BASIS data["candidate_list"] = _candidate_list(data.get("candidates") or [], data.get("year_lord")) data["source"] = SOURCE diff --git a/tests/test_year_lord_blocked_no_fallback.py b/tests/test_year_lord_blocked_no_fallback.py new file mode 100644 index 00000000..50bc6950 --- /dev/null +++ b/tests/test_year_lord_blocked_no_fallback.py @@ -0,0 +1,149 @@ +"""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