fix(ashtakavarga): sum Pinda from the shared Sodhita reduction

Upstream 30b43643. Yoga Pinda multiplied only the bindu in the planet's own sign. It now uses the Trikona and Ekadhipatya rows, with PVR table 28 including Virgo 6. PVR example 43 is 152. BUG-1384.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jesse-ux
2026-10-18 16:27:18 +08:00
co-authored by Grok (agent loop) Cursor
parent b5eb72343a
commit e8a7f3ac22
4 changed files with 116 additions and 23 deletions
+55 -21
View File
@@ -162,6 +162,8 @@ PLANET_WEIGHTS = {
'Sun': 5, 'Moon': 5, 'Mars': 8, 'Mercury': 5,
'Jupiter': 10, 'Venus': 7, 'Saturn': 5
}
# PVR §12.7 表 28。处女座是 6。不改用 Prasna Marga 的 5。
RASHI_PINDA_WEIGHTS = (7, 10, 8, 4, 10, 6, 7, 8, 9, 5, 11, 12)
# 行星列表
SEVEN_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
@@ -431,13 +433,17 @@ def calc_prastara_av(planets: Dict, asc_sign_idx: int) -> Dict:
def calc_yoga_pinda(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict:
"""
计算 Yoga Pinda / Shodhya Pinda 汇总。
"""Yoga Pinda from the same Trikona and Ekadhipatya rows as the Sodhita table.
本项目沿用既有 v2.1 口径:取行星本星座的 BAV bindu,分别乘以星座权重与行星权重;
两者相加作为 Yoga Pinda。该函数把原本嵌在 calc_ashtakavarga() 内部的结果提升为
一等契约,便于 API、前端与测试直接引用。
PVR §12.7 tables 28–29: twelve reduced signs times rashi weights (Virgo 6),
plus the seven planets' weights at the signs they occupy. A full seven-row
chart reuses ``sodhita_chain`` so the report cannot show two reductions.
"""
try:
from ashtakavarga_shodhana import occupied_signs, sodhita_chain
except ImportError:
from scripts.ashtakavarga_shodhana import occupied_signs, sodhita_chain
source_sign_idx = {}
for pname in SEVEN_PLANETS:
if pname in planets:
@@ -445,27 +451,51 @@ def calc_yoga_pinda(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict
if sign in SIGNS:
source_sign_idx[pname] = SIGNS.index(sign)
source_sign_idx['Lagna'] = asc_sign_idx
note = (
'PVR §12.7 表 28–29:十二座削减值乘星座权重(处女座 6),'
'再加七曜所在座的行星权重。与 Sodhita 表同一份削减。'
'同值且一占一空时按 Raman 15(c) 清空空宫。'
)
if any(pname not in source_sign_idx for pname in SEVEN_PLANETS):
return {
'method': 'Yoga Pinda', 'version': '2.0',
'profile': 'pvr_trikona_ekadhipatya_tables28_29',
'planets': {}, 'summary': {}, 'all_valid': False,
'validation_note': note + ' 七曜星座不全,不算 Graha Pinda。',
}
available = {}
for pname, row in bav_results.items():
bindus = row.get('bindus') if isinstance(row, dict) else None
if isinstance(bindus, list) and len(bindus) == 12:
available[pname] = {'bindus': bindus}
if set(SEVEN_PLANETS).issubset(available):
chain = sodhita_chain(available, planets)
trikona_rows = chain['trikona_bav']
reduced_rows = chain['sodhita_bav']
else:
trikona_rows = {
pname: calc_trikona_shodhana(row['bindus'])['reduced_scores']
for pname, row in available.items()
}
reduced_rows = calc_ekadhipatya_shodhana(trikona_rows, occupied_signs(planets))['rows']
rows = {}
all_valid = True
for pname in SEVEN_PLANETS:
bav_row = bav_results.get(pname, {})
bindus = bav_row.get('bindus', [])
reduced = reduced_rows.get(pname)
own_sign = source_sign_idx.get(pname)
if own_sign is None or len(bindus) != 12:
bindus = (available.get(pname) or {}).get('bindus')
if reduced is None or own_sign is None or bindus is None:
all_valid = False
continue
bindu_at_own = bindus[own_sign]
sign_weight = 4
planet_weight = PLANET_WEIGHTS.get(pname, 5)
rashi_pinda = bindu_at_own * sign_weight
graha_pinda = bindu_at_own * planet_weight
rashi_pinda = sum(value * weight for value, weight in zip(reduced, RASHI_PINDA_WEIGHTS))
graha_pinda = sum(
reduced[source_sign_idx[body]] * PLANET_WEIGHTS[body] for body in SEVEN_PLANETS
)
rows[pname] = {
'sign': SIGNS[own_sign],
'bindu_at_own_sign': bindu_at_own,
'rashi_weight': sign_weight,
'planet_weight': planet_weight,
'bindu_at_own_sign': bindus[own_sign],
'rashi_pinda': rashi_pinda,
'graha_pinda': graha_pinda,
'yoga_pinda': rashi_pinda + graha_pinda,
@@ -475,10 +505,14 @@ def calc_yoga_pinda(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict
strongest = ranked[0][0] if ranked else None
weakest = ranked[-1][0] if ranked else None
total = sum(item['yoga_pinda'] for item in rows.values())
return {
'method': 'Yoga Pinda',
'version': '1.0',
'version': '2.0',
'profile': 'pvr_trikona_ekadhipatya_tables28_29',
'rashi_weights': list(RASHI_PINDA_WEIGHTS),
'planet_weights': dict(PLANET_WEIGHTS),
'trikona_rows': trikona_rows,
'sodhita_rows': reduced_rows,
'planets': rows,
'summary': {
'strongest_planet': strongest,
@@ -487,7 +521,7 @@ def calc_yoga_pinda(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict
'average_yoga_pinda': round(total / len(rows), 2) if rows else 0,
},
'all_valid': all_valid and len(rows) == len(SEVEN_PLANETS),
'validation_note': 'Yoga Pinda = Rashi Pinda + Graha Pinda;当前复用既有 v2.1 Shodhya Pinda 权重口径。',
'validation_note': note,
}
@@ -569,5 +603,5 @@ def calc_ekadhipatya_shodhana(
'occupied_signs': sorted(occupied_signs),
'audit': audit,
'status': 'executed',
'claim_boundary': 'Applied after Trikona Shodhana; source profile follows PyJHora/PVR rule ordering.',
'claim_boundary': 'Applied after Trikona Shodhana. One occupied and one vacant sign with the same value clears the vacant sign (Raman 15(c)).',
}
+2 -2
View File
@@ -7254,9 +7254,9 @@ def render_pl9_markdown(packet: dict) -> str:
if shodhya_pinda:
out.extend([
'',
'#### Shodhya / Yoga Pinda(本地补充分值)',
'#### Shodhya / Yoga Pinda(PVR §12.7)',
'',
'本表透传本地 Bindu 权重计算的 Rashi/Graha/Yoga Pinda。不等同于 PL9 第 54 页的 Trikona / Ekadhipatya reduction 前后矩阵;该页逐阶段结构仍未闭环,所有行仅作 parameter_sensitive 补充数据。',
'本表与 Sodhita 表用同一份削减:先三方净化,再单宫主净化。同值且一占一空时按 Raman 15(c) 清空空宫。星座权重按 PVR 表 28(处女座 6),七曜权重按表 29。这是具名口径,不是经典定论。',
'',
'| 行星 | Rashi Pinda | Graha Pinda | Yoga Pinda | 本命星座 Bindu | 状态 |',
'|------|-------------|-------------|------------|----------------|------|',