fix(yogas): restore English combination text for the five-planet BVR rules; Kemadruma text says nodes are not counted (BUG-1187)

Follow-up to f3ed9509 (TASK-upstream-sync4-20261002 T5). The port dropped
combo_template_en from bvr_002/003/004/016/017/018, so the English edition had
no combination text for them; restored. kemadruma_yoga's text still said "only
the Sun and Moon" although the nodes no longer count. New tests: Ketu alone
next to the Moon leaves Kemadruma in place in both the rule and
detect_kemadruma; Saturn there cancels it and forms Sunaphaa.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-10-02 14:08:21 +08:00
co-authored by Claude Opus 5.5
parent 09c0efc2a1
commit 0a4e6d0aa4
2 changed files with 80 additions and 10 deletions
@@ -0,0 +1,62 @@
"""sync4 T5 follow-up (BUG-1187).
* Kemadruma: a node alone next to the Moon does not cancel it, in both the rule
version (kemadruma_yoga) and yoga_expansion.detect_kemadruma.
* The six five-planet BVR rules keep their English combination text (the port
dropped combo_template_en, so the English edition fell back to no text).
"""
from __future__ import annotations
import json
from pathlib import Path
from scripts.yoga_engine import YogaEngine
from scripts.yoga_expansion import detect_kemadruma
from tests.test_lunar_solar_yogas_count_five_planets import _chart
RULES = Path(__file__).resolve().parents[1] / "references" / "yoga_rules.json"
# Aries lagna; Moon in Taurus, Ketu alone in Gemini (2nd from Moon), Aries (12th) empty,
# no planet in Aries / Cancer / Libra / Capricorn (kendras from lagna).
ISOLATED = dict(Moon="Taurus", Ketu="Gemini", Rahu="Sagittarius", Sun="Leo", Mars="Leo",
Mercury="Virgo", Jupiter="Virgo", Venus="Leo", Saturn="Scorpio")
def test_ketu_next_to_the_moon_does_not_cancel_kemadruma() -> None:
planets = _chart(**ISOLATED)
names = {row["name"] for row in YogaEngine(str(RULES)).detect(planets, "Aries")}
assert "Kemadruma Yoga" in names
assert not {"Sunaphaa Yoga", "Anaphaa Yoga", "Duradhara Yoga"} & names
assert detect_kemadruma(planets)["present"] is True
def test_saturn_next_to_the_moon_cancels_kemadruma() -> None:
planets = _chart(**{**ISOLATED, "Saturn": "Gemini"})
names = {row["name"] for row in YogaEngine(str(RULES)).detect(planets, "Aries")}
assert "Kemadruma Yoga" not in names
assert "Sunaphaa Yoga" in names
assert detect_kemadruma(planets)["present"] is False
def _templates(node, acc):
if isinstance(node, dict):
if "combo_template" in node:
acc.append(node)
for value in node.values():
_templates(value, acc)
elif isinstance(node, list):
for value in node:
_templates(value, acc)
return acc
def test_five_planet_bvr_rules_keep_english_combination_text() -> None:
rules = {rule["id"]: rule for rule in json.loads(RULES.read_text(encoding="utf-8"))["rules"]}
for rule_id in ("bvr_002_sunapha_precise", "bvr_003_anapha_precise", "bvr_004_duradhara_precise",
"bvr_016_vesi_precise", "bvr_017_vosi_precise", "bvr_018_ubhayachara_precise",
"kemadruma_yoga"):
conditions = _templates(rules[rule_id]["logic"], [])
assert conditions, rule_id
for cond in conditions:
assert cond.get("combo_template_en"), rule_id