a47206dd8e
## 新建模块 (4个) - transit_trigger.py: Transit精确触发搜索 - 度数级二分搜索 find_exact_transit_date() - 区间批量搜索 search_transit_triggers() - Sade Sati + 逆行检测 - divisional_yoga.py: 分盘Yoga识别 - D9/D10/D12行星位置转换 - PMC/Raja/Dhana/Moon/Exchange Yoga在分盘中检测 - 多分盘综合报告 varga_yoga_report() - benchmarks/run_all_benchmarks.py: 统一Benchmark运行器 - Chara Dasha/Vimshottari/Shadbala 三轮 - scripts/oss_monitor.py: 开源项目自动监控 - 7个项目实时跟踪(Stars/更新/许可证) - 变更diff报告 ## Web应用新增 (2个Tab) - 🔍 验证Tab: 验前事反推 + 名人案例匹配 + 误区警告 - 🪐 过境Tab: 日期搜索 + API触发点展示 - Tab总数: 14→16 ## 测试 50/50 全通过 | Web构建: 24KB HTML + 329KB JS
315 lines
11 KiB
Python
315 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
分盘Yoga识别引擎 (v6.9.0)
|
||
在D9(Navamsa)、D10(Dasamsa)等分盘中运行Yoga检测。
|
||
分盘中的Yoga可以提供更精细的解读维度。
|
||
|
||
基于BPHS标准:Yoga在分盘中成立条件更严格,
|
||
因为分盘本身就是主盘的细化。
|
||
"""
|
||
from typing import Dict, List
|
||
import sys, os
|
||
|
||
# 星座常量
|
||
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'}
|
||
|
||
|
||
def _calc_navamsa_position(planet_lon: float) -> tuple:
|
||
"""计算行星在D9(Navamsa)中的位置"""
|
||
sign_idx = int(planet_lon / 30) % 12
|
||
deg_in_sign = planet_lon % 30
|
||
navamsa_size = 30.0 / 9 # 3°20'
|
||
navamsa_idx = int(deg_in_sign / navamsa_size)
|
||
# D9中星座映射(基于sign_index和navamsa_idx的BPHS规则)
|
||
if sign_idx in (0, 4, 8): # Fire: Aries, Leo, Sagittarius
|
||
d9_sign = (0 + navamsa_idx) % 12
|
||
elif sign_idx in (1, 5, 9): # Earth: Taurus, Virgo, Capricorn
|
||
d9_sign = (9 + navamsa_idx) % 12
|
||
elif sign_idx in (2, 6, 10): # Air: Gemini, Libra, Aquarius
|
||
d9_sign = (6 + navamsa_idx) % 12
|
||
else: # Water: Cancer, Scorpio, Pisces
|
||
d9_sign = (3 + navamsa_idx) % 12
|
||
return d9_sign, (deg_in_sign % navamsa_size) * 9
|
||
|
||
|
||
def _calc_dasamsa_position(planet_lon: float) -> tuple:
|
||
"""计算行星在D10(Dasamsa)中的位置"""
|
||
sign_idx = int(planet_lon / 30) % 12
|
||
deg_in_sign = planet_lon % 30
|
||
dasamsa_size = 30.0 / 10 # 3°
|
||
dasamsa_idx = int(deg_in_sign / dasamsa_size)
|
||
if sign_idx % 2 == 0: # Odd signs
|
||
d10_sign = (sign_idx + dasamsa_idx) % 12
|
||
else:
|
||
d10_sign = (sign_idx + 9 + dasamsa_idx) % 12
|
||
return d10_sign, (deg_in_sign % dasamsa_size) * 10
|
||
|
||
|
||
def _calc_dvadasamsa_position(planet_lon: float) -> tuple:
|
||
"""计算行星在D12(Dvadasamsa)中的位置"""
|
||
sign_idx = int(planet_lon / 30) % 12
|
||
deg_in_sign = planet_lon % 30
|
||
d12_size = 30.0 / 12 # 2°30'
|
||
d12_idx = int(deg_in_sign / d12_size)
|
||
d12_sign = (sign_idx * 12 + d12_idx) % 12
|
||
return d12_sign, (deg_in_sign % d12_size) * 12
|
||
|
||
|
||
def convert_to_varga(planets: Dict, varga: str = 'D9') -> Dict:
|
||
"""
|
||
将行星位置转换到分盘中。
|
||
|
||
Args:
|
||
planets: {planet_name: {'lon': float, 'sign': str}} 或 {planet_name: float}
|
||
varga: 'D9' | 'D10' | 'D12' | 'D16' | 'D20' | 'D24' | 'D27' | 'D30'
|
||
|
||
Returns:
|
||
{planet_name: {'sign': str, 'sign_idx': int, 'degree': float, 'house': int}}
|
||
"""
|
||
calc_fn = {
|
||
'D9': _calc_navamsa_position,
|
||
'D10': _calc_dasamsa_position,
|
||
'D12': _calc_dvadasamsa_position,
|
||
}.get(varga, _calc_navamsa_position)
|
||
|
||
result = {}
|
||
for pname, pdata in planets.items():
|
||
if isinstance(pdata, dict):
|
||
lon = pdata.get('lon', pdata.get('degree', 0))
|
||
else:
|
||
lon = float(pdata)
|
||
|
||
sign_idx, degree = calc_fn(lon % 360)
|
||
sign = SIGNS[sign_idx]
|
||
result[pname] = {'sign': sign, 'sign_idx': sign_idx, 'degree': round(degree, 2)}
|
||
|
||
return result
|
||
|
||
|
||
def detect_varga_yogas(planets: Dict, varga: str = 'D9', asc_sign: str = None) -> List[Dict]:
|
||
"""
|
||
在指定分盘中检测Yoga。
|
||
|
||
检测规则(适用于分盘):
|
||
1. Mahapurusha检测:行星在分盘中的Kendra并位于own/exalted sign
|
||
2. Raja Yoga:Kendra-Kona lord conjunction
|
||
3. Dhana Yoga:2H-11H lord connection
|
||
4. Moon Yogas:基于分盘Moon位置
|
||
|
||
Args:
|
||
planets: 原始行星数据(含经度)
|
||
varga: 分盘名称
|
||
asc_sign: 分盘上升星座(可选)
|
||
|
||
Returns:
|
||
[{'name': str, 'type': str, 'planets': list, 'strength': str, 'description': str}]
|
||
"""
|
||
varga_planets = convert_to_varga(planets, varga)
|
||
yogas = []
|
||
|
||
# 1. 检测分盘中的PMC
|
||
pmc = _detect_pmc_in_varga(varga_planets)
|
||
yogas.extend(pmc)
|
||
|
||
# 2. 检测分盘中的Kendra-Kona连接
|
||
raja = _detect_raja_in_varga(varga_planets)
|
||
yogas.extend(raja)
|
||
|
||
# 3. 检测分盘中的Dhana
|
||
dhana = _detect_dhana_in_varga(varga_planets)
|
||
yogas.extend(dhana)
|
||
|
||
# 4. 检测分盘中的Moon相关Yoga
|
||
moon = _detect_moon_varga_yogas(varga_planets)
|
||
yogas.extend(moon)
|
||
|
||
# 5. 检测Exchange (Parivartana)
|
||
exchange = _detect_exchange_in_varga(varga_planets)
|
||
yogas.extend(exchange)
|
||
|
||
return yogas
|
||
|
||
|
||
def _get_varga_lords(planets: Dict) -> Dict[str, str]:
|
||
"""获取分盘中每颗行星的主星"""
|
||
lords = {}
|
||
for pname, pdata in planets.items():
|
||
sign = pdata.get('sign', '')
|
||
lords[pname] = SIGN_LORDS.get(sign, '')
|
||
return lords
|
||
|
||
|
||
def _detect_pmc_in_varga(varga_planets: Dict) -> List[Dict]:
|
||
"""分盘中的PMC检测(简化)"""
|
||
yogas = []
|
||
pmc_configs = {
|
||
'Mars': {'sign': 'Capricorn', 'name': 'Ruchaka', 'house_need': (1, 4, 7, 10)},
|
||
'Mercury': {'sign': 'Virgo', 'name': 'Bhadra', 'house_need': (1, 4, 7, 10)},
|
||
'Jupiter': {'sign': 'Cancer', 'name': 'Hamsa', 'house_need': (1, 4, 7, 10)},
|
||
'Venus': {'sign': 'Pisces', 'name': 'Malavya', 'house_need': (1, 4, 7, 10)},
|
||
'Saturn': {'sign': 'Aquarius', 'name': 'Shasha', 'house_need': (1, 4, 7, 10)},
|
||
}
|
||
for planet, config in pmc_configs.items():
|
||
if planet in varga_planets:
|
||
pd = varga_planets[planet]
|
||
if pd.get('sign') in (config['sign'], SIGN_LORDS.get(config['sign'], '')):
|
||
yogas.append({
|
||
'name': f'{config["name"]} (在分盘中)',
|
||
'type': 'PMC_varga',
|
||
'planets': [planet],
|
||
'strength': '中等(分盘中成立)',
|
||
'description': f'{planet}在分盘中位于{config["sign"]}形成{config["name"]} Yoga',
|
||
})
|
||
return yogas
|
||
|
||
|
||
def _detect_raja_in_varga(varga_planets: Dict) -> List[Dict]:
|
||
"""分盘中的Raja Yoga检测"""
|
||
yogas = []
|
||
lords = _get_varga_lords(varga_planets)
|
||
|
||
# Kendra (1,4,7,10) lord + Kona (1,5,9) lord 在同一宫
|
||
kendra_lords = set()
|
||
kona_lords = set()
|
||
for pn, pd in varga_planets.items():
|
||
h = pd.get('house', 0)
|
||
if h in (1, 4, 7, 10):
|
||
kendra_lords.add(pn)
|
||
if h in (1, 5, 9):
|
||
kona_lords.add(pn)
|
||
|
||
# Kendra lord 和 Kona lord 形成关联
|
||
for kl in kendra_lords:
|
||
for kn in kona_lords:
|
||
if kl != kn:
|
||
kl_sign = varga_planets.get(kl, {}).get('sign', '')
|
||
kn_sign = varga_planets.get(kn, {}).get('sign', '')
|
||
if kl_sign and kl_sign == kn_sign:
|
||
yogas.append({
|
||
'name': 'Raja Yoga (分盘)',
|
||
'type': 'raja_varga',
|
||
'planets': [kl, kn],
|
||
'strength': '强(分盘中Kendra-Kona lord合相)',
|
||
'description': f'{kl}(Kendra主)和{kn}(Kona主)在分盘中合相于{kl_sign}',
|
||
})
|
||
return yogas
|
||
|
||
|
||
def _detect_dhana_in_varga(varga_planets: Dict) -> List[Dict]:
|
||
"""分盘中的Dhana Yoga"""
|
||
yogas = []
|
||
lords = _get_varga_lords(varga_planets)
|
||
|
||
# 2H lord 和 11H lord 的连接
|
||
h2_lord = h11_lord = None
|
||
for pn, pd in varga_planets.items():
|
||
h = pd.get('house', 0)
|
||
if h == 2:
|
||
h2_lord = pn
|
||
if h == 11:
|
||
h11_lord = pn
|
||
|
||
if h2_lord and h11_lord:
|
||
h2_sign = varga_planets[h2_lord].get('sign', '')
|
||
h11_sign = varga_planets[h11_lord].get('sign', '')
|
||
if h2_sign == h11_sign:
|
||
yogas.append({
|
||
'name': 'Dhana Yoga (分盘)',
|
||
'type': 'dhana_varga',
|
||
'planets': [h2_lord, h11_lord],
|
||
'strength': '强(2H-11H lord连接)',
|
||
'description': f'分盘中2H主{h2_lord}和11H主{h11_lord}形成Dhana Yoga',
|
||
})
|
||
return yogas
|
||
|
||
|
||
def _detect_moon_varga_yogas(varga_planets: Dict) -> List[Dict]:
|
||
"""分盘中Moon的Yoga"""
|
||
yogas = []
|
||
if 'Moon' not in varga_planets:
|
||
return yogas
|
||
|
||
moon = varga_planets['Moon']
|
||
moon_sign = moon.get('sign', '')
|
||
|
||
# Kemadruma检查
|
||
has_neighbor = False
|
||
for pn in ['Sun', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn']:
|
||
if pn in varga_planets:
|
||
ps = varga_planets[pn]
|
||
if ps.get('house', 0) in (moon.get('house', 0) - 1, moon.get('house', 0) + 1):
|
||
has_neighbor = True
|
||
break
|
||
if not has_neighbor:
|
||
yogas.append({
|
||
'name': 'Kemadruma (分盘)',
|
||
'type': 'moon_varga',
|
||
'planets': ['Moon'],
|
||
'strength': '挑战(分盘月独)',
|
||
'description': f'分盘Moon在{moon_sign}无邻星,形成Kemadruma',
|
||
})
|
||
|
||
return yogas
|
||
|
||
|
||
def _detect_exchange_in_varga(varga_planets: Dict) -> List[Dict]:
|
||
"""分盘中的星座交换(Parivartana)"""
|
||
yogas = []
|
||
lords = _get_varga_lords(varga_planets)
|
||
checked = set()
|
||
|
||
for p1 in ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn']:
|
||
for p2 in ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn']:
|
||
if p1 >= p2:
|
||
continue
|
||
if p1 not in varga_planets or p2 not in varga_planets:
|
||
continue
|
||
if (p1, p2) in checked:
|
||
continue
|
||
checked.add((p1, p2))
|
||
|
||
s1 = varga_planets[p1].get('sign', '')
|
||
s2 = varga_planets[p2].get('sign', '')
|
||
if SIGN_LORDS.get(s1, '') == p2 and SIGN_LORDS.get(s2, '') == p1:
|
||
yogas.append({
|
||
'name': 'Parivartana (分盘)',
|
||
'type': 'exchange_varga',
|
||
'planets': [p1, p2],
|
||
'strength': '强(星座交换)',
|
||
'description': f'{p1}({s1})和{p2}({s2})在分盘中形成Parivartana Yoga',
|
||
})
|
||
|
||
return yogas
|
||
|
||
|
||
def varga_yoga_report(planets: Dict, vargas: List[str] = None) -> Dict:
|
||
"""
|
||
生成多分盘Yoga综合报告。
|
||
|
||
Args:
|
||
planets: 原始行星数据
|
||
vargas: 要检查的分盘列表
|
||
|
||
Returns:
|
||
{'D9': [yoga_list], 'D10': [yoga_list], 'summary': str}
|
||
"""
|
||
if vargas is None:
|
||
vargas = ['D9', 'D10']
|
||
|
||
report = {}
|
||
total = 0
|
||
for v in vargas:
|
||
yogas = detect_varga_yogas(planets, v)
|
||
report[v] = yogas
|
||
total += len(yogas)
|
||
|
||
report['total_varga_yogas'] = total
|
||
report['vargas_checked'] = vargas
|
||
report['summary'] = f"在{len(vargas)}个分盘中发现{total}个Yoga"
|
||
return report
|