TASK-upstream-sync4-20261002 T6, decision 4. Visible parallel calculators were
found in three places and all now read jaimini:
- yogas_doshas.calc_arudha_lagna / calc_upapada_lagna (house number taken as an
Aries-based sign index, no 1/7 exception) are deleted; the module's
arudha_lagna / upapada_lagna fields come from jaimini.
- special_lagnas.calculate_arudha_lagna / calculate_upapada_lagna ("count from
the landing" 1/7 exception, which disagrees with jaimini when the lord is
4th/10th from the source) are deleted; the full reading's Arudha_Lagna /
Upapada_Lagna rows are built from jaimini padas.
- the transit multi-reference table took "ascendant degree + 12th-lord degree"
as AL; it now uses jaimini's A1.
jaimini's own exception counting is unchanged (astrologer question 5); the
upstream "count from the landing" variant is not ported. A10 in special_lagnas
keeps its own exception rule (outside decision 4; listed in progress).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
848 lines
34 KiB
Python
848 lines
34 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Yogas / Doshas / Special Lagnas 计算模块 v6.0.14
|
||
|
||
支持:
|
||
- Raj Yogas: 王者瑜伽(主星互涉形成权力格局)
|
||
- Dhana Yogas: 财富瑜伽(2/11宫主与吉星组合)
|
||
- Pancha Mahapurusha Yogas: 五王瑜伽(吉星在角宫入庙)
|
||
- Neecha Bhanga Raj Yoga: 落陷解除王者瑜伽
|
||
- Mangal Dosha (Kuja Dosha): 火星煞(火星在1/2/4/7/8/12宫)
|
||
- Kaal Sarp Dosha: 时间蛇煞(所有行星在Rahu-Ketu轴线一侧)
|
||
- Pitra Dosha: 父辈煞(Sun与Rahu合相/受克)
|
||
- Sade Sati: 土星七年(土星过Moon前后各1星座)
|
||
- Arudha Lagna (AL): 镜像上升,取自 jaimini.calc_arudha_padas(单一来源)
|
||
- Upapada Lagna (UL): 第12宫 Arudha,取自 jaimini.calc_arudha_padas(单一来源)
|
||
"""
|
||
|
||
from typing import Dict, List, Tuple, Optional
|
||
|
||
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'}
|
||
|
||
# 吉星/凶星分类
|
||
NATURAL_BENEFICS = {'Jupiter', 'Venus', 'Mercury', 'Moon'}
|
||
NATURAL_MALEFICS = {'Saturn', 'Mars', 'Sun', 'Rahu', 'Ketu'}
|
||
|
||
|
||
def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
|
||
"""
|
||
计算 Raj Yogas(王者瑜伽)——权力、地位、社会影响力格局 v7.0
|
||
|
||
经典 Raj Yoga 形成条件:
|
||
1. 角宫主(1/4/7/10宫主)与三方宫主(5/9宫主)同宫(conjunction)
|
||
2. 角宫主与三方宫主互看(mutual aspect)
|
||
3. 角宫主与三方宫主互容(parivartana)
|
||
4. 同一星同时掌管角宫和三方宫(dual lordship)
|
||
5. Viparita Raja Yoga:凶宫主(6/8/12宫主)落入另一个凶宫
|
||
|
||
参考:dashaflow yoga.py (MIT)
|
||
|
||
返回:检测到的 Raj Yogas 列表
|
||
"""
|
||
results = {'yogas': [], 'summary': ''}
|
||
|
||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||
|
||
# 提取宫主星信息
|
||
def _lord_of_house(house_num):
|
||
"""获取某宫的宫主星"""
|
||
lkey = f'H{house_num}_Lord'
|
||
if lkey in houses:
|
||
return houses[lkey]
|
||
# 从行星数据推断
|
||
asc_sign = houses.get('asc_sign', '')
|
||
if asc_sign and asc_sign in SIGN_LORDS:
|
||
asc_idx = SIGNS_LIST.index(asc_sign) if asc_sign in SIGNS_LIST else 0
|
||
sign_idx = (asc_idx + house_num - 1) % 12
|
||
return SIGN_LORDS[SIGNS_LIST[sign_idx]]
|
||
return None
|
||
|
||
def _get_planet_sign_idx(pname):
|
||
"""获取行星所在星座索引"""
|
||
pdata = planets_data.get(pname, {})
|
||
if isinstance(pdata, dict) and 'sign' in pdata:
|
||
sign = pdata['sign']
|
||
if sign in SIGNS_LIST:
|
||
return SIGNS_LIST.index(sign)
|
||
# 从经度推断
|
||
if isinstance(pdata, dict) and 'longitude' in pdata:
|
||
return int(pdata['longitude'] / 30) % 12
|
||
return None
|
||
|
||
def _get_planet_house(pname):
|
||
"""获取行星所在宫位"""
|
||
pdata = planets_data.get(pname, {})
|
||
if isinstance(pdata, dict) and 'house' in pdata:
|
||
return pdata['house']
|
||
return None
|
||
|
||
# 检查每对宫主星的组合
|
||
kendras = [1, 4, 7, 10] # 角宫
|
||
trikonas = [1, 5, 9] # 三方宫(含1宫)
|
||
dusthanas = [6, 8, 12] # 凶宫
|
||
|
||
# ── 条件4: 双重宫主星(同一星掌管角宫+三方宫)──
|
||
kendra_lords = {}
|
||
trikona_lords = {}
|
||
for h in kendras:
|
||
lord = _lord_of_house(h)
|
||
if lord:
|
||
kendra_lords.setdefault(lord, []).append(h)
|
||
for h in trikonas:
|
||
lord = _lord_of_house(h)
|
||
if lord:
|
||
trikona_lords.setdefault(lord, []).append(h)
|
||
|
||
dual_lords = set(kendra_lords.keys()) & set(trikona_lords.keys())
|
||
for lord in dual_lords:
|
||
k_houses = kendra_lords[lord]
|
||
t_houses = trikona_lords[lord]
|
||
lord_house = _get_planet_house(lord)
|
||
if lord_house and lord_house in kendras + trikonas:
|
||
yoga = {
|
||
'type': 'Raj Yoga',
|
||
'subtype': 'dual_lordship',
|
||
'combination': f'{lord}(H{k_houses}主+H{t_houses}主)',
|
||
'formation_house': lord_house,
|
||
'strength': 'strong' if lord_house in kendras else 'moderate',
|
||
'interpretation': f'Raj Yoga——{lord}同时掌管角宫{k_houses}和三方宫{t_houses},且在{lord_house}宫,双重权力格局',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
# ── 条件1+2+3: 角宫主 × 三方宫主 的组合检测 ──
|
||
pure_kendra_lords = set(kendra_lords.keys()) - dual_lords
|
||
pure_trikona_lords = set(trikona_lords.keys()) - dual_lords
|
||
|
||
for kl in pure_kendra_lords:
|
||
for tl in pure_trikona_lords:
|
||
kl_sign = _get_planet_sign_idx(kl)
|
||
tl_sign = _get_planet_sign_idx(tl)
|
||
|
||
if kl_sign is None or tl_sign is None:
|
||
continue
|
||
|
||
# 条件1: 同宫(conjunction)
|
||
if kl_sign == tl_sign:
|
||
house_from_asc = (kl_sign - (SIGNS_LIST.index(houses.get('asc_sign', 'Aries')) if houses.get('asc_sign') in SIGNS_LIST else 0)) % 12 + 1
|
||
yoga = {
|
||
'type': 'Raj Yoga',
|
||
'subtype': 'conjunction',
|
||
'combination': f'{kl}(角宫主) + {tl}(三方宫主)',
|
||
'formation_house': house_from_asc,
|
||
'strength': 'strong' if house_from_asc in kendras else 'moderate',
|
||
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)同宫在{SIGNS_LIST[kl_sign]},权力格局',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
# 条件2: 互看(mutual aspect: 7宫关系)
|
||
elif abs(kl_sign - tl_sign) == 6 or abs(kl_sign - tl_sign) == 6 + 12:
|
||
yoga = {
|
||
'type': 'Raj Yoga',
|
||
'subtype': 'mutual_aspect',
|
||
'combination': f'{kl}(角宫主) ↔ {tl}(三方宫主)',
|
||
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)互看(对宫相位),权力格局',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
# 条件3: 互容(parivartana)
|
||
else:
|
||
# 检查kl是否在tl掌管的星座,且tl是否在kl掌管的星座
|
||
kl_lord_signs = [] # kl掌管的星座
|
||
tl_lord_signs = [] # tl掌管的星座
|
||
for s_idx, s_name in enumerate(SIGNS_LIST):
|
||
if SIGN_LORDS[s_name] == kl:
|
||
kl_lord_signs.append(s_idx)
|
||
if SIGN_LORDS[s_name] == tl:
|
||
tl_lord_signs.append(s_idx)
|
||
|
||
if tl_sign in kl_lord_signs and kl_sign in tl_lord_signs:
|
||
yoga = {
|
||
'type': 'Raj Yoga',
|
||
'subtype': 'parivartana',
|
||
'combination': f'{kl}(角宫主) ⇄ {tl}(三方宫主)',
|
||
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)互容交换,权力格局强化',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
# ── 条件5: Viparita Raja Yoga ──
|
||
# 6/8/12宫主落入另一个凶宫(凶中凶=逆转大吉)
|
||
dusthana_lords = {}
|
||
for h in dusthanas:
|
||
lord = _lord_of_house(h)
|
||
if lord:
|
||
dusthana_lords[h] = lord
|
||
|
||
for h, lord in dusthana_lords.items():
|
||
lord_house = _get_planet_house(lord)
|
||
if lord_house and lord_house in dusthanas and lord_house != h:
|
||
yoga = {
|
||
'type': 'Viparita Raja Yoga',
|
||
'subtype': 'dusthana_in_dusthana',
|
||
'combination': f'H{h}_Lord({lord}) in H{lord_house}',
|
||
'interpretation': f'Viparita Raja Yoga——{h}宫主{lord}落入{lord_house}宫(凶中凶),先苦后甜逆转格局',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
results['summary'] = f"Raj Yoga检测:共{len(results['yogas'])}个格局"
|
||
return results
|
||
|
||
|
||
def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
|
||
"""
|
||
计算 Dhana Yogas(财富瑜伽)——财富积累格局 v7.0
|
||
|
||
经典 Dhana Yoga 形成条件(参考dashaflow yoga.py MIT):
|
||
1. 2宫主+11宫主同在角宫/三方宫
|
||
2. 5宫主+9宫主同宫或互看
|
||
3. 2宫主+9宫主(财富+幸运)同宫/互看
|
||
4. 11宫主+9宫主(收益+幸运)同宫/互看
|
||
5. 2/11宫主与吉星(Jupiter/Venus)同宫
|
||
|
||
返回:检测到的 Dhana Yogas 列表
|
||
"""
|
||
results = {'yogas': [], 'summary': ''}
|
||
|
||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||
KENDRA = {1, 4, 7, 10}
|
||
TRIKONA = {1, 5, 9}
|
||
GOOD_HOUSES = KENDRA | TRIKONA | {2, 11}
|
||
BENEFICS = {'Jupiter', 'Venus'}
|
||
|
||
def _lord_of_house(house_num):
|
||
lkey = f'H{house_num}_Lord'
|
||
if lkey in houses:
|
||
return houses[lkey]
|
||
asc_sign = houses.get('asc_sign', '')
|
||
if asc_sign and asc_sign in SIGN_LORDS:
|
||
asc_idx = SIGNS_LIST.index(asc_sign) if asc_sign in SIGNS_LIST else 0
|
||
sign_idx = (asc_idx + house_num - 1) % 12
|
||
return SIGN_LORDS[SIGNS_LIST[sign_idx]]
|
||
return None
|
||
|
||
def _get_planet_house(pname):
|
||
pdata = planets_data.get(pname, {})
|
||
if isinstance(pdata, dict) and 'house' in pdata:
|
||
return pdata['house']
|
||
return None
|
||
|
||
def _get_planet_sign(pname):
|
||
pdata = planets_data.get(pname, {})
|
||
if isinstance(pdata, dict) and 'sign' in pdata:
|
||
return pdata['sign']
|
||
return None
|
||
|
||
def _same_sign(p1, p2):
|
||
s1 = _get_planet_sign(p1)
|
||
s2 = _get_planet_sign(p2)
|
||
return s1 and s2 and s1 == s2
|
||
|
||
# ── 条件1: 2宫主+11宫主同在角宫/三方宫 ──
|
||
lord_2 = _lord_of_house(2)
|
||
lord_11 = _lord_of_house(11)
|
||
if lord_2 and lord_11:
|
||
h2 = _get_planet_house(lord_2)
|
||
h11 = _get_planet_house(lord_11)
|
||
if h2 and h11 and h2 in GOOD_HOUSES and h11 in GOOD_HOUSES:
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': '2nd_11th_lords_strong',
|
||
'combination': f'H2_Lord({lord_2}) in H{h2} + H11_Lord({lord_11}) in H{h11}',
|
||
'strength': 'strong' if h2 in KENDRA and h11 in KENDRA else 'moderate',
|
||
'interpretation': f'Dhana Yoga——2宫主{lord_2}(H{h2})与11宫主{lord_11}(H{h11})均落强宫,财富积累格局',
|
||
})
|
||
|
||
# ── 条件2: 5宫主+9宫主同宫/互看 ──
|
||
lord_5 = _lord_of_house(5)
|
||
lord_9 = _lord_of_house(9)
|
||
if lord_5 and lord_9:
|
||
if _same_sign(lord_5, lord_9):
|
||
s = _get_planet_sign(lord_5)
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': '5th_9th_conjunction',
|
||
'combination': f'H5_Lord({lord_5}) + H9_Lord({lord_9})',
|
||
'strength': 'strong',
|
||
'interpretation': f'Dhana Yoga——5宫主{lord_5}与9宫主{lord_9}同宫在{s},财富+幸运格局',
|
||
})
|
||
else:
|
||
h5 = _get_planet_house(lord_5)
|
||
h9 = _get_planet_house(lord_9)
|
||
if h5 and h9:
|
||
# 互看: 7宫关系
|
||
if abs(h5 - h9) == 6:
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': '5th_9th_mutual_aspect',
|
||
'combination': f'H5_Lord({lord_5}) ↔ H9_Lord({lord_9})',
|
||
'strength': 'moderate',
|
||
'interpretation': f'Dhana Yoga——5宫主{lord_5}(H{h5})与9宫主{lord_9}(H{h9})互看,财富格局',
|
||
})
|
||
# 同在角宫/三方宫
|
||
elif h5 in GOOD_HOUSES and h9 in GOOD_HOUSES:
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': '5th_9th_strong_houses',
|
||
'combination': f'H5_Lord({lord_5}) in H{h5} + H9_Lord({lord_9}) in H{h9}',
|
||
'strength': 'moderate',
|
||
'interpretation': f'Dhana Yoga——5宫主{lord_5}与9宫主{lord_9}均落强宫,财富格局',
|
||
})
|
||
|
||
# ── 条件3: 2宫主+9宫主同宫/互看 ──
|
||
if lord_2 and lord_9:
|
||
if _same_sign(lord_2, lord_9):
|
||
s = _get_planet_sign(lord_2)
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': '2nd_9th_conjunction',
|
||
'combination': f'H2_Lord({lord_2}) + H9_Lord({lord_9})',
|
||
'strength': 'strong',
|
||
'interpretation': f'Dhana Yoga——2宫主{lord_2}与9宫主{lord_9}同宫在{s},财富+幸运组合',
|
||
})
|
||
|
||
# ── 条件4: 11宫主+9宫主同宫/互看 ──
|
||
if lord_11 and lord_9:
|
||
if _same_sign(lord_11, lord_9):
|
||
s = _get_planet_sign(lord_11)
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': '11th_9th_conjunction',
|
||
'combination': f'H11_Lord({lord_11}) + H9_Lord({lord_9})',
|
||
'strength': 'strong',
|
||
'interpretation': f'Dhana Yoga——11宫主{lord_11}与9宫主{lord_9}同宫在{s},收益+幸运组合',
|
||
})
|
||
|
||
# ── 条件5: 2/11宫主与吉星同宫 ──
|
||
for wealth_lord, w_house in [(lord_2, 2), (lord_11, 11)]:
|
||
if not wealth_lord:
|
||
continue
|
||
for benefic in BENEFICS:
|
||
if _same_sign(wealth_lord, benefic):
|
||
s = _get_planet_sign(wealth_lord)
|
||
results['yogas'].append({
|
||
'type': 'Dhana Yoga',
|
||
'subtype': f'{w_house}th_lord_benefic_conjunction',
|
||
'combination': f'H{w_house}_Lord({wealth_lord}) + {benefic}',
|
||
'strength': 'moderate',
|
||
'interpretation': f'Dhana Yoga——{w_house}宫主{wealth_lord}与吉星{benefic}同宫在{s},财富助力格局',
|
||
})
|
||
|
||
results['summary'] = f"Dhana Yoga检测:共{len(results['yogas'])}个格局"
|
||
return results
|
||
|
||
|
||
def calc_pancha_mahapurusha_yoga(planets_data: Dict) -> Dict:
|
||
"""
|
||
计算 Pancha Mahapurusha Yogas(五王瑜伽)
|
||
|
||
条件:5颗吉星(Mars/Mercury/Jupiter/Venus/Saturn)在角宫(1/4/7/10)
|
||
且入庙(在自家星座)或擢升(在擢升星座)
|
||
|
||
5种瑜伽:
|
||
- Ruchaka Yoga: Mars在角宫入庙/擢升
|
||
- Bhadra Yoga: Mercury在角宫入庙/擢升
|
||
- Hamsa Yoga: Jupiter在角宫入庙/擢升
|
||
- Malavya Yoga: Venus在角宫入庙/擢升
|
||
- Sasa Yoga: Saturn在角宫入庙/擢升
|
||
"""
|
||
results = {'yogas': [], 'summary': ''}
|
||
|
||
# 擢升星座表
|
||
exaltation = {'Mars': 'Capricorn', 'Mercury': 'Virgo', 'Jupiter': 'Cancer',
|
||
'Venus': 'Pisces', 'Saturn': 'Libra'}
|
||
# 入庙星座表
|
||
own_sign = {'Mars': ['Aries', 'Scorpio'], 'Mercury': ['Gemini', 'Virgo'],
|
||
'Jupiter': ['Sagittarius', 'Pisces'], 'Venus': ['Taurus', 'Libra'],
|
||
'Saturn': ['Capricorn', 'Aquarius']}
|
||
|
||
for pname in ['Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
|
||
pdata = planets_data.get(pname, {})
|
||
if not isinstance(pdata, dict) or 'house' not in pdata:
|
||
continue
|
||
|
||
house = pdata['house']
|
||
sign = pdata.get('sign', '')
|
||
|
||
# 检查是否在角宫
|
||
if house not in [1, 4, 7, 10]:
|
||
continue
|
||
|
||
# 检查是否入庙或擢升
|
||
is_exalted = (sign == exaltation.get(pname, ''))
|
||
is_own = (sign in own_sign.get(pname, []))
|
||
|
||
if is_exalted or is_own:
|
||
yoga_names = {
|
||
'Mars': 'Ruchaka Yoga', 'Mercury': 'Bhadra Yoga',
|
||
'Jupiter': 'Hamsa Yoga', 'Venus': 'Malavya Yoga',
|
||
'Saturn': 'Sasa Yoga',
|
||
}
|
||
yoga = {
|
||
'type': yoga_names[pname],
|
||
'planet': pname,
|
||
'house': house,
|
||
'sign': sign,
|
||
'status': 'exalted' if is_exalted else 'own_sign',
|
||
'strength': 'very strong' if is_exalted else 'strong',
|
||
'interpretation': f'{yoga_names[pname]}——{pname}在{house}宫{sign}({"擢升" if is_exalted else "入庙"}),五王瑜伽之一',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
results['summary'] = f"Pancha Mahapurusha Yoga检测:共{len(results['yogas'])}个(最多5个)"
|
||
return results
|
||
|
||
|
||
def calc_nicha_bhanga_raj_yoga(planets_data: Dict, houses: Dict) -> Dict:
|
||
"""
|
||
计算 Neecha Bhanga Raj Yoga(落陷解除王者瑜伽)v7.0
|
||
|
||
条件(需满足落陷 + 解除,参考dashaflow yoga.py MIT):
|
||
1. 某行星落陷(在落陷星座)
|
||
解除条件(满足任一即可):
|
||
A. 落陷星座主星(dispositor)在Lagna角宫(1/4/7/10)
|
||
B. 落陷星座主星在Moon角宫
|
||
C. 擢升星座主星在Lagna角宫
|
||
D. 擢升星座主星在Moon角宫
|
||
E. 落陷星与落陷星座主星互容(parivartana)
|
||
F. 落陷星在Navamsa中入庙/擢升(vargottama缓解)
|
||
|
||
经典:落陷+落陷解除 = 王者瑜伽(先抑后扬,大器晚成)
|
||
"""
|
||
results = {'yogas': [], 'summary': ''}
|
||
|
||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||
|
||
# 落陷星座表
|
||
debilitation = {'Mars': 'Cancer', 'Mercury': 'Pisces', 'Jupiter': 'Capricorn',
|
||
'Venus': 'Virgo', 'Saturn': 'Aries', 'Sun': 'Libra',
|
||
'Moon': 'Scorpio'}
|
||
|
||
# 擢升星座表
|
||
exaltation = {'Sun': 'Aries', 'Moon': 'Taurus', 'Mars': 'Capricorn',
|
||
'Mercury': 'Virgo', 'Jupiter': 'Cancer', 'Venus': 'Pisces',
|
||
'Saturn': 'Libra'}
|
||
|
||
KENDRA = {1, 4, 7, 10}
|
||
|
||
def _get_planet_house(pname):
|
||
pdata = planets_data.get(pname, {})
|
||
if isinstance(pdata, dict) and 'house' in pdata:
|
||
return pdata['house']
|
||
return None
|
||
|
||
def _get_planet_sign(pname):
|
||
pdata = planets_data.get(pname, {})
|
||
if isinstance(pdata, dict) and 'sign' in pdata:
|
||
return pdata['sign']
|
||
return None
|
||
|
||
def _get_planet_sign_idx(pname):
|
||
s = _get_planet_sign(pname)
|
||
if s and s in SIGNS_LIST:
|
||
return SIGNS_LIST.index(s)
|
||
return None
|
||
|
||
def _get_moon_house():
|
||
return _get_planet_house('Moon')
|
||
|
||
def _house_from_moon(planet_name):
|
||
"""计算从Moon看某行星在第几宫"""
|
||
moon_idx = _get_planet_sign_idx('Moon')
|
||
p_idx = _get_planet_sign_idx(planet_name)
|
||
if moon_idx is not None and p_idx is not None:
|
||
return ((p_idx - moon_idx) % 12) + 1
|
||
return None
|
||
|
||
for pname, deb_sign in debilitation.items():
|
||
pdata = planets_data.get(pname, {})
|
||
if not isinstance(pdata, dict) or 'sign' not in pdata:
|
||
continue
|
||
|
||
sign = pdata['sign']
|
||
if sign != deb_sign:
|
||
continue # 没落陷
|
||
|
||
cancellation = False
|
||
cancel_reasons = []
|
||
|
||
# 条件A: 落陷星座主星(dispositor)在Lagna角宫
|
||
deb_lord = SIGN_LORDS.get(deb_sign, '')
|
||
deb_lord_house = _get_planet_house(deb_lord)
|
||
if deb_lord_house and deb_lord_house in KENDRA:
|
||
cancellation = True
|
||
cancel_reasons.append(f'定位星{deb_lord}在Lagna第{deb_lord_house}宫(角宫)')
|
||
|
||
# 条件B: 落陷星座主星在Moon角宫
|
||
if deb_lord:
|
||
hfm = _house_from_moon(deb_lord)
|
||
if hfm and hfm in KENDRA:
|
||
cancellation = True
|
||
cancel_reasons.append(f'定位星{deb_lord}在Moon第{hfm}宫(角宫)')
|
||
|
||
# 条件C: 擢升星座主星在Lagna角宫
|
||
exalt_sign = exaltation.get(pname, '')
|
||
exalt_lord = SIGN_LORDS.get(exalt_sign, '')
|
||
if exalt_lord:
|
||
exalt_lord_house = _get_planet_house(exalt_lord)
|
||
if exalt_lord_house and exalt_lord_house in KENDRA:
|
||
cancellation = True
|
||
cancel_reasons.append(f'擢升星主{exalt_lord}在Lagna第{exalt_lord_house}宫(角宫)')
|
||
|
||
# 条件D: 擢升星座主星在Moon角宫
|
||
if exalt_lord:
|
||
hfm = _house_from_moon(exalt_lord)
|
||
if hfm and hfm in KENDRA:
|
||
cancellation = True
|
||
cancel_reasons.append(f'擢升星主{exalt_lord}在Moon第{hfm}宫(角宫)')
|
||
|
||
# 条件E: 落陷星与定位星互容(parivartana)
|
||
if deb_lord and deb_lord != pname:
|
||
p_sign = _get_planet_sign(pname)
|
||
lord_sign = _get_planet_sign(deb_lord)
|
||
if p_sign and lord_sign:
|
||
# pname在deb_sign(由deb_lord掌管), deb_lord是否在pname掌管的星座?
|
||
pname_own_signs = [s for s, l in SIGN_LORDS.items() if l == pname]
|
||
if lord_sign in pname_own_signs:
|
||
cancellation = True
|
||
cancel_reasons.append(f'{pname}与{deb_lord}互容(Parivartana)')
|
||
|
||
# 条件F: Navamsa入庙/擢升缓解(如果有navamsa数据)
|
||
navamsa_sign = pdata.get('navamsa_sign')
|
||
if navamsa_sign:
|
||
own_signs = [s for s, l in SIGN_LORDS.items() if l == pname]
|
||
if navamsa_sign in own_signs or navamsa_sign == exaltation.get(pname, ''):
|
||
cancellation = True
|
||
cancel_reasons.append(f'{pname}在Navamsa中入庙/擢升({navamsa_sign})')
|
||
|
||
if cancellation:
|
||
# 量化解除程度
|
||
cancel_count = len(cancel_reasons)
|
||
strength = 'very strong' if cancel_count >= 3 else 'strong' if cancel_count >= 2 else 'moderate'
|
||
|
||
yoga = {
|
||
'type': 'Neecha Bhanga Raj Yoga',
|
||
'planet': pname,
|
||
'debilitated_sign': deb_sign,
|
||
'debility_lord': deb_lord,
|
||
'lord_house': deb_lord_house,
|
||
'exaltation_sign': exalt_sign,
|
||
'exaltation_lord': exalt_lord,
|
||
'cancellation_reasons': cancel_reasons,
|
||
'cancellation_count': cancel_count,
|
||
'strength': strength,
|
||
'interpretation': f'Neecha Bhanga Raj Yoga——{pname}在{deb_sign}落陷但解除({"; ".join(cancel_reasons)}),先抑后扬大器晚成',
|
||
}
|
||
results['yogas'].append(yoga)
|
||
|
||
results['summary'] = f"Neecha Bhanga Raj Yoga检测:共{len(results['yogas'])}个"
|
||
return results
|
||
|
||
|
||
def calc_mangal_dosha(planets_data: Dict, mars_house: int) -> Dict:
|
||
"""
|
||
计算 Mangal Dosha / Kuja Dosha(火星煞)
|
||
|
||
规则:Mars在1/2/4/7/8/12宫 → Mangal Dosha
|
||
影响:婚姻延迟/困难,配偶健康 issues
|
||
|
||
缓和条件:
|
||
- Mars在自身星座(Aries/Scorpio)→ 部分缓和
|
||
- Mars擢升(Capricorn)→ 完全解除
|
||
- 第七宫/第七宫主有吉星→ 部分缓和
|
||
- 双鱼/巨蟹上升 → 更麻烦(Mars在这些星座更凶)
|
||
"""
|
||
results = {'has_dosha': False, 'severity': 'none', 'remedies': []}
|
||
|
||
bad_houses = [1, 2, 4, 7, 8, 12]
|
||
if mars_house not in bad_houses:
|
||
results['summary'] = '无Mangal Dosha(Mars不在煞宫)'
|
||
return results
|
||
|
||
results['has_dosha'] = True
|
||
mars_data = planets_data.get('Mars', {})
|
||
mars_sign = mars_data.get('sign', '') if isinstance(mars_data, dict) else ''
|
||
|
||
# 判断严重程度
|
||
if mars_house in [7, 8]:
|
||
results['severity'] = 'high' # 7/8宫最凶
|
||
elif mars_house in [1, 2]:
|
||
results['severity'] = 'moderate'
|
||
else:
|
||
results['severity'] = 'low'
|
||
|
||
# 检查缓和条件
|
||
remedies = []
|
||
if mars_sign in ['Aries', 'Scorpio']:
|
||
remedies.append('Mars在自身星座,部分缓和')
|
||
results['severity'] = 'moderate' if results['severity'] == 'high' else 'low'
|
||
if mars_sign == 'Capricorn':
|
||
remedies.append('Mars擢升,完全解除')
|
||
results['severity'] = 'none'
|
||
results['has_dosha'] = False
|
||
|
||
results['remedies'] = remedies
|
||
results['mars_house'] = mars_house
|
||
results['mars_sign'] = mars_sign
|
||
results['summary'] = (f"{'无' if not results['has_dosha'] else '有'}Mangal Dosha——"
|
||
f"Mars在{mars_house}宫{mars_sign},严重程度:{results['severity']}")
|
||
return results
|
||
|
||
|
||
def calc_kaal_sarp_dosha(planets_data: Dict) -> Dict:
|
||
"""
|
||
计算 Kaal Sarp Dosha(时间蛇煞)
|
||
|
||
规则:所有行星(除Rahu/Ketu外)都在Rahu-Ketu轴线的同一侧
|
||
形成"蛇吞"格局,带来阻碍、延迟、心理压力
|
||
|
||
类型:
|
||
- 完全Kaal Sarp:所有行星在Rahu-Ketu之间(单一侧)
|
||
- 部分Kaal Sarp:大部分行星在Rahu-Ketu之间
|
||
"""
|
||
results = {'has_dosha': False, 'type': 'none', 'affected_houses': []}
|
||
|
||
rahu_data = planets_data.get('Rahu', {})
|
||
ketu_data = planets_data.get('Ketu', {})
|
||
|
||
if not isinstance(rahu_data, dict) or 'house' not in rahu_data:
|
||
results['summary'] = 'Rahu位置未知,无法判断Kaal Sarp Dosha'
|
||
return results
|
||
|
||
rahu_house = rahu_data['house']
|
||
ketu_house = ketu_data.get('house', 0) if isinstance(ketu_data, dict) else 0
|
||
|
||
# 确定Rahu-Ketu轴线的"之间"范围
|
||
# 简化处理:假设Rahu-Ketu在一条直线上(相差6宫)
|
||
axis_houses = list(range(rahu_house, rahu_house + 6))
|
||
axis_houses = [(h - 1) % 12 + 1 for h in axis_houses] # 转为1-12
|
||
|
||
# 统计在"之间"的行星数量
|
||
planets_in_axis = []
|
||
planets_outside = []
|
||
for pname in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
|
||
pdata = planets_data.get(pname, {})
|
||
if not isinstance(pdata, dict) or 'house' not in pdata:
|
||
continue
|
||
p_house = pdata['house']
|
||
if p_house in axis_houses:
|
||
planets_in_axis.append(pname)
|
||
else:
|
||
planets_outside.append(pname)
|
||
|
||
total_planets = len(planets_in_axis) + len(planets_outside)
|
||
|
||
if len(planets_in_axis) == total_planets and total_planets >= 7:
|
||
results['has_dosha'] = True
|
||
results['type'] = 'full'
|
||
results['summary'] = f'完全Kaal Sarp Dosha——所有{total_planets}颗行星在Rahu({rahu_house})—Ketu轴线之间'
|
||
elif len(planets_in_axis) >= total_planets * 0.7:
|
||
results['has_dosha'] = True
|
||
results['type'] = 'partial'
|
||
results['summary'] = f'部分Kaal Sarp Dosha——{len(planets_in_axis)}/{total_planets}颗行星在轴线之间'
|
||
else:
|
||
results['has_dosha'] = False
|
||
results['type'] = 'none'
|
||
results['summary'] = '无Kaal Sarp Dosha'
|
||
|
||
results['planets_in_axis'] = planets_in_axis
|
||
results['planets_outside'] = planets_outside
|
||
results['rahu_house'] = rahu_house
|
||
results['ketu_house'] = ketu_house
|
||
return results
|
||
|
||
|
||
def calc_pitra_dosha(planets_data: Dict, sun_house: int) -> Dict:
|
||
"""
|
||
计算 Pitra Dosha(父辈煞)
|
||
|
||
规则:
|
||
- Sun与Rahu合相(同宫或相邻宫)→ Pitra Dosha
|
||
- Sun受土星/火星克 → 可能Pitra Dosha
|
||
- 第9宫/第9宫主受克 → 父辈煞
|
||
|
||
影响:父辈业力、祖先未解问题、子女健康/生育问题
|
||
"""
|
||
results = {'has_dosha': False, 'indicators': []}
|
||
|
||
sun_data = planets_data.get('Sun', {})
|
||
rahu_data = planets_data.get('Rahu', {})
|
||
|
||
if not isinstance(sun_data, dict) or 'house' not in sun_data:
|
||
results['summary'] = 'Sun位置未知,无法判断Pitra Dosha'
|
||
return results
|
||
|
||
sun_house = sun_data['house']
|
||
sun_sign = sun_data.get('sign', '')
|
||
|
||
# 检查Sun-Rahu合相
|
||
if isinstance(rahu_data, dict) and 'house' in rahu_data:
|
||
rahu_house = rahu_data['house']
|
||
if abs(sun_house - rahu_house) <= 1 or (sun_house == rahu_house):
|
||
results['has_dosha'] = True
|
||
results['indicators'].append(f'Sun({sun_house}宫)与Rahu({rahu_house}宫)合相/相邻')
|
||
|
||
# 检查Sun受克(土星/火星在Sun宫或相邻宫)
|
||
for malefic in ['Saturn', 'Mars']:
|
||
mdata = planets_data.get(malefic, {})
|
||
if isinstance(mdata, dict) and 'house' in mdata:
|
||
m_house = mdata['house']
|
||
if abs(sun_house - m_house) <= 1:
|
||
results['has_dosha'] = True
|
||
results['indicators'].append(f'{malefic}({m_house}宫)克Sun({sun_house}宫)')
|
||
|
||
results['sun_house'] = sun_house
|
||
results['sun_sign'] = sun_sign
|
||
if not results['indicators']:
|
||
results['summary'] = '无Pitra Dosha明显迹象'
|
||
else:
|
||
results['summary'] = f'有Pitra Dosha迹象:{"; ".join(results["indicators"][:2])}'
|
||
return results
|
||
|
||
|
||
def calc_sade_sati(moon_sign: str, current_year: int = 2026) -> Dict:
|
||
"""
|
||
计算 Sade Sati(土星七年)
|
||
|
||
规则:土星过Moon所在星座及其前后各1星座,共约7.5年
|
||
每个阶段影响不同:
|
||
- 第一阶段(土星过前一星座):心理压力开始
|
||
- 第二阶段(土星过Moon星座):最困难,健康/关系/事业挑战
|
||
- 第三阶段(土星过后一星座):压力缓解,但有收获
|
||
|
||
参数:moon_sign(Moon星座),current_year(当前年份,用于判断是否在进行中)
|
||
|
||
返回:Sade Sati阶段信息
|
||
"""
|
||
results = {'is_active': False, 'phase': 'none', 'summary': ''}
|
||
|
||
if not moon_sign or moon_sign not in SIGNS:
|
||
results['summary'] = 'Moon星座未知,无法判断Sade Sati'
|
||
return results
|
||
|
||
moon_idx = SIGNS.index(moon_sign)
|
||
|
||
# 简化:只判断当前年份Saturn是否在Moon前后1星座
|
||
# 实际需要Saturn实时位置,这里只给框架
|
||
results['moon_sign'] = moon_sign
|
||
results['moon_idx'] = moon_idx
|
||
results['phases'] = {
|
||
'rising': SIGNS[(moon_idx - 1) % 12], # 前一星座
|
||
'peak': moon_sign, # Moon星座(最困难)
|
||
'setting': SIGNS[(moon_idx + 1) % 12], # 后一星座
|
||
}
|
||
results['summary'] = (f'Sade Sati阶段框架:上升期={results["phases"]["rising"]},'
|
||
f'高峰期={results["phases"]["peak"]}(最困难),'
|
||
f'下降期={results["phases"]["setting"]};'
|
||
f'需结合Saturn实际位置判断是否在进行中')
|
||
return results
|
||
|
||
|
||
def _arudha_from_jaimini(asc_sign: str, planets_data: Dict) -> Dict:
|
||
"""AL / UL 只有一个来源:jaimini.calc_arudha_padas(BUG-1190,TASK-upstream-sync4-20261002 T6)。
|
||
|
||
旧的 calc_arudha_lagna / calc_upapada_lagna 把「第几宫」当成白羊起算的星座序号,
|
||
且没有 1/7 宫例外,同一张盘与 jaimini 结果不同,已删除。1/7 宫例外的数法沿用
|
||
jaimini 现行口径(占星师清单第 5 题未决,本单不改)。
|
||
"""
|
||
empty = {
|
||
'arudha_lagna': {'arudha_lagna': None, 'al_house': None, 'source': 'jaimini.calc_arudha_padas'},
|
||
'upapada_lagna': {'upapada': None, 'ul_house': None, 'source': 'jaimini.calc_arudha_padas'},
|
||
}
|
||
if not asc_sign or asc_sign not in SIGNS:
|
||
return empty
|
||
try:
|
||
from jaimini import calc_arudha_padas
|
||
except ImportError: # package import (scripts.yogas_doshas)
|
||
from scripts.jaimini import calc_arudha_padas
|
||
planet_lons = {
|
||
name: data.get('degree_raw', data.get('degree'))
|
||
for name, data in (planets_data or {}).items()
|
||
if isinstance(data, dict) and isinstance(data.get('degree_raw', data.get('degree')), (int, float))
|
||
}
|
||
asc_idx = SIGNS.index(asc_sign)
|
||
padas = calc_arudha_padas(asc_idx, planet_lons)
|
||
|
||
def _house(pada):
|
||
return ((pada['sign_idx'] - asc_idx) % 12) + 1
|
||
|
||
al = padas.get('arudha_lagna')
|
||
ul = padas.get('upapada')
|
||
result = empty
|
||
if al:
|
||
result['arudha_lagna'] = {
|
||
'arudha_lagna': al['sign'], 'al_house': _house(al), 'source': 'jaimini.calc_arudha_padas',
|
||
'summary': f"Arudha Lagna (AL) = {al['sign']}(第{_house(al)}宫),取自 jaimini.calc_arudha_padas",
|
||
}
|
||
if ul:
|
||
result['upapada_lagna'] = {
|
||
'upapada': ul['sign'], 'ul_house': _house(ul), 'source': 'jaimini.calc_arudha_padas',
|
||
'summary': f"Upapada Lagna (UL) = {ul['sign']}(第{_house(ul)}宫),取自 jaimini.calc_arudha_padas",
|
||
}
|
||
return result
|
||
|
||
|
||
def calc_all_yogas_doshas(planets_data: Dict, houses: Dict,
|
||
asc_sign: str, asc_lord: str,
|
||
moon_sign: str, moon_house: int,
|
||
mars_house: int, sun_house: int,
|
||
house12_lord: str) -> Dict:
|
||
"""
|
||
批量计算所有 Yogas + Doshas + Special Lagnas(v6.0.14 主入口)
|
||
|
||
参数:
|
||
planets_data: 行星数据字典
|
||
houses: 宫位信息字典
|
||
asc_sign: 上升星座
|
||
asc_lord: 上升主星
|
||
moon_sign: Moon星座
|
||
moon_house: Moon宫位
|
||
mars_house: Mars宫位
|
||
sun_house: Sun宫位
|
||
house12_lord: 12宫主星
|
||
|
||
返回:完整结果字典
|
||
"""
|
||
results = {}
|
||
|
||
# Yogas
|
||
results['raj_yogas'] = calc_raj_yogas(planets_data, houses)
|
||
results['dhana_yogas'] = calc_dhana_yogas(planets_data, houses)
|
||
results['pancha_mahapurusha'] = calc_pancha_mahapurusha_yoga(planets_data)
|
||
results['nicha_bhanga_raj'] = calc_nicha_bhanga_raj_yoga(planets_data, houses)
|
||
|
||
# Doshas
|
||
results['mangal_dosha'] = calc_mangal_dosha(planets_data, mars_house)
|
||
results['kaal_sarp_dosha'] = calc_kaal_sarp_dosha(planets_data)
|
||
results['pitra_dosha'] = calc_pitra_dosha(planets_data, sun_house)
|
||
results['sade_sati'] = calc_sade_sati(moon_sign)
|
||
|
||
# Special Lagnas
|
||
results.update(_arudha_from_jaimini(asc_sign, planets_data))
|
||
|
||
# 总结
|
||
total_yogas_count = (len(results['raj_yogas']['yogas']) +
|
||
len(results['dhana_yogas']['yogas']) +
|
||
len(results['pancha_mahapurusha']['yogas']) +
|
||
len(results['nicha_bhanga_raj']['yogas']))
|
||
has_dosha = any([
|
||
results['mangal_dosha']['has_dosha'],
|
||
results['kaal_sarp_dosha']['has_dosha'],
|
||
results['pitra_dosha']['has_dosha'],
|
||
])
|
||
|
||
results['summary'] = (
|
||
f"Yogas共{total_yogas_count}个;"
|
||
f"Doshas: Mangal={'有' if results['mangal_dosha']['has_dosha'] else '无'}"
|
||
f"/Kaal Sarp={'有' if results['kaal_sarp_dosha']['has_dosha'] else '无'}"
|
||
f"/Pitra={'有' if results['pitra_dosha']['has_dosha'] else '无'}"
|
||
)
|
||
|
||
return results
|