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:
@@ -8,7 +8,39 @@ Love & Relationship Analysis(感情分析)引擎 v1.0
|
||||
7宫+Venus → Navamsa(D9) → Upapada(UL) → Darakaraka → Synastry
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
try:
|
||||
from punarphoo import circular_degree_gap, detect_punarphoo
|
||||
except ModuleNotFoundError: # pragma: no cover - script/module dual import
|
||||
from scripts.punarphoo import circular_degree_gap, detect_punarphoo
|
||||
|
||||
try:
|
||||
from timing_negative_holdout_audit import build_report as build_negative_holdout_report
|
||||
except ModuleNotFoundError: # pragma: no cover - script/module dual import
|
||||
from scripts.timing_negative_holdout_audit import build_report as build_negative_holdout_report
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
ROMANTIC_ACTIVATION_LORDS = ('5L', '7H_Moon', 'Punarphoo')
|
||||
LEGAL_MARRIAGE_LORDS = ('Venus', 'DK', 'UL')
|
||||
# PD never drives event_class. 5L / 7H Moon / 7L / Venus legal hits use MD/AD only.
|
||||
# Same-structure real cases (female + Saturn-7H chain) trigger at AD, not at
|
||||
# annual transits. Do not invent a month proxy when the case layer only supports AD.
|
||||
MD_AD_KEYS = (
|
||||
'maha_dasha', 'mahadasha', 'antar_dasha', 'antardasha', 'bhukti',
|
||||
)
|
||||
PD_KEYS = ('pratyantar', 'pratyantar_dasha')
|
||||
VENUS_TO_NATAL_MOON_ORB_DEG = 2.0
|
||||
EVENT_CLASSES = (
|
||||
'romantic_activation',
|
||||
'relationship_formation',
|
||||
'legal_marriage',
|
||||
)
|
||||
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
@@ -44,6 +76,163 @@ RELATIONSHIP_TIMING = {
|
||||
}
|
||||
|
||||
|
||||
def _lord_of_house(asc_sign: str, house: int) -> Optional[str]:
|
||||
if asc_sign not in SIGNS or house < 1 or house > 12:
|
||||
return None
|
||||
sign = SIGNS[(SIGNS.index(asc_sign) + house - 1) % 12]
|
||||
return SIGN_LORDS.get(sign)
|
||||
|
||||
|
||||
def _lords_from_keys(dasha_info: Optional[Dict], keys) -> List[str]:
|
||||
if not isinstance(dasha_info, dict):
|
||||
return []
|
||||
lords: List[str] = []
|
||||
for key in keys:
|
||||
value = dasha_info.get(key)
|
||||
if isinstance(value, dict):
|
||||
value = value.get('lord') or value.get('planet')
|
||||
if isinstance(value, str) and value:
|
||||
lords.append(value)
|
||||
return lords
|
||||
|
||||
|
||||
def _dasha_lords(dasha_info: Optional[Dict]) -> List[str]:
|
||||
"""MD/AD lords only. PD stays in `_pd_lords` and must not drive event_class."""
|
||||
return _lords_from_keys(dasha_info, MD_AD_KEYS)
|
||||
|
||||
|
||||
def _pd_lords(dasha_info: Optional[Dict]) -> List[str]:
|
||||
return _lords_from_keys(dasha_info, PD_KEYS)
|
||||
|
||||
|
||||
def classify_relationship_event_class(
|
||||
planets: Dict,
|
||||
asc_sign: str = 'Aries',
|
||||
dasha_info: Optional[Dict] = None,
|
||||
ul_sign: Optional[str] = None,
|
||||
darakaraka: Optional[str] = None,
|
||||
transit_saturn_longitude: Optional[float] = None,
|
||||
transit_venus_longitude: Optional[float] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Split romantic activation / relationship formation / legal marriage.
|
||||
|
||||
Punarphoo and 5L remain observation-only until negative holdout passes.
|
||||
Never flatten the three tracks into one 婚恋机会 label.
|
||||
MD/AD drive event_class. PD is recorded but never promotes a class.
|
||||
"""
|
||||
planets = planets if isinstance(planets, dict) else {}
|
||||
fifth_lord = _lord_of_house(asc_sign, 5)
|
||||
seventh_lord = _lord_of_house(asc_sign, 7)
|
||||
moon = planets.get('Moon') if isinstance(planets.get('Moon'), dict) else {}
|
||||
moon_house = moon.get('house')
|
||||
moon_lon = moon.get('longitude')
|
||||
if moon_lon is None:
|
||||
moon_lon = moon.get('lon')
|
||||
dasha_lords = _dasha_lords(dasha_info)
|
||||
pd_lords = _pd_lords(dasha_info)
|
||||
punarphoo = detect_punarphoo(planets, transit_saturn_longitude=transit_saturn_longitude)
|
||||
holdout = build_negative_holdout_report()
|
||||
|
||||
natal_romantic: List[str] = []
|
||||
if moon_house == 7:
|
||||
natal_romantic.append('7H_Moon')
|
||||
if punarphoo.get('natal_present'):
|
||||
natal_romantic.append('natal_Punarphoo')
|
||||
|
||||
venus_to_moon = None
|
||||
if moon_house == 7 and moon_lon is not None and transit_venus_longitude is not None:
|
||||
venus_orb = circular_degree_gap(float(moon_lon), float(transit_venus_longitude))
|
||||
venus_to_moon = {
|
||||
'status': 'observation_only',
|
||||
'active': venus_orb <= VENUS_TO_NATAL_MOON_ORB_DEG,
|
||||
'orb_deg': venus_orb,
|
||||
'orb_limit_deg': VENUS_TO_NATAL_MOON_ORB_DEG,
|
||||
'drives_event_class': False,
|
||||
'claim_boundary': (
|
||||
'7H Venus-to-Moon is annual and not a crush month. '
|
||||
'Record only; never add to romantic_hits or point a month. '
|
||||
'Falsified by empty 2019-2022 repeats and 2026-02-13→18.'
|
||||
),
|
||||
}
|
||||
|
||||
romantic_hits: List[str] = []
|
||||
if fifth_lord and fifth_lord in dasha_lords:
|
||||
romantic_hits.append('5L_dasha')
|
||||
if moon_house == 7 and 'Moon' in dasha_lords:
|
||||
romantic_hits.append('7H_Moon_dasha')
|
||||
if punarphoo.get('timing_observation_active'):
|
||||
romantic_hits.append('Punarphoo')
|
||||
# Venus-to-Moon is annual (~once/year). It does not drive event_class.
|
||||
|
||||
formation_hits: List[str] = []
|
||||
if seventh_lord and seventh_lord in dasha_lords:
|
||||
formation_hits.append('7L_dasha')
|
||||
|
||||
legal_hits: List[str] = []
|
||||
if 'Venus' in dasha_lords:
|
||||
legal_hits.append('Venus_dasha')
|
||||
|
||||
pd_hits: List[str] = []
|
||||
if fifth_lord and fifth_lord in pd_lords:
|
||||
pd_hits.append('5L_pd')
|
||||
if moon_house == 7 and 'Moon' in pd_lords:
|
||||
pd_hits.append('7H_Moon_pd')
|
||||
if seventh_lord and seventh_lord in pd_lords:
|
||||
pd_hits.append('7L_pd')
|
||||
if 'Venus' in pd_lords:
|
||||
pd_hits.append('Venus_pd')
|
||||
|
||||
natal_legal: List[str] = []
|
||||
if darakaraka:
|
||||
natal_legal.append('DK')
|
||||
if ul_sign:
|
||||
natal_legal.append('UL')
|
||||
|
||||
# Timing-only classification. Natal 7H Moon / Punarphoo / DK / UL stay structure flags.
|
||||
# Romantic timing (5L dasha / Moon dasha / Punarphoo return) outranks long 7L MD.
|
||||
# PD never drives event_class.
|
||||
active_classes: List[str] = []
|
||||
if romantic_hits:
|
||||
active_classes.append('romantic_activation')
|
||||
if legal_hits:
|
||||
active_classes.append('legal_marriage')
|
||||
if formation_hits:
|
||||
active_classes.append('relationship_formation')
|
||||
event_class = active_classes[0] if active_classes else None
|
||||
|
||||
return {
|
||||
'event_class': event_class,
|
||||
'romantic_activation': {
|
||||
'lords': list(ROMANTIC_ACTIVATION_LORDS),
|
||||
'hits': romantic_hits,
|
||||
'natal_structure': natal_romantic,
|
||||
'punarphoo': punarphoo,
|
||||
'venus_to_natal_moon': venus_to_moon,
|
||||
},
|
||||
'relationship_formation': {
|
||||
'hits': formation_hits,
|
||||
'seventh_lord': seventh_lord,
|
||||
},
|
||||
'legal_marriage': {
|
||||
'lords': list(LEGAL_MARRIAGE_LORDS),
|
||||
'hits': legal_hits,
|
||||
'natal_structure': natal_legal,
|
||||
},
|
||||
'pd_hits': pd_hits,
|
||||
'active_classes': active_classes,
|
||||
'negative_holdout': {
|
||||
'claim_status': holdout.get('claim_status'),
|
||||
'production_tuning_allowed': holdout.get('production_tuning_allowed'),
|
||||
},
|
||||
'claim_status': 'observation_only',
|
||||
'note': (
|
||||
'心动看 5L/7H月亮/Punarphoo,成对看 7L/DK/UL,法律婚看 Venus/DK/UL。'
|
||||
'Punarphoo 回照 ≠ 结婚;5L 大运 ≠ 法律婚。'
|
||||
'金星过本命月亮每年一次,不点月、不升格心动。不得合并成一句婚恋机会。'
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def analyze_relationship(planets: Dict, asc_sign: str = 'Aries',
|
||||
d9_positions: Dict = None, ul_sign: str = None,
|
||||
darakaraka: str = None, dasha_info: Dict = None) -> Dict:
|
||||
@@ -68,6 +257,7 @@ def analyze_relationship(planets: Dict, asc_sign: str = 'Aries',
|
||||
'strength': [],
|
||||
'challenges': [],
|
||||
'assessment': '',
|
||||
'event_class_split': {},
|
||||
}
|
||||
|
||||
# 1. Venus分析(关系核心指示星)
|
||||
@@ -140,15 +330,27 @@ def analyze_relationship(planets: Dict, asc_sign: str = 'Aries',
|
||||
'note': f'UL={ul_sign} — 社会视角下的伴侣形象和婚姻质量',
|
||||
})
|
||||
|
||||
# 7. 时间窗口
|
||||
# 7. 时间窗口:心动 / 成对 / 法律婚分轨,禁止合并成一句「婚恋机会」
|
||||
event_split = classify_relationship_event_class(
|
||||
planets,
|
||||
asc_sign=asc_sign,
|
||||
dasha_info=dasha_info,
|
||||
ul_sign=ul_sign,
|
||||
darakaraka=darakaraka,
|
||||
)
|
||||
analysis['event_class_split'] = event_split
|
||||
md = ''
|
||||
if dasha_info:
|
||||
md = dasha_info.get('maha_dasha', '')
|
||||
relationship_planets = ['Venus', 'Jupiter', 'Moon']
|
||||
if md in relationship_planets:
|
||||
analysis['timing'].append({
|
||||
'dasha': md,
|
||||
'note': f'当前{md}大运期间,感情/关系相关领域活跃',
|
||||
})
|
||||
md = dasha_info.get('maha_dasha', '') or dasha_info.get('mahadasha', '')
|
||||
event_class = event_split.get('event_class') or 'unclassified'
|
||||
analysis['timing'].append({
|
||||
'dasha': md,
|
||||
'event_class': event_class,
|
||||
'note': (
|
||||
f'当前{md or "未提供"}大运;事件类={event_class}。'
|
||||
'心动看 5L/7H月亮/Punarphoo,成对看 7L/DK/UL,法律婚看 Venus/DK/UL;不得合并。'
|
||||
),
|
||||
})
|
||||
|
||||
# 8. 综合评估
|
||||
venus_strength = 0
|
||||
|
||||
Reference in New Issue
Block a user