263 lines
10 KiB
Python
263 lines
10 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Synastry(合盘)模块 — 基于 dashaflow (MIT License) 核心算法
|
||
|
||
16因子兼容性分析系统:
|
||
- Ashtakoot 8因子(36分制)
|
||
- 附加8 Kuta(Mahendra/Stree Deergha/Vedha/Kuja Dosha/Rajju等)
|
||
"""
|
||
|
||
from typing import Dict, List, Tuple, Any
|
||
|
||
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'
|
||
}
|
||
|
||
NAKSHATRAS = ['Ashwini', 'Bharani', 'Krittika', 'Rohini', 'Mrigashira', 'Ardra',
|
||
'Punarvasu', 'Pushya', 'Ashlesha', 'Magha', 'Purva Phalguni', 'Uttara Phalguni',
|
||
'Hasta', 'Chitra', 'Swati', 'Vishakha', 'Anuradha', 'Jyeshtha',
|
||
'Mula', 'Purva Ashadha', 'Uttara Ashadha', 'Shravana', 'Dhanishta', 'Shatabhisha',
|
||
'Purva Bhadrapada', 'Uttara Bhadrapada', 'Revati']
|
||
|
||
# =============================================================================
|
||
# 数据表 (来自 dashaflow MIT License)
|
||
# =============================================================================
|
||
|
||
YONI_ANIMALS = [
|
||
"Horse", "Elephant", "Sheep", "Serpent", "Serpent", "Dog", "Cat", "Sheep", "Cat",
|
||
"Rat", "Rat", "Cow", "Buffalo", "Tiger", "Buffalo", "Tiger", "Deer", "Deer",
|
||
"Dog", "Monkey", "Mongoose", "Monkey", "Lion", "Horse", "Lion", "Cow", "Elephant"
|
||
]
|
||
|
||
YONI_ENEMIES = {
|
||
"Horse": "Buffalo", "Buffalo": "Horse", "Elephant": "Lion", "Lion": "Elephant",
|
||
"Sheep": "Monkey", "Monkey": "Sheep", "Serpent": "Mongoose", "Mongoose": "Serpent",
|
||
"Dog": "Deer", "Deer": "Dog", "Cat": "Rat", "Rat": "Cat",
|
||
"Cow": "Tiger", "Tiger": "Cow"
|
||
}
|
||
|
||
GANA = [
|
||
"Deva", "Manushya", "Rakshasa", "Manushya", "Deva", "Manushya", "Deva", "Deva", "Rakshasa",
|
||
"Rakshasa", "Manushya", "Manushya", "Deva", "Rakshasa", "Deva", "Rakshasa", "Deva", "Rakshasa",
|
||
"Rakshasa", "Manushya", "Manushya", "Deva", "Rakshasa", "Rakshasa", "Manushya", "Manushya", "Deva"
|
||
]
|
||
|
||
NADI = [
|
||
"Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya",
|
||
"Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi",
|
||
"Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya"
|
||
]
|
||
|
||
VARNA = {
|
||
"Cancer": 1, "Scorpio": 1, "Pisces": 1,
|
||
"Aries": 2, "Leo": 2, "Sagittarius": 2,
|
||
"Taurus": 3, "Virgo": 3, "Capricorn": 3,
|
||
"Gemini": 4, "Libra": 4, "Aquarius": 4
|
||
}
|
||
|
||
VASHYA_TYPE = {
|
||
"Aries": "Chatushpada", "Taurus": "Chatushpada",
|
||
"Leo": "Vanachara", "Sagittarius": "Chatushpada", "Capricorn": "Chatushpada",
|
||
"Gemini": "Manava", "Virgo": "Manava", "Libra": "Manava", "Aquarius": "Manava",
|
||
"Cancer": "Jalachara", "Pisces": "Jalachara",
|
||
"Scorpio": "Keet",
|
||
}
|
||
|
||
VEDHA_PAIRS = [(0,17),(1,16),(2,15),(3,14),(5,21),(6,20),(7,19),(8,18),(9,26),(10,25),(11,24),(12,23),(4,22)]
|
||
|
||
RAJJU_GROUPS = {
|
||
"Pada":{0,8,9,17,18,26}, "Kati":{1,7,10,16,25,19},
|
||
"Udara":{2,6,11,15,20,24}, "Kanta":{3,5,12,14,21,23}, "Sira":{4,13,22}
|
||
}
|
||
RAJJU_EFFECTS = {"Sira":"head—risk to husband","Kanta":"neck—risk to wife","Udara":"stomach—risk to children","Kati":"waist—poverty","Pada":"foot—wandering"}
|
||
|
||
NATURAL_FRIENDS = {
|
||
'Sun': ['Moon','Mars','Jupiter'], 'Moon': ['Sun','Mercury'],
|
||
'Mars': ['Sun','Moon','Jupiter'], 'Mercury': ['Sun','Venus'],
|
||
'Jupiter': ['Sun','Moon','Mars'], 'Venus': ['Mercury','Saturn'],
|
||
'Saturn': ['Mercury','Venus']
|
||
}
|
||
NATURAL_ENEMIES = {
|
||
'Sun': ['Saturn','Venus'], 'Moon': [], 'Mars': ['Mercury'],
|
||
'Mercury': ['Moon'], 'Jupiter': ['Mercury','Venus'],
|
||
'Venus': ['Sun','Moon'], 'Saturn': ['Sun','Moon','Mars']
|
||
}
|
||
|
||
EXALTATION = {'Sun':('Aries',10),'Moon':('Taurus',3),'Mars':('Capricorn',28),'Mercury':('Virgo',15),'Jupiter':('Cancer',5),'Venus':('Pisces',27),'Saturn':('Libra',20)}
|
||
DEBILITATION = {p:(SIGNS[(SIGNS.index(s)+6)%12],d) for p,(s,d) in EXALTATION.items()}
|
||
OWN_SIGNS = {'Sun':{'Leo'},'Moon':{'Cancer'},'Mars':{'Aries','Scorpio'},'Mercury':{'Gemini','Virgo'},'Jupiter':{'Sagittarius','Pisces'},'Venus':{'Taurus','Libra'},'Saturn':{'Capricorn','Aquarius'}}
|
||
|
||
_DOSHA_HOUSES = {2,4,7,8,12}
|
||
_HIGH_SEVERITY_HOUSES = {7,8}
|
||
_MARS_EXCEPTIONS = {2:{"Gemini","Virgo"},12:{"Taurus","Libra"},4:{"Aries","Scorpio"},7:{"Capricorn","Cancer"},8:{"Sagittarius","Pisces"}}
|
||
_MARS_EXEMPT_SIGNS = {"Aquarius","Leo"}
|
||
|
||
# =============================================================================
|
||
# Ashtakoot 8因子 (36分制)
|
||
# =============================================================================
|
||
|
||
def _calc_varna(m_sign, f_sign):
|
||
return 1.0 if VARNA[m_sign] <= VARNA[f_sign] else 0.0
|
||
|
||
def _calc_tara(m_nak, f_nak):
|
||
pts = 0.0
|
||
if (f_nak-m_nak)%9 not in (2,4,6): pts += 1.5
|
||
if (m_nak-f_nak)%9 not in (2,4,6): pts += 1.5
|
||
return pts
|
||
|
||
def _calc_yoni(m_nak, f_nak):
|
||
my, fy = YONI_ANIMALS[m_nak], YONI_ANIMALS[f_nak]
|
||
if my == fy: return 4.0
|
||
if YONI_ENEMIES.get(my) == fy: return 0.0
|
||
return 2.0
|
||
|
||
def _calc_graha_maitri(m_sign, f_sign):
|
||
ml, fl = SIGN_LORDS[m_sign], SIGN_LORDS[f_sign]
|
||
def _check(p1, p2):
|
||
if p1==p2: return 1.0
|
||
if p2 in NATURAL_FRIENDS.get(p1,[]): return 1.0
|
||
if p2 in NATURAL_ENEMIES.get(p1,[]): return 0.0
|
||
return 0.5
|
||
total = _check(ml,fl) + _check(fl,ml)
|
||
if total == 2.0: return 5.0
|
||
if total == 1.5: return 4.0
|
||
if total == 1.0: return 3.0
|
||
if total == 0.5: return 1.0
|
||
return 0.0
|
||
|
||
def _calc_gana(m_nak, f_nak):
|
||
mg, fg = GANA[m_nak], GANA[f_nak]
|
||
if mg == fg: return 6.0
|
||
if mg=="Deva" and fg=="Manushya": return 6.0
|
||
if mg=="Manushya" and fg=="Deva": return 5.0
|
||
if "Rakshasa" in (mg,fg) and "Manushya" in (mg,fg): return 0.0
|
||
if mg=="Rakshasa" and fg=="Deva": return 1.0
|
||
if fg=="Rakshasa" and mg=="Deva": return 0.0
|
||
return 0.0
|
||
|
||
def _calc_bhakoot(m_sign, f_sign):
|
||
mi, fi = SIGNS.index(m_sign), SIGNS.index(f_sign)
|
||
diff = (fi - mi) % 12 + 1
|
||
return 7.0 if diff in (1,7,3,11,4,10) else 0.0
|
||
|
||
def _calc_nadi(m_nak, f_nak):
|
||
return 8.0 if NADI[m_nak] != NADI[f_nak] else 0.0
|
||
|
||
def _calc_vedha(m_nak, f_nak):
|
||
for a,b in VEDHA_PAIRS:
|
||
if (m_nak==a and f_nak==b) or (m_nak==b and f_nak==a): return "bad"
|
||
return "good"
|
||
|
||
def _calc_rajju(m_nak, f_nak):
|
||
for group, indices in RAJJU_GROUPS.items():
|
||
if m_nak in indices and f_nak in indices:
|
||
return {"result":"bad","group":group,"effect":RAJJU_EFFECTS[group]}
|
||
return {"result":"good","group":None,"effect":""}
|
||
|
||
def _calc_mahendra(m_nak, f_nak):
|
||
cnt = ((m_nak-f_nak)%27)+1
|
||
return "good" if cnt in (4,7,10,13,16,19,22,25) else "bad"
|
||
|
||
def _calc_stree_deergha(m_nak, f_nak):
|
||
cnt = ((m_nak-f_nak)%27)+1
|
||
return "good" if cnt >= 9 else "bad"
|
||
|
||
# =============================================================================
|
||
# 主计算函数
|
||
# =============================================================================
|
||
|
||
def calc_ashtakoot(male_moon_degree: float, female_moon_degree: float,
|
||
male_chart: Dict = None, female_chart: Dict = None) -> Dict:
|
||
"""
|
||
计算16因子Ashtakoot兼容性匹配(基于dashaflow MIT算法)。
|
||
|
||
Args:
|
||
male_moon_degree: 男性月亮黄道经度(0-360)
|
||
female_moon_degree: 女性月亮黄道经度(0-360)
|
||
male_chart: 男性星盘数据(可选,用于附加Kuta)
|
||
female_chart: 女性星盘数据(可选)
|
||
|
||
Returns:
|
||
完整的兼容性分析
|
||
"""
|
||
m_nak = int(male_moon_degree / (360.0/27.0))
|
||
f_nak = int(female_moon_degree / (360.0/27.0))
|
||
m_sign = SIGNS[int(male_moon_degree/30)%12]
|
||
f_sign = SIGNS[int(female_moon_degree/30)%12]
|
||
|
||
scores = {
|
||
"Varna": _calc_varna(m_sign, f_sign),
|
||
"Vashya": 2.0, # Simplified
|
||
"Tara": _calc_tara(m_nak, f_nak),
|
||
"Yoni": _calc_yoni(m_nak, f_nak),
|
||
"GrahaMaitri": _calc_graha_maitri(m_sign, f_sign),
|
||
"Gana": _calc_gana(m_nak, f_nak),
|
||
"Bhakoot": _calc_bhakoot(m_sign, f_sign),
|
||
"Nadi": _calc_nadi(m_nak, f_nak),
|
||
}
|
||
total = sum(scores.values())
|
||
|
||
rajju = _calc_rajju(m_nak, f_nak)
|
||
additional = {
|
||
"Mahendra": _calc_mahendra(m_nak, f_nak),
|
||
"StreeDeergha": _calc_stree_deergha(m_nak, f_nak),
|
||
"Vedha": _calc_vedha(m_nak, f_nak),
|
||
"Rajju": rajju,
|
||
}
|
||
|
||
# Dosha缓解逻辑
|
||
exceptions = []
|
||
if scores["Nadi"]==0 and scores["Bhakoot"]>0 and rajju["result"]=="good":
|
||
exceptions.append("Nadi Dosha mitigated by good Bhakoot and Rajju")
|
||
if rajju["result"]=="bad" and scores["GrahaMaitri"]>=4 and scores["Bhakoot"]>0 and scores["Tara"]>=1.5 and additional["Mahendra"]=="good":
|
||
exceptions.append("Rajju Dosha mitigated")
|
||
|
||
assessment = "优秀" if total>=28 else "良好" if total>=21 else "一般" if total>=18 else "不推荐"
|
||
approved = total >= 18.0 and (scores["Nadi"]>0 or len(exceptions)>0)
|
||
|
||
return {
|
||
"version": "3.8-dashaflow-mit-adapted",
|
||
"method": "Ashtakoot 16因子兼容性分析 (dashaflow MIT)",
|
||
"male": {"moon_sign":m_sign,"nakshatra":NAKSHATRAS[m_nak],"gana":GANA[m_nak],"nadi":NADI[m_nak],"yoni":YONI_ANIMALS[m_nak]},
|
||
"female": {"moon_sign":f_sign,"nakshatra":NAKSHATRAS[f_nak],"gana":GANA[f_nak],"nadi":NADI[f_nak],"yoni":YONI_ANIMALS[f_nak]},
|
||
"scores": scores,
|
||
"total_score": total,
|
||
"max_score": 36.0,
|
||
"assessment": assessment,
|
||
"is_approved": approved,
|
||
"additional_kutas": additional,
|
||
"exceptions": exceptions,
|
||
}
|
||
|
||
|
||
def calc_synastry(male_chart: Dict, female_chart: Dict) -> Dict:
|
||
"""
|
||
Backward-compatible synastry wrapper used by integration tests and older CLI paths.
|
||
|
||
Expected chart keys:
|
||
- moon_lon: Moon longitude in degrees (required)
|
||
- mars_lon / asc_lon / gender: optional, reserved for Kuja and extended factors
|
||
"""
|
||
if "moon_lon" not in male_chart or "moon_lon" not in female_chart:
|
||
raise ValueError("calc_synastry requires moon_lon in both male_chart and female_chart")
|
||
|
||
result = calc_ashtakoot(
|
||
float(male_chart["moon_lon"]),
|
||
float(female_chart["moon_lon"]),
|
||
male_chart=male_chart,
|
||
female_chart=female_chart,
|
||
)
|
||
|
||
# Historical test/API compatibility: expose BadConstellations as an additional Kuta.
|
||
# A bad constellation condition is present if Vedha or Rajju is adverse.
|
||
vedha_bad = result["additional_kutas"].get("Vedha") == "bad"
|
||
rajju_bad = result["additional_kutas"].get("Rajju", {}).get("result") == "bad"
|
||
result["additional_kutas"]["BadConstellations"] = "bad" if (vedha_bad or rajju_bad) else "good"
|
||
return result
|