#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Narayana Dasha(Rishi Dasha / Padakrama Dasha)计算模块 Jyotish Vedic Astrology Skill — v6.0.20 Narayana Dasha 是 Parashara 传授的 Rishi-based 大运系统,与 Vimshottari 互补: - Vimshottari: Nakshatra-based, 120年固定周期 - Narayana: Rashi-based, 从 Lagna 起按黄道序推进,周期可变 算法(Lagna-based variant, BPHS Chapter 48): 1. 从 Lagna 星座开始 2. 每个星座的大运年数 = 从该星座数到其守护星所在星座的步数(含起点,不含终点) 3. 若守护星在本星座 → 12年 4. 按黄道顺序推进 12 星座,然后循环 5. Antardasha 按各星座年数比例分配 依赖: 需要 planet_lons 和 houses 数据(可从引擎传入) """ from typing import Dict, List, Optional, Tuple import math # ── 常量 ────────────────────────────────────────────────────────── SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo', 'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] 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', } SIGN_INDEX = {s:i for i,s in enumerate(SIGNS)} # Planet name → sign index lookup PLANET_SIGN_INDEX = { 'Sun': 4, 'Moon': 3, 'Mars': (0,7), 'Mercury': (2,5), 'Jupiter': (8,11), 'Venus': (1,6), 'Saturn': (9,10), 'Rahu': 10, 'Ketu': 7, # traditional assignments } # ========================================================================= # 核心计算 # ========================================================================= def _get_planet_sign(planet_name: str, planet_lons: Dict[str, float]) -> Optional[int]: """获取行星所在星座索引""" lon = planet_lons.get(planet_name) if lon is None: return None return int(lon / 30) % 12 def _count_signs_forward(src_idx: int, dest_idx: int) -> int: """从 src 数到 dest(含起点,不含终点),顺黄道方向。 若 src == dest → 12 年(守护星在本星座)""" if src_idx == dest_idx: return 12 count = (dest_idx - src_idx + 12) % 12 return count # 已含起点 def calc_narayana_mahadasha( lagna_sign_idx: int, planet_lons: Dict[str, float], start_year: float = 0.0, ) -> List[Dict]: """ 计算 Narayana Dasha 大运序列(第1周期)。 参数: lagna_sign_idx: Lagna 星座索引 (0-11) planet_lons: 行星经度字典 {planet_name: longitude_deg} start_year: 起始年份偏移(默认 0 = 出生时) 返回: list of dasha periods, each: { 'sign': str, 'sign_idx': int, 'lord': str, 'years': int, 'start_age': float, 'end_age': float, } """ periods = [] cum_years = start_year for i in range(12): sign_idx = (lagna_sign_idx + i) % 12 sign = SIGNS[sign_idx] lord = SIGN_LORDS[sign] # 获取 lord 所在的星座 lord_sign_idx = _get_planet_sign(lord, planet_lons) if lord_sign_idx is None: # fallback: 如果找不到 lord 位置,用 lord 的 Moolatrikona 或自身星座 lord_sign_idx = sign_idx # 保守假设:lord 在自己星座 years = _count_signs_forward(sign_idx, lord_sign_idx) periods.append({ 'sign': sign, 'sign_idx': sign_idx, 'lord': lord, 'lord_in_sign': SIGNS[lord_sign_idx], 'lord_sign_idx': lord_sign_idx, 'years': years, 'count_from_to': f'{sign}({sign_idx})→{SIGNS[lord_sign_idx]}({lord_sign_idx})', 'start_age': round(cum_years, 2), 'end_age': round(cum_years + years, 2), }) cum_years += years return periods def calc_narayana_antardasha( mahadasha_periods: List[Dict], md_sign_idx: int, ) -> List[Dict]: """ 计算给定 Mahadasha 的 Antardasha 子周期。 参数: mahadasha_periods: calc_narayana_mahadasha 的返回值 md_sign_idx: Mahadasha 星座索引 返回: list of antardasha periods """ # 找到对应的大运 md = None for p in mahadasha_periods: if p['sign_idx'] == md_sign_idx: md = p break if md is None: return [] return _subdivide_narayana_period( mahadasha_periods=mahadasha_periods, parent_period=md, start_sign_idx=md_sign_idx, parent_key='parent_md', parent_name=SIGNS[md_sign_idx], ) def calc_narayana_pratyantardasha( mahadasha_periods: List[Dict], antardasha_period: Dict, ) -> List[Dict]: """ 计算给定 Antardasha 的 Pratyantardasha 子周期。 返回的 start_age / end_age 与 Mahadasha、Antardasha 使用同一条绝对年龄轴, 方便 get_current_narayana_dasha 直接定位当前周期。 """ if not antardasha_period: return [] sign_idx = antardasha_period.get('sign_idx') if sign_idx is None: return [] return _subdivide_narayana_period( mahadasha_periods=mahadasha_periods, parent_period=antardasha_period, start_sign_idx=sign_idx, parent_key='parent_ad', parent_name=antardasha_period.get('sign', SIGNS[sign_idx]), ) def _subdivide_narayana_period( mahadasha_periods: List[Dict], parent_period: Dict, start_sign_idx: int, parent_key: str, parent_name: str, ) -> List[Dict]: """按 Narayana 星座年数权重切分父周期,返回绝对年龄轴上的子周期。""" if not mahadasha_periods: return [] period_by_sign = {p['sign_idx']: p for p in mahadasha_periods} denominator = sum(float(p.get('years', 0)) for p in mahadasha_periods) if denominator <= 0: return [] total_years = float(parent_period.get('years', 0)) parent_start = float(parent_period.get('start_age', 0)) parent_end = float(parent_period.get('end_age', parent_start + total_years)) if len(period_by_sign) == 12: weighted_sequence = [period_by_sign[(start_sign_idx + i) % 12] for i in range(12)] else: start_pos = next( (i for i, p in enumerate(mahadasha_periods) if p.get('sign_idx') == start_sign_idx), 0, ) weighted_sequence = mahadasha_periods[start_pos:] + mahadasha_periods[:start_pos] sub_periods = [] cum = parent_start sequence_len = len(weighted_sequence) for i, weighted_period in enumerate(weighted_sequence): sign_idx = weighted_period['sign_idx'] if i == sequence_len - 1: end_age = parent_end else: sub_years_raw = total_years * float(weighted_period.get('years', 0)) / denominator end_age = cum + sub_years_raw start_age = cum years = max(0.0, end_age - start_age) sub_periods.append({ 'sign': SIGNS[sign_idx], 'sign_idx': sign_idx, 'lord': SIGN_LORDS[SIGNS[sign_idx]], 'years': round(years, 4), 'start_age': round(start_age, 4), 'end_age': round(end_age, 4), parent_key: parent_name, 'sequence_index': i, }) cum = end_age return sub_periods def get_current_narayana_dasha( mahadasha_periods: List[Dict], current_age: float, ) -> Dict: """ 获取当前年龄对应的 Narayana Dasha 周期(Mahadasha + Antardasha)。 返回: { 'md': {...}, # 当前大运 'ad': {...}, # 当前小运 'pd': {...}, # 当前节运(Pratyantara,简化) 'remaining_years': float, } """ result = {'md': None, 'ad': None, 'pd': None, 'remaining_years': 0} # 处理多年期(可能跨多个周期) total_cycle = sum(p['years'] for p in mahadasha_periods) if total_cycle == 0: return result cycle_start = min(float(p.get('start_age', 0)) for p in mahadasha_periods) age_in_cycle = ((current_age - cycle_start) % total_cycle) + cycle_start # 找当前 MD for p in mahadasha_periods: if p['start_age'] <= age_in_cycle < p['end_age']: result['md'] = { 'sign': p['sign'], 'sign_idx': p['sign_idx'], 'lord': p['lord'], 'years': p['years'], 'start_age': p['start_age'], 'end_age': p['end_age'], } result['remaining_years'] = round(p['end_age'] - age_in_cycle, 2) # 计算 AD ads = calc_narayana_antardasha(mahadasha_periods, p['sign_idx']) for ad in ads: if ad['start_age'] <= age_in_cycle < ad['end_age']: result['ad'] = { 'sign': ad['sign'], 'sign_idx': ad['sign_idx'], 'lord': ad['lord'], 'years': ad['years'], 'start_age': ad['start_age'], 'end_age': ad['end_age'], } pds = calc_narayana_pratyantardasha(mahadasha_periods, ad) for pd in pds: if pd['start_age'] <= age_in_cycle < pd['end_age']: result['pd'] = { 'sign': pd['sign'], 'sign_idx': pd['sign_idx'], 'lord': pd['lord'], 'years': pd['years'], 'start_age': pd['start_age'], 'end_age': pd['end_age'], } break break break return result def narayana_dasha_full_report( lagna_sign_idx: int, planet_lons: Dict[str, float], current_age: float = 0, birth_year: int = 0, ) -> Dict: """ Narayana Dasha 完整报告。 参数: lagna_sign_idx: Lagna 星座索引 (0-11) planet_lons: 行星经度字典 current_age: 当前年龄 birth_year: 出生年份 返回: dict with mahadasha_sequence, current_dasha, total_cycle_years """ result = {} # 1. 完整大运序列 mahadasha = calc_narayana_mahadasha(lagna_sign_idx, planet_lons) total_cycle = sum(p['years'] for p in mahadasha) result['mahadasha_sequence'] = mahadasha result['total_cycle_years'] = total_cycle result['lagna_sign'] = SIGNS[lagna_sign_idx] result['lagna_sign_idx'] = lagna_sign_idx # 2. 当前大运 if current_age > 0: curr = get_current_narayana_dasha(mahadasha, current_age) result['current_dasha'] = curr # 当前日期(如果有出生年份) if birth_year > 0: curr_year = birth_year + int(current_age) result['current_year'] = curr_year result['current_age'] = current_age # 3. 简要解读 result['interpretation'] = _interpret_narayana(result, current_age) return result def _interpret_narayana(result: Dict, current_age: float) -> List[str]: """Narayana Dasha 简要解读""" lines = [] md_seq = result.get('mahadasha_sequence', []) if md_seq: total = sum(p['years'] for p in md_seq) lines.append(f"Narayana Dasha 完整周期: {total} 年(12 星座 × 可变年数)") lines.append(f"起运星座: {result.get('lagna_sign', '?')}(Lagna)") curr = result.get('current_dasha', {}) md = curr.get('md') if md: lord_cond = _check_lord_condition(md['sign_idx'], result.get('lagna_sign_idx', 0)) lines.append(f"当前 Narayana Mahadasha: {md['sign']}(守护星 {md['lord']},{md['years']}年)") lines.append(f" 剩余: {curr.get('remaining_years', 0):.1f}年") lines.append(f" {lord_cond}") ad = curr.get('ad') if ad: lines.append(f"当前 Antardasha: {ad['sign']}(守护星 {ad['lord']},{ad['years']}年)") # 与 Vimshottari 互补提示 lines.append("") lines.append("【与 Vimshottari 互补解读提示】") lines.append("Narayana Dasha 的星座主题与 Vimshottari 的行星主题形成互补。") lines.append("两者一致 → 事件确定性高;两者矛盾 → 混合影响,需看具体宫位。") return lines def _check_lord_condition(sign_idx: int, lagna_idx: int) -> str: """检查当前星座守护星与 Lagna 的关系""" house_from_lagna = (sign_idx - lagna_idx + 12) % 12 + 1 house_labels = { 1: 'Lagna(自我/身体)', 2: '2宫(财富/家庭)', 3: '3宫(兄弟/努力)', 4: '4宫(家庭/房产)', 5: '5宫(子女/创意)', 6: '6宫(健康/竞争)', 7: '7宫(婚姻/合作)', 8: '8宫(转型/遗产)', 9: '9宫(信仰/长途)', 10: '10宫(事业/地位)', 11: '11宫(收益/社交)', 12: '12宫(支出/灵性)', } return f" 从 Lagna 算 = {house_from_lagna}宫 {house_labels.get(house_from_lagna, '')}" # ========================================================================= # CLI 测试入口 # ========================================================================= if __name__ == '__main__': print("Narayana Dasha(Rishi Dasha)模块 v6.0.20") print() # 测试数据:用户星盘 Le Asc, Saturn MD test_lagna = 4 # Leo test_planets = { 'Sun': 19.07, 'Moon': 325.64, 'Mars': 98.12, 'Mercury': 32.56, 'Jupiter': 97.08, 'Venus': 55.01, 'Saturn': 306.96, 'Rahu': 342.27, 'Ketu': 162.27, } test_age = 33.13 # 2026-06-04 年龄 print(f"测试: Leo Asc, age={test_age}") print(f"行星经度: { {k: f'{v:.1f}' for k,v in test_planets.items()} }") print() result = narayana_dasha_full_report(test_lagna, test_planets, test_age, 1990) print("=== 大运序列 ===") for p in result['mahadasha_sequence']: print(f" {p['sign']:>12s} ({p['lord']:>7s} in {p['lord_in_sign']:>12s}): " f"{p['years']:2d}年 [{p['start_age']:5.1f}→{p['end_age']:5.1f}] " f"{p['count_from_to']}") print(f"\n总周期: {result['total_cycle_years']} 年") print("\n=== 当前 Dasha ===") curr = result.get('current_dasha', {}) md = curr.get('md') if md: print(f" MD: {md['sign']} ({md['lord']}) {md['years']}年") print(f" 剩余: {curr['remaining_years']}年") ad = curr.get('ad') if ad: print(f" AD: {ad['sign']} ({ad['lord']}) {ad['years']}年") print("\n=== 解读 ===") for line in result.get('interpretation', []): print(line)