v6.0.20: Narayana Dasha (Rishi Dasha) implementation

Narayana Dasha is Parashara's rashi-based dasha system, complementary to
Vimshottari (nakshatra-based). Algorithm: start from Lagna, each sign's
dasha years = count from the sign to its lord's sign (zodiacal order).

Implementation:
- scripts/narayana_dasha.py: core calculation module
  - calc_narayana_mahadasha(): 12-sign sequence from Lagna
  - calc_narayana_antardasha(): sub-periods within each MD
  - get_current_narayana_dasha(): locate current period by age
- scripts/cmd_narayana_dasha.py: CLI subcommand
- jyotish_engine.py: Step 4.13 in full-reading + narayana-dasha subparser

Verified with Leo Asc chart: total cycle 88 years, current Sagittarius MD
(31-38 yrs, 5H/creativity) complements Vimshottari Saturn MD.

Files: +2 new (narayana_dasha.py, cmd_narayana_dasha.py), +4 modified
This commit is contained in:
732642856
2026-06-04 11:08:05 +08:00
parent 78e6f12b43
commit e2aa76a786
6 changed files with 522 additions and 4 deletions
+37
View File
@@ -1,5 +1,42 @@
# 印度占星 Skill 更新日志
## v6.0.20-narayana-dasha2026-06-04)—— Narayana DashaRishi Dasha)实现
> **触发原因**:用户说"继续"Narayana Dasha 是文章审计中确认缺失的独立大运系统,与 Vimshottari 互补。
### 变更内容
- `scripts/narayana_dasha.py`(新文件 v6.0.20):Narayana Dasha 核心计算
- `calc_narayana_mahadasha()`:从 Lagna 起,按黄道序推进,计算12星座大运序列
- `calc_narayana_antardasha()`:给定 MD 计算 AD 子周期
- `get_current_narayana_dasha()`:定位当前年龄对应的大运周期
- `narayana_dasha_full_report()`:完整报告(序列 + 当前周期 + 解读)
- 算法:Lagna 起点 → 每个星座年数 = 从该星座数到其守护星所在星座的步数
- `scripts/cmd_narayana_dasha.py`(新文件 v6.0.20):`narayana-dasha` 子命令
- `scripts/jyotish_engine.py` 更新:
- 新增 `cmd_narayana_dasha` import 和注册
- `cmd_full_reading` 新增 Step 4.13Narayana Dasha 分析)
- `narayana-dasha` subparser 新增 `--age` 参数
- `references/technique_registry.json` 更新:新增 narayana_dashacovered, 41→42 entries
### 技法验证(用户星盘 Leo Asc)
| 周期 | 星座 | 年数 | 年龄区间 | 解读 |
|------|------|------|----------|------|
| 1 | Leo | 8 | 0→8 | Lagna 起运 |
| 2 | Virgo | 8 | 8→16 | |
| 3 | Libra | 7 | 16→23 | |
| 4 | Scorpio | 8 | 23→31 | |
| **5** | **Sagittarius** | **7** | **31→38** | **当前周期(5宫/创意)** |
| 6 | Capricorn | 1 | 38→39 | 快速过渡 |
| 7 | Aquarius | 12 | 39→51 | Saturn 入庙,长周期 |
| ... | ... | ... | ... | 总周期 88 年 |
**Vimshottari vs Narayana 互补**
- Vimshottari 当前:Saturn MD2020-2039)— 结构性、纪律性能量
- Narayana 当前:Sagittarius MD31→38)— 5宫、Jupiter 守护、创意扩张
- 合理解读:结构化创意释放(Saturn MD 下的 Sagittarius Narayana 周期)
## v6.0.19-sr-degraded-mode-fix2026-06-04)—— 修复无 swisseph 时 Muntha 跳过 bug
> **触发原因**v6.0.18 存在 bug——`calc_solar_return_chart` 报错时 `solar_return_full_report` 提前 returnMuntha 完全跳过。
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: jyotish-vedic-astrology
version: 6.0.19
version: 6.0.20
description: 印度占星(Jyotish)专业解盘与推运系统。核心能力:PDF星盘输入→严谨解盘→精确推运应期输出。触发词:印度占星、吠陀占星、Jyotish、解盘、推运、星盘分析、Dasha、Transit、Nakshatra、Yoga、出生时间矫正、PDF星盘、读取PDF、分析PDF星盘、现代解读、误判纠错、Varga分盘、综合分析、过境分析、合盘、婚姻匹配、年运盘、Prashna、Argala、Jaimini、Shadbala、Ashtakavarga、HTML报告、深度解盘。
---
+25 -2
View File
@@ -1,5 +1,5 @@
{
"version": "v6.0.19-sr-degraded-mode-fix",
"version": "v6.0.20-narayana-dasha",
"source_inspiration": [
{
"name": "jyotishyamitra",
@@ -818,6 +818,28 @@
],
"audit_label": "Bhrigu Pada Dasha",
"missing_impact": "Bhrigu 足步推进法缺失,婚姻时机精确预测受限(通用近似版已实现)。"
},
"narayana_dasha": {
"name": "Narayana Dasha (Rishi Dasha / Padakrama Dasha)",
"domains": [
"dasha",
"rashi",
"timing",
"narayana"
],
"status": "covered",
"knowledge_refs": [
"bphs-ch48-narayana-dasha.md"
],
"commands": [
"narayana-dasha",
"full-reading"
],
"output_paths": [
"modules.narayana_dasha"
],
"audit_label": "Narayana Dasha",
"missing_impact": "N/A - implemented in v6.0.20"
}
},
"routes": {
@@ -963,7 +985,8 @@
"muntha",
"prashna_integration",
"bhrigu_pada_dasha",
"solar_return"
"solar_return",
"narayana_dasha"
],
"optional_techniques": [
"bhava_chalit",
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Narayana Dasha 子命令
Jyotish Vedic Astrology Skill v6.0.20
"""
import json
def cmd_narayana_dasha(args, chart_data):
"""
narayana-dasha 子命令计算并报告 Narayana DashaRishi Dasha
用法:
python jyotish_engine.py narayana-dasha --year REDACTED_YEAR --month 4 --day 17 \\
--hour 14 --minute 45 --lat 36.47 --lon 114.2 --tz 8 --age 33
"""
if chart_data is None:
return "\n".join([
"╔══════════════════════════════════════════╗",
"║ Narayana DashaRishi Dasha",
"╚══════════════════════════════════════════╝",
"",
"错误:图表数据未生成(swisseph 可能未安装)。",
"Narayana Dasha 需要完整的行星经度数据。",
])
from narayana_dasha import narayana_dasha_full_report
planet_lons = {}
for pn, pd in chart_data.get('planets', {}).items():
if isinstance(pd, dict) and 'degree' in pd:
planet_lons[pn] = pd['degree']
asc_idx = chart_data.get('ascendant', {}).get('sign_idx', 0)
if isinstance(asc_idx, str):
asc_idx = int(asc_idx)
current_age = float(getattr(args, 'age', 0) or 0)
birth_year = getattr(args, 'year', 0) or 0
result = narayana_dasha_full_report(
lagna_sign_idx=asc_idx,
planet_lons=planet_lons,
current_age=current_age,
birth_year=birth_year,
)
# 格式化输出
lines = [
"╔══════════════════════════════════════════╗",
"║ Narayana DashaRishi Dasha",
"╚══════════════════════════════════════════╝",
"",
f"Lagna: {result['lagna_sign']}{result['lagna_sign_idx']}",
f"总周期: {result['total_cycle_years']}",
"",
"── 大运序列 ──",
]
for p in result['mahadasha_sequence']:
lines.append(
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}]"
)
curr = result.get('current_dasha', {})
md = curr.get('md')
if md:
lines.append("")
lines.append(f"── 当前大运(age={current_age:.1f})──")
lines.append(f" Mahadasha: {md['sign']}{md['lord']}{md['years']}")
lines.append(f" 剩余: {curr.get('remaining_years', 0):.1f}")
ad = curr.get('ad')
if ad:
lines.append(f" Antardasha: {ad['sign']}{ad['lord']}{ad['years']}")
lines.append("")
for line in result.get('interpretation', []):
lines.append(f" {line}")
return "\n".join(lines)
+26 -1
View File
@@ -62,6 +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
# ============================================================================
# 常量
@@ -3642,6 +3643,24 @@ def cmd_full_reading(args):
except Exception as e:
report['errors'].append(f"solar-return: {e}")
# ── Step 4.13: Narayana DashaRishi Dashav6.0.20)──
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
narayana_result = narayana_dasha_full_report(
lagna_sign_idx=asc_idx,
planet_lons=planet_lons,
current_age=current_age,
birth_year=args.year,
)
report['modules']['narayana_dasha'] = narayana_result
except Exception as e:
report['errors'].append(f"narayana-dasha: {e}")
# ── Step 5: 精确相位 ──
try:
from aspects import calc_all_aspects
@@ -4109,6 +4128,11 @@ def main():
_add_chart_args(p)
p.add_argument('--target-year', type=int, required=True, help='目标年份(计算该年太阳返照)')
# 21.6 narayana-dasha (v6.0.20新增)
p = sub.add_parser('narayana-dasha', help='Narayana DashaRishi Dasha)星座大运分析')
_add_chart_args(p)
p.add_argument('--age', type=float, default=None, help='当前年龄(用于定位大运位置)')
# 21. synastry (v3.7新增)
p = sub.add_parser('synastry', help='合盘分析(Ashta Koota 36分制)')
p.add_argument('--moon1', type=float, required=True, help='Person1月亮黄经')
@@ -4173,7 +4197,8 @@ def main():
'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,
'synastry': cmd_synastry, 'solar-return': cmd_solar_return, 'full-reading': cmd_full_reading, 'prashna': cmd_prashna,
'synastry': cmd_synastry, 'solar-return': cmd_solar_return, 'narayana-dasha': cmd_narayana_dasha,
'full-reading': cmd_full_reading, 'prashna': cmd_prashna,
'double-transit-pac': cmd_double_transit_pac,
'transit-ll7l': cmd_transit_ll7l, 'planetary-congregation': cmd_planetary_congregation,
'vivah-saham': cmd_vivah_saham}
+349
View File
@@ -0,0 +1,349 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Narayana DashaRishi 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 []
total_years = md['years']
sub_periods = []
cum = md['start_age']
# Antardasha 从 MD 星座开始,按黄道序推进
for i in range(12):
sign_idx = (md_sign_idx + i) % 12
lord_sign = mahadasha_periods[0]['lord_in_sign'] # placeholder
sub_years_ratio = mahadasha_periods[i]['years'] / sum(p['years'] for p in mahadasha_periods)
sub_years = round(total_years * sub_years_ratio, 2)
sub_periods.append({
'sign': SIGNS[sign_idx],
'sign_idx': sign_idx,
'lord': SIGN_LORDS[SIGNS[sign_idx]],
'years': sub_years,
'start_age': round(cum, 2),
'end_age': round(cum + sub_years, 2),
'parent_md': SIGNS[md_sign_idx],
})
cum += sub_years
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
age_in_cycle = current_age % total_cycle
# 找当前 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'])
elapsed_in_md = age_in_cycle - p['start_age']
for ad in ads:
if ad['start_age'] <= elapsed_in_md < ad['end_age']:
result['ad'] = {
'sign': ad['sign'],
'sign_idx': ad['sign_idx'],
'lord': ad['lord'],
'years': ad['years'],
'start_age': round(p['start_age'] + ad['start_age'], 2),
'end_age': round(p['start_age'] + ad['end_age'], 2),
}
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 DashaRishi 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, REDACTED_YEAR)
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)