Implement Nakshatra advanced dasha layer

This commit is contained in:
732642856
2026-06-04 12:19:11 +08:00
parent e0334cc723
commit 2d847b147e
9 changed files with 1513 additions and 91 deletions
+249
View File
@@ -0,0 +1,249 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
cmd_nakshatra_adv: Nakshatra Advanced CLI 子命令
Jyotish Vedic Astrology Skill — v6.0.22
子命令:
nakshatra-adv: 高级Nakshatra分析(Tara Bala / Chandra Bala / Sub-Lord / 综合报告)
nakshatra-dasha: 星宿大运推演(Ashtottari / Vimshottari Nakshatra-level / Transit Overlay
nakshatra-full: 综合星宿完整报告(本命 + 大运 + 过境)
"""
import sys
import os
from typing import Dict, Any, Optional
from datetime import datetime
def _compute_chart_from_args(args: Any) -> tuple:
"""
从 args 计算星盘数据。
返回 (chart, asc_idx, jd, ayanamsa, planet_lons, moon_lon, moon_nak_idx, asc_sign_idx)
"""
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from jyotish_engine import compute_chart_data
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return None, None, None, None, {}, 0, 0, 0
planets = chart.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 chart, asc_idx, jd, ayanamsa, {}, 0, 0, 0
moon_lon = planet_lons.get('Moon', 0)
moon_nak_idx = int(moon_lon / (360.0 / 27)) % 27
asc_sign_idx = asc_idx if asc_idx is not None else 0
return chart, asc_idx, jd, ayanamsa, planet_lons, moon_lon, moon_nak_idx, asc_sign_idx
def cmd_nakshatra_adv(args: Any) -> Dict[str, Any]:
"""
nakshatra-adv 子命令:高级Nakshatra分析(v3.7 → v6.0.22 升级)
支持 mode:
all — 全部(detail + tara + chandra + sublord + full
detail — Nakshatra详情
tara — Tara Bala
chandra — Chandra Bala (v6.0.22 新增)
combined — Tara + Chandra 双维综合 (v6.0.22 新增)
sublord — Sub-Lord KP
full — 综合星宿报告 (v6.0.22 新增)
"""
result = _compute_chart_from_args(args)
chart, asc_idx, jd, ayanamsa, planet_lons, moon_lon, moon_nak_idx, asc_sign_idx = result
if chart is None:
return {"error": "swisseph未安装"}
try:
from nakshatra_advanced import (
find_nakshatra, calc_all_tara_balas, calc_sub_lord,
calc_chandra_bala, calc_tara_chandra_combined,
calc_nakshatra_transits_natal, nakshatra_full_report,
nakshatra_compatibility,
)
except ImportError as e:
return {"error": f"nakshatra_advanced模块导入失败: {e}"}
mode = getattr(args, 'mode', 'all') or 'all'
# === mode: 'detail' ===
if mode in ('all', 'detail'):
detail = {}
for pn, lon in planet_lons.items():
detail[pn] = find_nakshatra(lon)
result_dict = {'planets': detail}
if mode == 'detail':
return result_dict
else:
result_dict = {}
# === mode: 'tara' ===
if mode in ('all', 'tara'):
tara = calc_all_tara_balas(moon_nak_idx, planet_lons)
if mode == 'tara':
return {'tara_bala': tara, 'moon_nakshatra_idx': moon_nak_idx}
result_dict['tara_bala'] = tara
# === mode: 'chandra' (v6.0.22 新增) ===
if mode in ('all', 'chandra'):
moon_sign_idx = int(moon_lon / 30) % 12
chandra = {}
for pn, lon in planet_lons.items():
p_sign = int(lon / 30) % 12
chandra[pn] = {
'planet_sign': p_sign,
'chandra': calc_chandra_bala(moon_sign_idx, p_sign),
}
if mode == 'chandra':
return {'chandra_bala': chandra, 'moon_sign_idx': moon_sign_idx}
result_dict['chandra_bala'] = chandra
# === mode: 'combined' (v6.0.22 新增) ===
if mode in ('all', 'combined'):
moon_sign_idx = int(moon_lon / 30) % 12
combined = calc_tara_chandra_combined(moon_nak_idx, moon_sign_idx, planet_lons)
if mode == 'combined':
return {'tara_chandra_combined': combined}
result_dict['tara_chandra_combined'] = combined
# === mode: 'sublord' ===
if mode in ('all', 'sublord'):
sublords = {pn: calc_sub_lord(lon) for pn, lon in planet_lons.items()}
if mode == 'sublord':
return {'sub_lords': sublords}
result_dict['sub_lords'] = sublords
# === mode: 'all' / 'full' — 综合报告 ===
if mode in ('all', 'full'):
full = nakshatra_full_report(chart)
result_dict.update(full)
return result_dict
def cmd_nakshatra_dasha(args: Any) -> Dict[str, Any]:
"""
nakshatra-dasha 子命令:星宿大运推演(v6.0.22 新增)
支持 mode:
ashtottari — 仅 Ashtottari Dasha
vimshottari — 仅 Vimshottari Nakshatra-level 拆解
overlay — 仅 Nakshatra Transit Overlay
all — 全部(默认)
"""
result = _compute_chart_from_args(args)
chart, asc_idx, jd, ayanamsa, planet_lons, moon_lon, moon_nak_idx, asc_sign_idx = result
if chart is None:
return {"error": "swisseph未安装"}
try:
from nakshatra_dasha import (
calc_ashtottari_dasha, calc_current_ashtottari,
calc_nakshatra_dasha_breakdown,
calc_nakshatra_transit_overlay,
nakshatra_dasha_full_report,
)
except ImportError as e:
return {"error": f"nakshatra_dasha模块导入失败: {e}"}
# 构建 birth_date_str
birth_date_str = f"{args.year}-{args.month:02d}-{args.day:02d}"
# 年龄
age = getattr(args, 'age', None)
if age is None:
try:
birth_date = datetime(args.year, args.month, args.day)
age = (datetime.now() - birth_date).days / 365.25
except:
age = 30.0
mode = getattr(args, 'mode', 'all') or 'all'
# 确定 Rahu 宫位
rahu_deg = planet_lons.get('Rahu', 0)
rahu_sign_idx = int(rahu_deg / 30) % 12
rahu_house = ((rahu_sign_idx - asc_sign_idx) % 12) + 1
if mode == 'ashtottari':
asht = calc_ashtottari_dasha(moon_lon, birth_date_str, rahu_house)
if asht['applicable']:
asht['current'] = calc_current_ashtottari(asht, age)
return asht
if mode == 'vimshottari':
return calc_nakshatra_dasha_breakdown(moon_lon, planet_lons, birth_date_str, age)
if mode == 'overlay':
# 需要过境数据:获取当前日期或 --transit-date
transit_date_str = getattr(args, 'transit_date', None) or datetime.now().strftime('%Y-%m-%d')
from jyotish_engine import _calc_sidereal_planets_for_jd
try:
import swisseph as swe
ty, tm, td = map(int, transit_date_str.split('-'))
transit_jd = swe.julday(ty, tm, td, 12.0 - args.tz)
transit_planets, transit_ayanamsa = _calc_sidereal_planets_for_jd(
transit_jd, node_mode=getattr(args, 'node_mode', 'mean'), include_ketu=True)
transit_lons = {}
for pn, pd in transit_planets.items():
if isinstance(pd, dict) and 'degree' in pd:
transit_lons[pn] = pd['degree']
overlay = calc_nakshatra_transit_overlay(planet_lons, transit_lons, moon_nak_idx)
overlay['transit_date'] = transit_date_str
return overlay
except ImportError:
return {"error": "swisseph 未安装,无法计算过境星宿"}
# mode == 'all': 综合报告
# 尝试获取过境数据
transit_lons = None
try:
import swisseph as swe
transit_date_str = getattr(args, 'transit_date', None) or datetime.now().strftime('%Y-%m-%d')
from jyotish_engine import _calc_sidereal_planets_for_jd
ty, tm, td = map(int, transit_date_str.split('-'))
transit_jd = swe.julday(ty, tm, td, 12.0 - args.tz)
transit_planets, _ = _calc_sidereal_planets_for_jd(
transit_jd, node_mode=getattr(args, 'node_mode', 'mean'), include_ketu=True)
transit_lons = {}
for pn, pd in transit_planets.items():
if isinstance(pd, dict) and 'degree' in pd:
transit_lons[pn] = pd['degree']
except:
pass
return nakshatra_dasha_full_report(chart, birth_date_str, age, transit_lons)
def cmd_nakshatra_full(args: Any) -> Dict[str, Any]:
"""
nakshatra-full 子命令:综合星宿完整报告(本命 + 大运 + 过境)— v6.0.22 新增
整合三个维度:
1. 本命 Nakshatra 分析(Tara/Chandra/Sub-Lord/兼容性)
2. Nakshatra Dasha 推演(Ashtottari + Vimshottari Nakshatra-level
3. Nakshatra Transit Overlay(当前过境星宿)
"""
adv_result = cmd_nakshatra_adv(args)
dasha_result = cmd_nakshatra_dasha(args)
return {
'system': 'Nakshatra Comprehensive Report(星宿综合报告)',
'version': 'v6.0.22',
'timestamp': datetime.now().isoformat(),
'nakshatra_advanced': adv_result,
'nakshatra_dasha': dasha_result,
}
+11 -1
View File
@@ -33,7 +33,17 @@ def cmd_narayana_dasha(args, chart_data):
if isinstance(pd, dict) and 'degree' in pd:
planet_lons[pn] = pd['degree']
asc_idx = chart_data.get('ascendant', {}).get('sign_idx', 0)
asc_idx = chart_data.get('ascendant_index')
if asc_idx is None:
asc_info = chart_data.get('ascendant', {}) if isinstance(chart_data, dict) else {}
asc_idx = asc_info.get('sign_idx', 0)
if asc_idx == 0 and isinstance(asc_info, dict):
# compute_chart_data 输出 ascendant.sign,但不一定输出 sign_idx;兼容旧结构
sign_name = asc_info.get('sign')
signs = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
if sign_name in signs:
asc_idx = signs.index(sign_name)
if isinstance(asc_idx, str):
asc_idx = int(asc_idx)
+90 -54
View File
@@ -62,7 +62,7 @@ try:
except ImportError:
HAS_SWE = False
from cmd_solar_return import cmd_solar_return # v6.0.18
from cmd_narayana_dasha import cmd_narayana_dasha # v6.0.20
from cmd_narayana_dasha import cmd_narayana_dasha as _cmd_narayana_dasha_impl # v6.0.20
from cmd_muhurta import cmd_muhurta # v6.0.21
# ============================================================================
@@ -2142,11 +2142,11 @@ def _assess_conjunction_quality(lord, houses, planets):
dusthana = {6, 8, 12}
trikona = {1, 5, 9}
kendra = {1, 4, 7, 10}
has_dusthana = any(h in dusthana for h in houses)
has_trikona = any(h in trikona for h in houses)
has_kendra = any(h in kendra for h in houses)
if has_dusthana and has_trikona:
return f"凶吉混合 — 挑战与成长并存"
elif has_dusthana and has_kendra:
@@ -2168,27 +2168,27 @@ def _conflict_arbitration(report):
conflicts = []
planets = report.get('planets', {})
audit = report.get('audit', {})
p1 = audit.get('P1_identity', {})
p7 = audit.get('P7_dignity', {})
p2 = audit.get('P2_health', {})
asc_lord = p1.get('asc_lord', '')
# 规则1: P1清理者+P7入旺 → 检查上升主是否掌管8/12宫(清理者角色)
# 清理者定义:掌管8宫或12宫的行星
SIGN_LORDS_MAP = {'Aries': 'Mars', 'Taurus': 'Venus', 'Gemini': 'Mercury', 'Cancer': 'Moon',
'Leo': 'Sun', 'Virgo': 'Mercury', 'Libra': 'Venus', 'Scorpio': 'Mars',
'Sagittarius': 'Jupiter', 'Capricorn': 'Saturn', 'Aquarius': 'Saturn', 'Pisces': 'Jupiter'}
asc_sign = p1.get('asc_sign', '')
asc_idx_local = SIGNS.index(asc_sign) if asc_sign in SIGNS else 0
# 找8宫主和12宫主
h8_sign = SIGNS[(asc_idx_local + 7) % 12]
h12_sign = SIGNS[(asc_idx_local + 11) % 12]
destroyer_lords = {SIGN_LORDS_MAP.get(h8_sign, ''), SIGN_LORDS_MAP.get(h12_sign, '')}
for dl in destroyer_lords:
if dl and dl in p7:
dl_status = p7[dl].get('status', '')
@@ -2199,7 +2199,7 @@ def _conflict_arbitration(report):
'verdict': '带毒高价值资产',
'instruction': f"{dl}既是清理者(掌8/12宫)又入旺/入庙,力量极强但方向凶险——禁止说逢凶化吉",
})
# 规则2: P5凶宫+BAV高 → 检查6/8/12宫的SAV是否 >28
asht_data = report.get('ashtakavarga', {})
house_scores = {}
@@ -2212,7 +2212,7 @@ def _conflict_arbitration(report):
house_scores = asht_result.get('house_scores', {})
except:
pass
for h in [6, 8, 12]:
hs = house_scores.get(f'house_{h}', {})
sav_score = hs.get('score', 0)
@@ -2224,7 +2224,7 @@ def _conflict_arbitration(report):
'verdict': '乱世出英雄',
'instruction': f"{h}宫是凶宫但SAV={sav_score}>28),在困境中反而能出成就",
})
# 规则3: P1吉+P2受损 → 上升主星状态好但太阳(健康指标)受损
sun_info = p2.get('sun_status', '')
lord_info = p7.get(asc_lord, {})
@@ -2236,7 +2236,7 @@ def _conflict_arbitration(report):
'verdict': '空有雄心无着力点',
'instruction': f"上升主{asc_lord}强健但太阳受损,有野心但执行力/健康跟不上",
})
return conflicts
@@ -2595,41 +2595,30 @@ def cmd_jaimini(args):
# ============================================================================
# 18. 高级Nakshatra分析(v3.7新增
# 18. 高级Nakshatra分析(v3.7 → v6.0.22 移至 cmd_nakshatra_adv.py
# ============================================================================
def cmd_nakshatra_adv(args):
def cmd_narayana_dasha(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
sys.path.insert(0, SCRIPT_DIR)
from nakshatra_advanced import find_nakshatra, calc_all_tara_balas, calc_sub_lord, nakshatra_compatibility
except ImportError as e:
return {"error": f"nakshatra_advanced模块导入失败: {e}"}
planets = chart.get('planets', {})
planet_lons = {}
for pn, pd in planets.items():
if isinstance(pd, dict) and 'degree' in pd:
planet_lons[pn] = pd['degree']
if chart is not None:
chart['ascendant_index'] = asc_idx
return _cmd_narayana_dasha_impl(args, chart)
result = {'planets': {}}
mode = args.mode or 'all'
if mode in ('all', 'detail'):
for pn, lon in planet_lons.items():
result['planets'][pn] = find_nakshatra(lon)
def cmd_nakshatra_adv(args):
from cmd_nakshatra_adv import cmd_nakshatra_adv as _impl
return _impl(args)
if mode in ('all', 'tara'):
moon_lon = planet_lons.get('Moon', 0)
moon_nak_idx = int(moon_lon / (360/27)) % 27
result['tara_bala'] = calc_all_tara_balas(moon_nak_idx, planet_lons)
if mode in ('all', 'sublord'):
result['sub_lords'] = {pn: calc_sub_lord(lon) for pn, lon in planet_lons.items()}
def cmd_nakshatra_dasha(args):
from cmd_nakshatra_adv import cmd_nakshatra_dasha as _impl
return _impl(args)
return result
def cmd_nakshatra_full(args):
from cmd_nakshatra_adv import cmd_nakshatra_full as _impl
return _impl(args)
# ============================================================================
@@ -3648,7 +3637,6 @@ def cmd_full_reading(args):
try:
from narayana_dasha import narayana_dasha_full_report
# 计算当前年龄(从出生到 today)
from datetime import datetime
birth_dt = datetime(args.year, args.month, args.day, args.hour, args.minute)
today_dt = datetime.strptime(args.today, '%Y-%m-%d') if hasattr(args, 'today') and args.today else datetime.now()
current_age = (today_dt - birth_dt).days / 365.25
@@ -3708,20 +3696,50 @@ def cmd_full_reading(args):
except Exception as e:
report['errors'].append(f"jaimini: {e}")
# ── Step 7: 高级Nakshatra ──
# ── Step 7: 高级Nakshatrav6.0.22 升级:含 Chandra Bala + 综合报告)──
try:
from nakshatra_advanced import find_nakshatra, calc_all_tara_balas, calc_sub_lord
nak_result = {'planets': {}}
for pn, lon in planet_lons.items():
nak_result['planets'][pn] = find_nakshatra(lon)
moon_nak_idx = int(planet_lons.get('Moon', 0) / (360/27)) % 27
nak_result['tara_bala'] = calc_all_tara_balas(moon_nak_idx, planet_lons)
nak_result['sub_lords'] = {pn: calc_sub_lord(lon) for pn, lon in planet_lons.items()}
report['modules']['nakshatra_advanced'] = nak_result
from nakshatra_advanced import nakshatra_full_report as nakshatra_report
nak_full = nakshatra_report(chart)
report['modules']['nakshatra_advanced'] = nak_full
except Exception as e:
report['errors'].append(f"nakshatra-adv: {e}")
# ── Step 7.5: Nakshatra Dasha 星宿大运(v6.0.22 新增)──
try:
from nakshatra_dasha import nakshatra_dasha_full_report
birth_date_str = f"{args.year}-{args.month:02d}-{args.day:02d}"
# 年龄
nak_age = args.age
if nak_age is None:
try:
birth_date = datetime(args.year, args.month, args.day)
nak_age = (datetime.now() - birth_date).days / 365.25
except:
nak_age = None
if nak_age is not None:
# 获取过境星宿数据
transit_lons_for_nak = None
try:
transit_ref = getattr(args, 'transit_date', None) or datetime.now().strftime('%Y-%m-%d')
ty, tm, td = map(int, transit_ref.split('-'))
transit_jd_nak = swe.julday(ty, tm, td, 12.0 - args.tz)
transit_pl_nak, _ = _calc_sidereal_planets_for_jd(
transit_jd_nak, node_mode=getattr(args, 'node_mode', 'mean'), include_ketu=True)
transit_lons_for_nak = {}
for pn, pd in transit_pl_nak.items():
if isinstance(pd, dict) and 'degree' in pd:
transit_lons_for_nak[pn] = pd['degree']
except:
pass
nak_dasha = nakshatra_dasha_full_report(
chart, birth_date_str, nak_age, transit_lons_for_nak)
report['modules']['nakshatra_dasha'] = nak_dasha
except Exception as e:
report['errors'].append(f"nakshatra-dasha: {e}")
# ── Step 8: Argala门闩 ──
try:
from argala import calc_argala
@@ -4109,10 +4127,27 @@ def main():
p.add_argument('--mode', default='all', choices=['all','karaka','dasha','karakamsha'], help='分析模式')
p.add_argument('--antardasha', action='store_true', help='Chara Dasha含Antardasha子周期(当前timing实现为partial')
# 18. nakshatra-adv (v3.7新增)
p = sub.add_parser('nakshatra-adv', help='高级Nakshatra分析')
# 18. nakshatra-adv (v3.7新增 → v6.0.22 升级)
p = sub.add_parser('nakshatra-adv', help='高级Nakshatra分析Tara/Chandra/Sub-Lord/综合)')
_add_chart_args(p)
p.add_argument('--mode', default='all', choices=['all','detail','tara','sublord'], help='分析模式')
p.add_argument('--mode', default='all',
choices=['all','detail','tara','chandra','combined','sublord','full'],
help='分析模式')
# 18.5 nakshatra-dasha (v6.0.22新增)
p = sub.add_parser('nakshatra-dasha', help='星宿大运推演(Ashtottari / Vimshottari Nakshatra-level / Transit Overlay')
_add_chart_args(p)
p.add_argument('--age', type=float, default=None, help='当前年龄(自动计算如果不提供)')
p.add_argument('--mode', default='all',
choices=['all','ashtottari','vimshottari','overlay'],
help='分析模式')
p.add_argument('--transit-date', default=None, help='过境日期 YYYY-MM-DDoverlay/all模式,默认今天)')
# 18.6 nakshatra-full (v6.0.22新增)
p = sub.add_parser('nakshatra-full', help='综合星宿完整报告(本命 + 大运 + 过境)')
_add_chart_args(p)
p.add_argument('--age', type=float, default=None, help='当前年龄')
p.add_argument('--transit-date', default=None, help='过境日期 YYYY-MM-DD(默认今天)')
# 19. argala (v3.7新增)
p = sub.add_parser('argala', help='Argala门闩系统')
@@ -4207,7 +4242,8 @@ def main():
'shadbala': cmd_shadbala, 'ashtakavarga': cmd_ashtakavarga, 'memory': cmd_memory,
'validate': cmd_validate, 'audit': cmd_audit, 'report': cmd_report,
'varga-full': cmd_varga_full, 'aspects': cmd_aspects, 'jaimini': cmd_jaimini,
'nakshatra-adv': cmd_nakshatra_adv, 'argala': cmd_argala, 'tajika': cmd_tajika,
'nakshatra-adv': cmd_nakshatra_adv, 'nakshatra-dasha': cmd_nakshatra_dasha,
'nakshatra-full': cmd_nakshatra_full, 'argala': cmd_argala, 'tajika': cmd_tajika,
'synastry': cmd_synastry, 'solar-return': cmd_solar_return,
'narayana-dasha': cmd_narayana_dasha, 'muhurta': cmd_muhurta,
'full-reading': cmd_full_reading, 'prashna': cmd_prashna,
+352 -25
View File
@@ -1,18 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
高级星宿分析模块 v1.0
高级星宿分析模块 v2.0
Nakshatra深度分析系统
支持:
- Tara Bala(星宿力量匹配)
- Tara Bala(星宿力量匹配 — Navatara 9星宿循环
- Chandra Bala(月座力量 — 12宫循环)
- Sub-Lord体系(KP占星)
- 精确Vimshottari起始点计算
- Nakshatra兼容性分析
- Navatara9星宿组)分类
- 综合星宿力量报告
"""
from typing import Dict, List, Tuple
from typing import Dict, List, Tuple, Optional
import math
from datetime import datetime, timedelta
NAK_LIST = [
("Ashwini","Ketu",7),("Bharani","Venus",20),("Krittika","Sun",6),
@@ -59,7 +62,7 @@ def find_nakshatra(longitude: float) -> Dict:
pada_span = nak_span / 4 # 每个Pada 3.333...°
pada = int(deg_in_nak / pada_span) + 1
deg_in_pada = deg_in_nak - (pada - 1) * pada_span
return {
'nakshatra': NAK_NAMES[nak_idx],
'nakshatra_idx': nak_idx,
@@ -76,7 +79,7 @@ def find_nakshatra(longitude: float) -> Dict:
def calc_tara_bala(moon_nak_idx: int, target_nak_idx: int) -> Dict:
"""
Tara Bala计算:月亮星宿到目标星宿的9星宿循环关系
用于判断某个行星/事件星宿与月亮星宿的关系
0=Janma(中性), 1=Sampat(吉), 2=Vipat(凶), 3=Kshema(吉),
4=Pratyak(凶), 5=Sadhana(吉), 6=Vadha(凶), 7=Mitra(吉), 8=ParamaMitra(大吉)
@@ -84,10 +87,10 @@ def calc_tara_bala(moon_nak_idx: int, target_nak_idx: int) -> Dict:
distance = (target_nak_idx - moon_nak_idx) % 9
tara_name = TARA_NAMES[distance]
tara_cn = TARA_CN[distance]
is_auspicious = distance in (1, 3, 5, 7, 8)
is_dangerous = distance in (2, 4, 6)
return {
'tara_index': distance,
'tara_name': tara_name,
@@ -115,25 +118,25 @@ def calc_all_tara_balas(moon_nak_idx: int, planet_lons: Dict[str, float]) -> Dic
def calc_vimshottari_start(moon_longitude: float) -> Dict:
"""
精确计算Vimshottari Dasha的起始点
返回月亮所在星宿的守护星、已用度数比例、剩余年数
"""
nak = find_nakshatra(moon_longitude)
nak_idx = nak['nakshatra_idx']
nak_lord = NAK_LORDS[nak_idx]
total_years = NAK_YEARS[nak_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
remaining_ratio = 1 - used_ratio
# 第一个Mahadasha的剩余年数
first_mahadasha_remaining = total_years * remaining_ratio
first_mahadasha_elapsed = total_years * used_ratio
return {
'moon_nakshatra': nak['nakshatra'],
'moon_nakshatra_lord': nak_lord,
@@ -151,7 +154,7 @@ def calc_sub_lord(longitude: float, division: int = 9) -> Dict:
"""
Sub-Lord计算(KP占星体系)
将星宿进一步细分为Sub-Lord和Sub-Sub-Lord
参数:
longitude: 恒星黄道经度
division: 细分层级(9=Sub-Lord, 81=Sub-Sub-Lord
@@ -159,24 +162,21 @@ def calc_sub_lord(longitude: float, division: int = 9) -> Dict:
# 主星宿
nak = find_nakshatra(longitude)
nak_span = 360.0 / 27
# Sub-Lord: 将星宿按守护星的大运年数比例细分
total_dasha_years = sum(NAK_YEARS) # 120年
nak_lord = NAK_NAMES[nak['nakshatra_idx']]
# 计算Sub-Lord
# Sub-Lord: 当前版本为简化版,直接按9等分。
# 完整 KP 体系应按 Vimshottari 年数比例不等分,后续需单独对标。
nak_lord = NAK_LORDS[nak['nakshatra_idx']]
deg_in_nak = nak['degree_in_nakshatra']
sub_lord_span = nak_span * (NAK_YEARS[NAK_LORDS.index(NAK_LORDS[nak['nakshatra_idx']])]) / total_dasha_years
# 简化版:直接按9等分
sub_span = nak_span / 9
sub_idx = int(deg_in_nak / sub_span) % 9
# Sub-Lord是第sub_idx个行星(从星宿守护星开始的大运顺序)
DASHA_ORDER = ["Ketu","Venus","Sun","Moon","Mars","Rahu","Jupiter","Saturn","Mercury"]
nak_lord_idx = DASHA_ORDER.index(NAK_LORDS[nak['nakshatra_idx']])
sub_lord = DASHA_ORDER[(nak_lord_idx + sub_idx) % 9]
return {
'nakshatra': nak['nakshatra'],
'nakshatra_lord': nak_lord,
@@ -195,17 +195,17 @@ def nakshatra_compatibility(nak1_idx: int, nak2_idx: int) -> Dict:
# Tara匹配
tara = calc_tara_bala(nak1_idx, nak2_idx)
tara_score = 3 if tara['is_auspicious'] else 0 if tara['is_dangerous'] else 1.5
# 元素匹配
el1 = NAK_ELEMENT.get(nak1_idx, '')
el2 = NAK_ELEMENT.get(nak2_idx, '')
el_compatible = el1 == el2 or (el1 in ('','') and el2 in ('','')) or (el1 in ('','') and el2 in ('',''))
# Gana匹配
g1 = NAK_GANA.get(nak1_idx, '')
g2 = NAK_GANA.get(nak2_idx, '')
gana_score = 6 if g1 == g2 else 3 if ('Dev' in g1 and 'Manushya' in g2) or ('Manushya' in g1 and 'Dev' in g2) else 0
return {
'nak1': NAK_NAMES[nak1_idx],
'nak2': NAK_NAMES[nak2_idx],
@@ -230,3 +230,330 @@ def _tara_interp(idx):
8: '至友Tara—大吉,最强吉Tara',
}
return interps.get(idx, '')
# ============================================================================
# Chandra Bala(月座力量)— v2.0 新增
# ============================================================================
# 12宫 Chandra Bala 名称(从月亮星座起算)
CHANDRA_NAMES = [
'Janma(生命)', 'Sampat(财富)', 'Vipat(危险)', 'Kshema(安全)',
'Pratyak(障碍)', 'Sadhana(成就)', 'Vadha(毁灭)', 'Mitra(友好)',
'ParamaMitra(至友)', 'Karma(业力)', 'Labha(收益)', 'Vyaya(消耗)',
]
CHANDRA_CN = {
0: '生命位', 1: '财富位', 2: '危险位', 3: '安全位',
4: '障碍位', 5: '成就位', 6: '毁灭位', 7: '友好位',
8: '至友位', 9: '业力位', 10: '收益位', 11: '消耗位',
}
def calc_chandra_bala(natal_moon_sign_idx: int, transit_moon_sign_idx: int) -> Dict:
"""
Chandra Bala(月座力量)计算 — v2.0 新增
基于月亮所在宫位(Rashi/Sign)的相对位置评估力量。
从本命月亮星座起算到当前(过境)月亮星座的12宫循环。
与 Tara Bala(星宿/Nakshatra 层面)互补,提供 Rashi/Sign 层面的力量评估。
常用于 Muhurta 择时和 Transit 过境分析。
参数:
natal_moon_sign_idx: 本命月亮星座索引 (0=Aries ~ 11=Pisces)
transit_moon_sign_idx: 过境月亮星座索引 (0=Aries ~ 11=Pisces)
返回:
dict: {
'rashi_from_moon': 0-11,
'chandra_name': 'Janma/Sampat/...',
'chandra_cn': '生命位/财富位/...',
'is_auspicious': bool,
'is_dangerous': bool,
'quality': 'auspicious'/'dangerous'/'neutral',
'interpretation': str,
}
"""
# 从本命月亮星座到过境月亮星座的步数(0-based, 12宫循环)
distance = (transit_moon_sign_idx - natal_moon_sign_idx) % 12
name = CHANDRA_NAMES[distance]
cn = CHANDRA_CN[distance]
# 吉位:Sampat(1), Kshema(3), Sadhana(5), Mitra(7), ParamaMitra(8), Labha(10)
# 凶位:Vipat(2), Pratyak(4), Vadha(6), Karma(9)
# 中性:Janma(0), Vyaya(11)
is_auspicious = distance in (1, 3, 5, 7, 8, 10)
is_dangerous = distance in (2, 4, 6, 9)
if is_auspicious:
quality = 'auspicious'
elif is_dangerous:
quality = 'dangerous'
else:
quality = 'neutral'
return {
'rashi_from_moon': distance,
'chandra_name': name,
'chandra_cn': cn,
'is_auspicious': is_auspicious,
'is_dangerous': is_dangerous,
'quality': quality,
'interpretation': _chandra_interp(distance),
}
def _chandra_interp(idx: int) -> str:
"""Chandra Bala 解释"""
interps = {
0: '生命位—代表身体和自我状态,中性',
1: '财富位—吉,有利于财务和资源获取',
2: '危险位—凶,可能遭遇损失和挑战',
3: '安全位—吉,提供保护和稳定感',
4: '障碍位—凶,面临困难和拖延',
5: '成就位—吉,有利于达成目标和获得成功',
6: '毁灭位—凶,需警惕冲突和破坏',
7: '友好位—吉,获得他人的支持和帮助',
8: '至友位—大吉,最强吉位,全面有利',
9: '业力位—凶,涉及过往业力的清算',
10: '收益位—吉,有利于收入和事业增长',
11: '消耗位—中性偏凶,可能消耗能量和资源',
}
return interps.get(idx, '')
def calc_tara_chandra_combined(
natal_moon_nak_idx: int,
natal_moon_sign_idx: int,
planet_lons: Dict[str, float],
transit_moon_lon: Optional[float] = None,
transit_moon_sign_idx: Optional[int] = None,
) -> Dict:
"""
Tara Bala + Chandra Bala 双维度综合行星力量分析 — v2.0 新增
同时评估:
1. Tara Bala(Star层面):月亮星宿到行星星宿的9星宿循环
2. Chandra BalaSign层面):月亮星座到行星星座的12宫循环
双吉 = 最强力,双凶 = 最弱力,一吉一凶 = 混合力量
参数:
natal_moon_nak_idx: 本命月亮星宿索引 (0-26)
natal_moon_sign_idx: 本命月亮星座索引 (0-11)
planet_lons: {行星名: 恒星黄道经度}
transit_moon_lon: 过境月亮经度(可选,用于过境分析)
transit_moon_sign_idx: 过境月亮星座索引(可选,用于过境分析)
返回:
dict: {planet: {tara, chandra, combined_score, interpretation}}
"""
results = {}
for pname, lon in planet_lons.items():
# Tara BalaNakshatra层面)
p_nak = find_nakshatra(lon)
tara = calc_tara_bala(natal_moon_nak_idx, p_nak['nakshatra_idx'])
# Chandra BalaRashi层面)
p_sign_idx = int(lon / 30) % 12
chandra = calc_chandra_bala(natal_moon_sign_idx, p_sign_idx)
# 综合评分
combined_score = _combined_score(tara, chandra)
results[pname] = {
'nakshatra': p_nak['nakshatra'],
'sign_idx': p_sign_idx,
'tara': tara,
'chandra': chandra,
'combined_score': combined_score,
'assessment': _combined_assessment(combined_score),
}
return results
def _combined_score(tara: Dict, chandra: Dict) -> str:
"""综合 Tara + Chandra 评分"""
t_good = tara['is_auspicious']
c_good = chandra['is_auspicious']
t_bad = tara['is_dangerous']
c_bad = chandra['is_dangerous']
if t_good and c_good:
return 'double_auspicious'
elif t_bad and c_bad:
return 'double_dangerous'
elif t_good and c_bad:
return 'tara_good_chandra_bad'
elif t_bad and c_good:
return 'tara_bad_chandra_good'
else:
# 至少有一方中性
if t_good or c_good:
return 'mixed_favorable'
elif t_bad or c_bad:
return 'mixed_unfavorable'
else:
return 'neutral'
def _combined_assessment(score: str) -> str:
"""综合评估说明"""
assessments = {
'double_auspicious': '双吉—Star与Sign层面均有利,最强力支持',
'double_dangerous': '双凶—Star与Sign层面均不利,需格外谨慎',
'tara_good_chandra_bad': '星吉宫凶—Nakshatra有利但Rashi层面受阻,可能好事多磨',
'tara_bad_chandra_good': '星凶宫吉—Nakshatra不利但Rashi层面有保护,或许有惊无险',
'mixed_favorable': '偏吉—至少一方有利,方向积极',
'mixed_unfavorable': '偏凶—至少一方不利,需注意',
'neutral': '中性—无明确吉凶指向',
}
return assessments.get(score, '')
def calc_nakshatra_transits_natal(
planet_lons: Dict[str, float],
) -> Dict:
"""
计算所有行星在星盘中的 Nakshatra 分布 — v2.0 新增
用于快速查看每个行星落在哪个星宿、Pada、守护星等信息。
返回:
dict: {planet: {nakshatra, pada, lord, gana, element, degree_in_nak}}
"""
results = {}
for pname, lon in planet_lons.items():
nak = find_nakshatra(lon)
results[pname] = {
'nakshatra': nak['nakshatra'],
'nakshatra_idx': nak['nakshatra_idx'],
'nakshatra_lord': nak['nakshatra_lord'],
'pada': nak['pada'],
'gana': nak['gana'],
'element': nak['element'],
'degree_in_nakshatra': nak['degree_in_nakshatra'],
'dasha_years': nak['dasha_years'],
}
return results
def nakshatra_full_report(
chart_data: Dict,
age: Optional[float] = None,
transit_date: Optional[str] = None,
) -> Dict:
"""
综合星宿力量完整报告 — v2.0 新增
包含:
1. 本命 Nakshatra 分布(所有行星)
2. Tara Bala 全部分析(以月亮星宿为基准)
3. Chandra Bala 全部分析(以月亮星座为基准)
4. Tara+Chandra 双维度综合评分
5. Nakshatra 兼容性(行星间的星宿关系)
6. Vimshottari 起始点信息
7. Sub-Lord KP 分析
参数:
chart_data: 星盘数据 (from jyotish_engine chart command)
age: 当前年龄(可选)
transit_date: 过境日期 YYYY-MM-DD(可选)
返回:
dict: 完整星宿报告
"""
planets = chart_data.get('planets', {})
planet_lons = {}
planet_signs = {}
for pn, pd in planets.items():
if isinstance(pd, dict) and 'degree' in pd:
planet_lons[pn] = pd['degree']
planet_signs[pn] = int(pd['degree'] / 30) % 12
if not planet_lons:
return {'error': '无法获取行星经度数据'}
moon_lon = planet_lons.get('Moon', 0)
moon_nak_idx = int(moon_lon / (360/27)) % 27
moon_sign_idx = int(moon_lon / 30) % 12
report = {}
# 1. 本命星宿分布
report['natal_nakshatras'] = calc_nakshatra_transits_natal(planet_lons)
# 2. Tara Bala
report['tara_bala'] = calc_all_tara_balas(moon_nak_idx, planet_lons)
# 3. Chandra Bala
chandra_bala = {}
for pn, lon in planet_lons.items():
p_sign = int(lon / 30) % 12
chandra_bala[pn] = {
'planet_sign': p_sign,
'chandra': calc_chandra_bala(moon_sign_idx, p_sign),
}
report['chandra_bala'] = chandra_bala
# 4. Tara + Chandra 双维综合
report['tara_chandra_combined'] = calc_tara_chandra_combined(
moon_nak_idx, moon_sign_idx, planet_lons)
# 5. Sub-Lords
report['sub_lords'] = {pn: calc_sub_lord(lon) for pn, lon in planet_lons.items()}
# 6. Vimshottari 起始点
report['vimshottari_start'] = calc_vimshottari_start(moon_lon)
# 7. 星宿力量排序
power_ranking = []
for pn, combined in report['tara_chandra_combined'].items():
score_val = _score_value(combined['combined_score'])
power_ranking.append({
'planet': pn,
'nakshatra': combined['nakshatra'],
'tara_name': combined['tara']['tara_cn'],
'tara_quality': combined['tara']['quality'],
'chandra_name': combined['chandra']['chandra_cn'],
'chandra_quality': combined['chandra']['quality'],
'combined': combined['combined_score'],
'score': score_val,
'assessment': combined['assessment'],
})
power_ranking.sort(key=lambda x: x['score'], reverse=True)
report['power_ranking'] = power_ranking
# 8. 汇总
double_auspicious = [p['planet'] for p in power_ranking if p['combined'] == 'double_auspicious']
double_dangerous = [p['planet'] for p in power_ranking if p['combined'] == 'double_dangerous']
report['summary'] = {
'moon_nakshatra': NAK_NAMES[moon_nak_idx],
'moon_nakshatra_lord': NAK_LORDS[moon_nak_idx],
'moon_sign_idx': moon_sign_idx,
'strongest_planets': [p['planet'] for p in power_ranking[:3]],
'weakest_planets': [p['planet'] for p in power_ranking[-3:]],
'double_auspicious_planets': double_auspicious,
'double_dangerous_planets': double_dangerous,
'tara_auspicious_count': sum(1 for p in power_ranking if p['tara_quality'] == 'auspicious'),
'chandra_auspicious_count': sum(1 for p in power_ranking if p['chandra_quality'] == 'auspicious'),
}
return report
def _score_value(combined: str) -> int:
"""将组合评分转为数值"""
scores = {
'double_auspicious': 100,
'tara_good_chandra_bad': 60,
'tara_bad_chandra_good': 60,
'mixed_favorable': 55,
'neutral': 50,
'mixed_unfavorable': 45,
'double_dangerous': 0,
}
return scores.get(combined, 50)
+692
View File
@@ -0,0 +1,692 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
星宿大运推演系统 v1.0 — Nakshatra Dasha Engine
支持:
- Ashtottari Dasha108年星宿大运,条件性系统)
- 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 不在 Kendra1/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 Dasha108年星宿大运)',
'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 Antardasha9个周期)"""
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')