Expose functional benefic malefic strict evidence

This commit is contained in:
732642856
2026-06-28 16:53:53 +08:00
parent a4ad9d716e
commit 8c404ec64d
4 changed files with 194 additions and 4 deletions
+2 -1
View File
@@ -52,8 +52,9 @@ This file is the small index for the current engineering fronts that still drive
- `/Users/wuyongnaren/Documents/印度占星/docs/superpowers/specs/2026-06-28-dignity-guardrail-v1-design.md`
- `/Users/wuyongnaren/Documents/印度占星/docs/research/dignity_guardrail_v1_boundary_audit_2026_06_28.md`
- `/Users/wuyongnaren/Documents/印度占星/docs/research/divisional_dignity_context_repair_audit_2026_06_28.md`
- `/Users/wuyongnaren/Documents/印度占星/docs/research/functional_benefic_malefic_strict_layer_audit_2026_06_28.md`
- D1-only dignity guardrail is landed.
- Divisional dignity context repair for D9/DK/Vimsopaka Navamsa is also landed.
- Still open:
- Vimsopaka semantic mapping for `NEECHA_BHANGA / GREAT_FRIEND / GREAT_ENEMY`
- a separate functional-role guardrail
- functional role now enters strict evidence; follow-up is Technique Audit Table rendering.
@@ -0,0 +1,42 @@
# Functional Benefic/Malefic Strict Layer Audit - 2026-06-28
## Scope
This audit records the first strict-workflow integration of the Functional Benefic/Malefic hard constraint from `AGENTS.md`.
## Implementation
Strict workflow evidence now exposes:
```json
{
"functional_benefic_malefic": {
"status": "used | blocked",
"ascendant": "Leo",
"functional_benefics": ["Sun", "Mars", "Jupiter"],
"functional_malefics": ["Mercury", "Moon", "Saturn", "Venus"],
"functional_neutrals": [],
"yogakarakas": ["Mars"],
"owned_houses": {"Sun": [1]},
"source": "strict_functional_benefic_malefic_v1"
}
}
```
## Boundary
- This is a functional house-lord role layer, not a natural benefic/malefic table.
- It is exposed as evidence and secondary context.
- It does not directly lift dominant labels.
- If chart ascendant or planets are unavailable, the layer remains `blocked` in evidence but does not pollute legacy secondary-context tests.
## Why It Matters
The project hard constraint says high-rigor timing or outcome readings must not judge benefic/malefic effects from natural planetary quality alone. This bridge makes the functional role layer visible to strict workflows so later adjudicators and Technique Audit Tables can use it.
## Verification
- `tests/test_mcp_strict_workflow_functional_layer.py`
- `tests/test_mcp_strict_workflow_relationship.py`
- `tests/test_mcp_strict_workflow_career.py`
- `tests/test_mcp_strict_workflow_finance.py`
+100 -3
View File
@@ -449,6 +449,91 @@ def _derive_external_activation_support(modules: Dict[str, Any], domain: str) ->
}
def _derive_functional_benefic_malefic(modules: Dict[str, Any]) -> Dict[str, Any]:
chart = _safe_get(modules, "chart")
ascendant = _safe_get(chart, "ascendant") if isinstance(chart, dict) else None
planets = _safe_get(chart, "planets") if isinstance(chart, dict) else None
if not isinstance(ascendant, dict) or not isinstance(planets, dict):
return {
"status": "blocked",
"ascendant": ascendant.get("sign") if isinstance(ascendant, dict) else None,
"functional_benefics": [],
"functional_malefics": [],
"effect_on_confidence": "Missing chart.ascendant or chart.planets; functional layer blocked.",
"source": "strict_functional_benefic_malefic_v1",
}
asc_sign = ascendant.get("sign")
asc_idx = _SIGN_TO_INDEX.get(asc_sign)
if asc_idx is None:
return {
"status": "blocked",
"ascendant": asc_sign,
"functional_benefics": [],
"functional_malefics": [],
"effect_on_confidence": "Functional layer blocked: unknown ascendant sign.",
"source": "strict_functional_benefic_malefic_v1",
}
owned_houses: Dict[str, List[int]] = {}
for house_num in range(1, 13):
sign = _SIGNS[(asc_idx + house_num - 1) % 12]
lord = _SIGN_LORDS.get(sign)
if lord:
owned_houses.setdefault(lord, []).append(house_num)
trines = {1, 5, 9}
kendras = {1, 4, 7, 10}
challenging = {3, 6, 8, 11, 12}
benefics: set[str] = set()
malefics: set[str] = set()
yogakarakas: set[str] = set()
neutrals: set[str] = set()
for planet in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"):
houses = owned_houses.get(planet, [])
if not houses:
continue
owns_trine = any(house in trines for house in houses)
owns_kendra = any(house in kendras for house in houses)
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)
benefics.add(planet)
elif owns_trine:
benefics.add(planet)
elif owns_challenge and 1 not in houses:
malefics.add(planet)
elif owns_kendra and planet in {"Jupiter", "Venus", "Mercury", "Moon"}:
neutrals.add(planet)
else:
neutrals.add(planet)
# Classical softening: Sun/Moon as 8L are not treated as harshly as other 8L.
eighth_lord = owned_houses and next(
(planet for planet, houses in owned_houses.items() if 8 in houses),
None,
)
if eighth_lord in {"Sun", "Moon"} and eighth_lord in malefics:
malefics.remove(eighth_lord)
neutrals.add(eighth_lord)
return {
"status": "used",
"ascendant": asc_sign,
"functional_benefics": sorted(benefics),
"functional_malefics": sorted(malefics),
"functional_neutrals": sorted(neutrals - benefics - malefics),
"yogakarakas": sorted(yogakarakas),
"owned_houses": {planet: houses for planet, houses in sorted(owned_houses.items())},
"effect_on_confidence": (
"High-rigor outputs must combine functional house-lord roles with natural roles; "
"conflicts should cap confidence or be explicitly noted."
),
"source": "strict_functional_benefic_malefic_v1",
}
def _derive_synastry_relationship_support(modules: Dict[str, Any]) -> Dict[str, Any]:
synastry = _safe_get(modules, "synastry")
base = {
@@ -902,6 +987,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
shadbala_component_audit = present.get("shadbala_component_audit") or {}
if shadbala_component_audit.get("status") in {"blocked", "incomplete"}:
secondary_context.append("shadbala_component_gap")
functional_layer = present.get("functional_benefic_malefic") or {}
if functional_layer.get("status") == "used":
secondary_context.append("functional_benefic_malefic_used")
if kakshya_career_support.get("level") == "supportive":
secondary_context.append("kakshya_career_support")
elif kakshya_career_support.get("level") == "obstructive":
@@ -990,6 +1078,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
shadbala_component_audit = present.get("shadbala_component_audit") or {}
if shadbala_component_audit.get("status") in {"blocked", "incomplete"}:
secondary_context.append("shadbala_component_gap")
functional_layer = present.get("functional_benefic_malefic") or {}
if functional_layer.get("status") == "used":
secondary_context.append("functional_benefic_malefic_used")
if argala_support.get("level") == "supportive":
secondary_context.append("argala_support")
elif argala_support.get("level") == "obstructive":
@@ -1125,6 +1216,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
shadbala_component_audit = present.get("shadbala_component_audit") or {}
if shadbala_component_audit.get("status") in {"blocked", "incomplete"}:
secondary_context.append("shadbala_component_gap")
functional_layer = present.get("functional_benefic_malefic") or {}
if functional_layer.get("status") == "used":
secondary_context.append("functional_benefic_malefic_used")
if pav_finance_support.get("level") == "supportive":
secondary_context.append("pav_finance_support")
if sodhita_finance_support.get("level") == "obstructive":
@@ -1304,8 +1398,9 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
present["kakshya_career_support"] = _derive_kakshya_career_support(_safe_get(modules, "kakshya"))
present["argala_support"] = _derive_argala_support(modules, 10)
present["external_activation"] = _derive_external_activation_support(modules, "career")
present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules)
missing = [key for key, value in present.items() if key not in {
"external_activation", "argala_support", "shadbala", "shadbala_component_audit", "kakshya_career_support"
"external_activation", "argala_support", "shadbala", "shadbala_component_audit", "kakshya_career_support", "functional_benefic_malefic"
} and value in (None, {}, [], "")]
convergence = present["career_convergence"] or {}
confidence_cap = "medium"
@@ -1362,9 +1457,10 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
present["argala_support"] = _derive_argala_support(modules, 7)
present["external_activation"] = _derive_external_activation_support(modules, "marriage")
present["dignity_guardrail"] = _derive_dignity_guardrail(route, present)
present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules)
missing = [
key for key, value in present.items()
if key not in {"chart", "external_activation", "dignity_guardrail", "jaimini_marriage_support", "jaimini_timing_support", "synastry_relationship_support", "argala_support", "shadbala", "shadbala_component_audit"}
if key not in {"chart", "external_activation", "dignity_guardrail", "jaimini_marriage_support", "jaimini_timing_support", "synastry_relationship_support", "argala_support", "shadbala", "shadbala_component_audit", "functional_benefic_malefic"}
and value in (None, {}, [], "")
]
convergence = present["marriage_convergence"] or {}
@@ -1434,8 +1530,9 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
present["kakshya_finance_support"] = _derive_kakshya_finance_support(_safe_get(modules, "kakshya"))
present["external_activation"] = _derive_external_activation_support(modules, "wealth")
present["dignity_guardrail"] = _derive_dignity_guardrail(route, present)
present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules)
missing = [key for key, value in present.items() if key not in {
"chart", "external_activation", "dignity_guardrail", "gains_convergence", "career_convergence", "avayogi_risk", "ashtakavarga_finance_support", "shadbala_component_audit", "asc_sign", "pav_finance_support", "sodhita_finance_support", "kakshya_finance_support"
"chart", "external_activation", "dignity_guardrail", "gains_convergence", "career_convergence", "avayogi_risk", "ashtakavarga_finance_support", "shadbala_component_audit", "asc_sign", "pav_finance_support", "sodhita_finance_support", "kakshya_finance_support", "functional_benefic_malefic"
} and value in (None, {}, [], "")]
convergence_hits: List[Dict[str, Any]] = [
item for item in [
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Regression tests for functional benefic/malefic strict evidence integration."""
from __future__ import annotations
from mcp_server import _collect_strict_evidence
def _base_relationship_result() -> dict:
return {
"modules": {
"chart": {
"ascendant": {"sign": "Leo"},
"planets": {
"Sun": {"sign": "Aquarius", "house": 7},
"Moon": {"sign": "Taurus", "house": 10},
"Mars": {"sign": "Gemini", "house": 11},
"Mercury": {"sign": "Capricorn", "house": 6},
"Jupiter": {"sign": "Sagittarius", "house": 5},
"Venus": {"sign": "Pisces", "house": 8},
"Saturn": {"sign": "Libra", "house": 3},
},
},
"varga_full": {"D9_Navamsa": {"summary": "ok"}},
"special_lagnas": {"Upapada_Lagna": {"sign": "Libra", "lord": "Venus"}},
"jaimini": {"darakaraka": {"planet": "Venus", "house": 7}},
"vivah_saham": {"sign": "Taurus", "house": 7},
"dasha": {"current_dasha": {"mahadasha": "Venus", "antardasha": "Moon"}},
"narayana_dasha": {"current_dasha": {"sign": "Libra", "lord": "Venus"}},
"dasa_convergence": {
"domain_activations": {
"marriage_partnership": {"convergence_level": "L3", "probability": "50-65%"}
}
},
}
}
def test_relationship_strict_workflow_exposes_functional_benefic_malefic_layer() -> None:
strict = _collect_strict_evidence("relationship", _base_relationship_result())
functional = strict["present_evidence"]["functional_benefic_malefic"]
assert functional["status"] == "used"
assert functional["ascendant"] == "Leo"
assert isinstance(functional["functional_benefics"], list)
assert isinstance(functional["functional_malefics"], list)
assert "Sun" in functional["functional_benefics"]
assert "Venus" in functional["functional_malefics"]
assert "functional_benefic_malefic_used" in strict["event_judgement"]["secondary_context"]