feat: add yoga logic validation and varga fixes

This commit is contained in:
732642856
2026-06-05 14:30:20 +08:00
parent ad69744617
commit 8da45c56b3
19 changed files with 34872 additions and 700 deletions
+99 -679
View File
@@ -64,6 +64,7 @@ except ImportError:
from cmd_solar_return import cmd_solar_return # v6.0.18
from cmd_narayana_dasha import cmd_narayana_dasha as _cmd_narayana_dasha_impl # v6.0.20
from cmd_muhurta import cmd_muhurta # v6.0.21
from yoga_engine import detect_yogas # v6.0.26: data-driven Yoga engine
# ============================================================================
# 常量
@@ -534,10 +535,23 @@ def cmd_dasha(args):
if not nak_info: return {"error": f"未找到Nakshatra: {args.nakshatra}"}
if args.pada: progress = (max(1, min(4, args.pada)) - 1) / 4 + 0.125
else:
return {"error": "请提供 --nakshatra 或 --moon-lon"}
# v6.0.27: Auto-calculate Moon's Nakshatra from birth datetime
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean')
)
if chart is None:
return {"error": "swisseph未安装,无法自动计算Nakshatra"}
moon = chart.get("planets", {}).get("Moon", {})
moon_lon = moon.get("degree_raw")
if moon_lon is None:
return {"error": "无法计算Moon位置"}
ns = 360.0 / 27; idx = int(moon_lon / ns); progress = (moon_lon % ns) / ns
nak_info = NAKSHATRA_LIST[idx % 27]
nak_name, start_lord, start_years = nak_info
birth_dt = datetime.strptime(args.birthdate, "%Y-%m-%d")
birthdate = args.birthdate or f"{args.year}-{args.month:02d}-{args.day:02d}"
birth_dt = datetime.strptime(birthdate, "%Y-%m-%d")
elapsed = progress * start_years; remaining = start_years - elapsed
dt = birth_dt - timedelta(days=elapsed * 365.25)
si = DASHA_ORDER.index(start_lord)
@@ -577,694 +591,69 @@ def cmd_dasha(args):
d["antardasha"] = current_ad or (sub[0] if sub else None)
current = d
return {"moon_nakshatra": nak_name, "birth_date": args.birthdate, "reference_date": today.strftime("%Y-%m-%d"), "timeline": timeline, "current_dasha": current}
return {"moon_nakshatra": nak_name, "birth_date": birthdate, "reference_date": today.strftime("%Y-%m-%d"), "timeline": timeline, "current_dasha": current}
# ============================================================================
# 3. Yoga识别
# ============================================================================
def cmd_yoga(args):
"""
Yoga 识别 —— v6.0.26 数据驱动引擎。
支持两种输入:
1. 兼容旧接口:--ascendant + --planets "Sun:Aries:1[:10.5],Moon:Cancer:4[:15]"
2. 出生信息直算:--year --month --day --hour --minute --lat --lon --tz [--node-mode]
"""
planets = {}
asc = args.ascendant or "Aries"
if args.planets:
for item in args.planets.split(','):
parts = item.strip().split(':')
if len(parts) >= 3:
planets[parts[0].strip()] = {"sign": parts[1].strip(), "house": int(parts[2].strip())}
asc = args.ascendant or "Aries"
pdata = {"sign": parts[1].strip(), "house": int(parts[2].strip())}
if len(parts) >= 4 and parts[3].strip() != "":
try:
pdata["degree"] = float(parts[3].strip())
except ValueError:
pass
planets[parts[0].strip()] = pdata
else:
required_birth_fields = ["year", "month", "day", "hour", "minute", "lat", "lon"]
has_birth_input = all(getattr(args, field, None) is not None for field in required_birth_fields)
if has_birth_input:
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, getattr(args, 'tz', 0), getattr(args, 'node_mode', 'mean')
)
if chart is None:
return {"error": "swisseph未安装"}
asc = chart.get("ascendant", {}).get("sign", asc)
for pname, pd in chart.get("planets", {}).items():
if isinstance(pd, dict) and "sign" in pd and "house" in pd:
planets[pname] = {
"sign": pd["sign"],
"house": pd["house"],
"degree": pd.get("degree_in_sign", pd.get("degree")),
}
ai = SIGNS.index(asc) if asc in SIGNS else 0
kl = list(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 4, 7, 10]]))
tl = list(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 5, 9]]))
yogas = []
# Raja Yoga
for k in kl:
for t in tl:
if k != t and k in planets and t in planets and planets[k]["house"] == planets[t]["house"]:
yogas.append({"name": "Raja Yoga", "name_cn": "王者格局", "combination": f"{k}+{t}同在第{planets[k]['house']}", "effects": ["权力地位", "事业成功", "社会影响力"], "strength": "" if planets[k]["sign"] in [EXALTATION.get(k, ''), SIGN_LORDS.get(planets[k]["sign"], '')] else ""})
# 调用数据驱动引擎(yoga_engine.py
yogas = detect_yogas(planets, asc)
# Mahapurusha Yoga
yoga_names = {'Mars': 'Ruchaka', 'Mercury': 'Bhadra', 'Jupiter': 'Hamsa', 'Venus': 'Malavya', 'Saturn': 'Sasa'}
for p, info in planets.items():
if info["house"] in [1, 4, 7, 10] and p in yoga_names:
if EXALTATION.get(p) == info["sign"] or SIGN_LORDS.get(info["sign"]) == p:
st = "入旺" if EXALTATION.get(p) == info["sign"] else "入庙"
yogas.append({"name": f"{yoga_names[p]} Yoga", "name_cn": f"{PLANET_CN.get(p, p)}{st}格局", "combination": f"{p}{st}{info['sign']}(第{info['house']}宫)", "effects": ["卓越才能", "领域领军", "人格魅力"], "strength": "极强" if st == "入旺" else ""})
# Gajakesari
if 'Jupiter' in planets and 'Moon' in planets:
jh = planets['Jupiter']['house']; mh = planets['Moon']['house']
if jh in [1, 4, 7, 10] and mh in [1, 4, 7, 10]:
yogas.append({"name": "Gajakesari Yoga", "name_cn": "象狮格局", "combination": f"木星第{jh}宫+月亮第{mh}", "effects": ["智慧学识", "财富名声", "道德品质"], "strength": ""})
# Neechabhanga
for p, info in planets.items():
if DEBILITATION.get(p) == info["sign"]:
dl = SIGN_LORDS[info["sign"]]
if dl in planets and planets[dl]["house"] in [1, 4, 7, 10]:
yogas.append({"name": "Neechabhanga Raja Yoga", "name_cn": "落陷取消格局", "combination": f"{p}落陷在{info['sign']}{dl}在角宫化解", "effects": ["克服困难", "逆境崛起", "转化能力"], "strength": "中强"})
# Dhana Yoga
wl = set(); wh = [2, 5, 9, 11]
for h in wh:
wl.add(SIGN_LORDS[SIGNS[(ai + h - 1) % 12]])
wc = sum(1 for w in wl if w in planets and planets[w]["house"] in wh)
if wc >= 2:
yogas.append({"name": "Dhana Yoga", "name_cn": "财富格局", "combination": f"{wc}个财富宫主星落入财富宫", "effects": ["财富积累", "物质成功", "投资收益"], "strength": "中强" if wc >= 3 else ""})
# Solar Yogas (Veshi/Voshi/Ubhayachari)
if 'Sun' in planets:
sun_h = planets['Sun']['house']
h2_from_sun = ((sun_h - 1 + 1) % 12) + 1 # 2nd from Sun
h12_from_sun = ((sun_h - 1 + 11) % 12) + 1 # 12th from Sun
planets_in_h2 = [p for p, info in planets.items() if info['house'] == h2_from_sun]
planets_in_h12 = [p for p, info in planets.items() if info['house'] == h12_from_sun]
if planets_in_h12 and not planets_in_h2:
yogas.append({"name": "Voshi Yoga", "name_cn": "太阳前瑜伽", "combination": f"{'+'.join(planets_in_h12)}在太阳第12宫", "effects": ["口才出众", "善于表达", "受人尊敬"], "strength": ""})
if planets_in_h2 and not planets_in_h12:
yogas.append({"name": "Veshi Yoga", "name_cn": "太阳后瑜伽", "combination": f"{'+'.join(planets_in_h2)}在太阳第2宫", "effects": ["财富充裕", "生活舒适", "性情愉快"], "strength": ""})
if planets_in_h2 and planets_in_h12:
yogas.append({"name": "Ubhayachari Yoga", "name_cn": "太阳双夹瑜伽", "combination": f"太阳两侧均有行星", "effects": ["性格坚毅", "口才与财富兼备", "受人爱戴"], "strength": ""})
# Lunar Yogas (Sunapha/Anapha/Durudhura/Kemadruma)
if 'Moon' in planets:
moon_h = planets['Moon']['house']
h2_from_moon = ((moon_h - 1 + 1) % 12) + 1
h12_from_moon = ((moon_h - 1 + 11) % 12) + 1
p_in_h2_moon = [p for p, info in planets.items() if p != 'Moon' and info['house'] == h2_from_moon]
p_in_h12_moon = [p for p, info in planets.items() if p != 'Moon' and info['house'] == h12_from_moon]
if p_in_h12_moon and not p_in_h2_moon:
yogas.append({"name": "Anapha Yoga", "name_cn": "月后瑜伽", "combination": f"{'+'.join(p_in_h12_moon)}在月亮第12宫", "effects": ["体格健壮", "名声良好", "品德高尚"], "strength": ""})
if p_in_h2_moon and not p_in_h12_moon:
yogas.append({"name": "Sunapha Yoga", "name_cn": "月前瑜伽", "combination": f"{'+'.join(p_in_h2_moon)}在月亮第2宫", "effects": ["自力更生", "财富充裕", "受人尊敬"], "strength": ""})
if p_in_h2_moon and p_in_h12_moon:
yogas.append({"name": "Durudhura Yoga", "name_cn": "月双夹瑜伽", "combination": f"月亮两侧均有行星", "effects": ["享受丰富", "善于辞令", "性格坚定"], "strength": ""})
# Kemadruma Yoga: Moon两侧(2宫和12宫)均无行星
if not p_in_h2_moon and not p_in_h12_moon:
yogas.append({"name": "Kemadruma Yoga", "name_cn": "空劫瑜伽", "combination": f"月亮两侧均无行星(2宫/12宫空)", "effects": ["人生艰辛", "需自力更生", "精神挑战"], "strength": ""})
# ========================================================================
# 扩展组合型 Yogav3.7.2 新增,共 25 条)
# ========================================================================
# --- 辅助函数 ---
def _house_of(planet_name):
"""获取行星所在宫位,不存在返回 None"""
return planets[planet_name]["house"] if planet_name in planets else None
def _sign_of(planet_name):
"""获取行星所在星座,不存在返回 None"""
return planets[planet_name]["sign"] if planet_name in planets else None
def _is_exalted(planet_name):
"""行星是否入旺"""
s = _sign_of(planet_name)
return s is not None and EXALTATION.get(planet_name) == s
def _is_own_sign(planet_name):
"""行星是否入庙"""
s = _sign_of(planet_name)
return s is not None and SIGN_LORDS.get(s) == planet_name
def _is_debilitated(planet_name):
"""行星是否落陷"""
s = _sign_of(planet_name)
return s is not None and DEBILITATION.get(planet_name) == s
def _is_kendra(house_num):
return house_num in [1, 4, 7, 10]
def _is_trikona(house_num):
return house_num in [1, 5, 9]
def _is_dusthana(house_num):
return house_num in [6, 8, 12]
def _is_upachaya(house_num):
return house_num in [3, 6, 10, 11]
def _planets_in_house(h):
"""获取落入第 h 宫的所有行星列表"""
return [p for p, info in planets.items() if info["house"] == h]
def _lord_of_house(h):
"""获取第 h 宫的宫主星"""
return SIGN_LORDS[SIGNS[(ai + h - 1) % 12]]
def _planet_in_kendra_or_trikona(planet_name):
"""行星是否在角宫或三方宫"""
h = _house_of(planet_name)
return h is not None and (_is_kendra(h) or _is_trikona(h))
# ========================================================================
# 1. Vipreet Raja Yoga(逆行王者格局)—— Harsha / Sarala / Vimala
# 来源:Phaladeepika。6/8/12 宫主星落入 6/8/12 宫(凶宫互落)→ 因祸得福
# ========================================================================
for dh_h, yoga_sub, yoga_cn in [(6, "Harsha", "喜悦逆行"), (8, "Sarala", "锐利逆行"), (12, "Vimala", "纯净逆行")]:
lord_dh = _lord_of_house(dh_h)
if lord_dh in planets:
lh = _house_of(lord_dh)
if lh in [6, 8, 12] and lh != dh_h:
yogas.append({"name": f"{yoga_sub} Vipreet Raja Yoga", "name_cn": f"{yoga_cn}格局", "combination": f"{dh_h}宫主星{lord_dh}落入第{lh}宫(凶宫互落)", "effects": ["因祸得福", "逆境崛起", "困境中成长"], "strength": "中强"})
# ========================================================================
# 2. Amala Yoga(无瑕格局)
# 来源:BPHS。10 宫(天顶)及其前方(从10宫算2宫即11宫)有吉星
# 简化版:10 宫或 11 宫有木星/金星/明亮水星
# ========================================================================
benefics_10_11 = [p for p in ['Jupiter', 'Venus', 'Mercury'] if p in planets and _house_of(p) in [10, 11] and not _is_debilitated(p)]
if benefics_10_11:
yogas.append({"name": "Amala Yoga", "name_cn": "无瑕格局", "combination": f"{''.join(benefics_10_11)}在10宫或11宫", "effects": ["名声清白", "受人敬仰", "事业有成"], "strength": "中强"})
# ========================================================================
# 3. Parvata Yoga(山岳格局)
# 来源:BPHS。角宫主星有力 + 6/8 宫主星落入对应凶宫
# 简化版:至少两个角宫主星在角宫或三方宫 + 6/8宫主不在1宫
# ========================================================================
kendra_lords_strong = sum(1 for k in kl if k in planets and _planet_in_kendra_or_trikona(k))
if kendra_lords_strong >= 2:
lord_6 = _lord_of_house(6)
lord_8 = _lord_of_house(8)
lord_6_ok = lord_6 in planets and _house_of(lord_6) != 1
lord_8_ok = lord_8 in planets and _house_of(lord_8) != 1
if lord_6_ok or lord_8_ok:
yogas.append({"name": "Parvata Yoga", "name_cn": "山岳格局", "combination": f"角宫主星有力+凶宫主星不落入1宫", "effects": ["智慧卓越", "富有口才", "品格高尚"], "strength": ""})
# ========================================================================
# 4. Kahala Yoga(勇气格局)
# 来源:BPHS。3 宫主与 10 宫主有连接(同宫/互换/相位),且落入角宫
# 简化版:3 宫主与 10 宫主同在一宫
# ========================================================================
lord_3 = _lord_of_house(3)
lord_10 = _lord_of_house(10)
if lord_3 in planets and lord_10 in planets:
if _house_of(lord_3) == _house_of(lord_10):
yogas.append({"name": "Kahala Yoga", "name_cn": "勇气格局", "combination": f"3宫主{lord_3}与10宫主{lord_10}同在第{_house_of(lord_3)}", "effects": ["意志坚定", "领导才能", "充满活力"], "strength": ""})
# ========================================================================
# 5. Sankha Yoga(海螺格局)
# 来源:BPHS。5 宫主 + 6 宫主落入角宫/三方宫,且上升主星有力
# 简化版:5宫主和6宫主均在角宫或三方宫
# ========================================================================
lord_5 = _lord_of_house(5)
lord_6 = _lord_of_house(6)
if lord_5 in planets and lord_6 in planets:
l5h = _house_of(lord_5)
l6h = _house_of(lord_6)
if (_is_kendra(l5h) or _is_trikona(l5h)) and (_is_kendra(l6h) or _is_trikona(l6h)):
yogas.append({"name": "Sankha Yoga", "name_cn": "海螺格局", "combination": f"5宫主{lord_5}在第{l5h}宫+6宫主{lord_6}在第{l6h}宫(均有力)", "effects": ["学识渊博", "品德高尚", "物质充裕"], "strength": "中强"})
# ========================================================================
# 6. Bheri Yoga(旗帜格局)
# 来源:BPHS。9 宫主落入 2 宫 + 木星/金星在角宫/三方宫 + 上升主与 9 宫主有关联
# 简化版:9 宫主在 2 宫 + 木星或金星在角宫
# ========================================================================
lord_9 = _lord_of_house(9)
if lord_9 in planets and _house_of(lord_9) == 2:
jv_in_kendra = [p for p in ['Jupiter', 'Venus'] if p in planets and _is_kendra(_house_of(p))]
if jv_in_kendra:
yogas.append({"name": "Bheri Yoga", "name_cn": "旗帜格局", "combination": f"9宫主{lord_9}在2宫+{''.join(jv_in_kendra)}在角宫", "effects": ["品德高尚", "长寿幸福", "受人敬仰"], "strength": ""})
# ========================================================================
# 7. Mridanga Yoga(鼓格局)
# 来源:BPHS。上升主星在入旺/入庙 + 其他行星在自身入旺/入庙星座
# 简化版:上升主星入旺或入庙
# ========================================================================
asc_lord = SIGN_LORDS.get(asc, '')
if asc_lord in planets and (_is_exalted(asc_lord) or _is_own_sign(asc_lord)):
other_strong = [p for p in planets if p != asc_lord and p not in ['Rahu', 'Ketu'] and (_is_exalted(p) or _is_own_sign(p))]
if other_strong:
st = "入旺" if _is_exalted(asc_lord) else "入庙"
yogas.append({"name": "Mridanga Yoga", "name_cn": "鼓格局", "combination": f"上升主星{asc_lord}{st}+{''.join(other_strong)}有力", "effects": ["权力权威", "个人魅力", "领导气质"], "strength": ""})
# ========================================================================
# 8. Sreenatha Yoga(幸运之主格局)
# 来源:BPHS。7 宫主星落入 10 宫且 10 宫主落入 1 宫
# ========================================================================
lord_7 = _lord_of_house(7)
if lord_7 in planets and lord_10 in planets:
if _house_of(lord_7) == 10 and _house_of(lord_10) == 1:
yogas.append({"name": "Sreenatha Yoga", "name_cn": "幸运之主格局", "combination": f"7宫主{lord_7}在10宫+10宫主{lord_10}在1宫", "effects": ["事业辉煌", "婚姻美满", "贵人相助"], "strength": ""})
# ========================================================================
# 9. Matsya Yoga(鱼格局)
# 来源:BPHS。1/5/9 宫中有吉星 + 6/8 宫有凶星 + 上升与木星/金星有连接
# 简化版:三方宫(5/9)有吉星 + 6/8宫有凶星
# ========================================================================
benefics = ['Jupiter', 'Venus', 'Mercury']
malefics = ['Mars', 'Saturn', 'Sun', 'Rahu', 'Ketu']
ben_in_trikona = [p for p in benefics if p in planets and _house_of(p) in [5, 9]]
mal_in_dusthana = [p for p in malefics if p in planets and _house_of(p) in [6, 8]]
if ben_in_trikona and mal_in_dusthana:
yogas.append({"name": "Matsya Yoga", "name_cn": "鱼格局", "combination": f"{''.join(ben_in_trikona)}在三方宫+{''.join(mal_in_dusthana)}在凶宫", "effects": ["聪慧过人", "直觉敏锐", "灵性成长"], "strength": ""})
# ========================================================================
# 10. Koorma Yoga(龟格局)
# 来源:BPHS。5/9 宫主落入三方宫 + 1 宫有吉星
# 简化版:5宫主或9宫主在1宫,且1宫有吉星
# ========================================================================
ben_in_1 = [p for p in benefics if p in planets and _house_of(p) == 1]
lord_5_or_9_in_1 = False
for lp in [lord_5, lord_9]:
if lp in planets and _house_of(lp) == 1:
lord_5_or_9_in_1 = True
if ben_in_1 and lord_5_or_9_in_1:
yogas.append({"name": "Koorma Yoga", "name_cn": "龟格局", "combination": f"吉星在1宫+5/9宫主在1宫", "effects": ["智慧深厚", "耐心坚韧", "精神修养"], "strength": ""})
# ========================================================================
# 11. Khadga Yoga(剑格局)
# 来源:BPHS。2 宫主落入 2 宫且不被凶星占据 + 9 宫主有力
# 简化版:2 宫主在 2 宫且 2 宫无凶星
# ========================================================================
lord_2 = _lord_of_house(2)
if lord_2 in planets and _house_of(lord_2) == 2:
mal_in_2 = [p for p in malefics if p in planets and _house_of(p) == 2]
if not mal_in_2:
yogas.append({"name": "Khadga Yoga", "name_cn": "剑格局", "combination": f"2宫主{lord_2}在2宫且2宫无凶星", "effects": ["财富充裕", "口才出众", "品格坚定"], "strength": ""})
# ========================================================================
# 12. Kusuma Yoga(花格局)
# 来源:BPHS。木星在 10 宫 + 月亮在 1 宫 + 太阳在 2 宫 + 金星在 9 宫
# ========================================================================
jup_10 = 'Jupiter' in planets and _house_of('Jupiter') == 10
moon_1 = 'Moon' in planets and _house_of('Moon') == 1
sun_2 = 'Sun' in planets and _house_of('Sun') == 2
ven_9 = 'Venus' in planets and _house_of('Venus') == 9
checks_kusuma = [jup_10, moon_1, sun_2, ven_9]
met_kusuma = sum(checks_kusuma)
if met_kusuma >= 3:
parts = []
if jup_10: parts.append("木星在10宫")
if moon_1: parts.append("月亮在1宫")
if sun_2: parts.append("太阳在2宫")
if ven_9: parts.append("金星在9宫")
yogas.append({"name": "Kusuma Yoga", "name_cn": "花格局", "combination": "+".join(parts), "effects": ["才貌双全", "受人爱戴", "幸福美满"], "strength": "" if met_kusuma == 4 else "中强"})
# ========================================================================
# 13. Chandra-Mangala Yoga(日月-火星格局)
# 来源:BPHS。火星与月亮同宫或在 2/12 宫关系
# ========================================================================
if 'Moon' in planets and 'Mars' in planets:
mh = _house_of('Moon')
marsh = _house_of('Mars')
if mh == marsh:
yogas.append({"name": "Chandra-Mangala Yoga", "name_cn": "日月火星同宫格局", "combination": f"火星与月亮同在第{mh}", "effects": ["财富积累", "行动力强", "情绪驱动成功"], "strength": "" if _is_exalted('Mars') or _is_own_sign('Mars') else ""})
elif abs(mh - marsh) in [1, 11]:
yogas.append({"name": "Chandra-Mangala Yoga", "name_cn": "日月火星关联格局", "combination": f"火星在第{marsh}宫,月亮在第{mh}宫(2/12宫关系)", "effects": ["财运亨通", "精力充沛", "商业头脑"], "strength": ""})
# ========================================================================
# 14. Dwi-Gurupada Yoga / Guru-Mangala Yoga(木火格局)
# 来源:木星与火星同宫或互换星座
# ========================================================================
if 'Jupiter' in planets and 'Mars' in planets:
jup_s = _sign_of('Jupiter')
mars_s = _sign_of('Mars')
jup_h = _house_of('Jupiter')
mars_h = _house_of('Mars')
if jup_h == mars_h:
yogas.append({"name": "Guru-Mangala Yoga", "name_cn": "木火同宫格局", "combination": f"木星与火星同在第{jup_h}", "effects": ["智慧与行动力兼备", "正义感强", "领导能力"], "strength": "中强"})
elif SIGN_LORDS.get(jup_s) == 'Mars' and SIGN_LORDS.get(mars_s) == 'Jupiter':
yogas.append({"name": "Guru-Mangala Yoga", "name_cn": "木火互容格局", "combination": f"木星在{jup_s}(火星守护)+火星在{mars_s}(木星守护)", "effects": ["智慧与行动力兼备", "正义感强", "领导能力"], "strength": ""})
# ========================================================================
# 15. Budhaditya Yoga(水日格局)
# 来源:BPHS。水星与太阳同宫
# ========================================================================
if 'Mercury' in planets and 'Sun' in planets:
if _house_of('Mercury') == _house_of('Sun'):
yogas.append({"name": "Budhaditya Yoga", "name_cn": "水日同宫格局", "combination": f"水星与太阳同在第{_house_of('Sun')}", "effects": ["智力超群", "口才出众", "学识渊博"], "strength": "中强" if not _is_debilitated('Mercury') else ""})
# ========================================================================
# 16. Lakshmi Yoga(拉克什米格局/财富女神格局)
# 来源:BPHS。9 宫主在入旺/入庙星座且落入 1 宫
# ========================================================================
if lord_9 in planets:
l9h = _house_of(lord_9)
if l9h == 1 and (_is_exalted(lord_9) or _is_own_sign(lord_9)):
st = "入旺" if _is_exalted(lord_9) else "入庙"
yogas.append({"name": "Lakshmi Yoga", "name_cn": "财富女神格局", "combination": f"9宫主{lord_9}{st}在1宫", "effects": ["财富充裕", "生活幸福", "品德高尚"], "strength": ""})
# ========================================================================
# 17. Vasumati Yoga(大地格局)
# 来源:BPHS。所有吉星(木星/金星/水星/月亮)在上升盘的 3/6/10/11 宫(成长宫)
# ========================================================================
upachaya_benefics = [p for p in ['Jupiter', 'Venus', 'Mercury', 'Moon'] if p in planets and _is_upachaya(_house_of(p))]
if len(upachaya_benefics) >= 3:
yogas.append({"name": "Vasumati Yoga", "name_cn": "大地格局", "combination": f"{len(upachaya_benefics)}颗吉星在成长宫(3/6/10/11)", "effects": ["财富丰厚", "生活富足", "物质成功"], "strength": "中强" if len(upachaya_benefics) >= 4 else ""})
# ========================================================================
# 18. Vanchana Kalusha Yoga / Papakartari Yoga(凶星夹宫格局)
# 来源:Brihat Jataka。某宫被两侧凶星夹击(前一宫和后一宫均有凶星)
# ========================================================================
for target_house in [1, 4, 7, 10, 5, 9]:
prev_h = ((target_house - 2) % 12) + 1
next_h = (target_house % 12) + 1
mal_before = [p for p in malefics if p in planets and _house_of(p) == prev_h]
mal_after = [p for p in malefics if p in planets and _house_of(p) == next_h]
if mal_before and mal_after:
house_names = {1: "命宫", 4: "田宅宫", 7: "夫妻宫", 10: "事业宫", 5: "子女宫", 9: "迁移宫"}
yogas.append({"name": "Papakartari Yoga", "name_cn": f"凶星夹{house_names.get(target_house, f'{target_house}')}格局", "combination": f"{target_house}宫被{''.join(mal_before)}{''.join(mal_after)}前后夹击", "effects": [f"{house_names.get(target_house, f'{target_house}')}领域受限", "需要额外努力", "阻碍与挑战"], "strength": ""})
break # 只报告一次
# ========================================================================
# 19. Subhakartari Yoga(吉星夹宫格局)
# 来源:Brihat Jataka。与 Papakartari 相反——某宫被两侧吉星包围
# ========================================================================
for target_house in [1, 4, 7, 10, 5, 9]:
prev_h = ((target_house - 2) % 12) + 1
next_h = (target_house % 12) + 1
ben_before = [p for p in benefics if p in planets and _house_of(p) == prev_h]
ben_after = [p for p in benefics if p in planets and _house_of(p) == next_h]
if ben_before and ben_after:
house_names = {1: "命宫", 4: "田宅宫", 7: "夫妻宫", 10: "事业宫", 5: "子女宫", 9: "迁移宫"}
yogas.append({"name": "Subhakartari Yoga", "name_cn": f"吉星护{house_names.get(target_house, f'{target_house}')}格局", "combination": f"{target_house}宫被{''.join(ben_before)}{''.join(ben_after)}吉星守护", "effects": [f"{house_names.get(target_house, f'{target_house}')}领域受保护", "顺利发展", "贵人助力"], "strength": ""})
break
# ========================================================================
# 20. Saraswati Yoga(辩才天女格局)
# 来源:BPHS。木星/金星/水星在 2/4/7/9/10 宫,且木星在自身友好星座
# ========================================================================
saraswati_houses = [2, 4, 7, 9, 10]
saraswati_planets = [p for p in ['Jupiter', 'Venus', 'Mercury'] if p in planets and _house_of(p) in saraswati_houses]
if len(saraswati_planets) >= 3 or (len(saraswati_planets) >= 2 and 'Jupiter' in saraswati_planets):
jup_dignity = _get_dignity_level('Jupiter', _sign_of('Jupiter')) if 'Jupiter' in planets else ''
if jup_dignity in ['EXALTED', 'OWN_SIGN', 'MOOLATRIKONA', 'FRIEND']:
yogas.append({"name": "Saraswati Yoga", "name_cn": "辩才天女格局", "combination": f"{''.join(saraswati_planets)}在吉宫(2/4/7/9/10)+木星有力", "effects": ["学问渊博", "艺术才华", "表达卓越"], "strength": "" if jup_dignity in ['EXALTED', 'OWN_SIGN'] else "中强"})
# ========================================================================
# 21. Hamsa Yoga(天鹅格局)—— 增强版 Mahapurusha
# 来源:BPHS。木星入旺/入庙在角宫(1/4/7/10)
# (注:基本版已包含在 Mahapurusha Yoga 中,这里增加三方宫扩展检测)
# ========================================================================
if 'Jupiter' in planets:
jup_h = _house_of('Jupiter')
if _is_trikona(jup_h) and (_is_exalted('Jupiter') or _is_own_sign('Jupiter')):
st = "入旺" if _is_exalted('Jupiter') else "入庙"
already_hamsa = any(y.get("name") == "Hamsa Yoga" for y in yogas)
if not already_hamsa:
yogas.append({"name": "Hamsa Yoga (Trikona)", "name_cn": f"木星{st}三方宫格局", "combination": f"木星{st}{_sign_of('Jupiter')}(第{jup_h}宫,三方宫)", "effects": ["智慧卓越", "精神导师", "品德高尚"], "strength": ""})
# ========================================================================
# 22. Chamara Yoga(拂尘格局)
# 来源:BPHS。上升主星在入旺/入庙 + 两个或以上吉星在角宫/三方宫
# ========================================================================
if asc_lord in planets and (_is_exalted(asc_lord) or _is_own_sign(asc_lord)):
ben_kt = [p for p in benefics if p in planets and _planet_in_kendra_or_trikona(p)]
if len(ben_kt) >= 2:
st = "入旺" if _is_exalted(asc_lord) else "入庙"
yogas.append({"name": "Chamara Yoga", "name_cn": "拂尘格局", "combination": f"上升主星{asc_lord}{st}+{''.join(ben_kt)}在角宫/三方宫", "effects": ["长寿健康", "受人尊敬", "智慧通达"], "strength": ""})
# ========================================================================
# 23. Akhanda Samrajya Yoga(永恒帝王格局)
# 来源:BPHS。木星在 2/5/9/11 宫 + 2 宫主不在 6/8/12 宫 + 11 宫主在 11 宫
# 简化版:木星在 2/5/9/11 + 11 宫主在 11 宫或角宫
# ========================================================================
if 'Jupiter' in planets and _house_of('Jupiter') in [2, 5, 9, 11]:
lord_11 = _lord_of_house(11)
if lord_11 in planets:
l11h = _house_of(lord_11)
if l11h == 11 or _is_kendra(l11h):
yogas.append({"name": "Akhanda Samrajya Yoga", "name_cn": "永恒帝王格局", "combination": f"木星在{_house_of('Jupiter')}宫+11宫主{lord_11}{l11h}", "effects": ["权力持久", "事业辉煌", "影响力广泛"], "strength": ""})
# ========================================================================
# 24. Gurumauli Yoga / Maha Bhagya Yoga(大运格局)
# 来源:经典文献。木星在 9 宫或与 9 宫主同宫
# ========================================================================
if 'Jupiter' in planets:
jup_h = _house_of('Jupiter')
if jup_h == 9:
yogas.append({"name": "Gurumauli Yoga", "name_cn": "至上师格局", "combination": f"木星在9宫(命运宫)", "effects": ["命运眷顾", "精神导师指引", "福德深厚"], "strength": ""})
elif lord_9 in planets and jup_h == _house_of(lord_9):
yogas.append({"name": "Gurumauli Yoga", "name_cn": "至上师关联格局", "combination": f"木星与9宫主{lord_9}同在第{jup_h}", "effects": ["命运眷顾", "贵人运强", "智慧通达"], "strength": "中强"})
# ========================================================================
# 25. Shakata Yoga(车格局——凶)
# 来源:BPHS。木星在 6/8/12 宫(从月亮算起),且月亮与木星不在 1/4/7/10 宫关系
# 简化版(从上升算):木星落入 6/8/12 宫
# ========================================================================
if 'Jupiter' in planets and _house_of('Jupiter') in [6, 8, 12]:
yogas.append({"name": "Shakata Yoga", "name_cn": "车格局(凶)", "combination": f"木星落入第{_house_of('Jupiter')}宫(凶宫)", "effects": ["财富不稳", "健康需注意", "精神起伏"], "strength": ""})
# ========================================================================
# 26. Amarishta Yoga / Daridra Yoga(贫困格局——凶)
# 来源:BPHS。11 宫主落入 6/8/12 宫,或 2 宫主落陷且在凶宫
# ========================================================================
lord_11 = _lord_of_house(11)
if lord_11 in planets and _house_of(lord_11) in [6, 8, 12]:
yogas.append({"name": "Daridra Yoga", "name_cn": "贫困格局(凶)", "combination": f"11宫主{lord_11}落入第{_house_of(lord_11)}宫(凶宫)", "effects": ["财务困难", "需努力积累", "谨慎理财"], "strength": ""})
if lord_2 in planets and _is_debilitated(lord_2) and _is_dusthana(_house_of(lord_2)):
yogas.append({"name": "Daridra Yoga (variant)", "name_cn": "贫困格局变体(凶)", "combination": f"2宫主{lord_2}落陷在凶宫第{_house_of(lord_2)}", "effects": ["财务压力", "家庭纠纷", "需节俭持家"], "strength": ""})
# ========================================================================
# 27. Dhan Yoga 增强版 —— Grahi Dhan Yoga
# 来源:Phaladeepika。1 宫主 + 2 宫主 + 11 宫主同在 2/11 宫
# ========================================================================
lords_wealth = {1: SIGN_LORDS[SIGNS[(ai + 0) % 12]], 2: lord_2, 11: _lord_of_house(11)}
for wh in [2, 11]:
count = sum(1 for h_num, lp in lords_wealth.items() if lp in planets and _house_of(lp) == wh)
if count >= 2:
in_house = [f"{h_num}宫主{lp}" for h_num, lp in lords_wealth.items() if lp in planets and _house_of(lp) == wh]
yogas.append({"name": "Grahi Dhana Yoga", "name_cn": "星聚财富格局", "combination": f"{''.join(in_house)}同在第{wh}", "effects": ["财运亨通", "投资有利", "收入丰厚"], "strength": ""})
break
# ========================================================================
# 扩展 Yoga 规则库 (v6.0.25)
# 目标:从 ~49 种扩展到 75+ 种
# ========================================================================
# --- A. 行星合相 YogaConjunction Yogas---
# A1. Budha-Shukra Yoga(水金同宫)
# 来源:BPHS。水星与金星同宫 → 艺术才华、口才、商业头脑
if 'Mercury' in planets and 'Venus' in planets:
if _house_of('Mercury') == _house_of('Venus'):
yogas.append({"name": "Budha-Shukra Yoga", "name_cn": "水金合相格局", "combination": f"水星与金星同在第{_house_of('Mercury')}", "effects": ["艺术才华", "商业头脑", "审美卓越"], "strength": "" if not _is_debilitated('Mercury') else ""})
# A2. Surya-Chandra Yoga(日月同宫)
# 来源:经典文献。太阳与月亮同宫 → 精神力量、领导力
if 'Sun' in planets and 'Moon' in planets:
if _house_of('Sun') == _house_of('Moon'):
yogas.append({"name": "Surya-Chandra Yoga", "name_cn": "日月合相格局", "combination": f"太阳与月亮同在第{_house_of('Sun')}", "effects": ["精神力量", "领导气质", "意志坚定"], "strength": ""})
# A3. Guru-Shukra Yoga(木金同宫)
# 来源:BPHS。木星与金星同宫 → 智慧与爱心的结合
if 'Jupiter' in planets and 'Venus' in planets:
if _house_of('Jupiter') == _house_of('Venus'):
yogas.append({"name": "Guru-Shukra Yoga", "name_cn": "木金合相格局", "combination": f"木星与金星同在第{_house_of('Jupiter')}", "effects": ["智慧与爱心兼备", "精神富足", "艺术造诣"], "strength": ""})
# A4. Shani-Rahu Yoga(土罗同宫)
# 来源:经典文献。土星与罗睺同宫 → 纪律与变革的结合,但可能带来阻碍
if 'Saturn' in planets and 'Rahu' in planets:
if _house_of('Saturn') == _house_of('Rahu'):
yogas.append({"name": "Shani-Rahu Yoga", "name_cn": "土罗合相格局", "combination": f"土星与罗睺同在第{_house_of('Saturn')}", "effects": ["纪律与变革", "突破传统", "但也可能带来阻碍"], "strength": ""})
# A5. Angaraka Yoga(火土同宫)
# 来源:BPHS。火星与土星同宫 → 冲突与纪律的张力
if 'Mars' in planets and 'Saturn' in planets:
if _house_of('Mars') == _house_of('Saturn'):
yogas.append({"name": "Angaraka Yoga", "name_cn": "火土合相格局", "combination": f"火星与土星同在第{_house_of('Mars')}", "effects": ["行动力与纪律", "也可能产生冲突", "需平衡"], "strength": ""})
# A6. Surya-Budha Yoga(日水合相 — 与 Budhaditya 不同,这个是更宽松的定义)
# 来源:经典文献。太阳与水星在 2° 以内 → 智力超群
if 'Sun' in planets and 'Mercury' in planets:
if _house_of('Sun') == _house_of('Mercury'):
yogas.append({"name": "Surya-Budha Yoga", "name_cn": "日水紧密合相格局", "combination": f"太阳与水星同在第{_house_of('Sun')}", "effects": ["智力超群", "沟通能力强", "学习能力佳"], "strength": ""})
# --- B. 宫位主星关联 YogaLord Relationship Yogas---
# B1. Dharma Karmadhipati Yoga(法业主格局)
# 来源:BPHS。9宫主与10宫主有关联(同宫/互换/互容)
lord_9 = _lord_of_house(9)
lord_10 = _lord_of_house(10)
if lord_9 in planets and lord_10 in planets:
l9h = _house_of(lord_9)
l10h = _house_of(lord_10)
if l9h == l10h:
yogas.append({"name": "Dharma Karmadhipati Yoga", "name_cn": "法业主同宫格局", "combination": f"9宫主{lord_9}与10宫主{lord_10}同在第{l9h}", "effects": ["事业与命运结合", "社会地位高", "精神事业"], "strength": ""})
elif SIGN_LORDS.get(_sign_of(lord_9)) == lord_10 and SIGN_LORDS.get(_sign_of(lord_10)) == lord_9:
yogas.append({"name": "Dharma Karmadhipati Yoga (Parivartana)", "name_cn": "法业主互换格局", "combination": f"9宫主{lord_9}与10宫主{lord_10}互换星座", "effects": ["事业与命运互换提升", "社会地位高", "贵人运强"], "strength": ""})
# B2. Raja Yoga 扩展 — 互落(Parivartana
# 来源:BPHS。Kendra 主与 Trikona 主互换星座
for klord in kl:
for tlord in tl:
if klord != tlord and klord in planets and tlord in planets:
if SIGN_LORDS.get(_sign_of(klord)) == tlord and SIGN_LORDS.get(_sign_of(tlord)) == klord:
yogas.append({"name": "Raja Yoga (Parivartana)", "name_cn": "王者互换格局", "combination": f"{klord}{tlord}互换星座", "effects": ["权力与财富互换提升", "社会地位显著", "事业成功"], "strength": "极强"})
# B3. Neecha Bhanga 扩展(更多条件)
# 来源:BPHS。条件2:落陷行星所在星座的守护星在 Kendra
for p, info in planets.items():
if DEBILITATION.get(p) == info["sign"]:
dl = SIGN_LORDS[info["sign"]]
if dl in planets and _is_kendra(_house_of(dl)):
already_nb = any("Neechabhanga" in y.get("name", "") and p in y.get("combination", "") for y in yogas)
if not already_nb:
yogas.append({"name": "Neechabhanga Raja Yoga (Variant)", "name_cn": "落陷取消格局(守护星版)", "combination": f"{p}落陷在{info['sign']},守护星{dl}在角宫", "effects": ["克服困难", "逆境崛起", "转化能力"], "strength": "中强"})
# 条件3:落陷行星自身在 Kendra
if _is_kendra(info["house"]):
already_nb = any("Neechabhanga" in y.get("name", "") and p in y.get("combination", "") for y in yogas)
if not already_nb:
yogas.append({"name": "Neechabhanga Raja Yoga (Kendra)", "name_cn": "落陷取消格局(角宫版)", "combination": f"{p}落陷但在角宫第{info['house']}", "effects": ["逆境中崛起", "转化困境为机遇"], "strength": "中强"})
# B4. Pancha Mahapurusha 扩展 — Moolatrikona
# 来源:BPHS。行星在 Moolatrikona 星座且在 Kendra
MOOLATRIKONA = {
'Sun': 'Leo', 'Moon': 'Taurus', 'Mars': 'Aries', 'Mercury': 'Virgo',
'Jupiter': 'Sagittarius', 'Venus': 'Libra', 'Saturn': 'Aquarius'
return {
"ascendant": asc,
"planets_analyzed": len(planets),
"kendra_lords": kl,
"trikona_lords": tl,
"yogas_detected": len(yogas),
"yogas": yogas,
"detected_yogas": yogas,
}
moola_names = {'Mars': 'Ruchaka (Moola)', 'Mercury': 'Bhadra (Moola)', 'Jupiter': 'Hamsa (Moola)',
'Venus': 'Malavya (Moola)', 'Saturn': 'Sasa (Moola)'}
for p, info in planets.items():
if info["house"] in [1, 4, 7, 10] and p in moola_names:
if MOOLATRIKONA.get(p) == info["sign"]:
already = any(y.get("name") == moola_names[p] for y in yogas)
if not already:
yogas.append({"name": moola_names[p], "name_cn": f"{PLANET_CN.get(p, p)}本宫格局", "combination": f"{p}在本宫{info['sign']}(第{info['house']}宫)", "effects": ["卓越才能", "领域领军", "人格魅力"], "strength": ""})
# B5. Viparita Raja 扩展
# 来源:BPHS。6/8/12 宫主互落(更宽松:任意两个凶宫主互落)
dusthana_pairs = [(6, 8), (6, 12), (8, 12), (8, 6), (12, 6), (12, 8)]
for h1, h2 in dusthana_pairs:
l1 = _lord_of_house(h1)
l2 = _lord_of_house(h2)
if l1 in planets and l2 in planets:
if _house_of(l1) == h2 and _house_of(l2) == h1:
already = any("Vipreet" in y.get("name", "") and f"{h1}" in y.get("combination", "") and f"{h2}" in y.get("combination", "") for y in yogas)
if not already:
yogas.append({"name": f"Vipreet Raja Yoga ({h1}-{h2})", "name_cn": f"逆行王者格局({h1}-{h2})", "combination": f"{h1}宫主{l1}{h2}宫,第{h2}宫主{l2}{h1}", "effects": ["因祸得福", "逆境崛起"], "strength": "中强"})
# --- C. Nabhasa Yogas(天空格局 — 基于行星分布模式)---
# C1. Sankhya Yoga(数字格局)
# 来源:BPHS。基于行星占据的宫位数量
occupied_houses = set(info["house"] for info in planets.values() if "house" in info)
num_occupied = len(occupied_houses)
if num_occupied == 1:
yogas.append({"name": "Gola Yoga", "name_cn": "球格局", "combination": "所有行星在同一宫", "effects": ["专注集中", "也可能过于极端"], "strength": ""})
elif num_occupied == 2:
yogas.append({"name": "Yuga Yoga", "name_cn": "双格局", "combination": "所有行星在两宫", "effects": ["双重性格", "生活两极化"], "strength": ""})
elif num_occupied == 3:
yogas.append({"name": "Sula Yoga", "name_cn": "三叉格局", "combination": "所有行星在三宫", "effects": ["行动力", "但也可能有冲突"], "strength": ""})
elif num_occupied == 4:
yogas.append({"name": "Kedara Yoga", "name_cn": "四重格局", "combination": "所有行星在四宫", "effects": ["稳定", "农业/地产运"], "strength": ""})
elif num_occupied == 7:
yogas.append({"name": "Veena Yoga", "name_cn": "琴格局", "combination": "行星分布在七宫", "effects": ["艺术才华", "生活丰富", "多才多艺"], "strength": ""})
# C2. Asraya Yoga(依托格局)
# 来源:BPHS。所有行星在 Kendra 或 Trikona
all_in_kt = all(_is_kendra(info["house"]) or _is_trikona(info["house"]) for info in planets.values() if "house" in info)
if all_in_kt and len(planets) >= 5:
yogas.append({"name": "Asraya Yoga", "name_cn": "依托格局", "combination": "所有行星在角宫或三方宫", "effects": ["生活稳定", "有依靠", "根基深厚"], "strength": "中强"})
# C3. Dala Yoga(分叶格局)
# 来源:BPHS。6/8/12 宫主在 Kendra
dala_count = sum(1 for dh in [6, 8, 12] if _lord_of_house(dh) in planets and _is_kendra(_house_of(_lord_of_house(dh))))
if dala_count >= 2:
yogas.append({"name": "Dala Yoga", "name_cn": "分叶格局", "combination": f"{dala_count}个凶宫主在角宫", "effects": ["逆境中崛起", "转化能力"], "strength": "中强"})
# C4. Maala Yoga(串珠格局)
# 来源:BPHS。所有吉星在 Kendra 的连续三宫
ben_in_kendra = sorted(set(_house_of(p) for p in benefics if p in planets and _is_kendra(_house_of(p))))
if len(ben_in_kendra) >= 3:
for i in range(len(ben_in_kendra) - 2):
if ben_in_kendra[i+1] - ben_in_kendra[i] == 1 and ben_in_kendra[i+2] - ben_in_kendra[i+1] == 1:
yogas.append({"name": "Maala Yoga", "name_cn": "串珠格局", "combination": f"吉星在连续的角宫", "effects": ["幸运连绵", "福气不断"], "strength": ""})
break
# --- D. 特殊条件 Yoga ---
# D1. Mahabhagya Yoga(大运格局)
# 来源:BPHS。白天出生:太阳/月亮/上升在奇数星座;夜晚:在偶数星座
# 简化版(不区分昼夜性别):检查奇偶星座分布
odd_signs = ['Aries', 'Gemini', 'Leo', 'Libra', 'Sagittarius', 'Aquarius']
even_signs = ['Taurus', 'Cancer', 'Virgo', 'Scorpio', 'Capricorn', 'Pisces']
asc_odd = asc in odd_signs
sun_odd = 'Sun' in planets and _sign_of('Sun') in odd_signs
moon_odd = 'Moon' in planets and _sign_of('Moon') in odd_signs
if asc_odd and sun_odd and moon_odd:
yogas.append({"name": "Mahabhagya Yoga", "name_cn": "大运格局", "combination": "上升/太阳/月亮均在奇数星座", "effects": ["命运眷顾", "人生顺遂", "贵人运强"], "strength": ""})
# D2. Pushkala Yoga(丰盈格局)
# 来源:BPHS。上升主有力 + 月亮在友好星座 + 上升有吉星
if asc_lord in planets and (_is_exalted(asc_lord) or _is_own_sign(asc_lord)):
moon_ok = 'Moon' in planets and _sign_of('Moon') not in [DEBILITATION.get('Moon', '')]
ben_in_asc = [p for p in benefics if p in planets and _house_of(p) == 1]
if moon_ok and ben_in_asc:
yogas.append({"name": "Pushkala Yoga", "name_cn": "丰盈格局", "combination": f"上升主有力+月亮状态良好+{''.join(ben_in_asc)}在1宫", "effects": ["生活富足", "社会地位高", "受人尊敬"], "strength": ""})
# D3. Adhi Yoga(上方格局)
# 来源:BPHS。6/8/12 宫上方有吉星(6上方=12宫,8上方=2宫,12上方=11宫)
adhi_houses = {6: 12, 8: 2, 12: 11}
for dh, uh in adhi_houses.items():
ben_above = [p for p in benefics if p in planets and _house_of(p) == uh]
if ben_above:
yogas.append({"name": "Adhi Yoga", "name_cn": "上方格局", "combination": f"{''.join(ben_above)}在第{uh}宫({dh}宫上方)", "effects": ["权威地位", "领导能力", "受人尊敬"], "strength": "中强"})
break # 只报告一次
# D4. Chatussagara Yoga(四角充盈格局)
# 来源:BPHS。1/4/7/10 每个角宫都有至少一颗行星
kendra_occupied = all(len(_planets_in_house(h)) > 0 for h in [1, 4, 7, 10])
if kendra_occupied:
yogas.append({"name": "Chatussagara Yoga", "name_cn": "四角充盈格局", "combination": "1/4/7/10宫均有行星", "effects": ["生活圆满", "各方面均衡发展", "命运眷顾"], "strength": ""})
# D5. Virinchi Yoga(创造格局)
# 来源:BPHS。上升主在 Kendra/Trikona + 5宫主在 Kendra + 木星有力
if asc_lord in planets and _planet_in_kendra_or_trikona(asc_lord):
if lord_5 in planets and _is_kendra(_house_of(lord_5)):
if 'Jupiter' in planets and (_is_exalted('Jupiter') or _is_own_sign('Jupiter') or _house_of('Jupiter') in [1, 5, 9]):
yogas.append({"name": "Virinchi Yoga", "name_cn": "创造格局", "combination": "上升主有力+5宫主在角宫+木星有力", "effects": ["创造力强", "智慧卓越", "精神修养"], "strength": ""})
# D6. Veenaa Yoga(琴格局)
# 来源:BPHS。2/5/9宫有吉星 + 上升主有力(与 Sankhya Veena 不同)
ben_in_259 = [p for p in benefics if p in planets and _house_of(p) in [2, 5, 9]]
if len(ben_in_259) >= 2 and asc_lord in planets and (_is_exalted(asc_lord) or _is_own_sign(asc_lord)):
yogas.append({"name": "Veenaa Yoga (Artistic)", "name_cn": "艺术琴格局", "combination": f"{''.join(ben_in_259)}在2/5/9宫+上升主有力", "effects": ["艺术才华", "音乐天赋", "审美卓越"], "strength": ""})
# D7. Kalanidhi Yoga(艺藏格局)
# 来源:BPHS。木星在2/5宫 + 被金星/水星关联
if 'Jupiter' in planets and _house_of('Jupiter') in [2, 5]:
assoc = [p for p in ['Venus', 'Mercury'] if p in planets and (_house_of(p) == _house_of('Jupiter') or abs(_house_of(p) - _house_of('Jupiter')) in [3, 6, 9])]
if assoc:
yogas.append({"name": "Kalanidhi Yoga", "name_cn": "艺藏格局", "combination": f"木星在{_house_of('Jupiter')}宫+{''.join(assoc)}关联", "effects": ["艺术才华", "学识渊博", "受人尊敬"], "strength": ""})
# D8. Saubhagya Yoga(幸运格局)
# 来源:BPHS。上升主在 Kendra 且有力 + 月亮在吉宫(1/5/9/10/11
if asc_lord in planets and _is_kendra(_house_of(asc_lord)) and (_is_exalted(asc_lord) or _is_own_sign(asc_lord)):
if 'Moon' in planets and _house_of('Moon') in [1, 5, 9, 10, 11]:
yogas.append({"name": "Saubhagya Yoga", "name_cn": "幸运格局", "combination": f"上升主{asc_lord}有力在角宫+月亮在{_house_of('Moon')}", "effects": ["幸运眷顾", "生活幸福", "婚姻美满"], "strength": ""})
# D9. Shubha Yoga(吉星吉宫格局)
# 来源:BPHS。所有吉星都在 Kendra/Trikona
all_benefics_kt = all(_planet_in_kendra_or_trikona(p) for p in benefics if p in planets)
if all_benefics_kt and len([p for p in benefics if p in planets]) >= 3:
yogas.append({"name": "Shubha Yoga", "name_cn": "全吉星格局", "combination": "所有吉星在角宫或三方宫", "effects": ["生活顺遂", "福气满满", "受人爱戴"], "strength": ""})
# D10. Graha Yuddha(行星战争)
# 来源:经典文献。两个行星在同一星座内距离 < 1°
for i, (p1, info1) in enumerate(planets.items()):
for p2, info2 in list(planets.items())[i+1:]:
if info1.get("sign") == info2.get("sign") and info1.get("degree") is not None and info2.get("degree") is not None:
if abs(info1["degree"] - info2["degree"]) < 1.0:
yogas.append({"name": f"Graha Yuddha ({p1}-{p2})", "name_cn": f"行星战争({PLANET_CN.get(p1,p1)}-{PLANET_CN.get(p2,p2)})", "combination": f"{p1}{p2}{info1['sign']}内相距{abs(info1['degree']-info2['degree']):.2f}°", "effects": ["行星力量竞争", "相关领域有张力"], "strength": ""})
# D11. Gajakesari 扩展(木星在月亮的 Kendra)
# 来源:BPHS。从月亮所在宫算,木星在 Kendra
if 'Jupiter' in planets and 'Moon' in planets:
jh = _house_of('Jupiter')
mh = _house_of('Moon')
diff = (jh - mh) % 12
if diff in [0, 3, 6, 9]:
already_gk = any("Gajakesari" in y.get("name", "") for y in yogas)
if not already_gk:
yogas.append({"name": "Gajakesari Yoga (from Moon)", "name_cn": "象狮格局(从月亮)", "combination": f"木星在第{jh}宫,月亮在第{mh}宫(Kendra关系)", "effects": ["智慧学识", "财富名声", "道德品质"], "strength": ""})
# D12. Pushya Yoga(成长格局)
# 来源:BPHS。吉星在 8/9/12 宫
ben_in_8912 = [p for p in benefics if p in planets and _house_of(p) in [8, 9, 12]]
if len(ben_in_8912) >= 2:
yogas.append({"name": "Pushya Yoga", "name_cn": "成长格局", "combination": f"{''.join(ben_in_8912)}在8/9/12宫", "effects": ["精神成长", "灵性提升", "命运眷顾"], "strength": "中强"})
return {"ascendant": asc, "planets_analyzed": len(planets), "kendra_lords": kl, "trikona_lords": tl, "yogas_detected": len(yogas), "yogas": yogas, "detected_yogas": yogas}
# ============================================================================
@@ -1469,11 +858,20 @@ def cmd_varga(args):
def navamsa(lon):
si = int(lon / 30); d = lon - si * 30; ni = int(d / (30/9))
el_starts = {0: 0, 1: 9, 2: 6, 3: 3}; return SIGNS[(el_starts[si % 4] + ni) % 12]
# BPHS: movable(0,3,6,9)=same, fixed(1,4,7,10)=+4, dual(2,5,8,11)=+8
if si % 3 == 0: # movable: 0,3,6,9
start = si
elif si % 3 == 1: # fixed: 1,4,7,10
start = (si + 4) % 12
else: # dual: 2,5,8,11
start = (si + 8) % 12
return SIGNS[(start + ni) % 12]
def dasamsa(lon):
si = int(lon / 30); d = lon - si * 30; di = int(d / 3)
return SIGNS[di % 12] if si % 2 == 0 else SIGNS[(6 + di) % 12]
# BPHS: odd signs(0,2,4,6,8,10)=same, even signs(1,3,5,7,9,11)=+8 (9th)
start = si if si % 2 == 0 else (si + 8) % 12
return SIGNS[(start + di) % 12]
result = {"birth_info": f"{args.year}-{args.month:02d}-{args.day:02d} {args.hour:02d}:{args.minute:02d}", "divisional_charts": {}}
if args.d9 or args.all:
@@ -2672,10 +2070,17 @@ def cmd_audit(args):
yoga_planets = {}
for pname, pd in planets.items():
if isinstance(pd, dict) and 'sign' in pd and 'house' in pd:
yoga_planets[pname] = {'sign': pd['sign'], 'house': pd['house']}
yoga_planets[pname] = {
'sign': pd['sign'],
'house': pd['house'],
'degree': pd.get('degree_in_sign', pd.get('degree')),
}
yoga_result = cmd_yoga(type('Args', (), {
'ascendant': asc_sign,
'planets': ','.join([f"{k}:{v['sign']}:{v['house']}" for k, v in yoga_planets.items()])
'planets': ','.join([
f"{k}:{v['sign']}:{v['house']}" + (f":{v['degree']}" if v.get('degree') is not None else "")
for k, v in yoga_planets.items()
])
})())
report['audit']['P12_yogas'] = {
'count': yoga_result.get('yogas_detected', 0),
@@ -4316,14 +3721,29 @@ def main():
# 2. dasha
p = sub.add_parser('dasha', help='计算Dasha大运')
# Birth datetime args: required only when nakshatra/moon-lon not provided
p.add_argument('--year', type=int, required=False)
p.add_argument('--month', type=int, required=False)
p.add_argument('--day', type=int, required=False)
p.add_argument('--hour', type=int, required=False)
p.add_argument('--minute', type=int, required=False)
p.add_argument('--lat', type=float, required=False)
p.add_argument('--lon', type=float, required=False)
p.add_argument('--tz', type=float, default=0)
p.add_argument('--node-mode', default='mean', choices=['mean', 'true'])
p.add_argument('--nakshatra', default=None); p.add_argument('--pada', type=int, default=None)
p.add_argument('--moon-lon', type=float, default=None); p.add_argument('--birthdate', required=True)
p.add_argument('--moon-lon', type=float, default=None); p.add_argument('--birthdate', default=None)
p.add_argument('--today', default=None)
p.add_argument('--years', type=int, default=10)
# 3. yoga
p = sub.add_parser('yoga', help='Yoga格局识别')
_add_chart_args(p)
p.add_argument('--ascendant', default=None)
p.add_argument('--planets', default=None, help="格式: 'Sun:Aries:9,Moon:Aquarius:7,...'")
p.add_argument('--planets', default=None, help="格式: 'Sun:Aries:9[:10.5],Moon:Aquarius:7[:15.2],...';若提供则优先使用并忽略出生信息")
for action in p._actions:
if action.dest in ['year', 'month', 'day', 'hour', 'minute', 'lat', 'lon']:
action.required = False
# 4. predict
p = sub.add_parser('predict', help='三层验证法事件预测')