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
+15
View File
@@ -36548,3 +36548,18 @@
- 相关记录:BUG-1381。
- 复发自:无。
- 修复版本:分支 `codex/upstream-sync8-20261017`。上游提交 `8eec9a54`。
## BUG-1384 | Yoga Pinda 只乘了本座 bindu
- 状态:resolved
- 首次发现 / 最近更新:2026-10-17 / 2026-10-18
- 来源:issue #80,任务书 T4。上游 `30b43643`。检索「calc_yoga_pinda」「rashi_weight」,没有更早的同类记录。BUG-1209 已经有标准削减链,Pinda 没接上。
- 现象:Pinda 只拿行星所在星座的 bindu,乘 4 再乘行星权重。PVR 例 43 应为 152,这条捷径算不出来。强弱排序因此偏掉。
- 触发条件:报告和 Ashtakavarga 接口调用 `calc_yoga_pinda`。
- 根因:没有做三方净化和单宫主净化,也没有把十二座、七曜分别加权。
- 修复:七行齐全时直接复用 `sodhita_chain`。星座权重按 PVR 表 28(处女座 6)。同值且一占一空时清空空宫,这是 Raman 15(c),原削减函数已经这样做。报告表头改成同一口径。
- 验证:`tests/test_ashtakavarga_pinda_source.py`。例 43 的 Mercury 为 77 + 75 = 152。整盘的 Pinda 削减行与 Sodhita 链相同。
- 防复发:上述测试。
- 相关记录:BUG-1209。
- 复发自:无。
- 修复版本:分支 `codex/upstream-sync8-20261017`。上游提交 `30b43643`。
+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 | 状态 |',
'|------|-------------|-------------|------------|----------------|------|',
+44
View File
@@ -0,0 +1,44 @@
from scripts.ashtakavarga import SEVEN_PLANETS, calc_ashtakavarga, calc_ekadhipatya_shodhana, calc_yoga_pinda
from scripts.ashtakavarga_shodhana import sodhita_chain
def test_pvr_example_43_full_sums_not_own_sign_shortcut():
# PVR examples 40–43. This Mercury row is already reduced and stays put.
bav = {'Mercury': {'bindus': [3, 1, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0]}}
planets = {name: {'sign': 'Leo'} for name in SEVEN_PLANETS}
for name in ('Sun', 'Mars', 'Mercury'):
planets[name] = {'sign': 'Gemini'}
planets['Venus'] = {'sign': 'Aries'}
row = calc_yoga_pinda(bav, planets, 0)['planets']['Mercury']
assert (row['rashi_pinda'], row['graha_pinda'], row['yoga_pinda']) == (77, 75, 152)
def test_virgo_multiplier_and_zero_trine_convention():
bav = {'Mercury': {'bindus': [0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0]}}
planets = {name: {'sign': 'Leo'} for name in SEVEN_PLANETS}
result = calc_yoga_pinda(bav, planets, 0)
assert result['sodhita_rows']['Mercury'][5] == 2
assert result['planets']['Mercury']['rashi_pinda'] == 12
assert result['planets']['Mercury']['graha_pinda'] == 0
def test_missing_planet_does_not_silently_emit_partial_graha_sum():
result = calc_yoga_pinda({'Sun': {'bindus': [1] * 12}}, {'Sun': {'sign': 'Leo'}}, 0)
assert result['all_valid'] is False
assert result['planets'] == {}
def test_raman_rule_15c_equal_pair_removes_only_vacant_value():
row = [0] * 12
row[0] = row[7] = 2
result = calc_ekadhipatya_shodhana({'Moon': row}, {0})['rows']['Moon']
assert result[0] == 2
assert result[7] == 0
def test_full_chart_pinda_reuses_the_sodhita_chain():
planets = {name: {'sign': 'Leo'} for name in SEVEN_PLANETS}
bav = calc_ashtakavarga(planets, asc_sign_idx=0)['bav']
pinda = calc_yoga_pinda(bav, planets, 0)
chain = sodhita_chain(bav, planets)
assert pinda['sodhita_rows'] == chain['sodhita_bav']