fix(functional): yogakaraka needs a kendra among 4/7/10 and a trikona among 5/9; grouping unchanged (BUG-1158 partial)

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 09:06:29 +08:00
co-authored by Claude Opus 5.5
parent 81f6e5130a
commit ffafa28b79
6 changed files with 58 additions and 9 deletions
+1 -1
View File
@@ -1848,7 +1848,7 @@ def _derive_functional_benefic_malefic(modules: Dict[str, Any]) -> Dict[str, Any
"yogakarakas": [],
"owned_houses": {},
"effect_on_confidence": "Missing chart.ascendant; functional layer blocked.",
"source": "strict_functional_benefic_malefic_v1",
"source": "strict_functional_benefic_malefic_v2",
}
return derive_functional_benefic_malefic(ascendant.get("sign"))
+12 -2
View File
@@ -26,7 +26,16 @@ SIGN_LORDS = {
"Pisces": "Jupiter",
}
PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")
SOURCE = "strict_functional_benefic_malefic_v1"
# v2 (2026-10-02, BUG-1158 partial, product decision on
# TASK-consult-career-yoga-functional-20261002 T1): a yogakaraka must own one
# kendra among 4 / 7 / 10 AND one trikona among 5 / 9 (BPHS ch.34 v13); the
# 1st house no longer counts for this test, so a lagna lord is not a
# yogakaraka by owning the 1st. The benefic / malefic / neutral grouping is
# unchanged from v1. The full per-lagna BPHS ch.34 table waits for an
# astrology consultant on branch codex/consult-career-yoga-functional-t1-20261002.
SOURCE = "strict_functional_benefic_malefic_v2"
YOGAKARAKA_KENDRAS = frozenset({4, 7, 10})
YOGAKARAKA_TRIKONAS = frozenset({5, 9})
def normalize_sign(value: Any) -> str | None:
@@ -80,7 +89,8 @@ def derive_functional_benefic_malefic(ascendant: Any) -> dict[str, Any]:
owns_challenge = any(house in challenging for house in houses)
if owns_trine and owns_kendra and planet not in {"Sun", "Moon"}:
yogakarakas.add(planet)
if YOGAKARAKA_KENDRAS & set(houses) and YOGAKARAKA_TRIKONAS & set(houses):
yogakarakas.add(planet)
benefics.add(planet)
elif owns_trine:
benefics.add(planet)
+2 -2
View File
@@ -3732,7 +3732,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
neutral_text = _escape(', '.join(str(item) for item in neutrals) or 'None')
yogakaraka_text = _escape(', '.join(str(item) for item in yogakarakas) or 'None')
confidence_text = _escape(snapshot.get('effect_on_confidence') or 'Functional role layer was used in the final judgement.')
source_text = _escape(snapshot.get('source') or 'strict_functional_benefic_malefic_v1')
source_text = _escape(snapshot.get('source') or 'strict_functional_benefic_malefic_v2')
summary = (
'<section data-functional-role-summary="true" '
@@ -7109,7 +7109,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
'yogakarakas': [],
'owned_houses': {},
'effect_on_confidence': f'未完成功能性吉凶星判定,需降低高严谨结论置信度: {exc}',
'source': 'strict_functional_benefic_malefic_v1',
'source': 'strict_functional_benefic_malefic_v2',
}
def _detect_yogas(self, planets, asc_idx):
+2 -2
View File
@@ -5197,7 +5197,7 @@ def render_pl9_markdown(packet: dict) -> str:
benefics = _planet_list_text(functional.get('functional_benefics') if isinstance(functional.get('functional_benefics'), list) else [], limit=7) or '-'
malefics = _planet_list_text(functional.get('functional_malefics') if isinstance(functional.get('functional_malefics'), list) else [], limit=7) or '-'
yogakarakas = _planet_list_text(functional.get('yogakarakas') if isinstance(functional.get('yogakarakas'), list) else [], limit=4) or '-'
rows.append(('功能性吉凶', f"吉星:{benefics};凶星:{malefics};Yogakaraka:{yogakarakas}", 'strict_functional_benefic_malefic_v1', '宫主功能属性必须与自然属性、分盘和大运交叉,不单独断事。'))
rows.append(('功能性吉凶', f"吉星:{benefics};凶星:{malefics};Yogakaraka:{yogakarakas}", 'strict_functional_benefic_malefic_v2', '宫主功能属性必须与自然属性、分盘和大运交叉,不单独断事。'))
shadbala = strength_sheet.get('shadbala') if isinstance(strength_sheet.get('shadbala'), dict) else {}
if shadbala:
@@ -10731,7 +10731,7 @@ def _functional_benefic_malefic_snapshot(planets, ascendant):
'yogakarakas': [],
'owned_houses': {},
'effect_on_confidence': f'未完成功能性吉凶星判定,需降低高严谨结论置信度: {exc}',
'source': 'strict_functional_benefic_malefic_v1',
'source': 'strict_functional_benefic_malefic_v2',
}
@@ -0,0 +1,33 @@
"""BUG-1158 partial (product decision 2026-10-02): yogakaraka needs a kendra among
4/7/10 AND a trikona among 5/9; the benefic / malefic / neutral grouping is unchanged."""
from __future__ import annotations
import pytest
from scripts.functional_benefics import SIGNS, derive_functional_benefic_malefic
YOGAKARAKAS = {
"Aries": [], "Taurus": ["Saturn"], "Gemini": [], "Cancer": ["Mars"], "Leo": ["Mars"], "Virgo": [],
"Libra": ["Saturn"], "Scorpio": [], "Sagittarius": [], "Capricorn": ["Venus"], "Aquarius": ["Venus"], "Pisces": [],
}
# v1 grouping, unchanged by the narrow fix (lagna lords stay benefic).
LAGNA_LORD_BENEFIC = {
"Aries": "Mars", "Taurus": "Venus", "Gemini": "Mercury", "Virgo": "Mercury", "Libra": "Venus",
"Scorpio": "Mars", "Sagittarius": "Jupiter", "Capricorn": "Saturn", "Aquarius": "Saturn", "Pisces": "Jupiter",
}
@pytest.mark.parametrize("sign", SIGNS)
def test_yogakaraka_needs_kendra_4_7_10_and_trikona_5_9(sign: str) -> None:
result = derive_functional_benefic_malefic(sign)
assert result["yogakarakas"] == YOGAKARAKAS[sign]
for planet in result["yogakarakas"]:
houses = set(result["owned_houses"][planet])
assert houses & {4, 7, 10} and houses & {5, 9}
assert planet in result["functional_benefics"]
@pytest.mark.parametrize("sign", sorted(LAGNA_LORD_BENEFIC))
def test_lagna_lord_stays_benefic(sign: str) -> None:
assert LAGNA_LORD_BENEFIC[sign] in derive_functional_benefic_malefic(sign)["functional_benefics"]
@@ -63,7 +63,10 @@ def test_functional_benefic_module_calculates_lagna_roles_without_planets() -> N
assert functional["owned_houses"]["Venus"] == [3, 10]
assert "Sun" in functional["functional_benefics"]
assert "Venus" in functional["functional_malefics"]
assert functional["source"] == "strict_functional_benefic_malefic_v1"
# 原值: source == "strict_functional_benefic_malefic_v1"
# 新值: "strict_functional_benefic_malefic_v2"
# 原因: 产品 2026-10-02 决定 yogakaraka 只认同时主 4/7/10 之一与 5/9 之一(BUG-1158 部分修复),吉凶分组不变
assert functional["source"] == "strict_functional_benefic_malefic_v2"
def test_relationship_functional_layer_uses_ascendant_even_when_planets_missing() -> None:
@@ -101,4 +104,7 @@ def test_oracle_functional_benefics_cli_exposes_json_contract() -> None:
assert "Sun" in report["functional_benefics"]
assert "Venus" in report["functional_malefics"]
assert "Mars" in report["yogakarakas"]
assert report["source"] == "strict_functional_benefic_malefic_v1"
# 原值: source == "strict_functional_benefic_malefic_v1"
# 新值: "strict_functional_benefic_malefic_v2"
# 原因: 产品 2026-10-02 决定 yogakaraka 只认同时主 4/7/10 之一与 5/9 之一(BUG-1158 部分修复),吉凶分组不变
assert report["source"] == "strict_functional_benefic_malefic_v2"