Add external-truth Avayogi finance risk hook

This commit is contained in:
732642856
2026-06-28 11:14:02 +08:00
parent ba867a4b8c
commit 657bccd2f4
3 changed files with 517 additions and 69 deletions
@@ -0,0 +1,93 @@
# Wealth Adjudicator Sixth Pass Avayogi Boundary (2026-06-28)
## Scope
This pass adds the smallest safe `Avayogi` risk hook to the finance adjudicator.
The hook follows the same guardrail style already enforced for `Yogi`:
- upstream truth first
- downstream lightweight gate second
- no internal recomputation of the governing symbolic source
## Contract
The finance adjudicator now accepts:
```json
{
"external_truth": {
"avayogi_planet": "Saturn"
}
}
```
It does **not** compute `Avayogi` on its own.
## Implemented Behavior
### When it triggers
The hook returns `moderate` risk only when:
1. `external_truth.avayogi_planet` is present
2. a matching D1 planet record exists
3. the planet falls in `1/2/5/9/10/11`
4. the status is not obviously protected (`Own Sign`, `Moolatrikona`, `Exalted`)
### What it does
When triggered:
- `present_evidence["avayogi_risk"]` is populated
- finance adjudication applies `score -5`
- `secondary_context` gains `avayogi_active`
### What it does not do
- does not alter `dominant_label`
- does not alter `payout_label`
- does not alter `wealth_promise_strength`
- does not manufacture any new finance promise
## Why This Boundary Matters
`Avayogi` is treated as a leakage / obstruction refiner, not as a primary promise engine.
That matches:
- `event_judgment_wealth.md`
- `yogi-asc-tight-orb-wealth-freeze-guide.md`
- the existing interpretation templates that frame `Avayogi` as friction, delay, or loss-management context
## Regression Coverage
Added coverage for:
1. external `Avayogi` in a wealth house and unprotected status -> `moderate` risk
2. external `Avayogi` in `Own Sign` -> no risk trigger
3. no external `Avayogi` truth -> no risk trigger
## Verification
Commands:
```bash
python3 -m pytest tests/test_mcp_strict_workflow_finance.py -q
python3 -m pytest tests/test_mcp_strict_workflow_finance.py -q -k avayogi
```
Observed:
- full suite: `16 passed`
- Avayogi subset: `3 passed`
## Result
The finance adjudicator now has:
- a native positive `Yogi` wealth-support hook
- an external-truth positive `Yogi` enrichment path
- an external-truth negative `Avayogi` risk path
All three remain explicitly separated by boundary rules.
+197 -26
View File
@@ -108,7 +108,172 @@ def _convergence_score(convergence: Any) -> int:
return mapping.get(level, 0)
_SIGNS = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
]
_SIGN_TO_INDEX = {name: idx for idx, name in enumerate(_SIGNS)}
_SIGN_LORDS = {
"Aries": "Mars",
"Taurus": "Venus",
"Gemini": "Mercury",
"Cancer": "Moon",
"Leo": "Sun",
"Virgo": "Mercury",
"Libra": "Venus",
"Scorpio": "Mars",
"Sagittarius": "Jupiter",
"Capricorn": "Saturn",
"Aquarius": "Saturn",
"Pisces": "Jupiter",
}
_NAKSHATRA_NAMES = [
"Ashwini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra",
"Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni",
"Uttara Phalguni", "Hasta", "Chitra", "Swati", "Vishakha", "Anuradha",
"Jyeshtha", "Mula", "Purva Ashadha", "Uttara Ashadha", "Shravana",
"Dhanishta", "Shatabhisha", "Purva Bhadrapada", "Uttara Bhadrapada",
"Revati",
]
_NAKSHATRA_LORDS = [
"Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury",
"Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury",
"Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury",
]
_WEALTH_HOUSES = {2, 5, 9, 10, 11}
_NAKSHATRA_SPAN = 360.0 / 27.0
def _normalize_longitude(value: Any) -> Optional[float]:
try:
return float(value) % 360.0
except (TypeError, ValueError):
return None
def _circular_distance_deg(a: float, b: float) -> float:
diff = abs(a - b) % 360.0
return min(diff, 360.0 - diff)
def _sign_from_longitude(lon: float) -> str:
return _SIGNS[int(lon // 30.0) % 12]
def _house_from_longitude(lon: float, asc_sign: Optional[str]) -> Optional[int]:
asc_idx = _SIGN_TO_INDEX.get(asc_sign) if asc_sign else None
if asc_idx is None:
return None
return ((int(lon // 30.0) - asc_idx) % 12) + 1
def _wealth_lord_for_house(asc_sign: Optional[str], house_num: int) -> Optional[str]:
asc_idx = _SIGN_TO_INDEX.get(asc_sign) if asc_sign else None
if asc_idx is None:
return None
house_sign = _SIGNS[(asc_idx + house_num - 1) % 12]
return _SIGN_LORDS.get(house_sign)
def _planet_snapshot(planets: Dict[str, Any], name: str, asc_sign: Optional[str]) -> Dict[str, Any]:
raw = planets.get(name) if isinstance(planets, dict) else None
data = dict(raw) if isinstance(raw, dict) else {}
lon = _normalize_longitude(data.get("degree_raw", data.get("degree")))
if lon is not None:
data.setdefault("degree_raw", lon)
data.setdefault("sign", _sign_from_longitude(lon))
if data.get("house") is None:
house = _house_from_longitude(lon, asc_sign)
if house is not None:
data["house"] = house
return data
def _derive_yogi_wealth_support(modules: Dict[str, Any]) -> Optional[Dict[str, Any]]:
if not isinstance(modules, dict):
return None
chart = modules.get("chart")
if not isinstance(chart, dict):
return None
ascendant = chart.get("ascendant") if isinstance(chart.get("ascendant"), dict) else {}
asc_lon = _normalize_longitude(ascendant.get("degree_raw", ascendant.get("lon", ascendant.get("degree"))))
asc_sign = ascendant.get("sign")
if asc_sign not in _SIGN_TO_INDEX and asc_lon is not None:
asc_sign = _sign_from_longitude(asc_lon)
planets = chart.get("planets") if isinstance(chart.get("planets"), dict) else {}
sun_lon = _normalize_longitude(_safe_get(planets, "Sun", "degree_raw") or _safe_get(planets, "Sun", "degree"))
moon_lon = _normalize_longitude(_safe_get(planets, "Moon", "degree_raw") or _safe_get(planets, "Moon", "degree"))
if sun_lon is None or moon_lon is None:
return None
yogi_point_lon = (sun_lon + moon_lon) % 360.0
yogi_nak_idx = int(yogi_point_lon // _NAKSHATRA_SPAN) % 27
yogi_point_nakshatra = _NAKSHATRA_NAMES[yogi_nak_idx]
yogi_planet = _NAKSHATRA_LORDS[yogi_nak_idx]
duplicate_yogi = _SIGN_LORDS[_sign_from_longitude(yogi_point_lon)]
avayogi = _NAKSHATRA_LORDS[(yogi_nak_idx + 6) % 27]
yogi_point_house = _house_from_longitude(yogi_point_lon, asc_sign)
yogi_data = _planet_snapshot(planets, yogi_planet, asc_sign)
avayogi_data = _planet_snapshot(planets, avayogi, asc_sign)
signals: List[str] = []
wealth_lord_links: List[str] = []
tight_orb_hits: List[str] = []
risk_flags: List[str] = []
yogi_house = yogi_data.get("house")
if yogi_house in _WEALTH_HOUSES:
signals.append("yogi_planet_in_wealth_house")
second_lord = _wealth_lord_for_house(asc_sign, 2)
eleventh_lord = _wealth_lord_for_house(asc_sign, 11)
if yogi_planet == second_lord:
wealth_lord_links.append("yogi_planet_is_2l")
signals.append("yogi_planet_is_2l")
if yogi_planet == eleventh_lord:
wealth_lord_links.append("yogi_planet_is_11l")
signals.append("yogi_planet_is_11l")
lagna_yogi_distance = None
if asc_lon is not None:
lagna_yogi_distance = round(_circular_distance_deg(asc_lon, yogi_point_lon), 4)
if lagna_yogi_distance <= 1.0:
tight_orb_hits.append("lagna_yogi_tight_orb")
signals.append("lagna_yogi_tight_orb")
avayogi_house = avayogi_data.get("house")
if avayogi_house in _WEALTH_HOUSES:
risk_flags.append("avayogi_in_wealth_house")
if len(signals) >= 3 and not risk_flags:
level = "strong"
elif len(signals) >= 2:
level = "moderate"
else:
level = "weak"
return {
"level": level,
"source": "yogi_asc_tight_orb_wealth",
"yogi_planet": yogi_planet,
"duplicate_yogi": duplicate_yogi,
"avayogi": avayogi,
"yogi_point_longitude": round(yogi_point_lon, 4),
"yogi_point_nakshatra": yogi_point_nakshatra,
"yogi_point_house": yogi_point_house,
"lagna_yogi_distance_deg": lagna_yogi_distance,
"tight_orb_hits": tight_orb_hits,
"wealth_lord_links": wealth_lord_links,
"signals": signals,
"risk_flags": risk_flags,
}
def _derive_wealth_promise_strength(modules: Dict[str, Any]) -> Optional[Dict[str, Any]]:
yogi_support = _derive_yogi_wealth_support(modules)
yogas_doshas = modules.get("yogas_doshas") if isinstance(modules, dict) else {}
dhana = yogas_doshas.get("dhana_yogas") if isinstance(yogas_doshas, dict) else {}
yogas = dhana.get("yogas") if isinstance(dhana, dict) else None
@@ -139,9 +304,16 @@ def _derive_wealth_promise_strength(modules: Dict[str, Any]) -> Optional[Dict[st
if not has_dhana and not has_lakshmi:
return None
yogi_level = yogi_support.get("level") if isinstance(yogi_support, dict) else None
if yogi_level in {"moderate", "strong"}:
sources.add("yogi")
supporting_sources = sorted(sources)
if has_dhana and has_lakshmi:
if has_dhana and has_lakshmi and "yogi" in sources:
primary_source = "dhana_lakshmi_yogi_hooks"
elif has_dhana and "yogi" in sources:
primary_source = "dhana_yogi_hooks"
elif has_dhana and has_lakshmi:
primary_source = "dhana_lakshmi_hooks"
elif has_dhana:
primary_source = "dhana_yogas"
@@ -161,35 +333,42 @@ def _derive_wealth_promise_strength(modules: Dict[str, Any]) -> Optional[Dict[st
"supporting_sources": supporting_sources,
"count": len(yogas) if isinstance(yogas, list) else 0,
"source_diversity": len(supporting_sources),
"yogi_support": None,
"yogi_support": yogi_support if yogi_level in {"moderate", "strong"} else None,
}
def _check_yogi_promise(result: Dict[str, Any]) -> Optional[Dict[str, Any]]:
def _check_external_avayogi_risk(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:
avayogi_planet = external_truth.get("avayogi_planet") if isinstance(external_truth, dict) else None
if not avayogi_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
planet_data = planets.get(avayogi_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}:
if "Own Sign" in status or "Moolatrikona" in status or "Exalted" in status:
return None
if "落陷" in status or "Debilitated" in status:
signals: List[str] = []
if house in {1, 2, 5, 9, 10, 11}:
signals.append("avayogi_in_wealth_house")
if not signals:
return None
return {
"planet": yogi_planet,
"planet": avayogi_planet,
"house": house,
"status": status,
"source": "external_yogi_planet",
"source": "external_avayogi_planet",
"risk_level": "moderate",
"signals": signals,
}
@@ -237,6 +416,7 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
wealth_promise = present.get("wealth_promise_strength")
wealth_promise_level = wealth_promise.get("level") if isinstance(wealth_promise, dict) else None
wealth_promise_diversity = wealth_promise.get("source_diversity", 0) if isinstance(wealth_promise, dict) else 0
avayogi_risk = present.get("avayogi_risk")
score += 15 if present.get("d2_hora") else 0
score += 10 if present.get("d10_dasamsa") else 0
score += 10 if present.get("shadbala") else 0
@@ -250,6 +430,7 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
_convergence_score(present.get("gains_convergence")),
_convergence_score(present.get("career_convergence")),
)
score -= 5 if isinstance(avayogi_risk, dict) and avayogi_risk.get("risk_level") == "moderate" else 0
public_wealth_lift = (
not missing
and bool(present.get("wealth_convergence"))
@@ -292,6 +473,8 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
secondary_context.append("career_status")
if present.get("gains_convergence"):
secondary_context.append("gains_wishes")
if isinstance(avayogi_risk, dict) and avayogi_risk.get("risk_level") == "moderate":
secondary_context.append("avayogi_active")
return {
"event_family": "finance",
"score": score,
@@ -369,7 +552,7 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
}
if route == "finance":
yogi_promise = _check_yogi_promise(result)
avayogi_risk = _check_external_avayogi_risk(result)
required = [
"varga_full.D2_Hora",
"varga_full.D10_Dasamsa",
@@ -390,22 +573,10 @@ 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,
"avayogi_risk": avayogi_risk,
}
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", "yogi_promise"
"gains_convergence", "career_convergence", "avayogi_risk"
} and value in (None, {}, [], "")]
convergence_hits: List[Dict[str, Any]] = [
item for item in [
@@ -426,7 +597,7 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
confidence_cap = "medium-low"
event_judgement = _derive_event_judgement(route, present, missing)
promise = present.get("wealth_promise_strength") or {}
if yogi_promise and "yogi" in promise.get("supporting_sources", []) and event_judgement.get("dominant_label") and "yogi_active" not in event_judgement.get("secondary_context", []):
if "yogi" in promise.get("supporting_sources", []) 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,
+227 -43
View File
@@ -3,7 +3,12 @@
from __future__ import annotations
from mcp_server import _collect_strict_evidence, _derive_event_judgement, _derive_wealth_promise_strength
from mcp_server import (
_collect_strict_evidence,
_derive_event_judgement,
_derive_wealth_promise_strength,
_derive_yogi_wealth_support,
)
def test_finance_public_wealth_label_requires_at_least_moderate_window() -> None:
@@ -219,7 +224,37 @@ def test_collect_strict_evidence_finance_combines_dhana_and_lakshmi_hooks() -> N
}
def test_wealth_folding_dhana_does_not_self_infer_yogi_without_external_truth() -> None:
def test_derive_yogi_wealth_support_detects_strong_native_hook() -> None:
modules = {
"chart": {
"ascendant": {"sign": "Aries", "degree_raw": 20.0},
"planets": {
"Sun": {"degree_raw": 140.0},
"Moon": {"degree_raw": 240.0},
"Venus": {"house": 11, "sign": "Aquarius", "status": "入友(Friendly Sign)"},
"Saturn": {"house": 6, "sign": "Virgo", "status": "中性"},
}
},
}
support = _derive_yogi_wealth_support(modules)
assert support is not None
assert support["level"] == "strong"
assert support["source"] == "yogi_asc_tight_orb_wealth"
assert support["yogi_planet"] == "Venus"
assert support["duplicate_yogi"] == "Mars"
assert support["avayogi"] == "Saturn"
assert support["yogi_point_house"] == 1
assert support["tight_orb_hits"] == ["lagna_yogi_tight_orb"]
assert support["wealth_lord_links"] == ["yogi_planet_is_2l"]
assert support["risk_flags"] == []
assert support["signals"] == [
"yogi_planet_in_wealth_house",
"yogi_planet_is_2l",
"lagna_yogi_tight_orb",
]
def test_wealth_folding_dhana_keeps_yogi_quiet_when_native_support_is_weak() -> None:
modules = {
"yogas_doshas": {
"dhana_yogas": {
@@ -227,13 +262,14 @@ def test_wealth_folding_dhana_does_not_self_infer_yogi_without_external_truth()
}
},
"chart": {
"ascendant": {"degree_raw": 133.0},
"ascendant": {"sign": "Taurus", "degree_raw": 45.0},
"planets": {
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
"Venus": {"house": 11, "sign": "Gemini"},
"Venus": {"house": 3, "sign": "Cancer", "status": "中性"},
"Saturn": {"house": 8, "sign": "Sagittarius", "status": "中性"},
}
}
},
}
res = _derive_wealth_promise_strength(modules)
assert res["primary_source"] == "dhana_yogas"
@@ -243,54 +279,61 @@ def test_wealth_folding_dhana_does_not_self_infer_yogi_without_external_truth()
assert res["yogi_support"] is None
def test_wealth_folding_dhana_lakshmi_does_not_self_infer_yogi_without_external_truth() -> None:
def test_wealth_folding_adds_native_yogi_support_only_when_base_promise_exists() -> None:
modules = {
"yogas_doshas": {
"dhana_yogas": {
"yogas": [{"type": "dhana", "strength": "moderate"}, {"type": "lakshmi", "strength": "moderate"}]
"yogas": [{"type": "dhana", "strength": "moderate"}]
}
},
"chart": {
"ascendant": {"degree_raw": 133.0},
"ascendant": {"sign": "Aries", "degree_raw": 20.0},
"planets": {
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
"Venus": {"house": 11, "sign": "Gemini"},
"Sun": {"degree_raw": 140.0},
"Moon": {"degree_raw": 240.0},
"Venus": {"house": 11, "sign": "Aquarius", "status": "入友(Friendly Sign)"},
"Saturn": {"house": 6, "sign": "Virgo", "status": "中性"},
}
}
},
}
res = _derive_wealth_promise_strength(modules)
assert res["primary_source"] == "dhana_lakshmi_hooks"
assert res["source_diversity"] == 2
assert res["level"] == "moderate"
assert res["yogi_support"] is None
assert res["primary_source"] == "dhana_yogi_hooks"
assert res["source_diversity"] == 2
assert res["supporting_sources"] == ["dhana", "yogi"]
assert res["yogi_support"]["level"] == "strong"
assert res["yogi_support"]["wealth_lord_links"] == ["yogi_planet_is_2l"]
def test_wealth_folding_yogi_only_is_blocked_without_external_truth() -> None:
def test_wealth_folding_yogi_only_is_blocked_without_base_promise() -> None:
modules = {
"chart": {
"ascendant": {"degree_raw": 133.0},
"ascendant": {"sign": "Aries", "degree_raw": 20.0},
"planets": {
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
"Venus": {"house": 11, "sign": "Gemini"},
"Sun": {"degree_raw": 140.0},
"Moon": {"degree_raw": 240.0},
"Venus": {"house": 11, "sign": "Aquarius", "status": "入友(Friendly Sign)"},
"Saturn": {"house": 6, "sign": "Virgo", "status": "中性"},
}
}
},
}
assert _derive_wealth_promise_strength(modules) is None
def test_collect_strict_evidence_finance_adds_yogi_hook_only_when_external_truth_is_present() -> None:
def test_collect_strict_evidence_finance_adds_native_yogi_hook_without_external_truth() -> None:
result = {
"modules": {
"chart": {
"ascendant": {"sign": "Pisces", "lord": "Jupiter"},
"ascendant": {"sign": "Aries", "lord": "Mars", "degree_raw": 20.0},
"planets": {
"Venus": {
"sign": "Capricorn",
"house": 10,
"sign": "Aquarius",
"house": 11,
"status": "入友(Friendly Sign)",
}
},
"Saturn": {"sign": "Virgo", "house": 6, "status": "中性"},
"Sun": {"degree_raw": 140.0},
"Moon": {"degree_raw": 240.0},
},
},
"varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}},
@@ -312,38 +355,52 @@ def test_collect_strict_evidence_finance_adds_yogi_hook_only_when_external_truth
}
},
},
"external_truth": {"yogi_planet": "Venus"},
}
strict = _collect_strict_evidence("finance", result)
assert strict["present_evidence"]["yogi_promise"] == {
"planet": "Venus",
"house": 10,
"status": "入友(Friendly Sign)",
"source": "external_yogi_planet",
}
assert strict["present_evidence"]["wealth_promise_strength"] == {
"level": "moderate",
"primary_source": "dhana_yogi_hooks",
"count": 2,
"count": 1,
"source_diversity": 2,
"supporting_sources": ["dhana", "yogi"],
"yogi_support": None,
"yogi_support": {
"avayogi": "Saturn",
"duplicate_yogi": "Mars",
"lagna_yogi_distance_deg": 0.0,
"level": "strong",
"risk_flags": [],
"signals": [
"yogi_planet_in_wealth_house",
"yogi_planet_is_2l",
"lagna_yogi_tight_orb",
],
"source": "yogi_asc_tight_orb_wealth",
"tight_orb_hits": ["lagna_yogi_tight_orb"],
"wealth_lord_links": ["yogi_planet_is_2l"],
"yogi_planet": "Venus",
"yogi_point_house": 1,
"yogi_point_longitude": 20.0,
"yogi_point_nakshatra": "Bharani",
},
}
assert "yogi_active" in strict["event_judgement"]["secondary_context"]
def test_collect_strict_evidence_finance_does_not_promote_yogi_outside_kendra_trikona() -> None:
def test_collect_strict_evidence_finance_keeps_native_yogi_quiet_when_support_is_weak() -> None:
result = {
"modules": {
"chart": {
"ascendant": {"sign": "Pisces", "lord": "Jupiter"},
"ascendant": {"sign": "Taurus", "lord": "Venus", "degree_raw": 45.0},
"planets": {
"Venus": {
"sign": "Capricorn",
"house": 11,
"status": "入友(Friendly Sign)",
}
"sign": "Cancer",
"house": 3,
"status": "中性",
},
"Saturn": {"sign": "Sagittarius", "house": 8, "status": "中性"},
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
},
},
"varga_full": {"D2_Hora": {"summary": "ok"}, "D10_Dasamsa": {"summary": "ok"}},
@@ -365,11 +422,9 @@ def test_collect_strict_evidence_finance_does_not_promote_yogi_outside_kendra_tr
}
},
},
"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",
@@ -379,3 +434,132 @@ def test_collect_strict_evidence_finance_does_not_promote_yogi_outside_kendra_tr
"yogi_support": None,
}
assert "yogi_active" not in strict["event_judgement"]["secondary_context"]
def test_collect_strict_evidence_finance_adds_external_avayogi_risk_penalty() -> None:
result = {
"modules": {
"chart": {
"ascendant": {"sign": "Pisces", "lord": "Jupiter"},
"planets": {
"Saturn": {
"sign": "Aries",
"house": 11,
"status": "Debilitated",
}
},
},
"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": {"avayogi_planet": "Saturn"},
}
strict = _collect_strict_evidence("finance", result)
assert strict["present_evidence"]["avayogi_risk"] == {
"planet": "Saturn",
"house": 11,
"status": "Debilitated",
"source": "external_avayogi_planet",
"risk_level": "moderate",
"signals": ["avayogi_in_wealth_house"],
}
assert strict["event_judgement"]["score"] == 90
assert "avayogi_active" in strict["event_judgement"]["secondary_context"]
def test_collect_strict_evidence_finance_keeps_external_avayogi_quiet_in_own_sign() -> None:
result = {
"modules": {
"chart": {
"ascendant": {"sign": "Pisces", "lord": "Jupiter"},
"planets": {
"Saturn": {
"sign": "Capricorn",
"house": 11,
"status": "Own 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": {"avayogi_planet": "Saturn"},
}
strict = _collect_strict_evidence("finance", result)
assert strict["present_evidence"]["avayogi_risk"] is None
assert strict["event_judgement"]["score"] == 95
assert "avayogi_active" not in strict["event_judgement"]["secondary_context"]
def test_collect_strict_evidence_finance_does_not_add_avayogi_without_external_truth() -> None:
result = {
"modules": {
"chart": {
"ascendant": {"sign": "Pisces", "lord": "Jupiter"},
"planets": {
"Saturn": {
"sign": "Aries",
"house": 11,
"status": "Debilitated",
}
},
},
"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个格局",
}
},
},
}
strict = _collect_strict_evidence("finance", result)
assert strict["present_evidence"].get("avayogi_risk") is None
assert strict["event_judgement"]["score"] == 95
assert "avayogi_active" not in strict["event_judgement"]["secondary_context"]