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
+18 -10
View File
@@ -1306,8 +1306,8 @@
"logic": {
"type": "custom",
"expr": "\n# PyJHora/BVR-5: none of the five true planets (Mars..Saturn) in 1st/2nd/12th from Moon;\n# the Sun and the nodes do not count (sync4 T5, BPHS / Raman 300 Combinations no. 5),\n# and no planets other than Moon in quadrants from Lagna.\nif 'Moon' not in ctx.planets:\n False\nelse:\n moon_h = house_of('Moon')\n moon_zone = {moon_h, ((moon_h - 1 + 1) % 12) + 1, ((moon_h - 1 + 11) % 12) + 1}\n ky1 = all(p in ['Moon', 'Sun', 'Rahu', 'Ketu'] for p, info in ctx.planets.items() if info.get('house') in moon_zone)\n ky2 = all(p == 'Moon' for p, info in ctx.planets.items() if info.get('house') in [1, 4, 7, 10])\n ky1 and ky2\n",
"combo_template": "月亮1/2/12宫仅日月,且Lagna角宫仅月亮",
"combo_template_en": "Only the Sun and Moon in the 1st/2nd/12th from the Moon, and only the Moon in the Kendras from the Lagna"
"combo_template": "月亮1/2/12宫无火水木金土(太阳与罗计不计),且Lagna角宫仅月亮",
"combo_template_en": "None of Mars to Saturn in the 1st/2nd/12th from the Moon (the Sun and the nodes are not counted), and only the Moon in the Kendras from the Lagna"
},
"effects": [
"人生艰辛",
@@ -9894,7 +9894,8 @@
"houses": [
2
],
"combo_template": "{planets}在太阳的第2宫"
"combo_template": "{planets}在太阳的第2宫",
"combo_template_en": "{planets} in the 2nd from the Sun"
},
"effects": [
"太阳后方有行星支持",
@@ -9936,7 +9937,8 @@
"houses": [
12
],
"combo_template": "{planets}在太阳的第12宫"
"combo_template": "{planets}在太阳的第12宫",
"combo_template_en": "{planets} in the 12th from the Sun"
},
"effects": [
"太阳前方有行星铺垫",
@@ -9983,7 +9985,8 @@
"houses": [
2
],
"combo_template": "{planets}在太阳的第2宫"
"combo_template": "{planets}在太阳的第2宫",
"combo_template_en": "{planets} in the 2nd from the Sun"
},
{
"type": "has_planet_in_house_from",
@@ -10001,7 +10004,8 @@
"houses": [
12
],
"combo_template": "{planets}在太阳的第12宫"
"combo_template": "{planets}在太阳的第12宫",
"combo_template_en": "{planets} in the 12th from the Sun"
}
]
},
@@ -10045,7 +10049,8 @@
"houses": [
2
],
"combo_template": "{planets}在月亮的第2宫"
"combo_template": "{planets}在月亮的第2宫",
"combo_template_en": "{planets} in the 2nd from the Moon"
},
"effects": [
"自力更生",
@@ -10089,7 +10094,8 @@
"houses": [
12
],
"combo_template": "{planets}在月亮的第12宫"
"combo_template": "{planets}在月亮的第12宫",
"combo_template_en": "{planets} in the 12th from the Moon"
},
"effects": [
"心智有背景支持",
@@ -10136,7 +10142,8 @@
"houses": [
2
],
"combo_template": "{planets}在月亮的第2宫"
"combo_template": "{planets}在月亮的第2宫",
"combo_template_en": "{planets} in the 2nd from the Moon"
},
{
"type": "has_planet_in_house_from",
@@ -10154,7 +10161,8 @@
"houses": [
12
],
"combo_template": "{planets}在月亮的第12宫"
"combo_template": "{planets}在月亮的第12宫",
"combo_template_en": "{planets} in the 12th from the Moon"
}
]
},
@@ -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