Files
Jyotisha/scripts/divisional_yoga.py
732642856 a47206dd8e v6.9.0: 7项遗漏任务一次性攻克
## 新建模块 (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
2026-06-11 22:07:47 +08:00

315 lines
11 KiB
Python
Raw Permalink 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
"""
分盘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 YogaKendra-Kona lord conjunction
3. Dhana Yoga2H-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