diff --git a/docs/research/ashtakavarga_deep_support_bridges_v1_2026_06_28.md b/docs/research/ashtakavarga_deep_support_bridges_v1_2026_06_28.md new file mode 100644 index 00000000..fb2b1db5 --- /dev/null +++ b/docs/research/ashtakavarga_deep_support_bridges_v1_2026_06_28.md @@ -0,0 +1,54 @@ +# Ashtakavarga Deep Support Bridges v1 + +Date: 2026-06-28 + +## Scope + +This pass extends finance strict adjudication beyond raw SAV house support and connects three already-computed evidence layers: + +- `PAV / Prastara AV` +- `Sodhita AV` +- `Kakshya` + +The bridge is intentionally conservative. All three layers are added as secondary support or friction context only. + +## Contract + +Finance strict evidence now accepts: + +- `present_evidence.pav_finance_support` +- `present_evidence.sodhita_finance_support` +- `present_evidence.kakshya_finance_support` + +These can add: + +- `pav_finance_support` +- `sodhita_wealth_friction` +- `kakshya_finance_support` +- `kakshya_finance_friction` + +to `event_judgement.secondary_context`. + +## Boundary + +This v1 pass does not: + +- directly change `dominant_label` +- directly change `payout_label` +- directly change score thresholds + +The goal is to make these layers visible and auditable in the adjudication chain before we let them influence window scoring. + +## Current Heuristics + +- `PAV`: marks support when a planet shows multiple strong contribution sources. +- `Sodhita`: marks friction when both 2H and 11H resolve to low purified support. +- `Kakshya`: marks support when average degree-level strength is high and friction when it is low. + +## Still Open + +- route-specific score weighting +- richer mapping from PAV contributor identity to wealth theme +- combining Kakshya with transit timing instead of route-level average only +- external benchmark closure against JHora/PyJHora examples + diff --git a/mcp_server.py b/mcp_server.py index 6467dd44..31c16d46 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -569,6 +569,93 @@ def _derive_shadbala_component_audit(planets: Any) -> Dict[str, Any]: return audit +def _derive_pav_finance_support(ashtakavarga: Any) -> Dict[str, Any]: + base = { + "level": "none", + "source": "ashtakavarga_pav_bridge_v1", + "signals": [], + "top_planets": [], + } + pav_summary = _safe_get(ashtakavarga, "pav", "pav_summary") + if not isinstance(pav_summary, dict): + return base + + top_planets: List[str] = [] + for planet, source_scores in pav_summary.items(): + if not isinstance(source_scores, dict): + continue + high_sources = [ + source for source, bindus in source_scores.items() + if isinstance(bindus, (int, float)) and bindus >= 5 + ] + if high_sources: + top_planets.append(str(planet)) + + if top_planets: + base["level"] = "supportive" + base["signals"] = ["pav_finance_support"] + base["top_planets"] = sorted(top_planets) + return base + + +def _derive_sodhita_finance_support(ashtakavarga: Any, asc_sign: Optional[str]) -> Dict[str, Any]: + base = { + "level": "none", + "source": "ashtakavarga_sodhita_bridge_v1", + "signals": [], + "target_houses": [2, 11], + "raw_scores": {}, + } + assessment = _safe_get(ashtakavarga, "sodhita", "sodhita_sav", "assessment") + asc_idx = _SIGN_TO_INDEX.get(asc_sign) if asc_sign else None + if not isinstance(assessment, list) or asc_idx is None: + return base + + sign_to_score: Dict[str, float] = {} + for row in assessment: + if not isinstance(row, dict): + continue + sign = row.get("sign") + score = row.get("score") + if sign in _SIGN_TO_INDEX and isinstance(score, (int, float)): + sign_to_score[str(sign)] = float(score) + + raw_scores: Dict[str, Any] = {} + numeric_scores: Dict[int, float] = {} + for house in (2, 11): + sign = _SIGNS[(asc_idx + house - 1) % 12] + if sign in sign_to_score: + numeric_scores[house] = sign_to_score[sign] + value = sign_to_score[sign] + raw_scores[str(house)] = int(value) if value.is_integer() else value + + base["raw_scores"] = raw_scores + if len(numeric_scores) == 2 and max(numeric_scores.values()) <= 19: + base["level"] = "obstructive" + base["signals"] = ["sodhita_wealth_friction"] + return base + + +def _derive_kakshya_finance_support(kakshya: Any) -> Dict[str, Any]: + base = { + "level": "none", + "source": "kakshya_finance_bridge_v1", + "signals": [], + "average_strength": None, + } + avg = _safe_get(kakshya, "summary", "average_strength") + if not isinstance(avg, (int, float)): + return base + base["average_strength"] = float(avg) + if avg >= 6.5: + base["level"] = "supportive" + base["signals"] = ["kakshya_finance_support"] + elif avg <= 4.5: + base["level"] = "obstructive" + base["signals"] = ["kakshya_finance_friction"] + return base + + def _sign_to_index(sign: str) -> Optional[int]: try: return _SIGNS.index(sign) @@ -939,6 +1026,17 @@ 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") + pav_finance_support = present.get("pav_finance_support") or {} + if pav_finance_support.get("level") == "supportive": + secondary_context.append("pav_finance_support") + sodhita_finance_support = present.get("sodhita_finance_support") or {} + if sodhita_finance_support.get("level") == "obstructive": + secondary_context.append("sodhita_wealth_friction") + kakshya_finance_support = present.get("kakshya_finance_support") or {} + if kakshya_finance_support.get("level") == "supportive": + secondary_context.append("kakshya_finance_support") + elif kakshya_finance_support.get("level") == "obstructive": + secondary_context.append("kakshya_finance_friction") if ashtakavarga_finance.get("level") == "supportive": secondary_context.append("ashtakavarga_wealth_support") elif ashtakavarga_finance.get("level") == "obstructive": @@ -1122,14 +1220,21 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An "wealth_promise_strength": _derive_wealth_promise_strength(modules), "avayogi_risk": avayogi_risk, } + present["asc_sign"] = _safe_get(modules, "chart", "ascendant", "sign") present["shadbala_component_audit"] = _derive_shadbala_component_audit(present["shadbala"]) present["ashtakavarga_finance_support"] = _derive_ashtakavarga_finance_support( present["ashtakavarga_house_scores"] ) + present["pav_finance_support"] = _derive_pav_finance_support(_safe_get(modules, "ashtakavarga")) + present["sodhita_finance_support"] = _derive_sodhita_finance_support( + _safe_get(modules, "ashtakavarga"), + present["asc_sign"], + ) + 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) 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" + "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" } and value in (None, {}, [], "")] convergence_hits: List[Dict[str, Any]] = [ item for item in [ diff --git a/tests/test_mcp_strict_workflow_finance.py b/tests/test_mcp_strict_workflow_finance.py index b3e80aaa..42ae7ac7 100644 --- a/tests/test_mcp_strict_workflow_finance.py +++ b/tests/test_mcp_strict_workflow_finance.py @@ -314,6 +314,196 @@ def test_collect_strict_evidence_finance_accepts_complete_shadbala_components() assert "shadbala_component_gap" not in strict["event_judgement"]["secondary_context"] +def test_collect_strict_evidence_finance_adds_pav_and_kakshya_support_as_secondary_context() -> None: + result = { + "modules": { + "chart": {"ascendant": {"sign": "Aries"}}, + "varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}}, + "shadbala": { + "planets": { + "Venus": { + "components": { + "sthana": 1.2, + "dig": 0.8, + "kala": 1.1, + "chesta": 0.9, + "naisargika": 0.7, + "drik": 0.4, + }, + "total_rupa": 5.1, + } + } + }, + "ashtakavarga": { + "house_scores": {"2": 33, "11": 35}, + "pav": { + "pav_summary": { + "Venus": {"Jupiter": 5, "Venus": 6, "Lagna": 5}, + "Saturn": {"Saturn": 2}, + } + }, + }, + "kakshya": { + "summary": {"average_strength": 6.8}, + "planets": { + "Venus": {"kakshya_strength": 7.4}, + "Jupiter": {"kakshya_strength": 6.9}, + }, + }, + "dasha": {"current_dasha": {"mahadasha": "Venus", "antardasha": "Mercury"}}, + "narayana_dasha": {"current_dasha": {"sign": "Taurus", "lord": "Venus"}}, + "dasa_convergence": { + "domain_activations": { + "wealth_family": {"convergence_level": "L1", "probability": "+15-20%"}, + "gains_wishes": {"convergence_level": "L1", "probability": "+15-20%"}, + "career_status": {"convergence_level": "L1", "probability": "+15-20%"}, + } + }, + "yogas_doshas": { + "dhana_yogas": { + "yogas": [{"type": "Dhana Yoga", "strength": "strong"}], + "summary": "Dhana Yoga检测:共1个格局", + } + }, + } + } + + strict = _collect_strict_evidence("finance", result) + + assert strict["present_evidence"]["pav_finance_support"] == { + "level": "supportive", + "source": "ashtakavarga_pav_bridge_v1", + "signals": ["pav_finance_support"], + "top_planets": ["Venus"], + } + assert strict["present_evidence"]["kakshya_finance_support"] == { + "level": "supportive", + "source": "kakshya_finance_bridge_v1", + "signals": ["kakshya_finance_support"], + "average_strength": 6.8, + } + assert "pav_finance_support" in strict["event_judgement"]["secondary_context"] + assert "kakshya_finance_support" in strict["event_judgement"]["secondary_context"] + + +def test_collect_strict_evidence_finance_flags_sodhita_wealth_friction() -> None: + result = { + "modules": { + "chart": {"ascendant": {"sign": "Aries"}}, + "varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}}, + "shadbala": { + "planets": { + "Venus": { + "components": { + "sthana": 1.2, + "dig": 0.8, + "kala": 1.1, + "chesta": 0.9, + "naisargika": 0.7, + "drik": 0.4, + }, + "total_rupa": 5.1, + } + } + }, + "ashtakavarga": { + "house_scores": {"2": 33, "11": 35}, + "sodhita": { + "sodhita_sav": { + "assessment": [ + {"sign": "Taurus", "score": 18, "level": "挑战"}, + {"sign": "Aquarius", "score": 17, "level": "挑战"}, + ] + } + }, + }, + "dasha": {"current_dasha": {"mahadasha": "Venus", "antardasha": "Mercury"}}, + "narayana_dasha": {"current_dasha": {"sign": "Taurus", "lord": "Venus"}}, + "dasa_convergence": { + "domain_activations": { + "wealth_family": {"convergence_level": "L1", "probability": "+15-20%"}, + "gains_wishes": {"convergence_level": "L1", "probability": "+15-20%"}, + "career_status": {"convergence_level": "L1", "probability": "+15-20%"}, + } + }, + "yogas_doshas": { + "dhana_yogas": { + "yogas": [{"type": "Dhana Yoga", "strength": "strong"}], + "summary": "Dhana Yoga检测:共1个格局", + } + }, + } + } + + strict = _collect_strict_evidence("finance", result) + + assert strict["present_evidence"]["sodhita_finance_support"] == { + "level": "obstructive", + "source": "ashtakavarga_sodhita_bridge_v1", + "signals": ["sodhita_wealth_friction"], + "target_houses": [2, 11], + "raw_scores": {"2": 18, "11": 17}, + } + assert "sodhita_wealth_friction" in strict["event_judgement"]["secondary_context"] + + +def test_collect_strict_evidence_finance_flags_kakshya_friction_as_secondary_only() -> None: + result = { + "modules": { + "chart": {"ascendant": {"sign": "Aries"}}, + "varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}}, + "shadbala": { + "planets": { + "Venus": { + "components": { + "sthana": 1.2, + "dig": 0.8, + "kala": 1.1, + "chesta": 0.9, + "naisargika": 0.7, + "drik": 0.4, + }, + "total_rupa": 5.1, + } + } + }, + "ashtakavarga": {"house_scores": {"2": 33, "11": 35}}, + "kakshya": { + "summary": {"average_strength": 4.1}, + "planets": { + "Venus": {"kakshya_strength": 4.0}, + }, + }, + "dasha": {"current_dasha": {"mahadasha": "Venus", "antardasha": "Mercury"}}, + "narayana_dasha": {"current_dasha": {"sign": "Taurus", "lord": "Venus"}}, + "dasa_convergence": { + "domain_activations": { + "wealth_family": {"convergence_level": "L1", "probability": "+15-20%"}, + "gains_wishes": {"convergence_level": "L1", "probability": "+15-20%"}, + "career_status": {"convergence_level": "L1", "probability": "+15-20%"}, + } + }, + "yogas_doshas": { + "dhana_yogas": { + "yogas": [{"type": "Dhana Yoga", "strength": "strong"}], + "summary": "Dhana Yoga检测:共1个格局", + } + }, + } + } + + strict = _collect_strict_evidence("finance", result) + + assert strict["present_evidence"]["kakshya_finance_support"] == { + "level": "obstructive", + "source": "kakshya_finance_bridge_v1", + "signals": ["kakshya_finance_friction"], + "average_strength": 4.1, + } + assert strict["event_judgement"]["dominant_label"] == "public_wealth_status" + assert "kakshya_finance_friction" in strict["event_judgement"]["secondary_context"] + + def test_collect_strict_evidence_finance_flags_low_wealth_ashtakavarga_friction() -> None: result = { "modules": {