Astrologer ruling 乙8. New scripts/ashtakavarga_shodhana.py: per BAV row Trikona then Ekadhipatya (the two existing ashtakavarga functions, previously only called by tests), Sodhita SAV = sum of reduced rows; occupied signs = seven grahas. Removed calc_sodhita_av (Sun/Mars/Saturn contribution removal labelled 'BPHS标准'). Wired into /api/ashtakavarga (result.sodhita, house_scores[*].sav_sodhita) and the full reading. Reader: SAV table 'Sodhita SAV | Raw SAV', house table gains Sodhita column, Sodhita BAV table before raw BAV; reference edition section rewritten. Card: sav_sodhita before sav_score plus sav_columns label. Personal report facts: score = Sodhita, rawScore/savRawTotal beside (optional schema fields). Fact tables: Sodhita column and Sodhita BAV subtable. Transit scoring unchanged (raw bindus). Golden: report-density-fictional-engine.json leaf worksheets.strengths_and_scores .ashtakavarga recaptured from the real engine (scripts/research/refresh_density_ fixture_ashtakavarga.py); bav/sav identical, every other leaf byte-equal. Changed assertions (three columns in PROGRESS): BUG-1200 house-table tests, report-fact-tables ashtakavarga columns/subtables, run_all t1/t57. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
574 lines
20 KiB
Python
574 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Ashtakavarga 计算模块 v2.1(八分法)
|
||
基于 Brihat Parashara Hora Shastra (BPHS) 完整标准表,并经 PVR/PyJHora 书例校准
|
||
|
||
核心修正(v2.0 vs v1.0):
|
||
- v1.0: BAV_BASE 只有自贡献 + 少量跨源贡献 → SAV=94 ❌
|
||
- v2.0: 完整 8×8 贡献矩阵(每颗行星 × 8来源)→ SAV=337 ✅
|
||
|
||
架构:
|
||
- BAV_CONTRIBUTION[planet][source] = 有利宫位列表
|
||
- 每颗行星的 BAV = 所有 8 个来源(7行星+Lagna)的贡献之和
|
||
- SAV = 7 颗行星 BAV 之和(Lagna 自身 BAV 单独展示,不计入 SAV)
|
||
- SAV 总和 = 337(宇宙常数)
|
||
|
||
PVR/PyJHora 书例校准(v2.1):
|
||
- 第六轮 benchmark 使用 PyJHora `pvr_tests.py` 中公开书例仲裁,修正 Moon/Venus 7 个贡献表项。
|
||
- 固定不变量保持:7行星 SAV=337,含 Lagna full SAV=386。
|
||
"""
|
||
|
||
from typing import Dict, List, Tuple
|
||
|
||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||
|
||
# ============================================================================
|
||
# 完整 BAV 贡献表(BPHS 标准法)
|
||
# BAV_CONTRIBUTION[planet][source] = 从 source 数起的有利宫位列表
|
||
# 每颗行星的 BAV 总和 = 所有 8 个来源的贡献数之和
|
||
# ============================================================================
|
||
|
||
# --- 太阳 BAV(总48) ---
|
||
# 来源: Sun(8) + Moon(4) + Mars(8) + Mercury(7) + Jupiter(4) + Venus(3) + Saturn(8) + Lagna(6) = 48
|
||
_SUN_BAV = {
|
||
'Sun': [1, 2, 4, 7, 8, 9, 10, 11],
|
||
'Moon': [3, 6, 10, 11],
|
||
'Mars': [1, 2, 4, 7, 8, 9, 10, 11],
|
||
'Mercury': [3, 5, 6, 9, 10, 11, 12],
|
||
'Jupiter': [5, 6, 9, 11],
|
||
'Venus': [6, 7, 12],
|
||
'Saturn': [1, 2, 4, 7, 8, 9, 10, 11],
|
||
'Lagna': [3, 4, 6, 10, 11, 12],
|
||
}
|
||
|
||
# --- 月亮 BAV(总49) ---
|
||
# 来源: Sun(6) + Moon(7) + Mars(6) + Mercury(8) + Jupiter(7) + Venus(7) + Saturn(4) + Lagna(4) = 49
|
||
# v2.1: PVR/PyJHora 书例校准 Moon/Mars/Jupiter/Lagna 贡献项。
|
||
_MOON_BAV = {
|
||
'Sun': [3, 6, 7, 8, 10, 11],
|
||
'Moon': [1, 3, 6, 7, 9, 10, 11],
|
||
'Mars': [2, 3, 5, 6, 10, 11],
|
||
'Mercury': [1, 3, 4, 5, 7, 8, 10, 11],
|
||
'Jupiter': [1, 2, 4, 7, 8, 10, 11],
|
||
'Venus': [3, 4, 5, 7, 9, 10, 11],
|
||
'Saturn': [3, 5, 6, 11],
|
||
'Lagna': [3, 6, 10, 11],
|
||
}
|
||
|
||
# --- 火星 BAV(总39) ---
|
||
# 来源: Sun(5) + Moon(3) + Mars(7) + Mercury(4) + Jupiter(4) + Venus(4) + Saturn(7) + Lagna(5) = 39
|
||
_MARS_BAV = {
|
||
'Sun': [3, 5, 6, 10, 11],
|
||
'Moon': [3, 6, 11],
|
||
'Mars': [1, 2, 4, 7, 8, 10, 11],
|
||
'Mercury': [3, 5, 6, 11],
|
||
'Jupiter': [6, 10, 11, 12],
|
||
'Venus': [6, 8, 11, 12],
|
||
'Saturn': [1, 4, 7, 8, 9, 10, 11],
|
||
'Lagna': [1, 3, 6, 10, 11],
|
||
}
|
||
|
||
# --- 水星 BAV(总54) ---
|
||
# 来源: Sun(5) + Moon(6) + Mars(8) + Mercury(8) + Jupiter(4) + Venus(8) + Saturn(8) + Lagna(7) = 54
|
||
_MERCURY_BAV = {
|
||
'Sun': [5, 6, 9, 11, 12],
|
||
'Moon': [2, 4, 6, 8, 10, 11],
|
||
'Mars': [1, 2, 4, 7, 8, 9, 10, 11],
|
||
'Mercury': [1, 3, 5, 6, 9, 10, 11, 12],
|
||
'Jupiter': [6, 8, 11, 12],
|
||
'Venus': [1, 2, 3, 4, 5, 8, 9, 11],
|
||
'Saturn': [1, 2, 4, 7, 8, 9, 10, 11],
|
||
'Lagna': [1, 2, 4, 6, 8, 10, 11],
|
||
}
|
||
|
||
# --- 木星 BAV(总56) ---
|
||
# 来源: Sun(9) + Moon(5) + Mars(7) + Mercury(8) + Jupiter(8) + Venus(6) + Saturn(4) + Lagna(9) = 56
|
||
_JUPITER_BAV = {
|
||
'Sun': [1, 2, 3, 4, 7, 8, 9, 10, 11],
|
||
'Moon': [2, 5, 7, 9, 11],
|
||
'Mars': [1, 2, 4, 7, 8, 10, 11],
|
||
'Mercury': [1, 2, 4, 5, 6, 9, 10, 11],
|
||
'Jupiter': [1, 2, 3, 4, 7, 8, 10, 11],
|
||
'Venus': [2, 5, 6, 9, 10, 11],
|
||
'Saturn': [3, 5, 6, 12],
|
||
'Lagna': [1, 2, 4, 5, 6, 7, 9, 10, 11],
|
||
}
|
||
|
||
# --- 金星 BAV(总52) ---
|
||
# 来源: Sun(3) + Moon(9) + Mars(6) + Mercury(5) + Jupiter(5) + Venus(9) + Saturn(7) + Lagna(8) = 52
|
||
# v2.1: PVR/PyJHora 书例校准 Mars/Mercury/Lagna 贡献项。
|
||
_VENUS_BAV = {
|
||
'Sun': [8, 11, 12],
|
||
'Moon': [1, 2, 3, 4, 5, 8, 9, 11, 12],
|
||
'Mars': [3, 4, 6, 9, 11, 12],
|
||
'Mercury': [3, 5, 6, 9, 11],
|
||
'Jupiter': [5, 8, 9, 10, 11],
|
||
'Venus': [1, 2, 3, 4, 5, 8, 9, 10, 11],
|
||
'Saturn': [3, 4, 5, 8, 9, 10, 11],
|
||
'Lagna': [1, 2, 3, 4, 5, 8, 9, 11],
|
||
}
|
||
|
||
# --- 土星 BAV(总39) ---
|
||
# 来源: Sun(7) + Moon(3) + Mars(6) + Mercury(6) + Jupiter(4) + Venus(3) + Saturn(4) + Lagna(6) = 39
|
||
_SATURN_BAV = {
|
||
'Sun': [1, 2, 4, 7, 8, 10, 11],
|
||
'Moon': [3, 6, 11],
|
||
'Mars': [3, 5, 6, 10, 11, 12],
|
||
'Mercury': [6, 8, 9, 10, 11, 12],
|
||
'Jupiter': [5, 6, 11, 12],
|
||
'Venus': [6, 11, 12],
|
||
'Saturn': [3, 5, 6, 11],
|
||
'Lagna': [1, 3, 4, 6, 10, 11],
|
||
}
|
||
|
||
# --- Lagna BAV(总49,不计入 SAV 337) ---
|
||
# 来源: Sun(6) + Moon(5) + Mars(5) + Mercury(7) + Jupiter(9) + Venus(7) + Saturn(6) + Lagna(4) = 49
|
||
_LAGNA_BAV = {
|
||
'Sun': [3, 4, 6, 10, 11, 12],
|
||
'Moon': [3, 6, 10, 11, 12],
|
||
'Mars': [1, 3, 6, 10, 11],
|
||
'Mercury': [1, 2, 4, 6, 8, 10, 11],
|
||
'Jupiter': [1, 2, 4, 5, 6, 7, 9, 10, 11],
|
||
'Venus': [1, 2, 3, 4, 5, 8, 9],
|
||
'Saturn': [1, 3, 4, 6, 10, 11],
|
||
'Lagna': [3, 6, 10, 11],
|
||
}
|
||
|
||
# 汇总为统一查找表
|
||
BAV_CONTRIBUTION = {
|
||
'Sun': _SUN_BAV,
|
||
'Moon': _MOON_BAV,
|
||
'Mars': _MARS_BAV,
|
||
'Mercury': _MERCURY_BAV,
|
||
'Jupiter': _JUPITER_BAV,
|
||
'Venus': _VENUS_BAV,
|
||
'Saturn': _SATURN_BAV,
|
||
'Lagna': _LAGNA_BAV,
|
||
}
|
||
|
||
# 各行星BAV固定总数(宇宙常数)
|
||
BAV_TOTALS = {
|
||
'Sun': 48, 'Moon': 49, 'Mars': 39, 'Mercury': 54,
|
||
'Jupiter': 56, 'Venus': 52, 'Saturn': 39, 'Lagna': 49
|
||
}
|
||
|
||
# 7颗行星 BAV 总和 = SAV 337
|
||
EXPECTED_SAV_TOTAL = 337
|
||
|
||
# Shodhya Pinda 行星权重
|
||
PLANET_WEIGHTS = {
|
||
'Sun': 5, 'Moon': 5, 'Mars': 8, 'Mercury': 5,
|
||
'Jupiter': 10, 'Venus': 7, 'Saturn': 5
|
||
}
|
||
|
||
# 行星列表
|
||
SEVEN_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
|
||
ALL_SOURCES = SEVEN_PLANETS + ['Lagna']
|
||
|
||
|
||
def _get_sign_idx(sign_name: str) -> int:
|
||
"""星座名称转索引"""
|
||
return SIGNS.index(sign_name) if sign_name in SIGNS else 0
|
||
|
||
|
||
def calc_ashtakavarga(planets: Dict, asc_sign_idx: int) -> Dict:
|
||
"""
|
||
计算完整 Ashtakavarga(v2.0 BPHS标准法)
|
||
|
||
Args:
|
||
planets: 行星数据 dict,每颗行星需要 {sign, degree, ...}
|
||
sign 是星座名称
|
||
asc_sign_idx: 上升星座在 SIGNS 中的索引 (0-11)
|
||
|
||
Returns:
|
||
完整的 Ashtakavarga 计算结果,含 BAV、SAV、校验
|
||
"""
|
||
# 1. 确定每个来源(7行星+Lagna)的星座索引
|
||
source_sign_idx = {}
|
||
for pname in SEVEN_PLANETS:
|
||
if pname in planets:
|
||
sign = planets[pname].get('sign', '')
|
||
if sign in SIGNS:
|
||
source_sign_idx[pname] = SIGNS.index(sign)
|
||
source_sign_idx['Lagna'] = asc_sign_idx
|
||
|
||
# 2. 为每颗行星计算 BAV
|
||
# BAV[planet][sign_i] = count of sources that contribute bindhu to sign_i
|
||
bav_results = {}
|
||
sav = [0] * 12 # 12星座的SAV(仅统计7行星贡献)
|
||
lagna_sav = [0] * 12 # Lagna单独的贡献
|
||
|
||
for planet in ALL_SOURCES:
|
||
contribution_rules = BAV_CONTRIBUTION[planet]
|
||
bav = [0] * 12 # 12星座的 bindhu 数
|
||
|
||
for source in ALL_SOURCES:
|
||
if source not in source_sign_idx:
|
||
continue
|
||
# 获取该 source 对该 planet BAV 的有利宫位
|
||
favorable_houses = contribution_rules.get(source, [])
|
||
source_idx = source_sign_idx[source]
|
||
|
||
# 从 source 所在星座开始,将有利宫位映射到实际星座
|
||
for house in favorable_houses:
|
||
target_sign = (source_idx + house - 1) % 12
|
||
bav[target_sign] += 1
|
||
|
||
bav_total = sum(bav)
|
||
expected = BAV_TOTALS.get(planet, 0)
|
||
|
||
bav_results[planet] = {
|
||
'bindus': bav,
|
||
'total': bav_total,
|
||
'expected_total': expected,
|
||
'valid': bav_total == expected,
|
||
'deviation': abs(bav_total - expected),
|
||
}
|
||
|
||
# 累加到 SAV(仅7行星)
|
||
if planet in SEVEN_PLANETS:
|
||
for i in range(12):
|
||
sav[i] += bav[i]
|
||
else:
|
||
# Lagna 的贡献单独记录
|
||
for i in range(12):
|
||
lagna_sav[i] += bav[i]
|
||
|
||
# 3. 校验 SAV 总分
|
||
sav_total = sum(sav)
|
||
|
||
# 4. SAV 评估
|
||
sav_assessment = []
|
||
for i in range(12):
|
||
score = sav[i]
|
||
if score >= 30:
|
||
level = "极吉"
|
||
elif score >= 28:
|
||
level = "吉利"
|
||
elif score >= 25:
|
||
level = "中等"
|
||
else:
|
||
level = "挑战"
|
||
sav_assessment.append({
|
||
'sign': SIGNS[i],
|
||
'score': score,
|
||
'level': level,
|
||
})
|
||
|
||
# 5. 含 Lagna 的完整 SAV(386 = 337 + 49)
|
||
full_sav = [sav[i] + lagna_sav[i] for i in range(12)]
|
||
|
||
# 6. Shodhya/Yoga Pinda 计算
|
||
yoga_pinda = calc_yoga_pinda(bav_results, planets, asc_sign_idx)
|
||
shodhya = {
|
||
pname: {
|
||
'rashi_pinda': row['rashi_pinda'],
|
||
'graha_pinda': row['graha_pinda'],
|
||
'total_pinda': row['yoga_pinda'],
|
||
'bindu_at_own_sign': row['bindu_at_own_sign'],
|
||
}
|
||
for pname, row in yoga_pinda.get('planets', {}).items()
|
||
}
|
||
|
||
# 7. 排名
|
||
ranked_sav = sorted(enumerate(sav_assessment), key=lambda x: x[1]['score'], reverse=True)
|
||
|
||
# 8. BAV 校验报告
|
||
bav_validation = []
|
||
all_valid = True
|
||
for planet in ALL_SOURCES:
|
||
r = bav_results[planet]
|
||
status = "✅" if r['valid'] else f"❌ 偏差{r['deviation']}"
|
||
if not r['valid']:
|
||
all_valid = False
|
||
bav_validation.append({
|
||
'planet': planet,
|
||
'actual': r['total'],
|
||
'expected': r['expected_total'],
|
||
'status': status,
|
||
})
|
||
|
||
return {
|
||
'method': 'Ashtakavarga八分法(BPHS/PVR书例校准v2.1)',
|
||
'version': '2.1',
|
||
'bav': bav_results,
|
||
'sav': {
|
||
'scores': {SIGNS[i]: sav[i] for i in range(12)},
|
||
'full_scores_with_lagna': {SIGNS[i]: full_sav[i] for i in range(12)},
|
||
'assessment': sav_assessment,
|
||
'total': sav_total,
|
||
'expected_total': EXPECTED_SAV_TOTAL,
|
||
'valid': sav_total == EXPECTED_SAV_TOTAL,
|
||
'deviation': abs(sav_total - EXPECTED_SAV_TOTAL),
|
||
'full_total_with_lagna': sum(full_sav),
|
||
},
|
||
'bav_validation': bav_validation,
|
||
'all_bav_valid': all_valid,
|
||
'shodhya_pinda': shodhya,
|
||
'yoga_pinda': yoga_pinda,
|
||
'strongest_signs': [SIGNS[i] for i, _ in ranked_sav[:3]],
|
||
'weakest_signs': [SIGNS[i] for i, _ in ranked_sav[-3:]],
|
||
'house_scores': _map_to_houses(sav_assessment, asc_sign_idx),
|
||
'house_scores_full': _map_to_houses_values(full_sav, asc_sign_idx),
|
||
}
|
||
|
||
|
||
def _map_to_houses(sav_assessment: List[Dict], asc_sign_idx: int) -> Dict:
|
||
"""将 SAV 分数映射到宫位"""
|
||
houses = {}
|
||
for house_num in range(1, 13):
|
||
sign_idx = (asc_sign_idx + house_num - 1) % 12
|
||
score = sav_assessment[sign_idx]['score']
|
||
level = sav_assessment[sign_idx]['level']
|
||
houses[f"house_{house_num}"] = {
|
||
'sign': SIGNS[sign_idx],
|
||
'sav_score': score,
|
||
'level': level,
|
||
}
|
||
return houses
|
||
|
||
|
||
def _map_to_houses_values(scores: List[int], asc_sign_idx: int) -> Dict:
|
||
"""将含Lagna的完整SAV映射到宫位"""
|
||
houses = {}
|
||
for house_num in range(1, 13):
|
||
sign_idx = (asc_sign_idx + house_num - 1) % 12
|
||
houses[f"house_{house_num}"] = {
|
||
'sign': SIGNS[sign_idx],
|
||
'sav_score': scores[sign_idx],
|
||
}
|
||
return houses
|
||
|
||
|
||
def calc_prastara_av(planets: Dict, asc_sign_idx: int) -> Dict:
|
||
"""
|
||
计算 Prastara Ashtakavarga (PAV) — 展开式八分行星力量表
|
||
|
||
PAV 是一个 7×12×8 的三维矩阵:
|
||
- 7 颗行星 (Sun, Moon, Mars, Mercury, Jupiter, Venus, Saturn)
|
||
- 12 个宫位/星座
|
||
- 8 个贡献源 (7行星 + Lagna)
|
||
|
||
每格为 0 或 1,表示该贡献源是否对该行星的该宫位贡献 bindu。
|
||
|
||
Args:
|
||
planets: 行星数据 dict,每颗行星需要 {sign, degree, ...}
|
||
asc_sign_idx: 上升星座在 SIGNS 中的索引 (0-11)
|
||
|
||
Returns:
|
||
{
|
||
'pav': {planet: {source: [12个0/1值]}},
|
||
'pav_summary': {planet: {source: total_bindus}},
|
||
'validation': {...}
|
||
}
|
||
"""
|
||
# 1. 确定每个来源的星座索引
|
||
source_sign_idx = {}
|
||
for pname in SEVEN_PLANETS:
|
||
if pname in planets:
|
||
sign = planets[pname].get('sign', '')
|
||
if sign in SIGNS:
|
||
source_sign_idx[pname] = SIGNS.index(sign)
|
||
source_sign_idx['Lagna'] = asc_sign_idx
|
||
|
||
# 2. 计算 PAV 三维矩阵
|
||
pav = {}
|
||
pav_summary = {}
|
||
|
||
for planet in SEVEN_PLANETS:
|
||
contribution_rules = BAV_CONTRIBUTION[planet]
|
||
pav[planet] = {}
|
||
pav_summary[planet] = {}
|
||
|
||
for source in ALL_SOURCES:
|
||
if source not in source_sign_idx:
|
||
continue
|
||
|
||
# 12个宫位的bindu标记(0或1)
|
||
bindu_markers = [0] * 12
|
||
favorable_houses = contribution_rules.get(source, [])
|
||
source_idx = source_sign_idx[source]
|
||
|
||
for house in favorable_houses:
|
||
target_sign = (source_idx + house - 1) % 12
|
||
bindu_markers[target_sign] = 1
|
||
|
||
pav[planet][source] = bindu_markers
|
||
pav_summary[planet][source] = sum(bindu_markers)
|
||
|
||
# 3. 验证:每行的总和应等于BAV对应值
|
||
validation = []
|
||
all_valid = True
|
||
for planet in SEVEN_PLANETS:
|
||
for source in ALL_SOURCES:
|
||
if source not in pav_summary.get(planet, {}):
|
||
continue
|
||
actual = pav_summary[planet][source]
|
||
# 从BAV_CONTRIBUTION查期望的贡献数(该source对planet的宫位数)
|
||
expected = len(BAV_CONTRIBUTION[planet].get(source, []))
|
||
valid = actual == expected
|
||
if not valid:
|
||
all_valid = False
|
||
validation.append({
|
||
'planet': planet,
|
||
'source': source,
|
||
'actual': actual,
|
||
'expected': expected,
|
||
'valid': valid,
|
||
})
|
||
|
||
return {
|
||
'method': 'Prastara Ashtakavarga (PAV)',
|
||
'version': '1.0',
|
||
'matrix_shape': {'planets': len(SEVEN_PLANETS), 'signs': 12, 'sources': len(ALL_SOURCES)},
|
||
'pav': pav,
|
||
'pav_summary': pav_summary,
|
||
'validation': validation,
|
||
'all_valid': all_valid,
|
||
}
|
||
|
||
|
||
def calc_yoga_pinda(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict:
|
||
"""
|
||
计算 Yoga Pinda / Shodhya Pinda 汇总。
|
||
|
||
本项目沿用既有 v2.1 口径:取行星本星座的 BAV bindu,分别乘以星座权重与行星权重;
|
||
两者相加作为 Yoga Pinda。该函数把原本嵌在 calc_ashtakavarga() 内部的结果提升为
|
||
一等契约,便于 API、前端与测试直接引用。
|
||
"""
|
||
source_sign_idx = {}
|
||
for pname in SEVEN_PLANETS:
|
||
if pname in planets:
|
||
sign = planets[pname].get('sign', '')
|
||
if sign in SIGNS:
|
||
source_sign_idx[pname] = SIGNS.index(sign)
|
||
source_sign_idx['Lagna'] = asc_sign_idx
|
||
|
||
rows = {}
|
||
all_valid = True
|
||
for pname in SEVEN_PLANETS:
|
||
bav_row = bav_results.get(pname, {})
|
||
bindus = bav_row.get('bindus', [])
|
||
own_sign = source_sign_idx.get(pname)
|
||
if own_sign is None or len(bindus) != 12:
|
||
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
|
||
rows[pname] = {
|
||
'sign': SIGNS[own_sign],
|
||
'bindu_at_own_sign': bindu_at_own,
|
||
'rashi_weight': sign_weight,
|
||
'planet_weight': planet_weight,
|
||
'rashi_pinda': rashi_pinda,
|
||
'graha_pinda': graha_pinda,
|
||
'yoga_pinda': rashi_pinda + graha_pinda,
|
||
}
|
||
|
||
ranked = sorted(rows.items(), key=lambda item: item[1]['yoga_pinda'], reverse=True)
|
||
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',
|
||
'planets': rows,
|
||
'summary': {
|
||
'strongest_planet': strongest,
|
||
'weakest_planet': weakest,
|
||
'total_yoga_pinda': total,
|
||
'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 权重口径。',
|
||
}
|
||
|
||
|
||
# calc_sodhita_av (removed, BUG-1209): it subtracted the Sun, Mars and Saturn
|
||
# contributions from every BAV and was labelled "BPHS标准"; that is not the BPHS
|
||
# Sodhita. The standard chain (Trikona -> Ekadhipatya per BAV row) is
|
||
# scripts/ashtakavarga_shodhana.py, built on the two functions below.
|
||
|
||
|
||
TRIKONA_SIGN_GROUPS = (
|
||
(0, 4, 8), # Aries, Leo, Sagittarius
|
||
(1, 5, 9), # Taurus, Virgo, Capricorn
|
||
(2, 6, 10), # Gemini, Libra, Aquarius
|
||
(3, 7, 11), # Cancer, Scorpio, Pisces
|
||
)
|
||
|
||
|
||
def calc_trikona_shodhana(sav_scores: List[int]) -> Dict:
|
||
"""Apply the classical Trikona Shodhana reduction to a 12-sign SAV row.
|
||
|
||
The minimum value in each elemental trine is removed from all three signs;
|
||
the original row remains in the result for auditability.
|
||
"""
|
||
if len(sav_scores) != 12:
|
||
raise ValueError('SAV scores must contain exactly 12 signs')
|
||
original = [int(value) for value in sav_scores]
|
||
reduced = original[:]
|
||
groups = []
|
||
for indexes in TRIKONA_SIGN_GROUPS:
|
||
baseline = min(original[index] for index in indexes)
|
||
for index in indexes:
|
||
reduced[index] -= baseline
|
||
groups.append({'sign_indexes': list(indexes), 'baseline_removed': baseline})
|
||
return {
|
||
'method': 'Trikona Shodhana',
|
||
'original_scores': original,
|
||
'reduced_scores': reduced,
|
||
'trikona_groups': groups,
|
||
'status': 'executed',
|
||
'claim_boundary': 'Standalone classical reduction; Ekadhipatya Shodhana remains a separate layer.',
|
||
}
|
||
|
||
|
||
EKADHIPATYA_SIGN_PAIRS = ((0, 7), (1, 6), (2, 5), (8, 11), (9, 10))
|
||
|
||
|
||
def calc_ekadhipatya_shodhana(
|
||
rows: Dict[str, List[int]], occupied_signs: set[int]
|
||
) -> Dict:
|
||
"""Apply PyJHora/PVR Ekadhipatya Shodhana to post-Trikona rows."""
|
||
reduced = {planet: list(values) for planet, values in rows.items()}
|
||
audit = []
|
||
for planet, values in reduced.items():
|
||
if len(values) != 12:
|
||
raise ValueError('each BAV row must contain exactly 12 signs')
|
||
for first, second in EKADHIPATYA_SIGN_PAIRS:
|
||
first_value, second_value = values[first], values[second]
|
||
if first_value == 0 or second_value == 0:
|
||
continue
|
||
first_occupied = first in occupied_signs
|
||
second_occupied = second in occupied_signs
|
||
before = [first_value, second_value]
|
||
if first_occupied and second_occupied:
|
||
continue
|
||
if first_occupied != second_occupied:
|
||
occupied, vacant = (first, second) if first_occupied else (second, first)
|
||
values[vacant] = values[occupied] if values[occupied] < values[vacant] else 0
|
||
elif first_value == second_value:
|
||
values[first] = values[second] = 0
|
||
elif first_value > second_value:
|
||
values[first] = second_value
|
||
else:
|
||
values[second] = first_value
|
||
audit.append({'planet': planet, 'pair': [first, second], 'before': before,
|
||
'after': [values[first], values[second]]})
|
||
return {
|
||
'method': 'Ekadhipatya Shodhana',
|
||
'rows': reduced,
|
||
'occupied_signs': sorted(occupied_signs),
|
||
'audit': audit,
|
||
'status': 'executed',
|
||
'claim_boundary': 'Applied after Trikona Shodhana; source profile follows PyJHora/PVR rule ordering.',
|
||
}
|