yoga_rules.json gains effects_en for every rule and an _en sibling for each Han-bearing string (combo_template, strength, source, accuracy_note); zh values untouched. reader_dasha_applicability renders language='en' from the same packet; the default zh output is byte-identical (pinned by sha256). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
111 lines
4.7 KiB
Python
111 lines
4.7 KiB
Python
"""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 <reason> | 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("|")
|