#!/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 权重口径。', } def calc_sodhita_av(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict: """ 计算 Sodhita Ashtakavarga — 净化后的八分行星力量表 Sodhita 算法(BPHS标准): 从每颗行星的每个宫位BAV中,减去Saturn、Mars和Sun的贡献(若结果为负取0)。 这反映了"减去凶星影响后的纯净力量"。 Args: bav_results: calc_ashtakavarga返回的bav结果 planets: 行星数据 dict asc_sign_idx: 上升星座索引 Returns: { 'sodhita_bav': {planet: [12个sodhita值]}, 'sodhita_sav': [12个sodhita SAV值], 'reduction_summary': {...} } """ # 1. 获取PAV展开表(需要知道每个贡献源的单独贡献) pav_data = calc_prastara_av(planets, asc_sign_idx) pav = pav_data['pav'] # 2. 定义需要减去的"凶星"贡献源 malefic_sources = ['Saturn', 'Mars', 'Sun'] # 3. 计算Sodhita BAV sodhita_bav = {} total_reduction = {} for planet in SEVEN_PLANETS: if planet not in pav: continue # 原始BAV(从所有8个来源) original_bav = [0] * 12 for source in ALL_SOURCES: if source in pav.get(planet, {}): for i in range(12): original_bav[i] += pav[planet][source][i] # 减去凶星贡献 reduction = [0] * 12 sodhita = [0] * 12 for i in range(12): malefic_sum = 0 for source in malefic_sources: if source in pav.get(planet, {}): malefic_sum += pav[planet][source][i] reduction[i] = malefic_sum sodhita[i] = max(0, original_bav[i] - malefic_sum) sodhita_bav[planet] = sodhita total_reduction[planet] = sum(reduction) # 4. 计算Sodhita SAV sodhita_sav = [0] * 12 for planet in SEVEN_PLANETS: if planet in sodhita_bav: for i in range(12): sodhita_sav[i] += sodhita_bav[planet][i] # 5. 评估 sodhita_assessment = [] for i in range(12): score = sodhita_sav[i] if score >= 25: level = "极吉" elif score >= 22: level = "吉利" elif score >= 19: level = "中等" else: level = "挑战" sodhita_assessment.append({ 'sign': SIGNS[i], 'score': score, 'level': level, }) return { 'method': 'Sodhita Ashtakavarga (BPHS标准)', 'version': '1.0', 'sodhita_bav': sodhita_bav, 'sodhita_sav': { 'scores': {SIGNS[i]: sodhita_sav[i] for i in range(12)}, 'assessment': sodhita_assessment, 'total': sum(sodhita_sav), }, 'reduction_summary': total_reduction, 'validation_note': 'Sodhita AV ≤ 原始BAV(逐宫位)', }