diff --git a/docs/research/wealth_adjudicator_fourth_pass_yogi_hook_audit_2026_06_28.md b/docs/research/wealth_adjudicator_fourth_pass_yogi_hook_audit_2026_06_28.md new file mode 100644 index 00000000..22868d40 --- /dev/null +++ b/docs/research/wealth_adjudicator_fourth_pass_yogi_hook_audit_2026_06_28.md @@ -0,0 +1,83 @@ +# Wealth Adjudicator Fourth Pass Yogi Hook Audit (2026-06-28) + +## Scope + +This pass closes the smallest safe version of the external-truth Yogi hook for the finance adjudicator. + +Guardrail kept intact: + +- Do not calculate `yogi_planet` inside this repository. +- Only accept `external_truth.yogi_planet` from upstream truth sources. +- Only allow the hook to influence finance promise folding when the supplied Yogi planet passes a lightweight D1 placement filter. + +## Root Cause Found + +The first failing test was not exposing a product bug. It was exposing a bad fixture: + +- the test expected a Yogi uplift, +- but the supplied `Venus` was in house `11`, +- while the agreed lightweight gate only allows Kendra/Trikona houses: `1, 4, 5, 7, 9, 10`. + +So the finance pipeline was correctly rejecting the hook. + +## Implemented Behavior + +### Positive path + +When all of the following are true: + +1. `external_truth.yogi_planet` is present +2. matching D1 planet data is present in `modules.chart.planets` +3. the planet is in `1/4/5/7/9/10` +4. the status is not debilitated + +Then: + +- `present_evidence.yogi_promise` is populated +- `wealth_promise_strength.supporting_sources` gains `"yogi"` +- `source_diversity` increases accordingly +- `count` increases by `1` +- `primary_source` upgrades to `dhana_yogi_hooks` when the combined sources are `dhana + yogi` +- `secondary_context` gains `yogi_active` when a finance dominant label already exists + +### Negative path + +If the externally supplied Yogi planet falls outside Kendra/Trikona, the hook does nothing: + +- `yogi_promise = None` +- no wealth promise promotion +- no `yogi_active` secondary context + +## Regression Coverage + +Added / confirmed: + +1. Yogi hook activates only when external truth is present and D1 placement passes the filter. +2. Yogi hook does not activate for house `11`. +3. Existing finance adjudicator regressions remain green. + +## Verification + +Command: + +```bash +python3 -m pytest tests/test_mcp_strict_workflow_finance.py -q +``` + +Observed: + +- `9 passed` + +Manual evidence probe also confirmed: + +- house `10` case -> `dhana_yogi_hooks` + `yogi_active` +- house `11` case -> unchanged `dhana_yogas` promise fold and no Yogi secondary context + +## Next Recommended Step + +Keep the same non-destructive pattern and extend only one notch: + +1. allow externally supplied Yogi truth to enrich `wealth_promise_strength` +2. do not compute Yogi internally +3. next, add a very small `Yogi` source-aware bump only through source structure, not direct verdict jumping +4. after that, move to `dominant_label + secondary_context` refinement for finance edge cases diff --git a/mcp_server.py b/mcp_server.py index a0c84c04..3ec14b63 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -145,6 +145,34 @@ def _derive_wealth_promise_strength(modules: Dict[str, Any]) -> Optional[Dict[st } +def _check_yogi_promise(result: Dict[str, Any]) -> Optional[Dict[str, Any]]: + external_truth = result.get("external_truth") if isinstance(result, dict) else {} + yogi_planet = external_truth.get("yogi_planet") if isinstance(external_truth, dict) else None + if not yogi_planet: + return None + + modules = result.get("modules", {}) if isinstance(result, dict) else {} + chart = modules.get("chart") if isinstance(modules, dict) else {} + planets = chart.get("planets") if isinstance(chart, dict) else {} + planet_data = planets.get(yogi_planet) if isinstance(planets, dict) else None + if not isinstance(planet_data, dict): + return None + + house = planet_data.get("house") + status = str(planet_data.get("status", "")) + if house not in {1, 4, 5, 7, 9, 10}: + return None + if "落陷" in status or "Debilitated" in status: + return None + + return { + "planet": yogi_planet, + "house": house, + "status": status, + "source": "external_yogi_planet", + } + + def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[str]) -> Dict[str, Any]: if route == "relationship": score = 0 @@ -321,6 +349,7 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An } if route == "finance": + yogi_promise = _check_yogi_promise(result) required = [ "varga_full.D2_Hora", "varga_full.D10_Dasamsa", @@ -341,9 +370,22 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An "gains_convergence": domain_activations.get("gains_wishes"), "career_convergence": domain_activations.get("career_status"), "wealth_promise_strength": _derive_wealth_promise_strength(modules), + "yogi_promise": yogi_promise, } + if present["wealth_promise_strength"] and yogi_promise: + promise = dict(present["wealth_promise_strength"]) + supporting_sources = sorted(set((promise.get("supporting_sources") or []) + ["yogi"])) + promise["supporting_sources"] = supporting_sources + promise["source_diversity"] = len(supporting_sources) + promise["count"] = int(promise.get("count", 0)) + 1 + promise["primary_source"] = ( + "dhana_lakshmi_yogi_hooks" if len(supporting_sources) >= 3 + else "dhana_yogi_hooks" if "dhana" in supporting_sources and "yogi" in supporting_sources and len(supporting_sources) == 2 + else promise.get("primary_source") + ) + present["wealth_promise_strength"] = promise missing = [key for key, value in present.items() if key not in { - "gains_convergence", "career_convergence" + "gains_convergence", "career_convergence", "yogi_promise" } and value in (None, {}, [], "")] convergence_hits: List[Dict[str, Any]] = [ item for item in [ @@ -363,6 +405,8 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An else: confidence_cap = "medium-low" event_judgement = _derive_event_judgement(route, present, missing) + if yogi_promise and event_judgement.get("dominant_label") and "yogi_active" not in event_judgement.get("secondary_context", []): + event_judgement["secondary_context"] = event_judgement.get("secondary_context", []) + ["yogi_active"] return { "question_type": route, "required_evidence": required, diff --git a/tests/test_mcp_strict_workflow_finance.py b/tests/test_mcp_strict_workflow_finance.py index 68da21a8..4c7c08b4 100644 --- a/tests/test_mcp_strict_workflow_finance.py +++ b/tests/test_mcp_strict_workflow_finance.py @@ -215,3 +215,96 @@ def test_collect_strict_evidence_finance_combines_dhana_and_lakshmi_hooks() -> N "source_diversity": 2, "supporting_sources": ["dhana", "lakshmi"], } + + +def test_collect_strict_evidence_finance_adds_yogi_hook_only_when_external_truth_is_present() -> None: + result = { + "modules": { + "chart": { + "ascendant": {"sign": "Pisces", "lord": "Jupiter"}, + "planets": { + "Venus": { + "sign": "Capricorn", + "house": 10, + "status": "入友(Friendly Sign)", + } + }, + }, + "varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}}, + "shadbala": {"planets": {"Venus": {"total_rupa": 8.2}}}, + "ashtakavarga": {"house_scores": {"2": 31, "11": 36}}, + "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": "moderate"}], + "summary": "Dhana检测:共1个格局", + } + }, + }, + "external_truth": {"yogi_planet": "Venus"}, + } + + strict = _collect_strict_evidence("finance", result) + assert strict["present_evidence"]["wealth_promise_strength"] == { + "level": "moderate", + "primary_source": "dhana_yogi_hooks", + "count": 2, + "source_diversity": 2, + "supporting_sources": ["dhana", "yogi"], + } + assert "yogi_active" in strict["event_judgement"]["secondary_context"] + + +def test_collect_strict_evidence_finance_does_not_promote_yogi_outside_kendra_trikona() -> None: + result = { + "modules": { + "chart": { + "ascendant": {"sign": "Pisces", "lord": "Jupiter"}, + "planets": { + "Venus": { + "sign": "Capricorn", + "house": 11, + "status": "入友(Friendly Sign)", + } + }, + }, + "varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}}, + "shadbala": {"planets": {"Venus": {"total_rupa": 8.2}}}, + "ashtakavarga": {"house_scores": {"2": 31, "11": 36}}, + "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": "moderate"}], + "summary": "Dhana检测:共1个格局", + } + }, + }, + "external_truth": {"yogi_planet": "Venus"}, + } + + strict = _collect_strict_evidence("finance", result) + assert strict["present_evidence"]["yogi_promise"] is None + assert strict["present_evidence"]["wealth_promise_strength"] == { + "level": "moderate", + "primary_source": "dhana_yogas", + "count": 1, + "source_diversity": 1, + "supporting_sources": ["dhana"], + } + assert "yogi_active" not in strict["event_judgement"]["secondary_context"]