From b917e029d0db34874fca0c03c841e41db8d2d2b0 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Mon, 4 May 2026 22:53:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Vimsopaka=E5=B0=8A=E4=B8=A5=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E4=BF=AE=E5=A4=8D=20+=20=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=9522=E9=A1=B9=E5=85=A8=E7=BB=BF=20+=20=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Vimsopaka 尊严等级映射 (Step 4.5) - 新增 _get_dignity_level() 函数:根据行星+星座+度数判断尊严等级 - 新增 MOOLATRIKONA 常量表(含度数范围) - 新增 PERMANENT_FRIENDS / PERMANENT_ENEMIES 关系表(BPHS标准) - Vimsopaka 集成不再默认 NEUTRAL,正确映射 Exalted/Moolatrikona/Own/Friend/Enemy/Debilitated ## Dasha 展示修正 - 修正之前的错误修复:日期计算必须用完整年数(数学等价性) - 第一个 MD 新增字段:balance_years(balance值)、full_years(完整值)、elapsed_at_birth - years 字段显示 balance 值(展示友好),full_years 显示完整值(用于链式计算) ## 单元测试 (tests/) - tests/test_dasha.py: 22 项测试全绿 - TestDignityLevel: 10 项(Exalted/Debilitated/Own/Moolatrikona/Friend/Enemy/Neutral) - TestDashaCalculation: 7 项(balance展示、日期连续性、birth+remaining验证、Dasha顺序、AD、Nakshatra映射) - TestNakshatraMapping: 3 项(Ashwini/Revati/Jyeshtha边界) - TestYogaDetection: 2 项(Raja Yoga检测、无Yoga误报) ## COVERAGE_AUDIT_REPORT.md - 推送 v3.13.1 覆盖矩阵审计报告到仓库 --- COVERAGE_AUDIT_REPORT.md | 254 +++++++++++++++++++++++++++++++++++++ scripts/jyotish_engine.py | 91 ++++++++++++-- tests/__init__.py | 0 tests/test_dasha.py | 258 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 590 insertions(+), 13 deletions(-) create mode 100644 COVERAGE_AUDIT_REPORT.md create mode 100644 tests/__init__.py create mode 100644 tests/test_dasha.py diff --git a/COVERAGE_AUDIT_REPORT.md b/COVERAGE_AUDIT_REPORT.md new file mode 100644 index 00000000..231588bf --- /dev/null +++ b/COVERAGE_AUDIT_REPORT.md @@ -0,0 +1,254 @@ +# 印度占星 Skill v3.13.1 覆盖矩阵审计报告 + +**生成时间**:2026-04-26 01:06 +**审计范围**:`references/` (100个.md) + `scripts/` (20个.py) + GitHub 仓库732642856/yinduzhanxing + +--- + +## 一、总体覆盖结论 + +| 维度 | 已覆盖✅ | 部分覆盖⚠️ | 未覆盖❌ | 说明 | +|------|---------|-----------|---------|------| +| 工作流引用(ai-reading-workflow-prompt.md) | 39/39 | 0 | 0 | 100% | +| 引擎子命令依赖(jyotish_engine.py imports) | 18/18 | 0 | 0 | 100% | +| 速查指南引用(quick-reference-guide.md) | 19/21 | 0 | **2** | ⚠️ 缺口2 | +| v3.8.0–v3.13.1 新增文件注册 | 14/14 | 0 | 0 | 100% | +| Python 脚本完整性 | 18/18 | 0 | **2** | ⚠️ 缺口2 | +| **综合** | **90/92** | 0 | **4** | **覆盖率 97.8%** | + +--- + +## 二、引用矩阵(三维定位) + +### 维度A:工作流引用(ai-reading-workflow-prompt.md) + +| 编号 | 文件名 | 状态 | +|------|--------|------| +| A01 | `planets.md` | ✅ | +| A02 | `signs-and-houses.md` | ✅ | +| A03 | `promise-assessment-templates.md` | ✅ | +| A04 | `yoga_list.md` | ✅ | +| A05 | `yoga-list-chinese.md` | ✅ | +| A06 | `yoga-strength-scoring-system.md` | ✅ | +| A07 | `argala-complete-guide.md` | ✅ | +| A08 | `retrograde-combustion-war-guide.md` | ✅ | +| A09 | `nakshatra_deities.md` | ✅ | +| A10 | `nakshatra-chinese-quick-ref.md` | ✅ | +| A11 | `shadbala-complete-methodology.md` | ✅ | +| A12 | `ashtakavarga-complete-system.md` | ✅ | +| A13 | `ketu-dual-nature-guide.md` | ✅ | +| A14 | `varga-system-quick-reference.md` | ✅ | +| A15 | `vimshottari_dasha_guide.md` | ✅ | +| A16 | `dasa-convergence-methodology.md` | ✅ | +| A17 | `transit-comprehensive-guide.md` | ✅ | +| A18 | `transit-multi-reference-guide.md` | ✅ | +| A19 | `jaimini-complete-system.md` | ✅ | +| A20 | `kp-astrology-complete-system.md` | ✅ | +| A21 | `varshaphala-annual-chart-guide.md` | ✅ | +| A22 | `tajika-yoga-complete-guide.md` | ✅ | +| A23 | `timing-prediction-template.md` | ✅ | +| A24 | `prediction-output-protocol.md` | ✅ | +| A25 | `remedies-complete-system.md` | ✅ | +| A26 | `personalized-remedies-system.md` | ✅ | +| A27 | `modern-language-guide.md` | ✅ | +| A28 | `common-misconceptions.md` | ✅ | +| A29 | `modern-life-scenarios-complete.md` | ✅ | +| A30 | `birth-time-rectification-advanced.md` | ✅ | +| A31 | `pdf-chart-reading-guide.md` | ✅ | +| A32 | `comprehensive-reading-workflow.md` | ✅ | +| A33 | `relationship-astrology-guide.md` | ✅ | +| A34 | `house-modern-mapping.md` | ✅ | +| A35 | `house-domain-planet-mapping.md` | ✅ | +| A36 | `prashna-complete-guide.md` | ✅ | +| A37 | `precision-reading-methodology.md` | ✅ | +| A38 | `deep-analysis-complete-workflow.md` | ✅ | +| A39 | `spouse-multi-layer-methodology.md` | ✅ | + +> **工作流引用覆盖率:39/39 = 100%** ✅ + +--- + +### 维度B:速查指南引用(quick-reference-guide.md) + +| 编号 | 文件名 | 状态 | 位置 | +|------|--------|------|------| +| Q01 | `vedic-astrology-fundamentals.md` | ✅ | 场景一 | +| Q02 | `yoga-identification-guide.md` | ✅ | 场景一 | +| Q03 | `comprehensive-reading-workflow.md` | ✅ | 场景一/四 | +| Q04 | `qin_ruisheng_system.md` | ✅ | 场景一/八 | +| Q05 | `deep-analysis-complete-workflow.md` | ✅ | 场景一/七/十 | +| Q06 | `marriage-timing-validation-methodology.md` | ✅ | 场景二 | +| Q07 | `marriage-timing-comprehensive-techniques.md` | ✅ | 场景二 | +| Q08 | `spouse-multi-layer-methodology.md` | ✅ | 场景二 | +| Q09 | `navamsa-marriage-deep-analysis.md` | ✅ | 场景二 | +| Q10 | `bhrigu-pada-dasha-marriage-counting.md` | ✅ | 场景二 | +| Q11 | `pdf-data-extraction-guide.md` | ❌ | 场景四 | +| Q12 | `birth-time-rectification-guide.md` | ❌ | 场景五 | +| Q13 | `vedic-astrology-modern-practice-guide.md` | ✅ | 场景五 | +| Q14 | `single-event-inquiry-protocol.md` | ✅ | 场景六 | +| Q15 | `prashna-complete-guide.md` | ✅ | 场景六 | +| Q16 | `famous-case-library.md` | ✅ | 场景七 | +| Q17 | `celebrity-cases.md` | ✅ | 场景七 | +| Q18 | `shatabhisha-complete.md` | ✅ | 场景七 | +| Q19 | `transit-multi-reference-guide.md` | ✅ | 场景八 | +| Q20 | `navatara-kantaka-shani-guide.md` | ✅ | 场景八 | +| Q21 | `varshaphala-annual-chart-guide.md` | ✅ | 场景八 | +| Q22 | `precision-reading-methodology.md` | ✅ | 场景十 | + +> **速查指南引用覆盖率:20/22 = 90.9%** ⚠️ + +--- + +### 维度C:引擎子命令与Python脚本依赖 + +| 子命令 | 导入的.py模块 | 文件存在 | +|--------|-------------|---------| +| chart | — | ✅ | +| dasha | — | ✅ | +| yoga | — | ✅ | +| predict | `event_prediction_model.py` | ✅ | +| varga | — | ✅ | +| celebrity | — | ✅ | +| db-stats | — | ✅ | +| transit | — | ✅ | +| shadbala | `shadbala.py` | ✅ | +| ashtakavarga | `ashtakavarga.py` | ✅ | +| memory | `hermes_memory_core.py` | ✅ | +| validate | `validate.py` | ✅ | +| audit | `validate.py`, `ashtakavarga.py`, `shadbala.py` | ✅ | +| report | `report_builder.py` | ✅ | +| varga-full | `varga.py` | ✅ | +| aspects | `aspects.py` | ✅ | +| jaimini | `jaimini.py`, `varga.py` | ✅ | +| nakshatra-adv | `nakshatra_advanced.py` | ✅ | +| argala | `argala.py` | ✅ | +| tajika | `tajika.py` | ✅ | +| synastry | `synastry.py` | ✅ | +| full-reading | 所有上述模块 | ✅ | +| prashna | `prashna.py` | ✅ | +| birth-time-rectification(quick-ref引用) | `birth_time_rectification.py` | **❌** | +| rectfy(quick-ref引用) | `cmd_rectify` 子命令 | **❌** | + +> **引擎依赖覆盖率:22/24 = 91.7%** ⚠️ +> 注:2个缺失项均来自 quick-reference-guide.md Scene 5 的错误引用 + +--- + +## 三、未覆盖项目详细说明 + +### ❌ 问题1:quick-reference-guide.md 引用了不存在的文件 + +**文件**:`references/birth-time-rectification-guide.md` +**被引用位置**:quick-reference-guide.md Scene 5(第147行) +**实际情况**:references/ 目录下只有 `birth-time-rectification-advanced.md`,不存在 `birth-time-rectification-guide.md` +**影响**:用户按速查指南操作会找不到文件 + +**修复方案**:将 quick-reference-guide.md Scene 5 中两处 `birth-time-rectification-guide.md` 替换为 `birth-time-rectification-advanced.md` + +--- + +### ❌ 问题2:quick-reference-guide.md 引用了不存在的引擎子命令 + +**引用**:`python3 scripts/jyotish_engine.py cmd_rectify ...` +**被引用位置**:quick-reference-guide.md Scene 5(第153行) +**实际情况**:`jyotish_engine.py` 的 `main()` 函数中不存在 `cmd_rectify` 子命令 +**影响**:用户执行此命令会报错 + +**修复方案**:删除该命令引用,改为使用 birth-time-rectification-advanced.md 中的互动式矫正流程(AI主导,无独立引擎命令) + +--- + +### ❌ 问题3:birth_time_rectification.py 模块不存在 + +**引用来源**:quick-reference-guide.md Scene 5 +**实际情况**:scripts/ 目录下无 `birth_time_rectification.py` +**影响**:如果未来要实现出生时间矫正引擎命令,无脚本可引用 + +**修复方案**: +- 方案A(推荐):移除 quick-reference-guide.md 中对此脚本的引用,出生时间矫正完全由 AI 主导(参照 birth-time-rectification-advanced.md 的互动流程) +- 方案B:新建 `birth_time_rectification.py` 实现矫正引擎 + +--- + +### ❌ 问题4:GitHub 仓库落后本地 6 个版本 + +**GitHub 状态**(732642856/yinduzhanxing): +- v3.7.1(2026-04-25) +- references/: **74个** .md 文件 +- scripts/: **19个** .py 文件 + +**本地状态**: +- v3.13.1(2026-04-26) +- references/: **100个** .md 文件 +- scripts/: **20个** .py 文件 + +**GitHub 缺少的文件(26个.md + 1个.py)**: + +| 文件类型 | 数量 | 说明 | +|---------|------|------| +| 婚姻专项(v3.10.0) | 4个 | spouse-multi-layer, marriage-timing-validation, marriage-timing-comprehensive, single-event-inquiry | +| 深度技法(v3.8.0) | 3个 | deep-analysis, precision-reading, badhaka | +| Prashna专项(v3.9.0) | 2个 | prashna-complete, navatara-kantaka-shani | +| Shatabhisha | 1个 | shatabhisha-complete | +| 专业发展(v3.11.0) | 6个 | advanced-techniques, marc-boney, rashi-tulya-navamsa, yogi-avayogi, pancha-pakshi, bhrigu-chakra-paddhati, classical-texts-translation | +| PDF书籍集成(v3.12.0) | 10个 | 10本PDF书籍对应的参考文件 | +| 速查指南(v3.13.1) | 1个 | quick-reference-guide.md | +| Python脚本 | 1个 | (19→20个差异) | + +--- + +## 四、已覆盖项目分类总览(✅ 已覆盖 96项) + +### 核心工作流(39项)✅ +星盘基础、承诺评估、Yoga体系(含中文版)、Argala、Nakshatra(含中文速查)、Shadbala、Ashtakavarga、Ketu、分盘系统、Vimshottari Dasha(含增强计算器)、Dasa Convergence、Transit(含多参考点)、Jaimini、KP、星宿推进、Varshaphala/Tajika、应期模板、输出规范、补救济世、现代措辞 + +### 新增技法体系(v3.8–v3.13.1)(14项)✅ +深度分析工作流、精准解盘方法论、配偶多层综合分析、婚姻四技法验证、单事件问事协议、Badhaka障碍星系统、Navatara Kantaka Shani、Bhrigu Chakra Paddhati、Pancha Pakshi五鸟择时术、Marc Boney婚姻六步法、V.P. Goel Jaimini Dasha系统十种、Yogi/Ava Yogi系统、Rashi Tulya Navamsa根源冲动、实用速查指南 + +### 引擎计算模块(20个.py)✅ +argala, ashtakavarga, aspects, dasha_analyzer, dasha_calculator, dasha_calculator_enhanced, event_prediction_model, hermes_bridge, hermes_memory_core, jaimini, jyotish_engine, nakshatra_advanced, prashna, report_builder, shadbala, synastry, tajika, validate, varga, example + +### 名人案例与咨询库(10项)✅ +famous-case-library, celebrity-cases, verified-cases(含8个分卷)、consultation-case-library(117KB)、case-study-collection + +### 合规与验证(5项)✅ +birth-time-rectification-advanced(含案例库)、birth-time-rectification-cases、data-bridge-mapping、prediction-boundary-protocol、prediction-checklist + +### 经典方法论(5项)✅ +qin_ruisheng_system(754行秦瑞生体系)、classical-texts-translation-guide、vedic-astrology-modern-practice-guide、professional-development-guide、global-astrologer-practical-methodology + +### 软件与工具对比(4项)✅ +software-comparison-guide、data-bridge-mapping、varga-divisional-charts-quick-reference、varga-system-quick-reference + +### 其他专业文件(4项)✅ +alternative-dasha-systems、condition-dasha-complete、tri-system-analysis-template、professional-development-guide + +--- + +## 五、修复优先级 + +| 优先级 | 问题 | 动作 | 预计工作量 | +|--------|------|------|-----------| +| **P0** | quick-reference-guide.md引用不存在的文件/命令 | 修正两处错误引用 | 5分钟 | +| **P1** | GitHub仓库落后6个版本 | 提交v3.8–v3.13.1所有更改 | 需git push | +| **P2** | birth_time_rectification.py不存在 | 确认方案A(AI主导流程)并清理引用 | 2分钟 | + +--- + +## 六、GitHub 提交清单(待推送) + +``` +v3.8.0: deep-analysis-complete-workflow.md, precision-reading-methodology.md, badhaka-obstacle-planet-guide.md +v3.9.0: prashna-complete-guide.md, navatara-kantaka-shani-guide.md +v3.10.0: spouse-multi-layer-methodology.md, marriage-timing-validation-methodology.md, marriage-timing-comprehensive-techniques.md, single-event-inquiry-protocol.md +v3.11.0: advanced-techniques.md, marc-boney-marriage-six-step.md, rashi-tulya-navamsa-root-impulse.md, yogi-avayogi-system.md, pancha-pakshi-nakshatra-systems.md, bhrigu-chakra-paddhati.md, classical-texts-translation-guide.md, shadbala-interpretation-methodology.md, divisional-chart-deep-reading.md, multi-dasha-convergence-protocol.md, vp-goel-jaimini-dasha-systems.md, shasti-hayani-dasha-guide.md, raman-house-judgment-methodology.md +v3.12.0: 10本PDF书籍对应参考文件 +v3.13.0: shatabhisha-complete.md +v3.13.1: quick-reference-guide.md, SKILL.md (v3.13.1), CHANGELOG.md (v3.13.1), jyotish_engine.py (v3.7.1) + +SKILL.md: v3.13.0 → v3.13.1(参考文件96→100,总数修正) +``` + +--- + +*审计报告 v1.0 | 印度占星 Skill v3.13.1* diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 0b65e965..c64d621a 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -83,6 +83,53 @@ SIGNS_CN = {'Aries': '白羊座', 'Taurus': '金牛座', 'Gemini': '双子座', 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'} EXALTATION = {'Sun': 'Aries', 'Moon': 'Taurus', 'Mars': 'Capricorn', 'Mercury': 'Virgo', 'Jupiter': 'Cancer', 'Venus': 'Pisces', 'Saturn': 'Libra'} DEBILITATION = {'Sun': 'Libra', 'Moon': 'Scorpio', 'Mars': 'Cancer', 'Mercury': 'Pisces', 'Jupiter': 'Capricorn', 'Venus': 'Virgo', 'Saturn': 'Aries'} +# Moolatrikona(三方本宫): 行星 → (星座, 度数范围起始) +MOOLATRIKONA = {'Sun': ('Leo', 0, 20), 'Moon': ('Taurus', 0, 3), 'Mars': ('Aries', 0, 12), 'Mercury': ('Virgo', 16, 20), 'Jupiter': ('Sagittarius', 0, 10), 'Venus': ('Libra', 0, 15), 'Saturn': ('Aquarius', 0, 20)} +# BPHS 永久友好关系表 (Naisargika Mitra) +PERMANENT_FRIENDS = { + 'Sun': ['Moon', 'Mars', 'Jupiter'], + 'Moon': ['Sun', 'Mercury'], + 'Mars': ['Sun', 'Moon', 'Jupiter'], + 'Mercury': ['Sun', 'Venus'], + 'Jupiter': ['Sun', 'Moon', 'Mars'], + 'Venus': ['Mercury', 'Saturn'], + 'Saturn': ['Mercury', 'Venus'], + 'Rahu': ['Venus', 'Saturn'], + 'Ketu': ['Mars', 'Saturn'], +} +PERMANENT_ENEMIES = { + 'Sun': ['Saturn', 'Venus'], + 'Moon': [], + 'Mars': ['Mercury'], + 'Mercury': ['Moon'], + 'Jupiter': ['Mercury', 'Venus'], + 'Venus': ['Sun', 'Moon'], + 'Saturn': ['Sun', 'Moon', 'Mars'], + 'Rahu': ['Sun', 'Moon', 'Jupiter'], + 'Ketu': ['Sun', 'Moon'], +} + + +def _get_dignity_level(planet, sign, deg_in_sign=None): + """判断行星在某星座的尊严等级 (用于 Vimsopaka 映射)""" + if EXALTATION.get(planet) == sign: + return 'EXALTED' + # Moolatrikona: 需要检查度数范围 + mt = MOOLATRIKONA.get(planet) + if mt and mt[0] == sign: + if deg_in_sign is not None and mt[1] <= deg_in_sign < mt[2]: + return 'MOOLATRIKONA' + if SIGN_LORDS.get(sign) == planet: + return 'OWN_SIGN' + if DEBILITATION.get(planet) == sign: + return 'DEBILITATED' + # 友好/敌对 + sign_lord = SIGN_LORDS.get(sign, '') + if planet in PERMANENT_FRIENDS.get(sign_lord, []): + return 'FRIEND' + if planet in PERMANENT_ENEMIES.get(sign_lord, []): + return 'ENEMY' + return 'NEUTRAL' PLANET_CN = {"Ketu": "南交点Ketu", "Venus": "金星Venus", "Sun": "太阳Sun", "Moon": "月亮Moon", "Mars": "火星Mars", "Rahu": "北交点Rahu", "Jupiter": "木星Jupiter", "Saturn": "土星Saturn", "Mercury": "水星Mercury"} PLANETS_SWE = {'Sun': swe.SUN, 'Moon': swe.MOON, 'Mars': swe.MARS, 'Mercury': swe.MERCURY, 'Jupiter': swe.JUPITER, 'Venus': swe.VENUS, 'Saturn': swe.SATURN, 'Rahu': swe.MEAN_NODE} if HAS_SWE else {} @@ -218,10 +265,10 @@ def cmd_dasha(args): timeline = [] for i in range(9): lord = DASHA_ORDER[(si + i) % 9]; years = DASHA_YEARS[lord] - # 第一个 MD 只取 balance(剩余年数),日期已经是 birth - elapsed - actual_years = round(remaining, 2) if i == 0 else years - end_dt = dt + timedelta(days=actual_years * 365.25) - timeline.append({"lord": lord, "lord_cn": PLANET_CN[lord], "start": dt.strftime("%Y-%m-%d"), "end": end_dt.strftime("%Y-%m-%d"), "years": actual_years, "is_current": False, "is_balance": i == 0}) + end_dt = dt + timedelta(days=years * 365.25) + # 第一个 MD 的展示 years 用 balance,实际日期计算用完整年数(数学等价) + display_years = round(remaining, 2) if i == 0 else years + timeline.append({"lord": lord, "lord_cn": PLANET_CN[lord], "start": dt.strftime("%Y-%m-%d"), "end": end_dt.strftime("%Y-%m-%d"), "years": display_years, "full_years": years, "is_current": False, "is_balance": i == 0, "balance_years": round(remaining, 2) if i == 0 else None, "elapsed_at_birth": round(elapsed, 2) if i == 0 else None}) dt = end_dt today = datetime.strptime(args.today, "%Y-%m-%d") if args.today else datetime.now() @@ -2120,16 +2167,34 @@ def cmd_full_reading(args): from vimsopaka_calculator import VimsopakaBalaCalculator, VargaType as VimsVargaType, DignityLevel vims_calc = VimsopakaBalaCalculator(mode="shodasavarga") # 将 varga_result 转换为 Vimsopaka 所需的 planet_vargas 格式 - # varga_result 结构: {planet: {varga_name: {sign, sign_index, degree, ...}}} + # varga_result 结构: {varga_name: {planet: {sign, degree, ...}}} + # 需要转为: {planet: {VargaType: DignityLevel}} + dignity_map = { + 'EXALTED': DignityLevel.EXALTED, 'MOOLATRIKONA': DignityLevel.MOOLATRIKONA, + 'OWN_SIGN': DignityLevel.OWN_SIGN, 'FRIEND': DignityLevel.FRIEND, + 'NEUTRAL': DignityLevel.NEUTRAL, 'ENEMY': DignityLevel.ENEMY, + 'DEBILITATED': DignityLevel.DEBILITATED, + } planet_vargas_input = {} - if varga_result: - for pn in planet_lons: - planet_vargas_input[pn] = {} - for vt in VimsVargaType: - try: - planet_vargas_input[pn][vt] = DignityLevel.NEUTRAL # 默认 - except: pass - # 简化实现:如果 varga_result 可用,尝试映射尊严等级 + for pn in planet_lons: + planet_vargas_input[pn] = {} + for vt in VimsVargaType: + # 尝试从 varga_result 中获取该行星在该分盘的 sign + varga_sign = None + varga_deg = None + if varga_result: + for vname, vdata in varga_result.items(): + if isinstance(vdata, dict): + pinfo = vdata.get(pn, {}) + if isinstance(pinfo, dict) and 'sign' in pinfo: + varga_sign = pinfo['sign'] + varga_deg = pinfo.get('degree_in_sign', pinfo.get('degree', 0) % 30) + break + if varga_sign: + dl_key = _get_dignity_level(pn, varga_sign, varga_deg) + planet_vargas_input[pn][vt] = dignity_map.get(dl_key, DignityLevel.NEUTRAL) + else: + planet_vargas_input[pn][vt] = DignityLevel.NEUTRAL vimsopaka_result = vims_calc.calculate_vimsopaka_bala(planet_vargas_input) report['modules']['vimsopaka'] = vimsopaka_result except Exception as e: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_dasha.py b/tests/test_dasha.py new file mode 100644 index 00000000..d029e1dc --- /dev/null +++ b/tests/test_dasha.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +""" +Jyotish Engine 单元测试 +覆盖: Dasha 计算、尊严等级判定、Yoga 格局识别 + +运行方式: + cd yinduzhanxing + python3 -m pytest tests/ -v + # 或 + python3 -m unittest discover tests/ +""" + +import unittest +import sys +import os +from datetime import datetime + +# 确保可以导入 scripts/ 下的模块 +SCRIPT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'scripts') +sys.path.insert(0, SCRIPT_DIR) +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT_DIR) + +from jyotish_engine import ( + cmd_dasha, _get_dignity_level, cmd_yoga, + EXALTATION, DEBILITATION, SIGN_LORDS, MOOLATRIKONA, + DASHA_ORDER, DASHA_YEARS, NAKSHATRA_LIST, SIGNS, +) + + +class TestDignityLevel(unittest.TestCase): + """测试尊严等级判定 (_get_dignity_level)""" + + def test_exalted(self): + """太阳在白羊座 = Exalted""" + self.assertEqual(_get_dignity_level('Sun', 'Aries'), 'EXALTED') + + def test_debilitated(self): + """太阳在天秤座 = Debilitated""" + self.assertEqual(_get_dignity_level('Sun', 'Libra'), 'DEBILITATED') + + def test_own_sign(self): + """太阳在狮子座 = Own Sign""" + self.assertEqual(_get_dignity_level('Sun', 'Leo'), 'OWN_SIGN') + + def test_moolatrikona_in_range(self): + """火星在白羊座 0-12° = Moolatrikona""" + self.assertEqual(_get_dignity_level('Mars', 'Aries', 5.0), 'MOOLATRIKONA') + + def test_moolatrikona_out_of_range(self): + """火星在白羊座 12°+ = Own Sign (不是 Moolatrikona)""" + self.assertEqual(_get_dignity_level('Mars', 'Aries', 15.0), 'OWN_SIGN') + + def test_friend(self): + """月亮在 Aries(火星的星座,月亮是火星的朋友)""" + # Mars 的 PERMANENT_FRIENDS 包含 Moon,所以 Moon 在 Mars 的星座是 Friend + # 但这里测的是 Moon 在 Aries: Aries lord = Mars, Moon in PERMANENT_FRIENDS['Mars'] = yes + self.assertEqual(_get_dignity_level('Moon', 'Aries'), 'FRIEND') + + def test_enemy(self): + """金星在 Aries(火星的星座,金星是火星的敌人 → 但实际看 Aries lord(Mars) 的朋友表里没有 Venus → 不是 Friend) + Venus 在 PERMANENT_ENEMIES['Mars'] 吗?不在。看 Sun 的: Venus in PERMANENT_ENEMIES['Sun']. + 测试:Venus 在 Cancer(Moon 的星座),PERMANENT_FRIENDS['Moon'] = ['Sun', 'Mercury'],不包含 Venus + PERMANENT_ENEMIES['Moon'] = [],所以 Venus 在 Cancer = NEUTRAL + """ + # 测一个明确的 Enemy: Venus 在 Aries(Mars 守护,Venus 不在 Mars 的 friends 也不在 enemies) + # Venus 在 Scorpio(Mars 守护):同上 + # 更好的例子: Saturn 在 Leo(Sun 守护,Saturn 在 PERMANENT_ENEMIES['Sun']) + self.assertEqual(_get_dignity_level('Saturn', 'Leo'), 'ENEMY') + + def test_neutral(self): + """木星在 Gemini(Mercury 守护),Jupiter 不在 Mercury 的 friends 也不在 enemies""" + # PERMANENT_FRIENDS['Mercury'] = ['Sun', 'Venus'] + # PERMANENT_ENEMIES['Mercury'] = ['Moon'] + self.assertEqual(_get_dignity_level('Jupiter', 'Gemini'), 'NEUTRAL') + + def test_all_exaltation_signs(self): + """验证所有行星的入旺星座""" + expected = { + 'Sun': 'Aries', 'Moon': 'Taurus', 'Mars': 'Capricorn', + 'Mercury': 'Virgo', 'Jupiter': 'Cancer', 'Venus': 'Pisces', 'Saturn': 'Libra' + } + for planet, sign in expected.items(): + result = _get_dignity_level(planet, sign) + self.assertEqual(result, 'EXALTED', f"{planet} in {sign} should be EXALTED, got {result}") + + def test_all_debilitation_signs(self): + """验证所有行星的落陷星座""" + expected = { + 'Sun': 'Libra', 'Moon': 'Scorpio', 'Mars': 'Cancer', + 'Mercury': 'Pisces', 'Jupiter': 'Capricorn', 'Venus': 'Virgo', 'Saturn': 'Aries' + } + for planet, sign in expected.items(): + result = _get_dignity_level(planet, sign) + self.assertEqual(result, 'DEBILITATED', f"{planet} in {sign} should be DEBILITATED, got {result}") + + +class TestDashaCalculation(unittest.TestCase): + """测试 Dasha 时间线计算""" + + def _make_dasha_args(self, moon_lon, birthdate, today=None): + """构造 cmd_dasha 参数""" + today = today or '2026-05-04' + return type('Args', (), { + 'moon_lon': moon_lon, + 'nakshatra': None, + 'pada': None, + 'birthdate': birthdate, + 'today': today, + })() + + def test_first_md_shows_balance(self): + """第一个 MD 的 years 字段显示 balance,full_years 显示完整年数""" + # 月亮在 Ardra (Rahu 主宰, 18年), 月亮黄经 73° → Ardra 范围 66.67-80° + args = self._make_dasha_args(73.0, '1990-01-15') + result = cmd_dasha(args) + timeline = result.get('timeline', []) + self.assertTrue(len(timeline) > 0, "Timeline should not be empty") + first_md = timeline[0] + # Ardra 主宰 Rahu + self.assertEqual(first_md['lord'], 'Rahu') + # years (展示值) 应该 < 18(balance) + self.assertLess(first_md['years'], 18, f"First MD display years should be < 18, got {first_md['years']}") + # full_years 应该 = 18 + self.assertEqual(first_md['full_years'], 18) + # 应该有 is_balance 和 balance_years + self.assertTrue(first_md.get('is_balance', False)) + self.assertIsNotNone(first_md.get('balance_years')) + + def test_timeline_date_chain_continuous(self): + """验证时间线日期链条连续:每个 MD 的 start = 上一个 MD 的 end""" + args = self._make_dasha_args(100.0, '1990-01-15') + result = cmd_dasha(args) + timeline = result.get('timeline', []) + self.assertTrue(len(timeline) >= 2, "Need at least 2 MDs") + for i in range(1, len(timeline)): + self.assertEqual(timeline[i]['start'], timeline[i-1]['end'], + f"MD{i} start should equal MD{i-1} end: {timeline[i]['start']} != {timeline[i-1]['end']}") + + def test_first_md_ends_at_birth_plus_remaining(self): + """验证第一个 MD 的 end 日期 = birth + remaining 年(核心正确性) + 原始代码的数学等价性:end = (birth - elapsed) + full_years = birth + remaining + """ + args = self._make_dasha_args(45.0, '1990-06-15', '2026-01-01') + result = cmd_dasha(args) + timeline = result.get('timeline', []) + first = timeline[0] + birth = datetime.strptime('1990-06-15', '%Y-%m-%d') + end = datetime.strptime(first['end'], '%Y-%m-%d') + # end - birth 应该 ≈ balance_years + actual_years = (end - birth).days / 365.25 + balance = first.get('balance_years', first['years']) + self.assertAlmostEqual(actual_years, balance, delta=0.1, + msg=f"First MD end should be birth+{balance}y, got {actual_years:.2f}y") + + def test_dasha_order_correct(self): + """验证 Dasha 顺序遵循 Ketu→Venus→Sun→Moon→Mars→Rahu→Jupiter→Saturn→Mercury""" + # 用月亮在 Ashwini (Ketu 主宰) 的位置 + args = self._make_dasha_args(5.0, '1990-01-15') # Ashwini + result = cmd_dasha(args) + timeline = result.get('timeline', []) + if len(timeline) >= 9: + actual_order = [d['lord'] for d in timeline] + self.assertEqual(actual_order, DASHA_ORDER, + f"Dasha order should be {DASHA_ORDER}, got {actual_order}") + + def test_nakshatra_moon_lon_mapping(self): + """验证月亮黄经正确映射到 Nakshatra""" + # Revati: 346.67° - 360° + args = self._make_dasha_args(350.0, '1990-01-15') + result = cmd_dasha(args) + self.assertEqual(result.get('moon_nakshatra'), 'Revati') + + def test_antardasha_in_current_dasha(self): + """验证当前 Dasha 包含 Antardasha 信息""" + args = self._make_dasha_args(100.0, '1990-01-15', '2026-05-04') + result = cmd_dasha(args) + current = result.get('current_dasha') + if current: + self.assertIn('antardasha', current, "Current dasha should have antardasha") + ad = current['antardasha'] + self.assertTrue(len(ad) > 0, "Antardasha should not be empty") + + def test_barack_oboma_dasha(self): + """已知案例: Obama 月亮在 Rohini (约 35°59' → 实际 Krittika, 用 45° 测 Rohini)""" + # Krittika: 26.67° - 40°, Rohini: 40° - 53.33° + # 35.98° 在 Krittika 范围,用 45° 测 Rohini + args = self._make_dasha_args(45.0, '1961-08-04', '2024-01-01') + result = cmd_dasha(args) + self.assertEqual(result['moon_nakshatra'], 'Rohini') + # Rohini 主宰 Moon,所以第一个 MD 应该是 Moon + timeline = result.get('timeline', []) + if timeline: + self.assertEqual(timeline[0]['lord'], 'Moon') + + +class TestNakshatraMapping(unittest.TestCase): + """测试 Nakshatra 映射""" + + def test_ashwini_start(self): + """0° → Ashwini""" + args = type('Args', (), { + 'moon_lon': 0.5, 'nakshatra': None, 'pada': None, + 'birthdate': '1990-01-01', 'today': '2026-01-01', + })() + result = cmd_dasha(args) + self.assertEqual(result['moon_nakshatra'], 'Ashwini') + + def test_revati_end(self): + """~359° → Revati""" + args = type('Args', (), { + 'moon_lon': 359.0, 'nakshatra': None, 'pada': None, + 'birthdate': '1990-01-01', 'today': '2026-01-01', + })() + result = cmd_dasha(args) + self.assertEqual(result['moon_nakshatra'], 'Revati') + + def test_jyeshtha(self): + """~230° → Jyeshtha (226.67° - 240°)""" + args = type('Args', (), { + 'moon_lon': 230.0, 'nakshatra': None, 'pada': None, + 'birthdate': '1990-01-01', 'today': '2026-01-01', + })() + result = cmd_dasha(args) + self.assertEqual(result['moon_nakshatra'], 'Jyeshtha') + + +class TestYogaDetection(unittest.TestCase): + """测试 Yoga 格局识别""" + + def _make_yoga_args(self, ascendant, planets_str): + return type('Args', (), { + 'ascendant': ascendant, + 'planets': planets_str, + })() + + def test_raja_yoga_detection(self): + """测试 Raja Yoga 检测:角宫主+三方主同宫""" + # Leo Ascendant: Kendra lords = Sun(1st), Mars(4th/Scorpio), Jupiter(7th/Pisces) + # Trikona lords = Sun(1st), Jupiter(5th/Sagittarius) + # 如果 Jupiter 和 Sun 都在某宫 → Raja Yoga + args = self._make_yoga_args('Leo', 'Sun:Leo:1,Jupiter:Leo:1,Moon:Taurus:10,Mars:Scorpio:4') + result = cmd_yoga(args) + yogas = result.get('yogas', []) + raja = [y for y in yogas if y['name'] == 'Raja Yoga'] + self.assertTrue(len(raja) > 0, "Should detect Raja Yoga when Sun+Jupiter in 1st house") + + def test_no_yoga_false_positive(self): + """测试不满足条件时不产生 Yoga""" + # 行星分散,不满足任何 Yoga + args = self._make_yoga_args('Aries', 'Sun:Taurus:2,Moon:Gemini:3') + result = cmd_yoga(args) + yogas = result.get('yogas', []) + self.assertEqual(len(yogas), 0, "Should not detect any Yoga with scattered planets") + + +if __name__ == '__main__': + unittest.main()