fix(relationship): freeze marriage event classes and add conditional dashas (BUG-608)
Venus/Jupiter/Moon periods no longer collapse into one marriage-opportunity line. Full reading reports the ten BPHS conditional dasha families; Skill 6.9.16. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Punarphoo observation detector.
|
||||
|
||||
Natal rule: Moon-Saturn conjunction with circular orb <= 10°.
|
||||
Timing observation: transiting Saturn returning to natal Moon.
|
||||
|
||||
Claim boundary (hard):
|
||||
- This module is observation-only.
|
||||
- Punarphoo natal presence or Saturn-return-to-natal-Moon is not marriage.
|
||||
- Do not promote to truth / legal_marriage before negative holdout passes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
PUNARPHOO_ORB_DEG = 10.0
|
||||
SATURN_RETURN_TO_MOON_ORB_DEG = 1.0
|
||||
CLAIM_BOUNDARY = (
|
||||
"Punarphoo return is not marriage. Observation only; "
|
||||
"negative holdout not passed; do not promote to truth."
|
||||
)
|
||||
|
||||
|
||||
def circular_degree_gap(a: float, b: float) -> float:
|
||||
gap = abs(float(a) - float(b)) % 360.0
|
||||
return min(gap, 360.0 - gap)
|
||||
|
||||
|
||||
def _longitude(pdata: Any) -> Optional[float]:
|
||||
if not isinstance(pdata, dict):
|
||||
return None
|
||||
for key in ("longitude", "lon", "degree"):
|
||||
value = pdata.get(key)
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value)
|
||||
return None
|
||||
|
||||
|
||||
def detect_natal_punarphoo(planets: Dict[str, Any] | None, orb_limit: float = PUNARPHOO_ORB_DEG) -> Dict[str, Any]:
|
||||
planets = planets if isinstance(planets, dict) else {}
|
||||
moon = planets.get("Moon") if isinstance(planets.get("Moon"), dict) else {}
|
||||
saturn = planets.get("Saturn") if isinstance(planets.get("Saturn"), dict) else {}
|
||||
moon_lon = _longitude(moon)
|
||||
saturn_lon = _longitude(saturn)
|
||||
same_house = moon.get("house") is not None and moon.get("house") == saturn.get("house")
|
||||
orb = circular_degree_gap(moon_lon, saturn_lon) if moon_lon is not None and saturn_lon is not None else None
|
||||
present = orb is not None and orb <= orb_limit
|
||||
return {
|
||||
"status": "observation_only",
|
||||
"natal_present": present,
|
||||
"orb_deg": orb,
|
||||
"orb_limit_deg": orb_limit,
|
||||
"same_house": bool(same_house),
|
||||
"moon_house": moon.get("house"),
|
||||
"saturn_house": saturn.get("house"),
|
||||
"claim_boundary": CLAIM_BOUNDARY,
|
||||
"source": "punarphoo_observation_v1",
|
||||
"production_tuning_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def detect_saturn_return_to_natal_moon(
|
||||
natal_moon_longitude: Optional[float],
|
||||
transit_saturn_longitude: Optional[float],
|
||||
orb_limit: float = SATURN_RETURN_TO_MOON_ORB_DEG,
|
||||
) -> Dict[str, Any]:
|
||||
if natal_moon_longitude is None or transit_saturn_longitude is None:
|
||||
return {
|
||||
"status": "observation_only",
|
||||
"active": False,
|
||||
"orb_deg": None,
|
||||
"orb_limit_deg": orb_limit,
|
||||
"claim_boundary": CLAIM_BOUNDARY,
|
||||
"source": "punarphoo_observation_v1",
|
||||
}
|
||||
orb = circular_degree_gap(natal_moon_longitude, transit_saturn_longitude)
|
||||
return {
|
||||
"status": "observation_only",
|
||||
"active": orb <= orb_limit,
|
||||
"orb_deg": orb,
|
||||
"orb_limit_deg": orb_limit,
|
||||
"claim_boundary": CLAIM_BOUNDARY,
|
||||
"source": "punarphoo_observation_v1",
|
||||
}
|
||||
|
||||
|
||||
def detect_punarphoo(
|
||||
planets: Dict[str, Any] | None,
|
||||
transit_saturn_longitude: Optional[float] = None,
|
||||
) -> Dict[str, Any]:
|
||||
natal = detect_natal_punarphoo(planets)
|
||||
moon_lon = _longitude((planets or {}).get("Moon")) if isinstance(planets, dict) else None
|
||||
transit = detect_saturn_return_to_natal_moon(moon_lon, transit_saturn_longitude)
|
||||
return {
|
||||
**natal,
|
||||
"saturn_return_to_natal_moon": transit,
|
||||
"timing_observation_active": bool(transit.get("active")),
|
||||
"claim_boundary": CLAIM_BOUNDARY,
|
||||
}
|
||||
Reference in New Issue
Block a user