693 lines
23 KiB
Python
693 lines
23 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
星宿大运推演系统 v1.0 — Nakshatra Dasha Engine
|
||
|
||
支持:
|
||
- Ashtottari Dasha(108年星宿大运,条件性系统)
|
||
- Vimshottari Nakshatra-level 详细拆解
|
||
- Nakshatra Transit Overlay(过境星宿叠加大运)
|
||
- Nakshatra-based 时间窗口推演
|
||
- 综合星宿大运报告
|
||
|
||
设计原则:
|
||
- Ashtottari 作为 Vimshottari 的补充系统(条件:Rahu 不在 Kendra 时适用)
|
||
- Nakshatra-level 拆解提供当前大运的星宿层面的精细时间线
|
||
- Transit Overlay 将过境星宿叠加上大运周期,提供精确时间窗口
|
||
"""
|
||
from typing import Dict, List, Optional, Tuple
|
||
import math
|
||
from datetime import datetime, timedelta
|
||
|
||
# 从 nakshatra_advanced 导入需要的数据
|
||
try:
|
||
from nakshatra_advanced import (
|
||
NAK_LIST, NAK_NAMES, NAK_LORDS, NAK_YEARS,
|
||
find_nakshatra, calc_tara_bala,
|
||
)
|
||
except ImportError:
|
||
import sys, os
|
||
sys.path.insert(0, os.path.dirname(__file__))
|
||
from nakshatra_advanced import (
|
||
NAK_LIST, NAK_NAMES, NAK_LORDS, NAK_YEARS,
|
||
find_nakshatra, calc_tara_bala,
|
||
)
|
||
|
||
# ============================================================================
|
||
# Ashtottari Dasha 数据表
|
||
# ============================================================================
|
||
|
||
# Ashtottari 行星年数(108年制)
|
||
ASHTOTTARI_YEARS = {
|
||
'Sun': 6, 'Moon': 15, 'Mars': 8, 'Mercury': 17,
|
||
'Saturn': 10, 'Jupiter': 19, 'Rahu': 12, 'Venus': 21,
|
||
}
|
||
ASHTOTTARI_TOTAL = sum(ASHTOTTARI_YEARS.values()) # 108
|
||
|
||
# Ashtottari 大运顺序(固定循环)
|
||
ASHTOTTARI_ORDER = ['Sun', 'Moon', 'Mars', 'Mercury', 'Saturn', 'Jupiter', 'Rahu', 'Venus']
|
||
|
||
# Nakshatra → Ashtottari 起始守护星映射
|
||
# 按 Vimshottari 星宿守护星对应 Ashtottari 起始点
|
||
# Ketu 星宿 → 不作为 Ashtottari 起点,回溯到上一个 Venus
|
||
NAK_TO_ASHTOTTARI_START = {
|
||
'Ashwini': 'Venus', # Ketu nakshatra → 上一周期末尾 Venus
|
||
'Bharani': 'Venus', # Venus
|
||
'Krittika': 'Sun', # Sun
|
||
'Rohini': 'Moon', # Moon
|
||
'Mrigashira': 'Mars', # Mars
|
||
'Ardra': 'Rahu', # Rahu
|
||
'Punarvasu': 'Jupiter', # Jupiter
|
||
'Pushya': 'Saturn', # Saturn
|
||
'Ashlesha': 'Mercury', # Mercury
|
||
'Magha': 'Venus', # Ketu → Venus
|
||
'Purva Phalguni': 'Venus', # Venus
|
||
'Uttara Phalguni': 'Sun', # Sun
|
||
'Hasta': 'Moon', # Moon
|
||
'Chitra': 'Mars', # Mars
|
||
'Swati': 'Rahu', # Rahu
|
||
'Vishakha': 'Jupiter', # Jupiter
|
||
'Anuradha': 'Saturn', # Saturn
|
||
'Jyeshtha': 'Mercury', # Mercury
|
||
'Mula': 'Venus', # Ketu → Venus
|
||
'Purva Ashadha': 'Venus', # Venus
|
||
'Uttara Ashadha': 'Sun', # Sun
|
||
'Shravana': 'Moon', # Moon
|
||
'Dhanishta': 'Mars', # Mars
|
||
'Shatabhisha': 'Rahu', # Rahu
|
||
'Purva Bhadrapada': 'Jupiter', # Jupiter
|
||
'Uttara Bhadrapada': 'Saturn', # Saturn
|
||
'Revati': 'Mercury', # Mercury
|
||
}
|
||
|
||
|
||
# ============================================================================
|
||
# 核心计算函数
|
||
# ============================================================================
|
||
|
||
def is_ashtottari_applicable(rahu_house: int) -> bool:
|
||
"""
|
||
判断 Ashtottari Dasha 是否适用
|
||
|
||
传统规则:当 Rahu 不在 Kendra(1/4/7/10宫)时,Ashtottari 优先。
|
||
若 Rahu 在 Kendra,则使用 Vimshottari。
|
||
|
||
参数:
|
||
rahu_house: Rahu 所在宫位 (1-12)
|
||
|
||
返回:
|
||
bool: True = Ashtottari 适用
|
||
"""
|
||
kendras = {1, 4, 7, 10}
|
||
return rahu_house not in kendras
|
||
|
||
|
||
def calc_ashtottari_dasha(
|
||
moon_longitude: float,
|
||
birth_date_str: str,
|
||
rahu_house: int,
|
||
) -> Dict:
|
||
"""
|
||
计算 Ashtottari Dasha 108年大运序列 — v1.0
|
||
|
||
参数:
|
||
moon_longitude: 月亮恒星黄道经度
|
||
birth_date_str: 出生日期 YYYY-MM-DD
|
||
rahu_house: Rahu 所在宫位 (1-12)
|
||
|
||
返回:
|
||
dict: {
|
||
'applicable': bool,
|
||
'reason': str,
|
||
'starting_lord': str,
|
||
'sequence': [...],
|
||
'total_years': 108,
|
||
}
|
||
"""
|
||
applicable = is_ashtottari_applicable(rahu_house)
|
||
|
||
# 月亮星宿信息
|
||
nak_data = find_nakshatra(moon_longitude)
|
||
moon_nak = nak_data['nakshatra']
|
||
moon_nak_idx = nak_data['nakshatra_idx']
|
||
|
||
# 起始守护星
|
||
starting_lord = NAK_TO_ASHTOTTARI_START.get(moon_nak, 'Sun')
|
||
|
||
# 计算起始点的已用/剩余比例
|
||
nak_span = 360.0 / 27
|
||
nak_start = moon_nak_idx * nak_span
|
||
deg_in_nak = moon_longitude - nak_start
|
||
used_ratio = deg_in_nak / nak_span
|
||
remaining_ratio = 1 - used_ratio
|
||
|
||
start_lord_years = ASHTOTTARI_YEARS.get(starting_lord, 6)
|
||
first_remaining = start_lord_years * remaining_ratio
|
||
|
||
# 构建大运序列
|
||
start_idx = ASHTOTTARI_ORDER.index(starting_lord)
|
||
sequence = []
|
||
cumulative = 0.0
|
||
|
||
# 首个大运的剩余部分
|
||
sequence.append({
|
||
'lord': starting_lord,
|
||
'full_years': start_lord_years,
|
||
'remaining_years': round(first_remaining, 4),
|
||
'elapsed_years': round(start_lord_years - first_remaining, 4),
|
||
'cumulative_from_birth': 0.0,
|
||
'cumulative_to_birth': round(first_remaining, 4),
|
||
'is_first': True,
|
||
})
|
||
cumulative += first_remaining
|
||
|
||
# 后续完整大运循环
|
||
idx = (start_idx + 1) % 8
|
||
cycle_count = 1
|
||
while cumulative < 120: # 超出人生预期范围
|
||
lord = ASHTOTTARI_ORDER[idx]
|
||
years = ASHTOTTARI_YEARS[lord]
|
||
sequence.append({
|
||
'lord': lord,
|
||
'full_years': years,
|
||
'remaining_years': years,
|
||
'elapsed_years': 0,
|
||
'cumulative_from_birth': round(cumulative, 4),
|
||
'cumulative_to_birth': round(cumulative + years, 4),
|
||
'is_first': False,
|
||
})
|
||
cumulative += years
|
||
idx = (idx + 1) % 8
|
||
cycle_count += 1
|
||
if cycle_count > 20: # 安全上限
|
||
break
|
||
|
||
# 出生日期
|
||
try:
|
||
birth_date = datetime.strptime(birth_date_str, '%Y-%m-%d')
|
||
except:
|
||
birth_date = datetime(2000, 1, 1)
|
||
|
||
# 为大运添加日期范围
|
||
for item in sequence:
|
||
from_years = item['cumulative_from_birth']
|
||
to_years = item['cumulative_to_birth']
|
||
item['date_from'] = _add_years_to_date(birth_date, from_years)
|
||
item['date_to'] = _add_years_to_date(birth_date, to_years)
|
||
item['age_from'] = round(from_years, 2)
|
||
item['age_to'] = round(to_years, 2)
|
||
|
||
if applicable:
|
||
reason = f"Rahu 在第{rahu_house}宫,不在 Kendra(1/4/7/10),Ashtottari 适用"
|
||
else:
|
||
reason = f"Rahu 在第{rahu_house}宫,在 Kendra,建议使用 Vimshottari"
|
||
|
||
return {
|
||
'system': 'Ashtottari Dasha(108年星宿大运)',
|
||
'applicable': applicable,
|
||
'reason': reason,
|
||
'rahu_house': rahu_house,
|
||
'moon_nakshatra': moon_nak,
|
||
'starting_lord': starting_lord,
|
||
'total_years': ASHTOTTARI_TOTAL,
|
||
'first_remaining_years': round(first_remaining, 4),
|
||
'sequence': sequence,
|
||
'note': ('Ashtottari 作为 Vimshottari 的补充系统,'
|
||
'当 Rahu 不在 Kendra 时提供替代视角。'
|
||
'两者各有侧重:Vimshottari 偏"心识",Ashtottari 偏"命运安排"。'),
|
||
}
|
||
|
||
|
||
def calc_current_ashtottari(
|
||
ashtottari_data: Dict,
|
||
age: float,
|
||
) -> Dict:
|
||
"""
|
||
从 Ashtottari 序列中定位当前年龄的大运
|
||
|
||
返回:
|
||
dict: {lord, years_remaining, progress, sub_periods, ...}
|
||
"""
|
||
if not ashtottari_data.get('applicable', False):
|
||
return {'applicable': False, 'reason': ashtottari_data.get('reason', '')}
|
||
|
||
sequence = ashtottari_data.get('sequence', [])
|
||
current = None
|
||
next_item = None
|
||
|
||
for i, item in enumerate(sequence):
|
||
if item['cumulative_from_birth'] <= age < item['cumulative_to_birth']:
|
||
current = item
|
||
if i + 1 < len(sequence):
|
||
next_item = sequence[i + 1]
|
||
break
|
||
|
||
if current is None:
|
||
return {'error': f'无法定位年龄 {age} 在 Ashtottari 序列中'}
|
||
|
||
years_elapsed_in_md = age - current['cumulative_from_birth']
|
||
years_remaining = current['cumulative_to_birth'] - age
|
||
progress = years_elapsed_in_md / current['remaining_years'] if current['remaining_years'] > 0 else 0
|
||
|
||
# 计算 Antardasha 子周期(简化:按比例分配)
|
||
antardasha_sequence = _calc_ashtottari_antardasha(current['lord'], current['remaining_years'])
|
||
|
||
# 定位当前 Antardasha
|
||
ad_cumulative = 0
|
||
current_ad = None
|
||
for ad in antardasha_sequence:
|
||
ad_end = ad_cumulative + ad['years']
|
||
if ad_cumulative <= years_elapsed_in_md < ad_end:
|
||
current_ad = {
|
||
**ad,
|
||
'progress_in_ad': (years_elapsed_in_md - ad_cumulative) / ad['years'] if ad['years'] > 0 else 0,
|
||
'remaining_years': round(ad_end - years_elapsed_in_md, 4),
|
||
}
|
||
break
|
||
ad_cumulative = ad_end
|
||
|
||
return {
|
||
'applicable': True,
|
||
'current_mahadasha': current['lord'],
|
||
'mahadasha_progress': round(progress, 4),
|
||
'years_elapsed_in_md': round(years_elapsed_in_md, 4),
|
||
'years_remaining_in_md': round(years_remaining, 4),
|
||
'age_from': current['age_from'],
|
||
'age_to': current['age_to'],
|
||
'next_mahadasha': next_item['lord'] if next_item else None,
|
||
'antardasha_sequence': antardasha_sequence,
|
||
'current_antardasha': current_ad,
|
||
}
|
||
|
||
|
||
def _calc_ashtottari_antardasha(md_lord: str, md_years: float) -> List[Dict]:
|
||
"""计算 Ashtottari Antardasha 子周期"""
|
||
md_start_idx = ASHTOTTARI_ORDER.index(md_lord)
|
||
total = sum(ASHTOTTARI_YEARS.values())
|
||
|
||
result = []
|
||
for i in range(8):
|
||
ad_lord = ASHTOTTARI_ORDER[(md_start_idx + i) % 8]
|
||
ad_years_total = ASHTOTTARI_YEARS[ad_lord]
|
||
# 按比例缩放
|
||
ad_years_in_md = md_years * ad_years_total / total
|
||
result.append({
|
||
'lord': ad_lord,
|
||
'years': round(ad_years_in_md, 4),
|
||
'months': round(ad_years_in_md * 12, 1),
|
||
})
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================================
|
||
# Vimshottari Nakshatra-level 拆解
|
||
# ============================================================================
|
||
|
||
# Vimshottari Dasha 顺序(9星循环)
|
||
VIMSHOTTARI_ORDER = ['Ketu', 'Venus', 'Sun', 'Moon', 'Mars', 'Rahu', 'Jupiter', 'Saturn', 'Mercury']
|
||
VIMSHOTTARI_YEARS = {'Ketu': 7, 'Venus': 20, 'Sun': 6, 'Moon': 10, 'Mars': 7, 'Rahu': 18, 'Jupiter': 16, 'Saturn': 19, 'Mercury': 17}
|
||
|
||
|
||
def calc_nakshatra_dasha_breakdown(
|
||
moon_longitude: float,
|
||
planet_lons: Dict[str, float],
|
||
birth_date_str: str,
|
||
age: float,
|
||
) -> Dict:
|
||
"""
|
||
Vimshottari Nakshatra-level 详细拆解 — v1.0
|
||
|
||
在当前 Vimshottari 大运/小运框架下,提供:
|
||
1. 大运守护星的 Nakshatra 位置及其 Tara 关系
|
||
2. 小运守护星的 Nakshatra 位置
|
||
3. Nakshatra Pada 层面的时间窗口
|
||
4. 月亮星宿到各大运/小运守护星星宿的 Tara Bala
|
||
|
||
参数:
|
||
moon_longitude: 月亮恒星黄道经度
|
||
planet_lons: {行星名: 恒星经度}
|
||
birth_date_str: 出生日期 YYYY-MM-DD
|
||
age: 当前年龄(岁)
|
||
|
||
返回:
|
||
dict: 星宿层面的大运拆解
|
||
"""
|
||
moon_nak_data = find_nakshatra(moon_longitude)
|
||
moon_nak_idx = moon_nak_data['nakshatra_idx']
|
||
|
||
# 计算 Vimshottari 起始
|
||
nak = find_nakshatra(moon_longitude)
|
||
nak_idx = nak['nakshatra_idx']
|
||
nak_span = 360.0 / 27
|
||
nak_start = nak_idx * nak_span
|
||
deg_in_nak = moon_longitude - nak_start
|
||
used_ratio = deg_in_nak / nak_span
|
||
|
||
# 起始守护星和剩余年数
|
||
start_lord = NAK_LORDS[nak_idx]
|
||
start_total = VIMSHOTTARI_YEARS.get(start_lord, 7)
|
||
first_remaining = start_total * (1 - used_ratio)
|
||
|
||
# 构建大运序列
|
||
start_idx = VIMSHOTTARI_ORDER.index(start_lord)
|
||
sequence = []
|
||
cumulative = 0.0
|
||
|
||
# 首个(剩余)大运
|
||
sequence.append({
|
||
'lord': start_lord,
|
||
'years': round(first_remaining, 4),
|
||
'cumulative_from': 0.0,
|
||
'is_first': True,
|
||
})
|
||
cumulative += first_remaining
|
||
|
||
idx = (start_idx + 1) % 9
|
||
for _ in range(15): # 足够覆盖到120岁
|
||
lord = VIMSHOTTARI_ORDER[idx]
|
||
years = VIMSHOTTARI_YEARS[lord]
|
||
sequence.append({
|
||
'lord': lord,
|
||
'years': round(years, 4),
|
||
'cumulative_from': round(cumulative, 4),
|
||
'is_first': False,
|
||
})
|
||
cumulative += years
|
||
idx = (idx + 1) % 9
|
||
if cumulative > 120:
|
||
break
|
||
|
||
# 定位当前大运
|
||
current_md = None
|
||
current_md_idx = -1
|
||
for i, item in enumerate(sequence):
|
||
if item['cumulative_from'] <= age < (item['cumulative_from'] + item['years']):
|
||
current_md = item
|
||
current_md_idx = i
|
||
break
|
||
|
||
if current_md is None:
|
||
return {'error': f'无法定位年龄 {age} 在 Vimshottari 序列中'}
|
||
|
||
md_lord = current_md['lord']
|
||
md_years = current_md['years']
|
||
md_start_age = current_md['cumulative_from']
|
||
years_in_md = age - md_start_age
|
||
|
||
# 大运守护星的 Nakshatra
|
||
md_lord_nak = None
|
||
if md_lord in planet_lons:
|
||
md_lord_nak = find_nakshatra(planet_lons[md_lord])
|
||
|
||
# 计算当前 Antardasha
|
||
antardasas = _calc_vimshottari_antardasha_full(md_lord, md_years)
|
||
|
||
# 定位当前 Antardasha
|
||
ad_cumulative = 0.0
|
||
current_ad = None
|
||
for ad in antardasas:
|
||
ad_end = ad_cumulative + ad['years']
|
||
if ad_cumulative <= years_in_md < ad_end:
|
||
current_ad = {
|
||
'lord': ad['lord'],
|
||
'years': ad['years'],
|
||
'progress': (years_in_md - ad_cumulative) / ad['years'] if ad['years'] > 0 else 0,
|
||
'remaining': round(ad_end - years_in_md, 4),
|
||
}
|
||
break
|
||
ad_cumulative = ad_end
|
||
|
||
# 当前 Antardasha 守护星的 Nakshatra
|
||
ad_lord_nak = None
|
||
if current_ad and current_ad['lord'] in planet_lons:
|
||
ad_lord_nak = find_nakshatra(planet_lons[current_ad['lord']])
|
||
|
||
# Nakshatra Tara 关系分析
|
||
tara_analysis = {}
|
||
if md_lord_nak:
|
||
tara_analysis['mahadasha_lord'] = {
|
||
'planet': md_lord,
|
||
'nakshatra': md_lord_nak['nakshatra'],
|
||
'pada': md_lord_nak['pada'],
|
||
'tara_to_moon': calc_tara_bala(moon_nak_idx, md_lord_nak['nakshatra_idx']),
|
||
}
|
||
if ad_lord_nak and current_ad:
|
||
tara_analysis['antardasha_lord'] = {
|
||
'planet': current_ad['lord'],
|
||
'nakshatra': ad_lord_nak['nakshatra'],
|
||
'pada': ad_lord_nak['pada'],
|
||
'tara_to_moon': calc_tara_bala(moon_nak_idx, ad_lord_nak['nakshatra_idx']),
|
||
}
|
||
|
||
# 星宿层面的时间窗口(Pada-level 细分)
|
||
pada_windows = []
|
||
if md_lord_nak and current_ad:
|
||
nak_span_deg = 360.0 / 27
|
||
pada_span_deg = nak_span_deg / 4
|
||
md_years_per_nak = md_years / 9 # 每个 Antardasha 时长近似
|
||
md_years_per_pada = md_years_per_nak / 4
|
||
|
||
for ad in antardasas:
|
||
if md_years_per_nak > 0:
|
||
ad_nak = find_nakshatra(planet_lons.get(ad['lord'], 0))
|
||
pada_windows.append({
|
||
'period': f"AD {ad['lord']}",
|
||
'nakshatra': ad_nak['nakshatra'],
|
||
'pada': ad_nak['pada'],
|
||
'years': round(ad['years'], 4),
|
||
'tara': calc_tara_bala(moon_nak_idx, ad_nak['nakshatra_idx']),
|
||
})
|
||
|
||
# 构建返回
|
||
birth_date = None
|
||
try:
|
||
birth_date = datetime.strptime(birth_date_str, '%Y-%m-%d')
|
||
except:
|
||
pass
|
||
|
||
return {
|
||
'system': 'Vimshottari Nakshatra-level Breakdown',
|
||
'moon_nakshatra': moon_nak_data['nakshatra'],
|
||
'moon_nakshatra_idx': moon_nak_idx,
|
||
'moon_pada': moon_nak_data['pada'],
|
||
'current_age': age,
|
||
'mahadasha': {
|
||
'lord': md_lord,
|
||
'years_total': round(md_years, 4),
|
||
'age_range': f"{round(md_start_age, 2)}-{round(md_start_age + md_years, 2)}",
|
||
'years_elapsed': round(years_in_md, 4),
|
||
'years_remaining': round(md_years - years_in_md, 4),
|
||
'progress': round(years_in_md / md_years, 4) if md_years > 0 else 0,
|
||
'nakshatra': md_lord_nak,
|
||
'tara': tara_analysis.get('mahadasha_lord', {}),
|
||
},
|
||
'antardasha': current_ad,
|
||
'tara_analysis': tara_analysis,
|
||
'pada_windows': pada_windows,
|
||
}
|
||
|
||
|
||
def _calc_vimshottari_antardasha_full(md_lord: str, md_years: float) -> List[Dict]:
|
||
"""计算完整的 Vimshottari Antardasha(9个周期)"""
|
||
md_start_idx = VIMSHOTTARI_ORDER.index(md_lord)
|
||
total_years = sum(VIMSHOTTARI_YEARS.values()) # 120
|
||
|
||
result = []
|
||
for i in range(9):
|
||
ad_lord = VIMSHOTTARI_ORDER[(md_start_idx + i) % 9]
|
||
ad_years_total = VIMSHOTTARI_YEARS[ad_lord]
|
||
ad_years = md_years * ad_years_total / total_years
|
||
result.append({
|
||
'lord': ad_lord,
|
||
'years': round(ad_years, 4),
|
||
'months': round(ad_years * 12, 1),
|
||
})
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================================
|
||
# Nakshatra Transit Overlay(过境星宿叠加大运)
|
||
# ============================================================================
|
||
|
||
def calc_nakshatra_transit_overlay(
|
||
natal_planet_lons: Dict[str, float],
|
||
transit_planet_lons: Dict[str, float],
|
||
moon_nak_idx: int,
|
||
) -> Dict:
|
||
"""
|
||
将过境星宿叠加在本命大运框架上 — v1.0
|
||
|
||
分析每个过境行星当前所在的星宿,及其:
|
||
1. 与本命月亮星宿的 Tara 关系
|
||
2. 与本命行星的星宿距离
|
||
3. 当前星宿的 Pada 位置
|
||
4. 吉凶评估
|
||
|
||
参数:
|
||
natal_planet_lons: 本命行星经度
|
||
transit_planet_lons: 过境行星经度
|
||
moon_nak_idx: 本命月亮星宿索引
|
||
|
||
返回:
|
||
dict: 过境星宿叠加分析
|
||
"""
|
||
results = {}
|
||
|
||
for pname, t_lon in transit_planet_lons.items():
|
||
t_nak = find_nakshatra(t_lon)
|
||
tara = calc_tara_bala(moon_nak_idx, t_nak['nakshatra_idx'])
|
||
|
||
# 过境星宿是否与某本命行星的星宿重叠
|
||
same_nak_as_natal = []
|
||
n_lon = natal_planet_lons.get(pname)
|
||
if n_lon is not None:
|
||
n_nak = find_nakshatra(n_lon)
|
||
if n_nak['nakshatra_idx'] == t_nak['nakshatra_idx']:
|
||
same_nak_as_natal.append('self')
|
||
|
||
for np, nl in natal_planet_lons.items():
|
||
if np == pname:
|
||
continue
|
||
n_nak = find_nakshatra(nl)
|
||
if n_nak['nakshatra_idx'] == t_nak['nakshatra_idx']:
|
||
same_nak_as_natal.append(np)
|
||
|
||
# 评估
|
||
if tara['is_auspicious']:
|
||
transit_quality = 'favorable'
|
||
elif tara['is_dangerous']:
|
||
transit_quality = 'challenging'
|
||
else:
|
||
transit_quality = 'neutral'
|
||
|
||
if same_nak_as_natal:
|
||
transit_quality = transit_quality + '_nak_conjunction'
|
||
note = f"过境星宿与本命{'/'.join(same_nak_as_natal)}同星宿,叠加效应增强"
|
||
else:
|
||
note = ''
|
||
|
||
results[pname] = {
|
||
'transit_nakshatra': t_nak['nakshatra'],
|
||
'transit_nakshatra_lord': t_nak['nakshatra_lord'],
|
||
'transit_pada': t_nak['pada'],
|
||
'tara_to_natal_moon': tara,
|
||
'same_nakshatra_as': same_nak_as_natal if same_nak_as_natal else None,
|
||
'quality': transit_quality,
|
||
'note': note,
|
||
'gana': t_nak['gana'],
|
||
'element': t_nak['element'],
|
||
}
|
||
|
||
return results
|
||
|
||
|
||
# ============================================================================
|
||
# 综合星宿大运报告
|
||
# ============================================================================
|
||
|
||
def nakshatra_dasha_full_report(
|
||
chart_data: Dict,
|
||
birth_date_str: str,
|
||
age: float,
|
||
transit_planet_lons: Optional[Dict[str, float]] = None,
|
||
) -> Dict:
|
||
"""
|
||
综合星宿大运完整报告 — v1.0
|
||
|
||
包含:
|
||
1. Ashtottari Dasha(条件性,Rahu 不在 Kendra 时计算)
|
||
2. Vimshottari Nakshatra-level 拆解
|
||
3. Nakshatra Transit Overlay(如提供过境数据)
|
||
4. 综合时间窗口与 Tara 评估
|
||
|
||
参数:
|
||
chart_data: 星盘数据
|
||
birth_date_str: 出生日期 YYYY-MM-DD
|
||
age: 当前年龄
|
||
transit_planet_lons: 过境行星经度(可选)
|
||
|
||
返回:
|
||
dict: 完整星宿大运报告
|
||
"""
|
||
planets = chart_data.get('planets', {})
|
||
planet_lons = {}
|
||
for pn, pd in planets.items():
|
||
if isinstance(pd, dict) and 'degree' in pd:
|
||
planet_lons[pn] = pd['degree']
|
||
|
||
if not planet_lons:
|
||
return {'error': '无法获取行星经度数据'}
|
||
|
||
moon_lon = planet_lons.get('Moon', 0)
|
||
moon_nak = find_nakshatra(moon_lon)
|
||
moon_nak_idx = moon_nak['nakshatra_idx']
|
||
|
||
# 确定 Rahu 宫位
|
||
asc_idx = chart_data.get('ascendant_index', 0)
|
||
rahu_deg = None
|
||
rahu_house = 1 # 默认
|
||
if 'Rahu' in planet_lons:
|
||
rahu_deg = planet_lons['Rahu']
|
||
rahu_sign_idx = int(rahu_deg / 30) % 12
|
||
rahu_house = ((rahu_sign_idx - asc_idx) % 12) + 1
|
||
|
||
report = {}
|
||
|
||
# 1. Ashtottari Dasha
|
||
report['ashtottari'] = calc_ashtottari_dasha(moon_lon, birth_date_str, rahu_house)
|
||
if report['ashtottari']['applicable']:
|
||
report['ashtottari_current'] = calc_current_ashtottari(report['ashtottari'], age)
|
||
|
||
# 2. Vimshottari Nakshatra-level
|
||
report['vimshottari_nak_breakdown'] = calc_nakshatra_dasha_breakdown(
|
||
moon_lon, planet_lons, birth_date_str, age)
|
||
|
||
# 3. Nakshatra Transit Overlay
|
||
if transit_planet_lons:
|
||
report['transit_overlay'] = calc_nakshatra_transit_overlay(
|
||
planet_lons, transit_planet_lons, moon_nak_idx)
|
||
|
||
# 4. 汇总
|
||
md_info = report['vimshottari_nak_breakdown'].get('mahadasha', {})
|
||
ad_info = report['vimshottari_nak_breakdown'].get('antardasha', {})
|
||
|
||
summary = {
|
||
'moon_nakshatra': moon_nak['nakshatra'],
|
||
'moon_nakshatra_lord': moon_nak['nakshatra_lord'],
|
||
'moon_pada': moon_nak['pada'],
|
||
'current_age': age,
|
||
'vimshottari_md': md_info.get('lord', 'N/A'),
|
||
'vimshottari_ad': ad_info.get('lord', 'N/A') if ad_info else 'N/A',
|
||
'ashtottari_applicable': report['ashtottari']['applicable'],
|
||
}
|
||
|
||
if report['ashtottari']['applicable'] and 'ashtottari_current' in report:
|
||
ac = report['ashtottari_current']
|
||
summary['ashtottari_md'] = ac.get('current_mahadasha', 'N/A')
|
||
if ac.get('current_antardasha'):
|
||
summary['ashtottari_ad'] = ac['current_antardasha']['lord']
|
||
|
||
# Dasha 系统的 Nakshatra Tara 评估
|
||
tara_md = report['vimshottari_nak_breakdown'].get('tara_analysis', {}).get('mahadasha_lord', {})
|
||
tara_ad = report['vimshottari_nak_breakdown'].get('tara_analysis', {}).get('antardasha_lord', {})
|
||
|
||
summary['md_tara_to_moon'] = tara_md.get('tara_to_moon', {}).get('tara_cn', 'N/A') if tara_md else 'N/A'
|
||
summary['ad_tara_to_moon'] = tara_ad.get('tara_to_moon', {}).get('tara_cn', 'N/A') if tara_ad else 'N/A'
|
||
|
||
report['summary'] = summary
|
||
|
||
return report
|
||
|
||
|
||
# ============================================================================
|
||
# 工具函数
|
||
# ============================================================================
|
||
|
||
def _add_years_to_date(dt: datetime, years: float) -> str:
|
||
"""日期 + 年数 → ISO 日期字符串"""
|
||
total_days = years * 365.25
|
||
new_dt = dt + timedelta(days=total_days)
|
||
return new_dt.strftime('%Y-%m-%d')
|