Files
Jyotisha/scripts/bhava_bala.py
T
Jesse_ChenandClaude Opus 5.5 6feb1a4aaa feat(bhava-bala): reports use the formal three components; local +2/-1.5 score hidden (T4, BUG-1210)
Astrologer ruling 甲13. shadbala.calc_bhava_bala renamed calc_local_bhava_score and
removed from calc_shadbala's result (it fed the report's Bhava Bala table). New
bhava_bala.bhava_bala_from_chart (Bhavadhipati = lord's Shadbala virupas, Bhava
Dig, Bhava Drishti; equal houses as /api/bhava_bala; total and total_rupas; the
module's invented level bands not shown) drives modules.bhava_bala in the full
reading. Reader table: Bhavadhipati | Dig | Drishti | Total (Virupa) | Total
(Rupa); reference edition table and wealth/summary lines read the formal totals.
Other users of the local score: none outside shadbala.py (remedies and health
narrative read Shadbala rupas only).

Changed assertions (three columns in PROGRESS): test_shadbala_complete local-score
tests renamed; test_report_longform_gaps2 fixture rows and heading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-03 12:45:58 +08:00

288 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Bhava Bala(宫位力量)计算模块
基于jyotishganit (MIT License) 核心算法适配
三组件:
1. Bhava Adhipathi Bala - 宫位主星的Shadbala总分
2. Bhava Dig Bala - 基于星座性质的方向力量
3. Bhava Drik Bala - 基于Sputa Drishti的行星对宫位中点相位
"""
from typing import Dict, List
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
SIGN_LORDS = {
'Aries': 'Mars', 'Taurus': 'Venus', 'Gemini': 'Mercury', 'Cancer': 'Moon',
'Leo': 'Sun', 'Virgo': 'Mercury', 'Libra': 'Venus', 'Scorpio': 'Mars',
'Sagittarius': 'Jupiter', 'Capricorn': 'Saturn', 'Aquarius': 'Saturn', 'Pisces': 'Jupiter'
}
# 星座性质分类(BPHS标准)
# nara=人类, chatushpada=四足, jalachara=水栖, keeta=虫类, vanaspati=植物
SIGN_NATURE = {
'Aries': 'chatushpada', 'Taurus': 'chatushpada',
'Gemini': 'nara', 'Cancer': 'jalachara',
'Leo': 'chatushpada', 'Virgo': 'nara',
'Libra': 'nara', 'Scorpio': 'keeta',
'Sagittarius': 'nara', 'Capricorn': 'jalachara',
'Aquarius': 'nara', 'Pisces': 'jalachara',
}
# 宫位力量基于星座性质(BPHS标准,单位Virupas)
# 索引0-11对应第1-12宫
BHAVA_STRENGTH_FROM_SIGN_NATURE = {
'nara': [30, 20, 10, 20, 10, 20, 30, 20, 10, 20, 10, 20],
'chatushpada': [30, 20, 10, 20, 10, 20, 30, 20, 10, 20, 10, 20],
'jalachara': [20, 30, 10, 30, 10, 30, 20, 30, 10, 30, 10, 30],
'keeta': [10, 20, 30, 20, 30, 20, 10, 20, 30, 20, 30, 20],
'vanaspati': [20, 10, 20, 10, 20, 30, 20, 10, 20, 10, 20, 30],
}
# Sputa Drishti值表(基于角距离的连续相位力量)
def _get_sputa_drishti(angle_diff: float) -> float:
"""基于jxotishganit MIT算法,将精确角距离转换为Sputa Drishti值(0-60)"""
ad = angle_diff % 360
if ad > 180:
ad = 360 - ad
if ad <= 1.0: return 60.0
if ad <= 3.0: return 57.0
if ad <= 7.0: return 51.0
if ad <= 10.0: return 45.0
if ad <= 15.0: return 40.5
if ad <= 20.0: return 30.0
if ad <= 25.0: return 22.5
if ad <= 30.0: return 15.0
if ad <= 40.0: return 12.0
if ad <= 50.0: return 9.0
if ad <= 60.0: return 6.0
if ad <= 75.0: return 4.5
if ad <= 90.0: return 3.0
if ad <= 120.0: return 2.25
if ad <= 150.0: return 1.5
return 0.0
def calc_bhava_adhipathi_bala(house_signs: List[str], planet_shadbala: Dict) -> List[float]:
"""
Bhava Adhipathi Bala:宫位主星的Shadbala总分作为宫位力量基础。
Args:
house_signs: 12宫位的星座名称列表
planet_shadbala: 行星Shadbala总分 dict(Virupas)
Returns:
12个宫位的adhipathi bala值
"""
bala = []
for sign_name in house_signs:
lord = SIGN_LORDS.get(sign_name, '')
bala.append(planet_shadbala.get(lord, 0))
return bala
def calc_bhava_dig_bala(house_signs: List[str], house_degrees: List[float]) -> List[float]:
"""
Bhava Dig Bala:基于星座性质的宫位方向力量。
Args:
house_signs: 12宫位的星座名称列表
house_degrees: 12宫位的宫头度数列表
Returns:
12个宫位的dig bala值
"""
bala = []
for hno, sign_name in enumerate(house_signs):
nature_key = sign_name
deg = house_degrees[hno] if hno < len(house_degrees) else 15.0
# Sagittarius和Capricorn的双重性质处理
if sign_name == 'Sagittarius':
nature_key = 'Sagittarius_first_half' if deg < 15.0 else 'Sagittarius_second_half'
elif sign_name == 'Capricorn':
nature_key = 'Capricorn_first_half' if deg < 15.0 else 'Capricorn_second_half'
# 处理双性星座
if 'first_half' in nature_key:
# Sagittarius前半=nara, Capricorn前半=jalachara
real_nature = 'nara' if 'Sagittarius' in nature_key else 'jalachara'
elif 'second_half' in nature_key:
real_nature = 'chatushpada' if 'Sagittarius' in nature_key else 'jalachara'
else:
real_nature = SIGN_NATURE.get(sign_name, 'nara')
# 查找对应值
strengths = BHAVA_STRENGTH_FROM_SIGN_NATURE.get(real_nature, BHAVA_STRENGTH_FROM_SIGN_NATURE['nara'])
bala.append(float(strengths[hno] if hno < len(strengths) else 20))
return bala
def calc_bhava_drik_bala(house_signs: List[str], house_degrees: List[float],
asc_sign: str, asc_degree: float,
planet_positions: Dict, planet_shadbala: Dict) -> List[float]:
"""
Bhava Drik Bala:所有行星对宫位中点(Bhava Madhya)的Sputa Drishti相位。
基于jyotishganit MIT算法:计算行星到宫位中点的精确角距离,
使用Sputa Drishti将吉星相位减凶星相位后除以4。
Args:
house_signs: 12宫位的星座名称
house_degrees: 12宫位的宫头度数
asc_sign: 上升星座名称
asc_degree: 上升度数(0-30)
planet_positions: 行星位置 dict {planet: {'sign': str, 'degree': float}}
planet_shadbala: 行星Shadbala dict
Returns:
12个宫位的drik bala值
"""
BENEFICS = ['Jupiter', 'Venus', 'Mercury']
MALEFICS = ['Saturn', 'Mars', 'Sun']
asc_sign_idx = SIGNS.index(asc_sign) if asc_sign in SIGNS else 0
asc_long = asc_sign_idx * 30 + asc_degree
bala = []
for house_idx in range(12):
# 计算宫位中点(Equal House系统中,宫头 + 15°)
house_cusp = (asc_long + house_idx * 30) % 360
house_midpoint = (house_cusp + 15) % 360
benefic_sputa = 0.0
malefic_sputa = 0.0
for planet_name, planet_data in planet_positions.items():
if planet_name not in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
continue
p_sign = planet_data.get('sign', '')
p_deg = planet_data.get('degree', 0)
if p_sign not in SIGNS:
continue
p_sign_idx = SIGNS.index(p_sign)
p_long = p_sign_idx * 30 + (p_deg % 30)
# 角距离
diff = (p_long - house_midpoint + 180) % 360 - 180 # -180 to +180
dist_deg = abs(diff)
# Sputa Drishti值
sputa = _get_sputa_drishti(dist_deg)
if planet_name in BENEFICS:
benefic_sputa += sputa
elif planet_name in MALEFICS:
malefic_sputa += sputa
drishtipinda = benefic_sputa - malefic_sputa
house_drikbala = min(drishtipinda / 4.0, 20.0)
bala.append(round(house_drikbala, 3))
return bala
def calc_bhava_bala(house_signs: List[str], house_degrees: List[float],
asc_sign: str, asc_degree: float,
planet_positions: Dict, planet_shadbala: Dict) -> Dict:
"""
计算完整Bhava Bala(宫位三元力量)。
Args:
house_signs: 12宫位的星座名称
house_degrees: 12宫位的宫头度数
asc_sign: 上升星座名称
asc_degree: 上升度数
planet_positions: 行星位置
planet_shadbala: 行星Shadbala总分
Returns:
完整的Bhava Bala计算结果
"""
adhipathi = calc_bhava_adhipathi_bala(house_signs, planet_shadbala)
dig_bala = calc_bhava_dig_bala(house_signs, house_degrees)
drik_bala = calc_bhava_drik_bala(house_signs, house_degrees, asc_sign, asc_degree,
planet_positions, planet_shadbala)
houses = []
for i in range(12):
total = adhipathi[i] + dig_bala[i] + drik_bala[i]
if total >= 60:
level = "极强"
elif total >= 50:
level = "强"
elif total >= 40:
level = "中强"
elif total >= 30:
level = "中等"
elif total >= 20:
level = "弱"
else:
level = "极弱"
houses.append({
'house': i + 1,
'sign': house_signs[i] if i < len(house_signs) else SIGNS[i],
'adhipathi_bala': round(adhipathi[i], 2),
'dig_bala': round(dig_bala[i], 2),
'drik_bala': round(drik_bala[i], 2),
'total': round(total, 2),
'level': level,
})
return {
'method': 'Bhava Bala三元力量(基于jyotishganit MIT算法)',
'version': '1.0',
'houses': houses,
'strongest_house': max(houses, key=lambda h: h['total'])['house'],
'weakest_house': min(houses, key=lambda h: h['total'])['house'],
}
def bhava_bala_from_chart(planets: Dict, asc_sign: str, asc_degree: float, shadbala_planets: Dict) -> Dict:
"""Formal three-component Bhava Bala for one D1 chart (BUG-1210, ruling 甲13).
`planets` rows carry `sign` and a degree (`degree_in_sign`, `degree` or a
longitude); `shadbala_planets` is `shadbala.calc_shadbala(...)['planets']`,
whose `total_virupas` is each lord's Shadbala (Bhavadhipati Bala). Houses are
equal houses from the Lagna degree, as `/api/bhava_bala` uses. Totals are in
Virupas; `total_rupas` = total / 60. The `level` bands of `calc_bhava_bala`
are not a classical scale and are not displayed.
"""
if asc_sign not in SIGNS:
return {}
asc_idx = SIGNS.index(asc_sign)
house_signs = [SIGNS[(asc_idx + i) % 12] for i in range(12)]
house_degrees = [float(asc_degree) % 30 for _ in range(12)]
positions = {}
for name, row in (planets or {}).items():
if not isinstance(row, dict) or row.get('sign') not in SIGNS:
continue
degree = row.get('degree_in_sign')
if degree is None:
degree = row.get('degree', 0)
positions[name] = {'sign': row['sign'], 'degree': float(degree or 0) % 30}
virupas = {}
for name, row in (shadbala_planets or {}).items():
value = row.get('total_virupas') if isinstance(row, dict) else None
if isinstance(value, (int, float)):
virupas[name] = float(value)
if len(virupas) < 7:
return {}
result = calc_bhava_bala(house_signs, house_degrees, asc_sign, float(asc_degree) % 30, positions, virupas)
for house in result['houses']:
house['lord'] = SIGN_LORDS.get(house['sign'])
house['total_rupas'] = round(house['total'] / 60.0, 2)
house.pop('level', None)
result['method'] = 'Bhava Bala 三分量:Bhavadhipati(宫主 Shadbala)+ Bhava Dig + Bhava Drishti(jyotishganit MIT 算法,等宫)'
result['unit'] = 'virupa'
result['source'] = 'scripts/bhava_bala.py'
return result