Files
Jyotisha/scripts/bhava_bala.py
T
jesse-uxGrok (agent loop)Cursor
6cbae4ce23 fix(bhava): use the named mirrored equal-house strength profile
Upstream a4d4a249. Dig tables, Capricorn's first half, the sputa formula, and the benefic set now match jyotishganit_mirrored_equal_house_v2. Mercury is neutral. The result names that profile and does not call it a classical ruling. BUG-1385.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 16:32:26 +08:00

289 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': [60, 50, 40, 30, 20, 10, 0, 10, 20, 30, 40, 50],
'chatushpada': [30, 20, 10, 0, 10, 20, 30, 40, 50, 60, 50, 40],
'jalachara': [30, 40, 50, 60, 50, 40, 30, 20, 10, 0, 10, 20],
'keeta': [0, 10, 20, 30, 40, 50, 60, 50, 40, 30, 20, 10],
}
def _get_sputa_drishti(angle_diff: float, planet: str = 'Sun') -> float:
"""jyotishganit 镜像角公式。火、木、土在特殊相位上另有一段。不是本仓方向性 Shadbala。"""
ad = abs(angle_diff) % 360
if ad > 180:
ad = 360 - ad
if ad < 30:
strength = 0.0
elif ad < 60:
strength = (ad - 30) / 2
elif ad < 90:
strength = ad - 45
elif ad < 120:
strength = 30 + (120 - ad) / 2
elif ad < 150:
strength = 150 - ad
else:
strength = 2 * (ad - 150)
for special in {'Mars': (90, 210), 'Jupiter': (120, 240), 'Saturn': (60, 270)}.get(planet, ()):
distance = abs(ad - special)
if distance <= 15:
strength = max(strength, 60 * (1 - distance / 15))
return strength
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 前半是人象,Capricorn 前半是四足,不是水象。
real_nature = 'nara' if 'Sagittarius' in nature_key else 'chatushpada'
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', 'Moon']
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, planet_name)
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': 'jyotishganit_mirrored_equal_house_v2',
'version': '2.0',
'score_unit': 'Virupas',
'method_boundary': '具名实现:镜像相位、吉星固定为月亮木星金星、水星中性、上升等宫中点。不是本仓方向性 Shadbala,也不是经典定论。',
'claim_boundary': 'Bhava Bala 只是三分量原始分数,不能单独证明某项人生结果。',
'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'] = 'jyotishganit_mirrored_equal_house_v2:宫主 Shadbala + Dig + Drishti。吉星为月亮、木星、金星,水星中性。不是经典定论。'
result['unit'] = 'virupa'
result['source'] = 'scripts/bhava_bala.py'
return result