v6.1.6: Add five-system dasha convergence

This commit is contained in:
732642856
2026-06-07 20:16:47 +08:00
parent 067b400625
commit 57e426515d
8 changed files with 402 additions and 196 deletions
+15 -6
View File
@@ -174,17 +174,26 @@ def calculate_ashtottari_dasha(birth_info: dict) -> dict:
major_periods = _build_major_periods(start_lord_idx, birth_dt)
# Determine current period
# Determine current period. Major periods repeat every 108 years; older natives
# should still return a current period instead of None after the first cycle.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % TOTAL_CYCLE
cumulative = 0.0
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // TOTAL_CYCLE) + 1
break
cumulative += years
return {
"applicable": True,
+195 -35
View File
@@ -2506,9 +2506,9 @@ def _calc_transit_multi_reference(planets, asc_idx, asc_deg, planet_lons, transi
# ============================================================================
# Dasa Convergence 系统交叉验证(v4.5.0 P1补齐
# Dasa Convergence 系统交叉验证(v6.1.6
# 基于 dasa-convergence-methodology.md
# 系统:Vimshottari + Chara Dasha + Yogini
# 系统:Vimshottari + Chara Dasha + Yogini + Ashtottari + Kalachakra
# ============================================================================
# Yogini Dasha 常量
YOGINI_ORDER = ['Mangala', 'Pingala', 'Dhanya', 'Bhramari', 'Bhadrika', 'Ulka', 'Siddha', 'Sankata']
@@ -2589,10 +2589,11 @@ def _calc_yogini_dasha(moon_lon, birthdate_str):
}
def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, planet_lons, asc_idx):
def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, planet_lons, asc_idx, ashtottari_result=None, kalachakra_result=None):
"""
⭐ v4.5.0: Dasa Convergence 系统交叉验证
Vimshottari + Chara Dasha + Yogini 三系统同时激活同一生活领域时,概率大幅提升
⭐ v6.1.6: Dasa Convergence 系统交叉验证
Vimshottari + Chara Dasha + Yogini + Ashtottari + Kalachakra 同时激活同一生活领域时,概率大幅提升
Chara Dasha 当前仍按 skill 规范降级为 partial,只作为低权重辅助。
"""
# 提取各系统当前周期
convergence_data = {'systems': {}}
@@ -2628,11 +2629,33 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
# 系统3: Yogini
if isinstance(yogini_result, dict):
cur_yog = yogini_result.get('current_yogini', {})
cur_yog = yogini_result.get('current_yogini') or yogini_result.get('current') or {}
convergence_data['systems']['yogini'] = {
'yogini': cur_yog.get('yogini') if isinstance(cur_yog, dict) else None,
'years': cur_yog.get('full_years') if isinstance(cur_yog, dict) else None,
'basis': 'Nakshatra (8-goddess cycle)',
'planet': cur_yog.get('planet') if isinstance(cur_yog, dict) else None,
'years': cur_yog.get('full_years', cur_yog.get('years')) if isinstance(cur_yog, dict) else None,
'basis': 'Nakshatra/Lagna 36-year cycle',
}
# 系统4: Ashtottari Dasha(条件性)
if isinstance(ashtottari_result, dict):
cur_ash = ashtottari_result.get('current') or {}
convergence_data['systems']['ashtottari'] = {
'applicable': ashtottari_result.get('applicable', True),
'planet': cur_ash.get('planet') if isinstance(cur_ash, dict) else None,
'years': cur_ash.get('years') if isinstance(cur_ash, dict) else None,
'basis': 'Conditional Nakshatra/Paksha 108-year cycle',
}
# 系统5: Kalachakra Dasha
if isinstance(kalachakra_result, dict):
cur_kal = kalachakra_result.get('current') or {}
convergence_data['systems']['kalachakra'] = {
'mode': kalachakra_result.get('mode'),
'lord': cur_kal.get('lord') if isinstance(cur_kal, dict) else None,
'rashi': cur_kal.get('rashi') if isinstance(cur_kal, dict) else None,
'years': cur_kal.get('years') if isinstance(cur_kal, dict) else None,
'basis': 'Moon Nakshatra Pada / Rashi-year cycle',
}
# 宫位主题映射
@@ -2691,6 +2714,73 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
'reason': f'Chara大运星座{cd_sign}{house}',
})
# Yogini / Ashtottari: 当前行星是否掌管或落入该宫
for system_key, system_label, planet_key in [
('yogini', 'Yogini', 'planet'),
('ashtottari', 'Ashtottari', 'planet'),
]:
sys_data = convergence_data['systems'].get(system_key, {})
planet = sys_data.get(planet_key)
if not planet or not isinstance(planet, str):
continue
target_sign_idx = (asc_idx + house - 1) % 12
target_sign = SIGNS[target_sign_idx]
target_lord = SIGN_LORDS.get(target_sign, '')
if planet == target_lord:
activations.append({
'system': system_label,
'level': 'maha',
'planet': planet,
'reason': f'{system_label}当前主星{planet}{house}宫({target_sign})的宫主星',
})
if planet in planet_lons:
p_sign_idx = int(planet_lons.get(planet, 0) / 30) % 12
p_house = ((p_sign_idx - asc_idx) % 12) + 1
if p_house == house:
activations.append({
'system': system_label,
'level': 'maha',
'planet': planet,
'reason': f'{system_label}当前主星{planet}落在{house}',
})
# Kalachakra: 当前 Rashi 是否关联该宫,当前 lord 是否掌管或落入该宫
kal = convergence_data['systems'].get('kalachakra', {})
if kal:
kal_rashi = kal.get('rashi')
if kal_rashi in SIGNS:
kal_sign_idx = SIGNS.index(kal_rashi)
kal_house_from_asc = ((kal_sign_idx - asc_idx) % 12) + 1
if kal_house_from_asc == house:
activations.append({
'system': 'Kalachakra',
'level': 'maha_rashi',
'sign': kal_rashi,
'reason': f'Kalachakra当前推运星座{kal_rashi}{house}',
})
kal_lord = kal.get('lord')
if kal_lord and isinstance(kal_lord, str):
target_sign_idx = (asc_idx + house - 1) % 12
target_sign = SIGNS[target_sign_idx]
target_lord = SIGN_LORDS.get(target_sign, '')
if kal_lord == target_lord:
activations.append({
'system': 'Kalachakra',
'level': 'maha_lord',
'planet': kal_lord,
'reason': f'Kalachakra当前主星{kal_lord}{house}宫({target_sign})的宫主星',
})
if kal_lord in planet_lons:
p_sign_idx = int(planet_lons.get(kal_lord, 0) / 30) % 12
p_house = ((p_sign_idx - asc_idx) % 12) + 1
if p_house == house:
activations.append({
'system': 'Kalachakra',
'level': 'maha_lord',
'planet': kal_lord,
'reason': f'Kalachakra当前主星{kal_lord}落在{house}',
})
if activations:
domain_activations[domain] = {
'house': house,
@@ -2701,7 +2791,11 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
# 收敛等级评估
for domain, info in domain_activations.items():
sc = info['system_count']
if sc >= 3:
if sc >= 4:
info['convergence_level'] = 'L5'
info['probability'] = '85-92%'
info['interpretation'] = '四个及以上推运系统同时激活,顶级收敛信号'
elif sc >= 3:
info['convergence_level'] = 'L4'
info['probability'] = '75-85%'
info['interpretation'] = '三系统同时激活,极强信号'
@@ -2721,7 +2815,7 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
'systems_summary': convergence_data['systems'],
'domain_activations': domain_activations,
'top_convergent_domains': [(d, info['convergence_level']) for d, info in top_domains],
'protocol': 'v4.5.0 Dasa Convergence 系统交叉验证。收敛等级: L1(单系统)→L3(双系统)→L4(三系统)所有预测必须标注收敛等级',
'protocol': 'v6.1.6 Dasa Convergence 系统交叉验证。收敛等级: L1(单系统)→L3(双系统)→L4(三系统)→L5(四个及以上系统)。Chara Dasha 当前为 partial所有预测必须由 Vimshottari/Transit/Varga 等独立层确认',
}
@@ -3194,36 +3288,61 @@ def cmd_full_reading(args):
try:
# Pancha Pakshi(五鸟系统,需要出生 Nakshatra)
# 先尝试从 nakshatra_advanced 获取出生 Nakshatra
nakshatra_num = None
if 'moon_nakshatra' in dir() or 'moon_nak' in locals():
pass # 动态获取
# 从 planets 数据推算 NakshatraMoon 的度数为基准)
# v6.1.6: 对齐 pancha_pakshi.py 现有公共接口 get_pancha_pakshi_schedule()
moon_deg = planet_lons.get('Moon', 0)
nak_num = int(moon_deg / 13.3333333) + 1
nak_num = int(moon_deg / (360.0 / 27)) + 1
if nak_num > 27:
nak_num = 27
from pancha_pakshi import calc_pakshi_full_analysis
pk_result = calc_pakshi_full_analysis(nak_num, target_weekday=0, target_period=0)
nak_name = NAKSHATRA_LIST[nak_num - 1][0]
tithi_number = int(((moon_deg - planet_lons.get('Sun', 0)) % 360) / 12) + 1
paksha = 'shukla' if 1 <= tithi_number <= 15 else 'krishna'
from pancha_pakshi import get_pancha_pakshi_schedule
pk_result = get_pancha_pakshi_schedule(
birth_nakshatra=nak_name,
paksha=paksha,
date=f"{args.year}-{args.month:02d}-{args.day:02d}",
)
pk_result['input_context'] = {
'moon_nakshatra_index': nak_num - 1,
'moon_nakshatra': nak_name,
'tithi_number': tithi_number,
'paksha': paksha,
}
report['modules']['pancha_pakshi'] = pk_result
except Exception as e:
report['errors'].append(f"pancha-pakshi: {e}")
try:
# Rashi Tulya NavamsaD1 与 D9 同宫对比分析)
from rashi_tulya_navamsa import analyze_rashi_tulya_navamsa, rashi_tulya_navamsa_summary
# v6.1.6: 对齐 rashi_tulya_navamsa.py 现有公共接口 analyze_rtn(chart_data)
from rashi_tulya_navamsa import analyze_rtn
varga_full = report['modules'].get('varga_full', {})
d9_data = varga_full.get('D9_Navamsa', {})
if d9_data and d9_data.get('planets') and d9_data.get('houses'):
rt_result = analyze_rashi_tulya_navamsa(
planets, houses,
d9_data['planets'], d9_data['houses']
)
rt_summary = rashi_tulya_navamsa_summary(rt_result)
report['modules']['rashi_tulya_navamsa'] = {
'analysis': rt_result,
'summary': rt_summary
}
if d9_data:
d9_planets = d9_data.get('planets') if isinstance(d9_data, dict) else None
if not d9_planets and isinstance(d9_data, dict):
d9_planets = {
pn: pd for pn, pd in d9_data.items()
if isinstance(pd, dict) and pn not in ('_meta', 'Ascendant') and 'sign' in pd
}
if d9_planets:
rt_chart = {
'ascendant': chart.get('ascendant', {}),
'planets': planets,
'context': {'navamsa_planets': d9_planets},
}
rt_result = analyze_rtn(rt_chart)
report['modules']['rashi_tulya_navamsa'] = {
'analysis': rt_result,
'summary': {
'strength_score': rt_result.get('strength_score'),
'weakness_score': rt_result.get('weakness_score'),
'exalted_cancelled_count': len(rt_result.get('exalted_cancelled', [])),
'debilitated_cancelled_count': len(rt_result.get('debilitated_cancelled', [])),
},
}
else:
report['modules']['rashi_tulya_navamsa'] = {'note': 'D9 planet data incomplete, skip Rashi Tulya Navamsa'}
else:
report['modules']['rashi_tulya_navamsa'] = {'note': 'D9 data incomplete, skip Rashi Tulya Navamsa'}
except Exception as e:
@@ -3585,15 +3704,56 @@ def cmd_full_reading(args):
except Exception as e:
report['errors'].append(f"transit-multi-ref: {e}")
# ── Step 18: Dasa Convergence 系统交叉验证 (v4.5.0 P1) ──
# ── Step 18: Dasa Convergence 系统交叉验证 (v6.1.6) ──
try:
dasha_data = report['modules'].get('dasha', {})
jaimini_data = report['modules'].get('jaimini', {})
chara_dasha_data = jaimini_data.get('chara_dasha', {}) if isinstance(jaimini_data, dict) else {}
birthdate_str = f"{args.year}-{args.month:02d}-{args.day:02d}"
yogini_data = _calc_yogini_dasha(planet_lons.get('Moon', 0), birthdate_str)
report['modules']['yogini_dasha'] = yogini_data
convergence = _calc_dasa_convergence(dasha_data, chara_dasha_data, yogini_data, planet_lons, asc_idx)
moon_lon = planet_lons.get('Moon', 0)
moon_nakshatra_index = int(moon_lon / (360 / 27)) % 27
moon_pada = int((moon_lon % (360 / 27)) / (360 / 108)) + 1
tithi_number = int(((moon_lon - planet_lons.get('Sun', 0)) % 360) / 12) + 1
birth_info_for_alt_dasha = {
'birth_datetime': datetime(args.year, args.month, args.day, args.hour, args.minute),
'moon_nakshatra_index': moon_nakshatra_index,
'moon_pada': moon_pada,
'is_shukla_paksha': 1 <= tithi_number <= 15,
'lagna_rashi_index': asc_idx,
}
try:
from yogini_dasha import calculate_yogini_dasha
yogini_data = calculate_yogini_dasha(birth_info_for_alt_dasha)
report['modules']['yogini_dasha'] = yogini_data
except Exception as alt_e:
yogini_data = {'error': str(alt_e)}
report['modules']['yogini_dasha'] = yogini_data
try:
from ashtottari_dasha import calculate_ashtottari_dasha
ashtottari_data = calculate_ashtottari_dasha(birth_info_for_alt_dasha)
report['modules']['ashtottari_dasha'] = ashtottari_data
except Exception as alt_e:
ashtottari_data = {'error': str(alt_e)}
report['modules']['ashtottari_dasha'] = ashtottari_data
try:
from kalachakra_dasha import calculate_kalachakra_dasha
kalachakra_data = calculate_kalachakra_dasha(birth_info_for_alt_dasha)
report['modules']['kalachakra_dasha'] = kalachakra_data
except Exception as alt_e:
kalachakra_data = {'error': str(alt_e)}
report['modules']['kalachakra_dasha'] = kalachakra_data
convergence = _calc_dasa_convergence(
dasha_data,
chara_dasha_data,
yogini_data,
planet_lons,
asc_idx,
ashtottari_result=ashtottari_data,
kalachakra_result=kalachakra_data,
)
report['modules']['dasa_convergence'] = convergence
except Exception as e:
report['errors'].append(f"dasa-convergence: {e}")
@@ -3651,7 +3811,7 @@ def cmd_full_reading(args):
'modules_computed': module_count,
'errors': error_count,
'status': 'complete' if error_count == 0 else f'{error_count} errors',
'next_step': '⭐ v4.5.0: P1缺口已补齐。新增 transit_multi_reference(四参考点) + dasa_convergence(系统交叉) + yogini_dasha + d9_navamsa_expanded(逐行星尊严展开)。AI必须使用四参考点分析Transit,Dasa预测必须标注收敛等级。',
'next_step': '⭐ v6.1.6: full-reading 已输出 transit_multi_reference(四参考点) + dasa_convergence(系统交叉) + yogini_dasha + ashtottari_dasha + kalachakra_dasha + d9_navamsa_expanded。AI必须使用四参考点分析Transit,Dasa预测必须标注多系统收敛等级。',
}
return report
+15 -6
View File
@@ -217,17 +217,26 @@ def calculate_kalachakra_dasha(birth_info: dict) -> dict:
starting_lord = major_periods[0]["lord"] if major_periods else None
starting_rashi = major_periods[0]["rashi"] if major_periods else None
# Determine current period
# Determine current period. Kalachakra cycles through the generated Rashi-year
# sequence repeatedly; older natives should still return a current period.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % total_cycle if total_cycle else age_years
cumulative = 0.0
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // total_cycle) + 1 if total_cycle else 1
break
cumulative += years
return {
"mode": mode,
+15 -6
View File
@@ -136,17 +136,26 @@ def calculate_yogini_dasha(birth_info: dict) -> dict:
major_periods = _build_major_periods(start_idx, birth_dt)
# Determine current period
# Determine current period. Yogini repeats every 36 years; older natives
# should still return a current period instead of None after the first cycle.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % TOTAL_CYCLE
cumulative = 0.0
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // TOTAL_CYCLE) + 1
break
cumulative += years
return {
"major": major_periods,