"""Static English dictionary for the English report edition. The zh wording stays authoritative and unchanged; every Han-bearing rule string in references/yoga_rules.json carries an ``_en`` sibling, and the auxiliary dasha applicability table renders in English from the same packet. """ from __future__ import annotations import hashlib import itertools import json import re from pathlib import Path from scripts.reader_dasha_applicability import REASON_LABELS, render_dasha_applicability ROOT = Path(__file__).resolve().parents[1] RULES = json.loads((ROOT / "references" / "yoga_rules.json").read_text(encoding="utf-8"))["rules"] HAN = re.compile(r"[㐀-鿿豈-﫿]") PLACEHOLDER = re.compile(r"\{[^{}]*\}") # Keys whose Han value is paired by an existing English key rather than a `_en` sibling. PAIRED = {"name_cn": "name"} def _walk(node, path=()): if isinstance(node, dict): yield path, node for key, value in node.items(): yield from _walk(value, path + (key,)) elif isinstance(node, list): for index, value in enumerate(node): yield from _walk(value, path + (index,)) def test_every_rule_has_parallel_english_effects(): for rule in RULES: effects = rule.get("effects") if effects is None: continue english = rule.get("effects_en") assert isinstance(english, list) and len(english) == len(effects), rule["id"] for zh, en in zip(effects, english): assert isinstance(en, str) and en.strip(), (rule["id"], zh) assert not HAN.search(en), (rule["id"], en) if not HAN.search(zh): assert en == zh, (rule["id"], zh, en) def test_every_han_string_has_an_english_sibling(): for rule in RULES: for path, node in _walk(rule): for key, value in node.items(): if not isinstance(value, str) or not HAN.search(value) or key.endswith("_en"): continue sibling = PAIRED.get(key, f"{key}_en") english = node.get(sibling) assert isinstance(english, str) and english.strip(), (rule["id"], path, key) assert not HAN.search(english), (rule["id"], path, sibling, english) def test_english_templates_keep_the_same_placeholders(): checked = 0 for rule in RULES: for path, node in _walk(rule): zh = node.get("combo_template") if isinstance(zh, str): assert sorted(PLACEHOLDER.findall(node.get("combo_template_en", zh))) == sorted(PLACEHOLDER.findall(zh)), (rule["id"], path) checked += 1 assert checked >= 150 def _applicability_packets(): reasons = [None, *REASON_LABELS, "Moon Nakshatra 'X' does not qualify for Ashtottari Dasha under Y", "odd | pipe"] statuses = [None, "not_applicable", "blocked", "missing_in_local", "unavailable", "ok"] packets = [{}, {"worksheets": {}}, {"worksheets": {"timing_and_predictive_systems": {"dasha_master_pack": {"families": {}}}}}] for index, (status, reason, applicable) in enumerate(itertools.product(statuses, reasons, [None, True, False])): families = { "vimshottari": {}, "narayana": {"status": status, "reason": reason, "mahadasha_sequence": [1] if index % 2 else []}, "ashtottari": {"status": status, "reason": reason, "applicable": applicable}, "kala_chakra": {"periods": [1]}, } packets.append({"worksheets": {"timing_and_predictive_systems": { "dasha_master_pack": {"families": families}, "ashtottari_dasha": {"applicable": applicable, "reason": reason}, "annual_tajika_pack": {"mudda_dasha": {"status": status}}, }}}) return packets # sha256 of the zh output for every synthetic packet, taken from the module # before the English path was added: zh must stay byte-identical. ZH_APPLICABILITY_SHA256 = "b3194019f11f476f75327a7e76c94099eff7d50c5301d215c43cb45c000d278d" def test_dasha_applicability_zh_is_unchanged(): packets = _applicability_packets() rendered = "\x00".join(render_dasha_applicability(packet) for packet in packets) assert hashlib.sha256(rendered.encode("utf-8")).hexdigest() == ZH_APPLICABILITY_SHA256 assert all(render_dasha_applicability(packet, language="zh") == render_dasha_applicability(packet) for packet in packets) def test_dasha_applicability_en_has_no_han_and_same_rows(): for packet in _applicability_packets(): zh = render_dasha_applicability(packet) en = render_dasha_applicability(packet, language="en") assert not HAN.search(en), en assert zh.count("\n") == en.count("\n") assert zh.count("|") == en.count("|")