v6.2.0: 全面技法宝库升级 — 16个新模块 + 12个文件优化

## 新增模块 (16个)
### P0 精度修复
- ashtakavarga: calc_prastara_av() + calc_sodhita_av()
- kakshya.py: Kakshya评分系统 (8区间×3.75°)
- shadbala.py: Sputa Drishti + Yuddha Bala

### P1 核心升级
- bhava_bala.py: 宫位三元力量 (jyotishganit MIT)
- pancha_mahapurusha.py: PMC完整检测含4层失效条件
- sade_sati.py: Sade Sati+Kantaka Shani
- sudarshana_chakra.py: 三参考点盘+收敛分析
- tajika.py: Sahams 7→36 + Tajika Yogas 10种
- birth_time_rectifier.py: 生时矫正

### P2 覆盖扩展
- kp_system.py: KP Sublord+ABCD Significator (diliprk/VedicAstro MIT)
- synastry.py: 16因子合盘36分制 (dashaflow MIT)
- muhurtha_election.py: 6活动选举 (dashaflow MIT)
- career_analysis.py: 结构化事业引擎
- relationship_analysis.py: 结构化感情引擎
- conditional_dashas.py: Dwisaptati+Shattrimsa+Dwadashottari
- divisional_charts_extended: D81/D108/D144
- remedies.py: 5类补救系统

## 修改文件
jaimini/dasha_calculator/shadbala/SKILL.md/COVERAGE_AUDIT等12个

## 开源复用: 4个MIT项目
This commit is contained in:
732642856
2026-06-11 19:02:56 +08:00
parent c4c0eff67a
commit a4f8620a71
210 changed files with 49022 additions and 828 deletions
+205 -499
View File
@@ -1,529 +1,235 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
synastry.py — 合盘分析模块
Ashta Koota 36分制 + Mangal Dosha + Papasamya + Dasha兼容性
Synastry(合盘)模块 — 基于 dashaflow (MIT License) 核心算法
依赖: swisseph (可选,用于从出生数据直接计算)
16因子兼容性分析系统:
- Ashtakoot 8因子(36分制)
- 附加8 KutaMahendra/Stree Deergha/Vedha/Kuja Dosha/Rajju等)
"""
import math
from typing import Dict, List, Tuple, Any
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
# 27 Nakshatras: (name, lord, yoni)
NAK27 = [
("Ashwini",0),("Bharani",1),("Krittika",2),("Rohini",3),("Mrigashira",4),
("Ardra",5),("Punarvasu",6),("Pushya",7),("Ashlesha",8),("Magha",0),
("Purva Phalguni",1),("Uttara Phalguni",2),("Hasta",3),("Chitra",4),
("Swati",5),("Vishakha",6),("Anuradha",7),("Jyeshtha",8),("Mula",0),
("Purva Ashadha",1),("Uttara Ashadha",2),("Shravana",3),("Dhanishta",4),
("Shatabhisha",5),("Purva Bhadrapada",6),("Uttara Bhadrapada",7),("Revati",8),
]
# Gana: Deva(0)=0,1,6,7,14; Manushya(1)=2,4,8,10,12,15,18,20,22; Rakshasa(2)=3,5,9,11,13,16,17,19,21,23,24,25,26
GANA_MAP = [0,0,1,2,1,2,0,0,1,2,1,2,1,2,0,1,2,2,2,2,1,2,1,2,2,2,2]
# Yoni: 14种动物配对
YONI_MAP = [0,1,2,3,4,5,6,7,2,0,1,8,3,4,5,6,7,8,9,1,8,3,10,4,5,6,11]
YONI_NAME = ['Horse','Elephant','Sheep','Serpent','Dog','Cat','Rat','Cow','Buffalo',
'Tiger','Lion','Fish','?','?']
# Nadi: 3种 (Vata=0, Pitta=1, Kapha=2)
NADI_MAP = [0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2]
# Varna: 4种种姓 (Brahmin=3, Kshatriya=2, Vaishya=1, Shudra=0)
VARNA_MAP = [0,2,1,3,0,2,1,3,0,2,1,3,0,2,1,3,0,2,1,3,0,2,1,3,0,2,1]
# Vashya: sign-based
VASHYA_SIGN = [0,1,2,3,0,1,2,3,0,1,2,3] # 0=quadruped,1=biped,2=insect,3=water
# Dasha years for Vimshottari
DASHA_YEARS = {"Ketu":7,"Venus":20,"Sun":6,"Moon":10,"Mars":7,"Rahu":18,"Jupiter":16,"Saturn":19,"Mercury":17}
DASHA_ORDER = ["Ketu","Venus","Sun","Moon","Mars","Rahu","Jupiter","Saturn","Mercury"]
def _nak_idx(lon):
"""黄经→Nakshatra索引(0-26)"""
return int(lon / (360/27)) % 27
def _sign_idx(lon):
return int(lon / 30) % 12
# ============================================================================
# Ashta Koota 8维度评分
# ============================================================================
def _calc_varna(n1, n2):
"""Varna Koota (1分) - 精神兼容"""
v1, v2 = VARNA_MAP[n1], VARNA_MAP[n2]
if v2 >= v1: return {'score':1,'max':1,'v1':v1,'v2':v2,'note':'女方种姓≥男方,吉'}
return {'score':0,'max':1,'v1':v1,'v2':v2,'note':'女方种姓<男方,不吉'}
def _calc_vashya(s1, s2):
"""Vashya Koota (2分) - 控制力"""
v1, v2 = VASHYA_SIGN[s1], VASHYA_SIGN[s2]
if v1 == v2: return {'score':2,'max':2,'note':'同Vashya,满分'}
# 互补对
pairs = {(0,3),(3,0),(1,2),(2,1)}
if (v1,v2) in pairs: return {'score':1,'max':2,'note':'互补Vashya,半分'}
return {'score':0,'max':2,'note':'Vashya不合'}
def _calc_tara(n1, n2):
"""Tara Koota (3分) - 星宿距离吉凶"""
diff = (n2 - n1) % 9
auspicious = {0,2,4,6,8} # Janma,Sampat,Kshema,Pratyak,Sadhana → 2,4,6,8,0 mod9
# 好Tara: Sampat(2),Kshema(4),Sadhana(6),Priya(8)
good = {2,4,6,8}
if diff in good: return {'score':3,'max':3,'diff':diff,'note':'吉Tara'}
if diff == 0: return {'score':2,'max':3,'diff':diff,'note':'中性Tara(Janma)'}
return {'score':0,'max':3,'diff':diff,'note':'凶Tara'}
def _calc_yoni(n1, n2):
"""Yoni Koota (4分) - 生理兼容"""
y1, y2 = YONI_MAP[n1], YONI_MAP[n2]
if y1 == y2: return {'score':4,'max':4,'note':'同Yoni,满分'}
# 敌对Yoni
enemy = {(0,5),(5,0),(2,4),(4,2),(3,2),(2,3)}
if (y1,y2) in enemy: return {'score':0,'max':4,'note':'敌对Yoni'}
# 友好Yoni
friendly = {(0,7),(7,0),(1,8),(8,1),(6,9),(9,6)}
if (y1,y2) in friendly: return {'score':3,'max':4,'note':'友好Yoni'}
return {'score':1,'max':4,'note':'中性Yoni'}
def _calc_graha_maitri(lord1, lord2):
"""Graha Maitri Koota (5分) - 行星友谊"""
# 行星友谊表
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'],'Rahu':['Venus','Saturn'],
'Ketu':['Venus','Saturn']}
enemies = {'Sun':['Saturn','Venus'],'Moon':['Mercury'],'Mars':['Mercury','Saturn'],
'Mercury':['Mars','Jupiter','Saturn'],'Jupiter':['Mercury','Venus'],
'Venus':['Sun','Moon','Mars'],'Saturn':['Sun','Moon','Mars'],
'Rahu':['Sun','Moon','Mars'],'Ketu':['Sun','Moon','Mars']}
if lord1 == lord2: return {'score':5,'max':5,'note':'同主星,满分'}
l2_friends = friends.get(lord2,[])
l2_enemies = enemies.get(lord2,[])
if lord1 in l2_friends: return {'score':4,'max':5,'note':f'{lord1}{lord2}的友星'}
if lord1 in l2_enemies: return {'score':0,'max':5,'note':f'{lord1}{lord2}的敌星'}
return {'score':2,'max':5,'note':f'{lord1}{lord2}中性'}
def _calc_gana(n1, n2):
"""Gana Koota (6分) - 气质类型"""
g1, g2 = GANA_MAP[n1], GANA_MAP[n2]
gana_name = {0:'Deva(天神)',1:'Manushya(人类)',2:'Rakshasa(罗刹)'}
if g1 == g2: return {'score':6,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'同Gana,满分'}
# Deva+Manushya
combo = {g1,g2}
if combo == {0,1}: return {'score':5,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'天神+人类,良好'}
if combo == {1,2}: return {'score':2,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'人类+罗刹,较差'}
# Deva+Rakshasa
return {'score':1,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'天神+罗刹,很差'}
def _calc_bhakuta(n1, n2):
"""Bhakuta Koota (7分) - 情感+经济"""
diff = (n2 - n1) % 9
# 满分组合
full = {1,3,5,7}
if diff in full: return {'score':7,'max':7,'diff':diff,'note':'吉Bhakuta,满分'}
if diff == 0: return {'score':4,'max':7,'diff':diff,'note':'中性Bhakuta'}
return {'score':0,'max':7,'diff':diff,'note':'凶Bhakuta,零分'}
def _calc_nadi(n1, n2):
"""Nadi Koota (8分) - 生命力/健康"""
na1, na2 = NADI_MAP[n1], NADI_MAP[n2]
nadi_name = {0:'Vata(风)',1:'Pitta(胆汁)',2:'Kapha(粘液)'}
if na1 == na2:
return {'score':0,'max':8,'n1':nadi_name[na1],'n2':nadi_name[na2],
'note':'同Nadi=不吉(传统认为影响后代健康)'}
return {'score':8,'max':8,'n1':nadi_name[na1],'n2':nadi_name[na2],
'note':'不同Nadi=吉,互补生命力'}
# ============================================================================
# dashaflow MIT增强:8分制之外的附加Kuta
# ============================================================================
def _calc_mahendra(n1, n2):
"""Mahendra Kuta:男方星宿从女方星宿数起为4/7/10/13/16/19/22/25则吉。"""
count = ((n1 - n2) % 27) + 1
return {'result': 'good' if count in {4, 7, 10, 13, 16, 19, 22, 25} else 'bad', 'count': count}
def _calc_stree_deergha(n1, n2):
"""Stree Deergha:男方星宿从女方星宿数起至少9宿为吉。"""
count = ((n1 - n2) % 27) + 1
return {'result': 'good' if count >= 9 else 'bad', 'count': count}
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)
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']
def _calc_vedha(n1, n2):
"""Vedha Kuta:特定星宿对形成阻隔。"""
pair = tuple(sorted((n1, n2)))
bad = pair in {tuple(sorted(p)) for p in VEDHA_PAIRS}
return {'result': 'bad' if bad else 'good', 'pair': pair}
# =============================================================================
# 数据表 (来自 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},
"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': '头部Rajju:传统认为影响丈夫寿命',
'Kanta': '颈部Rajju:传统认为影响妻子寿命',
'Udara': '腹部Rajju:传统认为影响子女',
'Kati': '腰部Rajju:传统认为影响财务稳定',
'Pada': '足部Rajju:传统认为迁动奔波较多',
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'}}
def _rajju_group(n):
_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 n in indices:
return group
return None
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_rajju(n1, n2):
g1 = _rajju_group(n1)
g2 = _rajju_group(n2)
if g1 and g1 == g2:
return {'result': 'bad', 'group': g1, 'effect': RAJJU_EFFECTS.get(g1, '')}
return {'result': 'good', 'group1': g1, 'group2': g2, 'effect': ''}
def _calc_stree_deergha(m_nak, f_nak):
cnt = ((m_nak-f_nak)%27)+1
return "good" if cnt >= 9 else "bad"
# =============================================================================
# 主计算函数
# =============================================================================
def _calc_bad_constellations(n1, n2):
"""坏星宿简化检测:Moola/Ashlesha/Jyeshtha/Vishakha等传统敏感宿。"""
bad = []
names = {8: 'Ashlesha', 15: 'Vishakha', 17: 'Jyeshtha', 18: 'Moola'}
for label, n in [('person1', n1), ('person2', n2)]:
if n in names:
bad.append({'person': label, 'nakshatra': names[n]})
return {'result': 'bad' if bad else 'good', 'issues': 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: 女性星盘数据(可选)
def _calc_additional_kutas(n1, n2, base_scores):
"""复用dashaflow 16因子婚配思路,补充传统8分制以外的Kuta。"""
mahendra = _calc_mahendra(n1, n2)
stree = _calc_stree_deergha(n1, n2)
vedha = _calc_vedha(n1, n2)
rajju = _calc_rajju(n1, n2)
bad_const = _calc_bad_constellations(n1, n2)
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 base_scores.get('Nadi(生命力)', {}).get('score') == 0 and base_scores.get('Bhakuta(情感经济)', {}).get('score', 0) > 0 and rajju['result'] == 'good':
exceptions.append('Nadi Dosha因Bhakuta与Rajju良好而减弱')
if rajju['result'] == 'bad' and base_scores.get('Graha Maitri(行星友谊)', {}).get('score', 0) >= 4 and base_scores.get('Tara(星宿距离)', {}).get('score', 0) >= 1.5:
exceptions.append('Rajju Dosha因Graha Maitri与Tara较好而减弱')
return {
'method': 'dashaflow MIT adapted additional kutas',
'Mahendra': mahendra,
'StreeDeergha': stree,
'Vedha': vedha,
'Rajju': rajju,
'BadConstellations': bad_const,
'exceptions': 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")
# ============================================================================
# 合盘主函数
# ============================================================================
def calc_synastry(person1: dict, person2: dict) -> dict:
"""
计算两人的完整合盘分析
person1/person2 格式:
{
'moon_lon': float, # 月亮黄经(恒星黄道)
'mars_lon': float, # 火星黄经(恒星黄道) (可选)
'asc_lon': float, # 上升黄经(恒星黄道) (可选)
'nakshatra_lord': str, # 月亮Nakshatra主星 (可选,自动计算)
'gender': str, # 'M' or 'F' (影响Varna方向)
}
返回: 完整合盘报告 dict
"""
m1 = person1.get('moon_lon', 0)
m2 = person2.get('moon_lon', 0)
n1 = _nak_idx(m1)
n2 = _nak_idx(m2)
s1 = _sign_idx(m1)
s2 = _sign_idx(m2)
# Nakshatra主星
lords = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
lord1 = person1.get('nakshatra_lord') or lords[n1 % 9]
lord2 = person2.get('nakshatra_lord') or lords[n2 % 9]
# Ashta Koota 8维度
varna = _calc_varna(n1, n2)
vashya = _calc_vashya(s1, s2)
tara = _calc_tara(n1, n2)
yoni = _calc_yoni(n1, n2)
graha = _calc_graha_maitri(lord1, lord2)
gana = _calc_gana(n1, n2)
bhakuta = _calc_bhakuta(n1, n2)
nadi = _calc_nadi(n1, n2)
kootas = [
('Varna(精神)', varna), ('Vashya(控制)', vashya), ('Tara(星宿距离)', tara),
('Yoni(生理)', yoni), ('Graha Maitri(行星友谊)', graha),
('Gana(气质)', gana), ('Bhakuta(情感经济)', bhakuta), ('Nadi(生命力)', nadi),
]
koota_map = {k: v for k, v in kootas}
additional_kutas = _calc_additional_kutas(n1, n2, koota_map)
total = sum(k[1]['score'] for k in kootas)
max_total = sum(k[1]['max'] for k in kootas)
pct = round(total / max_total * 100, 1) if max_total else 0
# 综合判定
verdict = _compatibility_verdict(total, pct)
# Mangal Dosha 检查
dosha1 = _check_mangal_dosha(person1)
dosha2 = _check_mangal_dosha(person2)
mangal_note = _dosha_match(dosha1, dosha2)
# Papasamya (双方凶星抵消度)
papasamya = _calc_papasamya(person1, person2)
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 {
'method': 'Ashta Koota 36分制 + Mangal Dosha + Papasamya + dashaflow附加Kuta',
'version': '3.8-dashaflow-mit-adapted',
'person1': {'moon_nakshatra': NAK27[n1][0], 'nak_idx': n1, 'moon_sign': SIGNS[s1]},
'person2': {'moon_nakshatra': NAK27[n2][0], 'nak_idx': n2, 'moon_sign': SIGNS[s2]},
'kootas': koota_map,
'additional_kutas': additional_kutas,
'total_score': total,
'max_score': max_total,
'percentage': pct,
'verdict': verdict,
'mangal_dosha': {'person1': dosha1, 'person2': dosha2, 'compatibility': mangal_note},
'papasamya': papasamya,
'recommendations': _generate_recommendations(total, pct, kootas, mangal_note),
"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 _compatibility_verdict(score, pct):
"""综合判定"""
if pct >= 75: return {'level':'极佳','color':'绿色','cn':'强烈推荐,天然契合'}
if pct >= 60: return {'level':'良好','color':'蓝绿','cn':'推荐,小问题可化解'}
if pct >= 45: return {'level':'一般','color':'黄色','cn':'需要努力调和'}
if pct >= 30: return {'level':'较差','color':'橙色','cn':'不推荐,阻力较大'}
return {'level':'很差','color':'红色','cn':'强烈不推荐,冲突严重'}
def _check_mangal_dosha(person):
"""Mangal Dosha(火星凶相)检查"""
mars_lon = person.get('mars_lon')
if mars_lon is None:
return {'has_dosha': None, 'note': '缺少火星数据'}
mars_house = person.get('mars_house')
mars_sign = _sign_idx(mars_lon) if mars_house is None else None
# 需要计算火星在第几宫(相对于上升)
asc_lon = person.get('asc_lon')
if asc_lon is not None and mars_house is None:
asc_si = _sign_idx(asc_lon)
mars_si = _sign_idx(mars_lon)
mars_house = ((mars_si - asc_si) % 12) + 1
if mars_house is None:
return {'has_dosha': None, 'note': '缺少上升数据,无法确定宫位'}
# Mangal Dosha: 火星在1/2/4/7/8/12宫
dosha_houses = {1, 2, 4, 7, 8, 12}
has = mars_house in dosha_houses
# 强度分级
severity = ''
if has:
if mars_house in {7, 8}:
severity = ''
elif mars_house in {1, 2}:
severity = ''
else:
severity = ''
return {
'has_dosha': has,
'severity': severity if has else '',
'mars_house': mars_house,
'note': f'火星在第{mars_house}' + ('→有Mangal Dosha' if has else '→无Mangal Dosha'),
}
def _dosha_match(dosha1, dosha2):
"""双方Mangal Dosha匹配"""
d1 = dosha1.get('has_dosha')
d2 = dosha2.get('has_dosha')
if d1 is None or d2 is None:
return {'status':'数据不足','note':'缺少一方火星数据'}
if d1 and d2:
return {'status':'双Dosha匹配','note':'双方都有Mangal Dosha,互相抵消→可接受'}
if d1 and not d2:
return {'status':'仅一方Dosha','note':'仅Person1有DoshaPerson2需有足够力量承受'}
if not d1 and d2:
return {'status':'仅一方Dosha','note':'仅Person2有DoshaPerson1需有足够力量承受'}
return {'status':'双方无Dosha','note':'双方均无Mangal Dosha→理想'}
def _calc_papasamya(p1, p2):
"""Papasamya - 双方凶星点数抵消"""
# 简化版:检查太阳/火星/土星/北交点在凶宫(6/8/12)的情况
def _count_papa(person):
count = 0
for key in ['sun_house','mars_house','saturn_house','rahu_house']:
h = person.get(key)
if h and h in {6, 8, 12}:
count += 1
return count
c1 = _count_papa(p1)
c2 = _count_papa(p2)
diff = abs(c1 - c2)
if diff == 0:
balance = '均衡'
note = '双方凶星负担相当,Papasamya良好'
elif diff == 1:
balance = '轻微失衡'
note = '一方凶星负担略重,可接受'
else:
balance = '明显失衡'
note = '双方凶星负担差异较大,需注意'
return {'person1_papa': c1, 'person2_papa': c2, 'difference': diff,
'balance': balance, 'note': note}
def _generate_recommendations(score, pct, kootas, mangal_note):
"""生成建议"""
recs = []
# 低分维度警告
for name, data in kootas:
if data['score'] < data['max'] * 0.3:
recs.append(f"{name}得分低({data['score']}/{data['max']}){data['note']}")
# Nadi特别警告
nadi_score = kootas[7][1]['score'] # Nadi是第8个
if nadi_score == 0:
recs.append("🔴 Nadi Koota零分!传统认为同Nadi婚姻可能影响后代健康,建议咨询专业占星师")
recs.append("化解方案:Nadi Dosha Pooja、佩戴对应宝石、或寻找Nadi不同的伴侣")
# Mangal Dosha建议
if mangal_note.get('status') == '仅一方Dosha':
recs.append("⚠ 一方有Mangal Dosha:建议找有Dosha的伴侣匹配,或进行Mangal Dosha化解仪式")
recs.append("化解方案:Kumbh Vivah(与陶罐结婚仪式)、在28岁后结婚、或匹配有Dosha的对象")
# 总分建议
if pct >= 60:
recs.append(f"✅ 总分{pct}%,在可接受范围内。建议深入了解彼此价值观和生活目标")
elif pct >= 45:
recs.append(f"⚠ 总分{pct}%,需要双方更多努力和理解。建议进行详细的合盘咨询")
else:
recs.append(f"❌ 总分{pct}%,传统认为阻力较大。强烈建议咨询专业占星师后再做决定")
return recs
# ============================================================================
# Dasha 兼容性分析
# ============================================================================
def calc_dasha_compatibility(dasha1: list, dasha2: list, start_year: int) -> dict:
"""
分析两人Dasha时间线的兼容性
dasha1/dasha2: [{'lord':'Venus','start':'1990-01-01','end':'2010-01-01','years':20}, ...]
start_year: 分析起始年份
"""
from datetime import datetime
periods = []
conflicts = []
harmonies = []
# 行星兼容性
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'],'Rahu':['Venus','Saturn'],'Ketu':['Venus','Saturn']}
for d1 in dasha1:
for d2 in dasha2:
# 检查时间重叠
try:
s1 = datetime.strptime(d1['start'],'%Y-%m-%d')
e1 = datetime.strptime(d1['end'],'%Y-%m-%d')
s2 = datetime.strptime(d2['start'],'%Y-%m-%d')
e2 = datetime.strptime(d2['end'],'%Y-%m-%d')
except:
continue
overlap_start = max(s1, s2)
overlap_end = min(e1, e2)
if overlap_start >= overlap_end:
continue
if overlap_start.year < start_year:
continue
lord1 = d1.get('lord','')
lord2 = d2.get('lord','')
# 判断关系
l2_friends = friends.get(lord2, [])
rel = '中性'
if lord1 == lord2:
rel = '同步'
elif lord1 in l2_friends:
rel = '友好'
elif lord1 in ['Rahu','Ketu'] or lord2 in ['Rahu','Ketu']:
rel = '变化'
else:
rel = '紧张'
period_info = {
'period': f"{overlap_start.strftime('%Y-%m')} ~ {overlap_end.strftime('%Y-%m')}",
'person1_dasha': lord1,
'person2_dasha': lord2,
'relationship': rel,
}
periods.append(period_info)
if rel in ('紧张','变化'):
conflicts.append(period_info)
elif rel in ('同步','友好'):
harmonies.append(period_info)
return {
'method': 'Dasha时间线兼容性',
'periods_analyzed': len(periods),
'conflicts': conflicts[:5],
'harmonies': harmonies[:5],
'conflict_count': len(conflicts),
'harmony_count': len(harmonies),
'assessment': '和谐' if len(harmonies) > len(conflicts) * 1.5 else
('一般' if len(harmonies) >= len(conflicts) else '紧张'),
}
# ============================================================================
# CLI 入口
# ============================================================================
if __name__ == '__main__':
import json
import sys
# 示例用法
if len(sys.argv) > 1 and sys.argv[1] == 'test':
p1 = {'moon_lon': 45.5, 'mars_lon': 120.3, 'asc_lon': 150.0, 'gender': 'M'}
p2 = {'moon_lon': 200.7, 'mars_lon': 30.1, 'asc_lon': 60.0, 'gender': 'F'}
result = calc_synastry(p1, p2)
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
print("Usage: python synastry.py test")
print(" test - 运行示例测试")