fix(dignity): record Neecha Bhanga kendras and clip the first dasha from its full parent (BUG-1268, upstream 4a807e7d)
This commit is contained in:
+104
-28
@@ -612,32 +612,83 @@ def _build_planetary_friendship_snapshot(planets_data):
|
||||
),
|
||||
}
|
||||
|
||||
def _check_neecha_bhanga(planet, sign, planets_data):
|
||||
"""Check for Cancellation of Debilitation."""
|
||||
def _neecha_bhanga_evidence(planet, sign, planets_data):
|
||||
"""Record cancellation conditions. A hit is not a Raja Yoga."""
|
||||
evidence = {
|
||||
'planet': planet,
|
||||
'debilitation_sign': sign,
|
||||
'成立': False,
|
||||
'reasons': [],
|
||||
'conditions_checked': [
|
||||
'dispositor_exalted',
|
||||
'exaltation_lord_conjunct_debilitation_sign',
|
||||
'dispositor_kendra_from_moon',
|
||||
'dispositor_kendra_from_lagna',
|
||||
],
|
||||
'royal_yoga_label_allowed': False,
|
||||
'claim_boundary': (
|
||||
'Neecha Bhanga evidence records cancellation conditions only; it does not by itself prove a Raja Yoga, '
|
||||
'complete reversal of weakness, or a guaranteed outcome.'
|
||||
),
|
||||
}
|
||||
if not planets_data:
|
||||
return False
|
||||
|
||||
evidence['status'] = 'blocked_missing_planets'
|
||||
return evidence
|
||||
|
||||
sign_lord = SIGN_LORDS.get(sign)
|
||||
|
||||
# Condition 1: Dispositor is exalted.
|
||||
lord_data = planets_data.get(sign_lord, {}) if sign_lord else {}
|
||||
moon_sign = (planets_data.get('Moon') or {}).get('sign') if isinstance(planets_data.get('Moon'), dict) else None
|
||||
lord_sign = lord_data.get('sign') if isinstance(lord_data, dict) else None
|
||||
signs = list(SIGN_LORDS)
|
||||
if lord_sign in signs and moon_sign in signs:
|
||||
moon_house = (signs.index(lord_sign) - signs.index(moon_sign)) % 12 + 1
|
||||
if moon_house in (1, 4, 7, 10):
|
||||
evidence['成立'] = True
|
||||
evidence['reasons'].append({
|
||||
'condition': 'dispositor_kendra_from_moon',
|
||||
'planet': sign_lord,
|
||||
'house_from_moon': moon_house,
|
||||
})
|
||||
if isinstance(lord_data, dict) and lord_data.get('house') in (1, 4, 7, 10):
|
||||
evidence['成立'] = True
|
||||
evidence['reasons'].append({
|
||||
'condition': 'dispositor_kendra_from_lagna',
|
||||
'planet': sign_lord,
|
||||
'house': lord_data['house'],
|
||||
})
|
||||
|
||||
if sign_lord and sign_lord in planets_data:
|
||||
lord_sign = planets_data[sign_lord].get('sign')
|
||||
if lord_sign and EXALTATION.get(sign_lord) == lord_sign:
|
||||
return True
|
||||
|
||||
# Condition 2: Planet exalted in this sign is conjunct.
|
||||
evidence['成立'] = True
|
||||
evidence['reasons'].append({
|
||||
'condition': 'dispositor_exalted',
|
||||
'planet': sign_lord,
|
||||
'sign': lord_sign,
|
||||
})
|
||||
|
||||
exalted_planet = None
|
||||
for p, ex_sign in EXALTATION.items():
|
||||
if ex_sign == sign:
|
||||
exalted_planet = p
|
||||
break
|
||||
|
||||
if exalted_planet and exalted_planet in planets_data:
|
||||
ex_p_sign = planets_data[exalted_planet].get('sign')
|
||||
if ex_p_sign == sign:
|
||||
return True
|
||||
|
||||
return False
|
||||
evidence['成立'] = True
|
||||
evidence['reasons'].append({
|
||||
'condition': 'exaltation_lord_conjunct_debilitation_sign',
|
||||
'planet': exalted_planet,
|
||||
'sign': sign,
|
||||
})
|
||||
|
||||
evidence['status'] = 'executed'
|
||||
return evidence
|
||||
|
||||
|
||||
def _check_neecha_bhanga(planet, sign, planets_data):
|
||||
"""Boolean compatibility wrapper. Details live on the evidence helper."""
|
||||
return bool(_neecha_bhanga_evidence(planet, sign, planets_data).get('成立'))
|
||||
|
||||
|
||||
def _build_dignity_context(chart_data):
|
||||
@@ -652,6 +703,7 @@ def _build_dignity_context(chart_data):
|
||||
context[pn] = {
|
||||
'sign': pd.get('sign'),
|
||||
'degree_in_sign': pd.get('degree_in_sign', pd.get('degree', 0) % 30),
|
||||
'house': pd.get('house'),
|
||||
}
|
||||
return context
|
||||
|
||||
@@ -6808,7 +6860,8 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
bhava_bala = strength_sheet.get('bhava_bala') if isinstance(strength_sheet.get('bhava_bala'), dict) else {}
|
||||
strongest = _humanize_reader_token(shadbala.get('strongest')) if isinstance(shadbala, dict) else None
|
||||
weakest = _humanize_reader_token(shadbala.get('weakest')) if isinstance(shadbala, dict) else None
|
||||
houses = bhava_bala.get('houses') if isinstance(bhava_bala.get('houses'), list) else []
|
||||
from special_lagnas import bhava_house_rows
|
||||
houses = bhava_house_rows(bhava_bala)
|
||||
top_house = None
|
||||
top_score = None
|
||||
weak_house = None
|
||||
@@ -7653,7 +7706,8 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
)
|
||||
out.append('')
|
||||
if bhava_bala:
|
||||
rows = bhava_bala.get('houses') if isinstance(bhava_bala.get('houses'), list) else []
|
||||
from special_lagnas import bhava_house_rows
|
||||
rows = bhava_house_rows(bhava_bala)
|
||||
if rows:
|
||||
out.extend([
|
||||
'#### Bhava Bala(三分量)',
|
||||
@@ -12470,29 +12524,51 @@ def cmd_dasha(args):
|
||||
|
||||
today = datetime.strptime(args.today, "%Y-%m-%d") if args.today else datetime.now()
|
||||
current = None
|
||||
year_days = 365.25
|
||||
for d in timeline:
|
||||
ds = datetime.strptime(d["start"], "%Y-%m-%d"); de = datetime.strptime(d["end"], "%Y-%m-%d")
|
||||
total_days = (de - ds).days; li = DASHA_ORDER.index(d["lord"])
|
||||
# v3.7.2: 为所有 Mahadasha 计算 Antardasha(不仅当前大运)
|
||||
sub = []; sdt = ds
|
||||
# 出生只截断第一大运的可见段。小运和更小运从完整父段起点排,再裁到出生之后。
|
||||
parent_start = datetime.fromisoformat(d["start_datetime"])
|
||||
parent_end = datetime.fromisoformat(d["end_datetime"])
|
||||
visible_origin = birth_dt if d.get("is_balance") else parent_start
|
||||
total_duration = timedelta(days=float(d["full_years"]) * year_days)
|
||||
li = DASHA_ORDER.index(d["lord"])
|
||||
sub = []
|
||||
sdt = parent_start
|
||||
for j in range(9):
|
||||
sl = DASHA_ORDER[(li + j) % 9]; sd = total_days * DASHA_YEARS[sl] / 120
|
||||
se = sdt + timedelta(days=sd)
|
||||
is_cur = sdt <= today < se
|
||||
sl = DASHA_ORDER[(li + j) % 9]
|
||||
se = parent_end if j == 8 else sdt + (total_duration * (DASHA_YEARS[sl] / 120.0))
|
||||
if se <= visible_origin:
|
||||
sdt = se
|
||||
continue
|
||||
visible_start = max(sdt, visible_origin)
|
||||
children = _build_pratyantar_timeline(d["lord"], sl, sdt, se, reference_dt=today)
|
||||
visible_children = []
|
||||
for child in children:
|
||||
child_start = datetime.fromisoformat(child["start_datetime"])
|
||||
child_end = datetime.fromisoformat(child["end_datetime"])
|
||||
if child_end <= visible_origin:
|
||||
continue
|
||||
if child_start < visible_origin:
|
||||
child["unclipped_start_datetime"] = child_start.isoformat(timespec="seconds")
|
||||
child["start"] = visible_origin.strftime("%Y-%m-%d")
|
||||
child["start_datetime"] = visible_origin.isoformat(timespec="seconds")
|
||||
child["is_current"] = bool(visible_origin <= today < child_end)
|
||||
visible_children.append(child)
|
||||
sub.append({
|
||||
"lord": sl,
|
||||
"lord_cn": PLANET_CN[sl],
|
||||
"start": sdt.strftime("%Y-%m-%d"),
|
||||
"start": visible_start.strftime("%Y-%m-%d"),
|
||||
"end": se.strftime("%Y-%m-%d"),
|
||||
"start_datetime": sdt.isoformat(timespec="seconds"),
|
||||
"start_datetime": visible_start.isoformat(timespec="seconds"),
|
||||
"unclipped_start_datetime": sdt.isoformat(timespec="seconds"),
|
||||
"end_datetime": se.isoformat(timespec="seconds"),
|
||||
"is_current": is_cur,
|
||||
"pratyantar_dasha_timeline": _build_pratyantar_timeline(
|
||||
d["lord"], sl, sdt, se, reference_dt=today,
|
||||
),
|
||||
"is_current": visible_start <= today < se,
|
||||
"pratyantar_dasha_timeline": visible_children,
|
||||
})
|
||||
sdt = se
|
||||
d["antardasha_timeline"] = sub
|
||||
ds = datetime.strptime(d["start"], "%Y-%m-%d")
|
||||
de = datetime.strptime(d["end"], "%Y-%m-%d")
|
||||
if ds <= today < de:
|
||||
d["is_current"] = True
|
||||
# 从 antardasha 列表中提取当前正在运行的 antardasha
|
||||
|
||||
@@ -1197,11 +1197,11 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
)
|
||||
lines.append('')
|
||||
bhava_bala = _dict(strengths.get('bhava_bala'))
|
||||
bhava_rows = _list(bhava_bala.get('houses')) or [
|
||||
{'house': key, **value}
|
||||
for key, value in bhava_bala.items()
|
||||
if str(key).isdigit() and isinstance(value, dict)
|
||||
]
|
||||
try:
|
||||
from special_lagnas import bhava_house_rows
|
||||
except ImportError:
|
||||
from scripts.special_lagnas import bhava_house_rows
|
||||
bhava_rows = bhava_house_rows(bhava_bala)
|
||||
if bhava_rows:
|
||||
# BUG-1210 (甲13): the formal three components (scripts/bhava_bala.py);
|
||||
# the local +2 / -1.5 house score is never shown.
|
||||
@@ -1211,7 +1211,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
'| House | Sign | Lord | Bhavadhipati | Dig | Drishti | Total (Virupa) | Total (Rupa) |',
|
||||
'|-------|------|------|--------------|-----|---------|----------------|--------------|',
|
||||
])
|
||||
for row in bhava_rows[:12]:
|
||||
for row in bhava_rows:
|
||||
if not isinstance(row, dict) or row.get('total') is None:
|
||||
continue
|
||||
lines.append(
|
||||
@@ -1292,8 +1292,8 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
lines.extend(['### Jaimini Karaka、Arudha Pada 与特殊 Lagna', ''])
|
||||
if karaka_table:
|
||||
lines.extend([
|
||||
'| Karaka | Planet | Degree | Domain |',
|
||||
'|--------|--------|--------|--------|',
|
||||
'| Karaka | Planet | Degree | Ranking degree | Domain |',
|
||||
'|--------|--------|--------|----------------|--------|',
|
||||
])
|
||||
for name, row in karaka_table.items():
|
||||
if not isinstance(row, dict):
|
||||
@@ -1301,6 +1301,7 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
lines.append(
|
||||
f"| {_cell(row.get('cn_name') or name)} | {_cell(_token(row.get('planet')))} | "
|
||||
f"{_cell(_degree(row.get('degree_in_sign') or row.get('sign_degree')))} | "
|
||||
f"{_cell(_degree(row.get('effective_degree_in_sign', row.get('degree_in_sign'))))} | "
|
||||
f"{_cell(row.get('domain'))} |"
|
||||
)
|
||||
lines.append('')
|
||||
|
||||
Reference in New Issue
Block a user