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:
+205
-499
@@ -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 Kuta(Mahendra/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有Dosha,Person2需有足够力量承受'}
|
||||
if not d1 and d2:
|
||||
return {'status':'仅一方Dosha','note':'仅Person2有Dosha,Person1需有足够力量承受'}
|
||||
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 - 运行示例测试")
|
||||
|
||||
Reference in New Issue
Block a user