From ffafa28b791dcd7b359520c304910a37b30ed631 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Fri, 2 Oct 2026 09:06:29 +0800 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE --- mcp_server.py | 2 +- scripts/functional_benefics.py | 14 ++++++-- scripts/jyotish_api_server.py | 4 +-- scripts/jyotish_engine.py | 4 +-- ...st_functional_yogakaraka_kendra_trikona.py | 33 +++++++++++++++++++ ...st_mcp_strict_workflow_functional_layer.py | 10 ++++-- 6 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 tests/test_functional_yogakaraka_kendra_trikona.py diff --git a/mcp_server.py b/mcp_server.py index 52d5e9dc..ee08b0e3 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -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")) diff --git a/scripts/functional_benefics.py b/scripts/functional_benefics.py index eaf973e6..77e9c1d6 100644 --- a/scripts/functional_benefics.py +++ b/scripts/functional_benefics.py @@ -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) diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index e6f55228..0328c8e3 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -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 = ( '
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', } diff --git a/tests/test_functional_yogakaraka_kendra_trikona.py b/tests/test_functional_yogakaraka_kendra_trikona.py new file mode 100644 index 00000000..26a68798 --- /dev/null +++ b/tests/test_functional_yogakaraka_kendra_trikona.py @@ -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"] diff --git a/tests/test_mcp_strict_workflow_functional_layer.py b/tests/test_mcp_strict_workflow_functional_layer.py index f604e875..83b13fc0 100644 --- a/tests/test_mcp_strict_workflow_functional_layer.py +++ b/tests/test_mcp_strict_workflow_functional_layer.py @@ -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"