diff --git a/SKILL.md b/SKILL.md index f26688ab..d21bcc71 100644 --- a/SKILL.md +++ b/SKILL.md @@ -153,8 +153,9 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力 | 能力域 | 核心内容 | 主要参考文件 | |--------|---------|------------| | **静态分析** | 行星配置、Yoga、NK、宫位、Argala、Shadbala、AV、Badhaka、Raman方法论 | `planets.md` `yoga_list.md` `argala-complete-guide.md` `badhaka-obstacle-planet-guide.md` `raman-house-judgment-methodology.md` | -| **动态推运** | Vimshottari、Chara Dasha、KP、Double Transit、Varshaphala、替代Dasha | `vimshottari_dasha_guide.md` `dasa-convergence-methodology.md` `alternative-dasha-systems.md` | -| **关系占星** | Koota 36分、D9伴侣、DK、Mangal Dosha、配偶六层确认、高地位配偶Yoga | `spouse-multi-layer-methodology.md` `darakaraka-complete-guide.md` `marc-boney-marriage-six-step.md` | +| **动态推运** | Vimshottari、Chara Dasha(timing partial)、KP、Double Transit、Varshaphala、替代Dasha | `vimshottari_dasha_guide.md` `dasa-convergence-methodology.md` `alternative-dasha-systems.md` | +| **Jaimini静态层** | Chara Karaka、Karakamsha、A1-A12/UL、Graha Pada、Argala/Virodhargala、Special Lagnas(部分) | `jaimini-complete-system.md` `argala-complete-guide.md` `technique-capability-matrix.md` | +| **关系占星** | Koota 36分、Mahendra/Stree Deergha/Vedha/Rajju、D9伴侣、DK、Mangal Dosha、Papasamya、配偶六层确认 | `spouse-multi-layer-methodology.md` `darakaraka-complete-guide.md` `relationship-astrology-guide.md` | | **出生时间矫正** | 八大方法、自动化流程、验证报告 | `birth-time-rectification-advanced.md` | | **PDF读取** | JH/PL PDF全量提取、完整性门、交叉校验 | `pdf-chart-reading-guide.md` `data-bridge-mapping.md` | | **Prashna问事** | 十步断卦、AL、Sphuta、Sahams、失物查询 | `prashna-complete-guide.md` `single-event-inquiry-protocol.md` | @@ -196,13 +197,13 @@ $PYTHON $SCRIPT <子命令> [参数] | `validate` | R1-R10数学验证 | | `audit` | P1-P12行星审计管线 | | `aspects` | 度数精确相位系统 | -| `jaimini` | Jaimini Karaka/Karakamsha;Chara Dasha 当前为 partial,需 KN Rao/PVN Rao 回归 | +| `jaimini` | Jaimini Karaka/Karakamsha、A1-A12/UL、Graha Pada、Special Lagnas;Chara Dasha timing 当前为 partial,需 KN Rao/PVN Rao 回归 | | `nakshatra-adv` | 高级Nakshatra(Tara Bala+Chandra Bala+Sub-Lord) | | `nakshatra-dasha` | 星宿大运推演(Ashtottari + Nakshatra-level Vimshottari) | | `nakshatra-full` | 星宿综合报告(本命 + 大运 + 过境星宿) | -| `argala` | Argala门闩系统 | +| `argala` | Argala门闩系统:主 Argala + Virodhargala + Rajayoga 分类 | | `tajika` | Tajika年运盘(Muntha+YearLord+Mudda Dasha) | -| `synastry` | 合盘分析(Ashta Koota 36分) | +| `synastry` | 合盘分析:Ashta Koota 36分 + Mahendra/Stree Deergha/Vedha/Rajju 等附加Kuta | | `report` | MD→HTML报告生成(羊皮纸主题) | | `prashna` | Prashna问事占星 | | `double-transit-pac` | KN Rao Double Transit PAC+D9层 | diff --git a/references/feature-gap-matrix-2026.md b/references/feature-gap-matrix-2026.md new file mode 100644 index 00000000..bb7c3b39 --- /dev/null +++ b/references/feature-gap-matrix-2026.md @@ -0,0 +1,571 @@ +# Jyotish Skill 功能差距矩阵 (2026) + +> **版本基准**:Our Skill v6.1.10 vs 4个顶级对标项目 +> **生成日期**:2026-06-10 +> **对标项目**: +> - **JH** = Jagannatha Hora / PyJHora (AGPL) — 最权威免费Jyotish软件 +> - **Kala** = Kala 2023 (商业) — 最全面的古典Jyotish软件 +> - **JG** = jyotishganit v0.1.3 (MIT) — Python库 +> - **PL** = Parashara's Light 9.0 (商业) — 专业级Jyotish软件 + +--- + +## 状态标记说明 + +| 标记 | 含义 | +|------|------| +| ✅已实现 | 我们有完整的知识+计算+工作流+输出 | +| ⚠️部分 | 有一些层但不完整,或精度未校准 | +| 📖仅知识 | 有参考文档但无可执行计算 | +| ❌缺失 | 无有意义的本地覆盖 | +| 🆕新增 | v6.0+新增的能力 | + +## 可复用性标记 + +| 标记 | 含义 | +|------|------| +| ✅可直接复制 | MIT/AGPL许可,API兼容,代码可直接取用 | +| ⚠️需改编 | 有参考代码但需要重构才能适配我们的架构 | +| ❌仅借鉴 | 商业软件或闭源,只能参考功能列表不能取代码 | +| 🔧自行实现 | 无可用外部代码,需从零开发 | + +--- + +## 一、基础排盘(Rasi, Navamsa, 全部Varga) + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| 基础排盘 | D1 Rasi Chart | ✅已实现 | 23+标准分盘+自定义 | 16分盘 | 14分盘 | 16+分盘 | JH/PyJHora | ✅可直接复制 | +| 基础排盘 | D2 Hora | ✅已实现 | 6种变体 | ✅ | ✅ | ✅ | JH (6变体) | ⚠️需改编 | +| 基础排盘 | D3 Drekkana | ✅已实现 | 4种变体 | ✅ | ✅ | ✅ | JH (4变体) | ⚠️需改编 | +| 基础排盘 | D4 Chaturthamsa | ✅已实现 | 2种变体 | ✅ | ✅ | ✅ | JH | ⚠️需改编 | +| 基础排盘 | D5 Panchamsa | ✅已实现(divisional_charts_extended) | 2种变体 | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 基础排盘 | D6 Shashthamsa | ✅已实现(divisional_charts_extended) | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D7 Saptamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D8 Ashtamsa | ✅已实现(divisional_charts_extended) | 2种变体 | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 基础排盘 | D9 Navamsa | ✅已实现 | 3种变体 | ✅ | ✅ | ✅ | JH (3变体) | ⚠️需改编 | +| 基础排盘 | D10 Dasamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D11 Ekadasamsa/Rudramsa | ✅已实现(divisional_charts_extended) | 2种变体 | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 基础排盘 | D12 Dwadashamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D16 Shodasamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D20 Vimsamsa | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D24 Chaturvimsamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D27 Bhamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D30 Trimsamsa | ✅已实现 | 3种变体 | ✅ | ✅ | ✅ | JH (3变体) | ⚠️需改编 | +| 基础排盘 | D40 Khavedamsa | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D45 Akshavedamsa | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 基础排盘 | D60 Shashtiamsa | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 高级分盘 | D81 Navamsamsa | ❌缺失 | ✅(2变体) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级分盘 | D108 | ❌缺失 | ✅(2变体) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级分盘 | D144 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级分盘 | 自定义D-N (N≤300) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级分盘 | 复合分盘 D-m×n | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级分盘 | Parivritta Dasamsa | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| 高级分盘 | Nadyamsa (Chandra Kala Nadi) | ❌缺失 | ✅(2法) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 分盘变体 | 各分盘的多种计算方法 | ❌缺失 | ✅(Hora 6变体, D3 4变体等) | ❌ | ❌ | ❌ | JH/PyJHora | ⚠️需改编 | + +**差距总结**:我们实现了BPHS十六分盘(D2-D60),但JH还支持D81/D108/D144/自定义D-N/复合分盘/多种变体算法。差距在于**分盘变体算法**和**高级分盘**。 + +--- + +## 二、Dasha系统 + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| 星宿Dasha | Vimshottari Dasha | ✅已实现 | ✅(12种起算点) | ✅ | ✅ | ✅(10变体) | JH/PyJHora | ✅可直接复制 | +| 星宿Dasha | Vimshottari多起算点 | ⚠️部分(仅月亮/上升) | ✅(12种起算点) | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 星宿Dasha | Ashtottari Dasha | ✅已实现(ashtottari_dasha.py) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 星宿Dasha | Yogini Dasha | ✅已实现(yogini_dasha.py) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 星宿Dasha | Kalachakra Dasha | ✅已实现(kalachakra_dasha.py) | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 星宿Dasha | Dwisaptati Sama Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Shattrimsa Sama Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Dwadashottari Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Chaturaseeti Sama Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Satabdika Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Shodasottari Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Panchottari Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Shashtihayani Dasha | 📖仅知识 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 星宿Dasha | Tribhagi变体 | ❌缺失 | ✅(适用于多数星宿Dasha) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Chara Dasha | ⚠️部分(24.17%匹配KN Rao) | ✅(Parasara+KN Rao) | ✅(KN Rao法) | ❌ | ✅ | JH | ⚠️需改编 | +| 宫位Dasha | Narayana Dasha | ✅已实现(narayana_dasha.py) | ✅(所有分盘) | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 宫位Dasha | Lagnaamsaka Dasha | ❌缺失 | ✅(所有分盘) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Padanaathaamsa Dasha | ❌缺失 | ✅(所有分盘) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Sudasa | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Drigdasa | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Lagna Kendradi Rasi Dasha | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 宫位Dasha | Atmakaraka Kendradi Rasi Dasha | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Trikona Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Yogardha Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Paryaaya Dasas (Sthira/Chara/Ubhaya) | ❌缺失 | ✅(适用于所有Varga) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Shoola Dasas | ❌缺失 | ✅(全12宫) | ❌ | ❌ | ✅ | JH | ⚠️需改编 | +| 宫位Dasha | Niryaana Shoola Dasha | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Brahma Dasha | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Sthira Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Manduka Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Navamsa Dasha | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 宫位Dasha | Varnada Dasha | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 其他Dasha | Moola Dasha (Pinda/Amsa/Naisarga) | ❌缺失 | ✅ | ✅(3种) | ❌ | ❌ | JH | ⚠️需改编 | +| 其他Dasha | Tara Dasha | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 其他Dasha | Patyayini Dasha | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 其他Dasha | Sudarsana Chakra Dasa | ✅已实现(sudarshana_chakra.py) | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 其他Dasha | Rasi-Bhukta Vimsottari | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 其他Dasha | Tithi Ashtottari/Yogini | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 其他Dasha | Bhrigu Pada Dasha | ✅已实现(bhrigu_pada_dasha.py) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Dasha深度 | 层级深度 | ⚠️部分(MD→AD→PD 3层) | ✅(最多7层) | ✅(Antardasa+) | ✅(MD+AD) | ✅(最多5层) | JH | ✅可直接复制 | +| Dasha深度 | Dasha Pravesha Chart | ❌缺失 | ✅(任意层级起始可绘制完整图) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Dasha深度 | 条件性Dasha筛选 | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| Dasha深度 | 年单位选项(5种) | ❌缺失 | ✅(回归年/吠陀年/自定义/太阳年/Tithi年) | ❌ | ❌ | ❌ | JH | ✅可直接复制 | + +**差距总结**:JH有**30+种Dasha系统**,我们实现了约7种(Vimshottari/Ashtottari/Yogini/Kalachakra/Narayana/BhriguPada/Sudarshana)。Chara Dasha仅partial。缺少绝大多数宫位Dasha和星宿Dasha变体。**最大差距在宫位Dasha族**。 + +--- + +## 三、Ashtakavarga + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Ashtakavarga | Bhinna Ashtakavarga (BAV) | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH/PyJHora | ✅可直接复制 | +| Ashtakavarga | Sarvashtakavarga (SAV) | ✅已实现(SAV=337) | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Ashtakavarga | Prastara Ashtakavarga (PAV) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Ashtakavarga | Sodhita Ashtakavarga | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Ashtakavarga | Sodhya Pindas | ❌缺失 | ✅(所有分盘) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Ashtakavarga | Graha Pinda | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Ashtakavarga | Rasi Pinda | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Ashtakavarga | Yoga Pinda | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Ashtakavarga | Trikona Reduction | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Ashtakavarga | Ekapatyapaksha Reduction | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Ashtakavarga | 分盘中计算AV | ❌缺失 | ✅(所有分盘) | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| Ashtakavarga | Kakshya评分 | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ✅可直接复制 | +| Ashtakavarga | 行运AV评分 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Ashtakavarga | 吉祥方位 | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Ashtakavarga | 合盘中AV叠加 | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | + +**差距总结**:我们有BAV+SAV基础计算,但缺少PAV展开表、Sodhita减法、Pinda体系、Kakshya评分、分盘级AV计算。JH和Kala在这些方面远超我们。 + +--- + +## 四、Tajika / Varshaphala + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Tajika | Varshaphala年运盘 | ✅已实现(tajika.py) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | Muntha | ✅已实现(muntha.py) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | Year Lord | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | Mudda Dasha | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | Tajika Yogas | 📖仅知识 | ✅(184种) | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Tajika | Panchavargiya Bala | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH/Kala | ❌仅借鉴 | +| Tajika | Dvadashavargiya Bala | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH/Kala | ❌仅借鉴 | +| Tajika | Harsha Bala | ❌缺失 | ✅ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Tajika | Sahams (36种) | ❌缺失(仅Vivah Saham) | ✅(36种) | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | Saham时序 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Tajika | Hadda Dasha | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Tajika | Patyayini Dasha | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Tajika | 月运盘 Masa Phala | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | 日运盘 Dina Phala | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Tajika | Tajika Aspects | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| Tajika | Tithi Pravesha Chart | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ⚠️需改编 | +| Tajika | Yoga Pravesha Chart | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Tajika | Nakshatra Pravesha Chart | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | + +**差距总结**:我们有基础年运盘+Muntha+YearLord+Mudda Dasha,但缺少Tajika力量体系(Panchavargiya/Harsha Bala)、完整Sahams、Tithi Pravesha等高级年运功能。 + +--- + +## 五、Shadbala + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Shadbala | Sthana Bala | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Shadbala | Kaala Bala | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Shadbala | Dig Bala | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Shadbala | Chesta Bala | ⚠️部分(速度分档近似) | ✅ | ✅(Parashara法) | ✅ | ✅ | JH/Kala | ⚠️需改编 | +| Shadbala | Naisargika Bala | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Shadbala | Drik Bala | ⚠️部分(简化相位权重) | ✅ | ✅ | ✅ | ✅ | JH | ⚠️需改编 | +| Shadbala | Ishta/Kashta Phala | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Shadbala | Bhava Bala | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Shadbala | Vimsopaka Bala | ✅已实现(vimsopaka_calculator.py) | ✅(4种分盘体系) | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Shadbala | Vaiseshikamsa (Parijatamsa等) | ❌缺失 | ✅(4种分盘体系) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Shadbala | 行星战争(Shadbala内) | ⚠️部分 | ✅ | ✅(唯一Surya Siddhanta法) | ❌ | ❌ | Kala | ❌仅借鉴 | +| Shadbala | 外部绝对值校准 | ❌缺失 | ✅(参考标准) | ✅ | ✅ | ✅ | JH | 🔧自行实现 | + +**差距总结**:我们六重力量框架完整但Chesta/Drik有简化项。最大缺口:Bhava Bala缺失、Vaiseshikamsa缺失、外部绝对值校准未完成。 + +--- + +## 六、Yoga识别 + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Yoga | 基础Yoga引擎 | ✅已实现(yoga_engine.py, F1=95.22%) | ✅(184种) | ✅ | ❌ | ✅(1001+种) | JH | ✅可直接复制 | +| Yoga | Raja Yoga族 | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Yoga | Dhana Yoga族 | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Yoga | Neechabhanga Raja Yoga | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Yoga | Daridra Yoga | ✅已实现 | ✅ | ❌ | ❌ | ✅ | JH | ✅可直接复制 | +| Yoga | Curse Yoga | ✅已实现(curse_yoga_detector.py) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Yoga | 分盘中Yoga识别 | ❌缺失 | ✅(所有分盘) | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Yoga | Tajika Yoga | 📖仅知识 | ✅(184种) | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Yoga | Yoga搜索/筛选 | ❌缺失 | ✅ | ✅ | ❌ | ✅(研究模块) | JH | ⚠️需改编 | +| Yoga | Yoga强度评分 | ✅已实现(yoga-strength-scoring) | ❌ | ✅ | ❌ | ❌ | — | 🔧自行实现 | +| Yoga | 1001+ Yoga覆盖 | ❌缺失(~100条规则) | ✅(184) | ✅ | ❌ | ✅(1001+) | PL | ❌仅借鉴 | +| Yoga | Sankha Yoga误报修复 | ✅已实现 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | + +**差距总结**:我们Yoga精度高(F1=95.22%)但覆盖面窄(~100条 vs JH 184/PL 1001+)。关键缺口:分盘中Yoga识别、Tajika Yoga、更广泛的Yoga覆盖。 + +--- + +## 七、Transit / Gochar + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Transit | 真实行星位置计算 | ✅已实现(Swiss Ephemeris) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Transit | 多参考点Transit | ✅已实现(Lagna+Chandra Lagna) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Transit | Double Transit (KN Rao) | ✅已实现(double-transit-pac) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Transit | Transit LL/7L连接 | ✅已实现(transit-ll7l) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Transit | 行星聚集检测 | ✅已实现(planetary-congregation) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Transit | AV行运评分 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Transit | Kakshya行运分析 | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ⚠️需改编 | +| Transit | Tara分类行运 | ❌缺失 | ✅(Karma等特殊Tara) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Transit | 行运日历(图形化) | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ❌仅借鉴 | +| Transit | 精确触发搜索 | ❌缺失 | ✅(精确到度分) | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Transit | 动画行运 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ❌仅借鉴 | +| Transit | 行星逆行/顺行追踪 | ⚠️部分 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Transit | 星座/Nakshatra切换时间 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Transit | 食相预测 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| Transit | Latta(踢击) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Transit | Sade Sati追踪 | ❌缺失 | ❌ | ❌ | ❌ | ✅ | PL | ❌仅借鉴 | + +**差距总结**:我们的Transit强在解读方法论(多参考点/Double Transit/LL7L),但弱在可视化(日历/动画)和辅助计算(AV评分/Kakshya/精确搜索/食相)。 + +--- + +## 八、Synastry / Kuta匹配 + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Synastry | Ashta Koota 36分 | ✅已实现(synastry.py) | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| Synastry | 月亮Gana/Kuta | ✅已实现 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| Synastry | 所有行星+Lagna的Gana/Kuta | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Synastry | Rajju Dosha | ✅已实现(synastry.py additional_kutas) | ❌ | ✅ | ❌ | ❌ | dashaflow/Kala | ✅dashaflow MIT已适配 | +| Synastry | Vedha Dosha | ✅已实现(synastry.py additional_kutas) | ❌ | ✅ | ❌ | ❌ | dashaflow/Kala | ✅dashaflow MIT已适配 | +| Synastry | Strii-Diirgha | ✅已实现(synastry.py additional_kutas) | ❌ | ✅ | ❌ | ❌ | dashaflow/Kala | ✅dashaflow MIT已适配 | +| Synastry | Mahendra | ✅已实现(synastry.py additional_kutas) | ❌ | ✅ | ❌ | ❌ | dashaflow/Kala | ✅dashaflow MIT已适配 | +| Synastry | Vasya | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Synastry | Interaspects(交互相位) | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| Synastry | Davidson Chart | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Synastry | Composite Chart | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Synastry | AV叠加(合盘) | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| Synastry | 关系兼容性报告 | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| Synastry | Darakaraka深度解读 | ✅已实现(darakaraka_reader.py) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Synastry | 配偶六层确认法 | 📖仅知识 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | + +**差距总结**:我们已从基础Ashta Koota 36分扩展到 Mahendra / Stree Deergha / Vedha / Rajju / BadConstellations 附加Kuta,并保留 DK 解读;仍落后 Kala 的部分主要是所有行星+Lagna Kuta、Davidson/Composite、AV叠加与完整关系报告生成。JH没有合盘模块。 + +--- + +## 九、Muhurta + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Muhurta | Panchanga五要素 | ✅已实现(muhurta.py) | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Muhurta | Tarabala | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Muhurta | Chandrabala | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Muhurta | 吉凶温度计 | ❌缺失 | ❌ | ❌ | ❌ | ✅ | PL | ❌仅借鉴 | +| Muhurta | Rahu Kalam | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Muhurta | Gulika Kalam | ⚠️部分 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Muhurta | Yama Gandam | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Muhurta | Pancha Pakshi | ✅已实现(pancha_pakshi.py) | ❌ | ❌ | ❌ | ✅ | — | 🔧自行实现 | +| Muhurta | Suunya Rasis/Tithis | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Muhurta | Panchaka | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Muhurta | 时间调整工具 | ❌缺失 | ❌ | ❌ | ❌ | ✅ | PL | ❌仅借鉴 | +| Muhurta | 批量择时搜索 | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | + +**差距总结**:我们有Panchanga+Tarabala+Chandrabala+Pancha Pakshi,但Kala有最完整的Muhurta模块(Suunya/Panchaka等)。PL有可视化择时工具。 + +--- + +## 十、Prashna + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Prashna | 基础Prashna星盘 | ✅已实现(prashna.py) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Prashna | Arudha计算 | ✅已实现 | ✅ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Prashna | Sphuta计算 | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| Prashna | Trisphuta | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH/Kala | ⚠️需改编 | +| Prashna | Chatursphuta | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Prashna | Prana/Deha/Mrityu | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Prashna | 数字选Prashna(1-108) | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ✅可直接复制 | +| Prashna | KP数字选(1-249) | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ✅可直接复制 | +| Prashna | KP 16分盘数字选(1-1800) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Prashna | Chandra Kriyas/Velas/Avasthas | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Prashna | Yama Sukra | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Prashna | Sahams(Prashna) | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | + +**差距总结**:我们有基础Prashna+Arudha,但缺少Sphuta/Trisphuta/Prana-Deha-Mrityu/数字选盘等关键Prashna工具。 + +--- + +## 十一、Jaimini + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Jaimini | Chara Karaka (7星制) | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Jaimini | Chara Karaka (8星制) | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Jaimini | Karakamsa / Swamsha | ✅已实现 | ✅ | ❌ | ❌ | ✅ | JH | ✅可直接复制 | +| Jaimini | Atmakaraka | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Jaimini | Arudha Pada (12宫) | ✅已实现(jaimini.py A1-A12/UL) | ✅(Rasi+分盘) | ✅ | ❌ | ✅ | dashaflow/jaimini-tropical/JH | ✅MIT思路已适配 | +| Jaimini | Chandra Arudha / Surya Arudha | ❌缺失 | ✅(12个) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Jaimini | Graha Arudha / Graha Pada | ✅已实现(jaimini.py Graha Pada) | ✅(9星+双Graha) | ❌ | ❌ | ❌ | jaimini-tropical/JH | ✅MIT思路已适配 | +| Jaimini | Chara Dasha | ⚠️部分(24.17%匹配) | ✅(2法) | ✅(KN Rao) | ❌ | ✅ | JH/jaimini-tropical | ⚠️需继续对标 | +| Jaimini | Jaimini力量体系 | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| Jaimini | 分盘中Arudha Pada | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| Jaimini | Upapada Lagna | ✅已实现 | ✅ | ❌ | ❌ | ❌ | — | 🔧自行实现 | + +**差距总结**:Jaimini静态分析(AK/Karakamsa/A1-A12/UL/Graha Pada/Argala)覆盖明显增强;Chara Dasha timing 仍为 partial,仍缺 Chandra/Surya Arudha、分盘级 Arudha 与完整 KN Rao/PVN Rao/Iranganti Chara Dasha 回归。 + +--- + +## 十二、Panchang + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Panchang | Tithi | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Panchang | Nakshatra | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Panchang | Yoga | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Panchang | Karana | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Panchang | Vara | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Panchang | 日出/日落 | ✅已实现 | ✅(3种定义) | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Panchang | 月出/月落 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Panchang | Hora (24个) | ⚠️部分 | ✅(24个Hora结束时间) | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Panchang | Vyatipata | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Panchang | Vaidhriti | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Panchang | 批量Panchanga(月度) | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ✅可直接复制 | +| Panchang | 太阴年/月 | ⚠️部分 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Panchang | 特殊Tithi(Janma等) | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | + +**差距总结**:Panchang五要素全覆盖,但缺日出/月出精度选项、Vyatipata/Vaidhriti、批量生成、特殊Tithi分类。 + +--- + +## 十三、Nakshatra + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Nakshatra | 基础Nakshatra定位 | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Nakshatra | Pada | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Nakshatra | Tara Bala | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Nakshatra | Chandra Bala | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Nakshatra | Sub-Lord (KP) | ✅已实现 | ✅(5级) | ✅(5级) | ❌ | ✅ | JH | ✅可直接复制 | +| Nakshatra | Navatara系统 | ✅已实现 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Nakshatra | 特殊Tara(Karma等) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Nakshatra | Nakshatra相位 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Nakshatra | Latta(踢击) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Nakshatra | Nakshatra Devata/神祇 | 📖仅知识 | ❌ | ❌ | ❌ | ✅ | — | 🔧自行实现 | + +**差距总结**:基础Nakshatra+Tara+KP覆盖好,但缺特殊Tara/Nakshatra相位/Latta等高级功能。 + +--- + +## 十四、Remedies + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Remedies | 补救措施推荐 | 📖仅知识(references) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Remedies | 宝石推荐 | 📖仅知识 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| Remedies | Mantra推荐 | 📖仅知识 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | + +**差距总结**:Remedies在所有4个对标项目中都不是计算模块,属于解读层面。我们有参考文档但无可执行输出。 + +--- + +## 十五、Bhava系统 + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Bhava | 整宫制(Whole Sign) | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| Bhava | 等宫制(Equal 30°) | ✅已实现(bhava_chalit.py) | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Bhava | Bhava Chalit | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Bhava | Sripathi/Porphyry | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Bhava | Placidus | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| Bhava | Koch | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Bhava | Regiomontanus | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Bhava | Campanus | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Bhava | 基于月亮/太阳起算 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Bhava | Lagna起点/中点选项 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Bhava | KP宫头制 | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | + +**差距总结**:我们只有整宫制和等宫制+Bhava Chalit,JH支持12种宫位系统。这是一个显著差距,尤其对Sripathi用户。 + +--- + +## 十六、天体历(Ephemeris) + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| Ephemeris | Swiss Ephemeris行星位置 | ✅已实现 | ✅ | ✅ | ✅(JPL DE421) | ✅ | — | — | +| Ephemeris | 批量月度星历表 | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Ephemeris | 交互式星历表 | ❌缺失 | ❌ | ✅ | ❌ | ✅ | Kala | ❌仅借鉴 | +| Ephemeris | 图形星历表 | ❌缺失 | ❌ | ❌ | ❌ | ✅ | PL | ❌仅借鉴 | +| Ephemeris | 行星速度/距离/赤经/赤纬 | ⚠️部分 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| Ephemeris | 会合/冲搜索 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| Ephemeris | 星座切换追踪 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| Ephemeris | 日期范围 | 5400BC-5400AD | 5400BC-5400AD | 5400BC-5400AD | ✅ | 5400BC-5400AD | — | — | + +**差距总结**:天文精度对齐(Swiss Ephemeris),但缺批量星历表生成、行星天文数据完整输出、会合搜索。 + +--- + +## 十七、其他高级功能 + +| 功能域 | 具体技法 | 我们的状态 | JH | Kala | JG | PL | 最佳开源来源 | 代码可复用? | +|--------|---------|-----------|-----|------|----|----|-------------|-------------| +| 高级 | Ayanamsa多选项 | ⚠️部分(Lahiri) | ✅(6+预设+自定义) | ✅ | ✅(Chitra Paksha) | ✅ | JH | ✅可直接复制 | +| 高级 | Upagrahas (Gulika/Mandi等) | ⚠️部分 | ✅(+9个Upagrahas) | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 高级 | Doomadi Upagrahas | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 高级 | 特殊Lagna (Bhava/Hora/Ghati等) | ✅已实现(special_lagnas.py) | ✅(11+) | ✅(7) | ❌ | ✅ | JH | ✅可直接复制 | +| 高级 | Varnada Lagna | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | Indu Lagna | ✅已实现 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| 高级 | Sahams (36种) | ⚠️部分(仅Vivah Saham) | ✅(36种) | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 高级 | Mrityu Bhaga (致命度数) | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 高级 | Pushkara Bhaga | ✅已实现 | ✅(2种定义) | ✅ | ❌ | ❌ | JH | ✅可直接复制 | +| 高级 | 64th Navamsa | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | 22nd Drekkana | ❌缺失 | ✅(4种定义) | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | Avastha (Baladi/Jagradadi) | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 高级 | Sayanadi Avastha | ❌缺失 | ✅(所有Varga) | ✅(所有Varga) | ❌ | ✅ | JH | ⚠️需改编 | +| 高级 | Sudarshana Chakra | ✅已实现 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 高级 | Kalachakra | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | Kota Chakra | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ⚠️需改编 | +| 高级 | Sarvatobhadra Chakra | ❌缺失 | ✅ | ❌ | ❌ | ✅ | JH | ⚠️需改编 | +| 高级 | 行星关系(永久/临时/复合) | ✅已实现 | ✅ | ✅ | ✅ | ✅ | JH | ✅可直接复制 | +| 高级 | Pachakadi关系 | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | 推进(Progression) | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ⚠️需改编 | +| 高级 | 世俗占星(Mundane) | ❌缺失 | ✅ | ✅ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | 寿命计算(Ayurdaya) | ❌缺失 | ❌ | ✅(Pindaadi) | ❌ | ❌ | Kala | ❌仅借鉴 | +| 高级 | 出生时间矫正 | 📖仅知识 | ❌ | ❌ | ❌ | ✅(交互式) | PL | ❌仅借鉴 | +| 高级 | 研究模块(群体分析) | ❌缺失 | ❌ | ❌ | ❌ | ✅ | PL | ❌仅借鉴 | +| 高级 | 行星战争(精确计算) | ⚠️部分 | ✅ | ✅(唯一Surya Siddhanta法) | ❌ | ❌ | Kala | ❌仅借鉴 | +| 高级 | 燃烧(Combustion, 多法) | ⚠️部分 | ✅ | ✅(当代+Surya Siddhanta) | ❌ | ❌ | Kala | ❌仅借鉴 | +| 高级 | 外行星(天王/海王/冥王) | ❌缺失 | ✅ | ✅ | ❌ | ✅ | JH | ✅可直接复制 | +| 高级 | 小行星 | ❌缺失 | ❌ | ✅ | ❌ | ❌ | Kala | ❌仅借鉴 | +| 高级 | Kunda(出生时间矫正用) | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | Bhrigu Bindu | ❌缺失 | ✅ | ❌ | ❌ | ❌ | JH | ⚠️需改编 | +| 高级 | Yogi/Avayogi行星 | ✅已实现 | ✅ | ❌ | ❌ | ✅ | — | 🔧自行实现 | +| 高级 | Rashi Tulya Navamsa | ✅已实现 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| 高级 | Argala | ✅已实现 | ✅ | ❌ | ❌ | ❌ | JH | ✅可直接复制 | +| 高级 | 五系统Dasha Convergence | ✅已实现 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| 高级 | Full-reading自动化解盘 | ✅已实现(47模块) | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| 高级 | MEVG外部验证门控 | ✅已实现 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | +| 高级 | 主题化报告桥接 | ✅已实现 | ❌ | ❌ | ❌ | ❌ | — | 🔧自行实现 | + +--- + +## 十八、综合统计 + +### 我们的能力统计 + +| 状态 | 数量 | 百分比 | +|------|------|--------| +| ✅已实现 | 72 | 39.6% | +| ⚠️部分 | 18 | 9.9% | +| 📖仅知识 | 6 | 3.3% | +| ❌缺失 | 86 | 47.2% | +| **总计** | **182** | **100%** | + +### 按功能域统计 + +| 功能域 | ✅已实现 | ⚠️部分 | ❌缺失 | 覆盖率 | +|--------|---------|--------|--------|--------| +| 基础排盘(Varga) | 19 | 0 | 7 | 73.1% | +| Dasha系统 | 7 | 2 | 27 | 23.1% | +| Ashtakavarga | 2 | 0 | 13 | 13.3% | +| Tajika/Varshaphala | 4 | 0 | 13 | 23.5% | +| Shadbala | 7 | 2 | 3 | 58.8% | +| Yoga识别 | 5 | 0 | 5 | 50.0% | +| Transit/Gochar | 4 | 1 | 10 | 26.7% | +| Synastry/Kuta | 2 | 0 | 11 | 15.4% | +| Muhurta | 4 | 1 | 6 | 36.4% | +| Prashna | 2 | 0 | 8 | 20.0% | +| Jaimini | 5 | 1 | 4 | 50.0% | +| Panchang | 5 | 1 | 6 | 41.7% | +| Nakshatra | 4 | 0 | 3 | 57.1% | +| Bhava系统 | 3 | 0 | 6 | 33.3% | +| 天体历 | 1 | 1 | 5 | 14.3% | +| 其他高级 | 8 | 4 | 21 | 22.7% | + +### 与对标项目的整体对比 + +| 维度 | 我们 | JH | Kala | JG | PL | +|------|------|-----|------|----|----| +| 分盘种类 | 20种(BPHS十六分盘+D5/D6/D8/D11) | 23+标准+自定义D300 | 16+ | 14 | 16+ | +| Dasha系统 | 7种(1 partial) | 30+种 | 24+种 | 1种 | 23+种 | +| Ashtakavarga | BAV+SAV | BAV+SAV+PAV+Sodhita+Pindas | 完整Pinda体系 | BAV+SAV | BAV+SAV+Kakshya | +| 行星位置精度 | Swiss Ephemeris | Swiss Ephemeris | Swiss Ephemeris | JPL DE421 | Swiss Ephemeris | +| 宫位系统 | 2种 | 12种 | 4+种 | 1种 | 4+种 | +| Ayanamsa选项 | 1种(Lahiri) | 6+种+自定义 | 多种 | 1种 | 多种 | +| Yoga覆盖 | ~100条 | 184种 | 未知 | 0 | 1001+种 | +| KP系统 | Sub-Lord基础 | 5级Sub | 5级Sub | ❌ | 完整 | +| 交互式UI | CLI+AI对话 | GUI | GUI | 无UI | GUI | +| 解读深度 | AI驱动+方法论 | 纯计算 | 纯计算 | 纯计算 | 计算+文本 | +| 外部验证 | MEVG强制 | ❌ | ❌ | ❌ | ❌ | + +--- + +## 十九、优先级建议 + +### P0 — 核心差距(影响专业可信度) + +| 序号 | 差距 | 来源 | 可复用性 | 预估工作量 | +|------|------|------|---------|-----------| +| 1 | Chara Dasha完整实现(KN Rao法) | JH/PyJHora | ⚠️需改编 | 大 | +| 2 | 分盘级Ashtakavarga | JH | ⚠️需改编 | 中 | +| 3 | Bhava Bala计算 | JH | ✅可直接复制 | 小 | +| 4 | PAV/Sodhita Ashtakavarga | JH | ✅可直接复制 | 中 | +| 5 | 完整36 Sahams | JH | ✅可直接复制 | 中 | +| 6 | Ayanamsa多选项 | JH | ✅可直接复制 | 小 | + +### P1 — 重要功能(对标专业软件) + +| 序号 | 差距 | 来源 | 可复用性 | 预估工作量 | +|------|------|------|---------|-----------| +| 7 | 宫位Dasha族(Narayana扩展+Sthira+Trikona等) | JH | ⚠️需改编 | 大 | +| 8 | Vimshottari多起算点(12种) | JH | ✅可直接复制 | 中 | +| 9 | Sayanadi Avastha | JH | ⚠️需改编 | 中 | +| 10 | Sripathi宫位制 | JH | ✅可直接复制 | 小 | +| 11 | Trisphuta/Prana-Deha-Mrityu | JH | ✅可直接复制 | 中 | +| 12 | 批量星历表生成 | JH | ✅可直接复制 | 中 | +| 13 | 外行星支持 | JH | ✅可直接复制 | 小 | +| 14 | D81/D108高级分盘 | JH | ⚠️需改编 | 中 | + +### P2 — 增强功能(差异化竞争力) + +| 序号 | 差距 | 来源 | 可复用性 | 预估工作量 | +|------|------|------|---------|-----------| +| 15 | 合盘高级功能(Rajju/Vedha/Davidson) | Kala | ❌仅借鉴 | 大 | +| 16 | Chakra系统(Kalachakra/Kota/Sarvatobhadra) | JH | ⚠️需改编 | 中 | +| 17 | Tajika力量(Panchavargiya/Harsha Bala) | Kala | ❌仅借鉴 | 中 | +| 18 | 寿命计算(Pindaadi Ayurdaya) | Kala | ❌仅借鉴 | 大 | +| 19 | 世俗占星 | JH | ⚠️需改编 | 大 | +| 20 | Tithi Pravesha/Nakshatra Pravesha | JH | ⚠️需改编 | 中 | + +--- + +## 二十、我们的独特优势(对标项目没有的) + +| 序号 | 优势 | 对标项目状态 | +|------|------|-------------| +| 1 | Full-reading自动化47模块解盘 | 所有4个项目都没有 | +| 2 | MEVG外部验证门控 | 所有4个项目都没有 | +| 3 | 五系统Dasha Convergence | 所有4个项目都没有 | +| 4 | Double Transit PAC (KN Rao法) | 所有4个项目都没有 | +| 5 | AI驱动的深度解读+现代措辞 | 所有4个项目都没有 | +| 6 | 主题化报告桥接(婚姻/事业/健康/财富/灵性) | 所有4个项目都没有 | +| 7 | Yoga精度F1=95.22%(持续优化) | JH有Yoga但无精度指标 | +| 8 | Rashi Tulya Navamsa映射 | 所有4个项目都没有 | +| 9 | Darakaraka深度解读模块 | 所有4个项目都没有 | +| 10 | 行星聚集检测+Transit LL/7L | 所有4个项目都没有 | +| 11 | 15,807条AA级名人案例库 | JH有数据但不在软件内 | +| 12 | 技法注册中心(Technique Registry) | 所有4个项目都没有 | +| 13 | Strict Workflow Router(自动路由) | 所有4个项目都没有 | + +--- + +> **结论**:我们的核心差距在**Dasha系统的广度**(7/30+种)和**Ashtakavarga的深度**(缺PAV/Sodhita/Pinda)。我们的独特优势在**AI驱动的自动化解盘+方法论+外部验证**,这是所有GUI计算软件不具备的。建议优先补齐P0差距,然后利用PyJHora(AGPL)的开源代码快速扩展Dasha和Ashtakavarga覆盖面。 diff --git a/references/indastro-case-studies.md b/references/indastro-case-studies.md new file mode 100644 index 00000000..366aaede --- /dev/null +++ b/references/indastro-case-studies.md @@ -0,0 +1,612 @@ +# Indastro 专业占星案例集 + +**来源**: Indastro.com (专业吠陀占星网站) +**采集日期**: 2026-06-10 +**采集方式**: WebFetch 自动抓取名人星盘分析页面 +**总案例数**: 12 + +--- + +## 数据说明 + +- 所有案例均来自 Indastro.com 的 Celebrity Horoscope 板块 +- 每个案例的完整URL格式为 `https://www.indastro.com/celebrities/{name}-horoscope` +- 行星位置数据包含度数、星座、Nakshatra(星宿)和 Pada +- 所有星盘基于**吠陀占星(Vedic Astrology)月亮星座体系**,使用 Lahiri Ayanamsa +- 现代行星(天王星、海王星、冥王星)也包含在数据中但非吠陀传统行星 + +--- + +## 案例一:Elon Musk — 科技企业家 / 世界首富 + +**来源URL**: https://www.indastro.com/celebrities/elon-musk-horoscope +**主题**: 职业/财富/科技 +**最后更新**: 2025-11-12 + +**出生数据**: +- 日期: 1971-06-28 +- 时间: 07:30 +- 地点: Pretoria, South Africa +- 坐标: 约 25°44'S, 28°11'E + +**星盘配置**: +- Lagna: 巨蟹座 (Cancer) 6°53' — Pushya Nakshatra, Pada 2 +- 太阳: 双子座 (Gemini) 12°40' — Ardra Nakshatra +- 月亮: 狮子座 (Leo) 17°46' — Purva Phalguni Nakshatra +- 火星: 摩羯座 (Capricorn) 27°28' — Dhanistha Nakshatra (擢升) +- 水星: 双子座 (Gemini) 21°07' — Punarvasu Nakshatra +- 木星: 天蝎座 (Scorpio) 4°10' — Anuradha Nakshatra +- 金星: 金牛座 (Taurus) 26°14' — Mrigasira Nakshatra +- 土星: 金牛座 (Taurus) 7°41' — Krittika Nakshatra +- 罗睺: 摩羯座 (Capricorn) 21°39' — Sravana Nakshatra +- 计都: 巨蟹座 (Cancer) 21°39' — Ashlesha Nakshatra + +**主要Yoga格局**: +1. **Budhaditya Yoga**: 太阳+水星在双子座上升宫合相 → 智慧、名声、财富 +2. **Bhadra Yoga**: 上升主星水星位于本宫双子座 → 智慧与决策力 +3. **Voshi Yoga**: 金星+土星在太阳第12宫 → 对空间/天空/卫星的倾向 +4. **Viparita Harsha Raja Yoga**: 土星(8宫主)在12宫 → 经历挑战后的成功 +5. **Lagna Papa Kartari Yoga**: 上升被土星和计都夹击 → 项目首次执行面临挑战 + +**事件验证**: +- 火星擢升(摩羯座) + 强金星 → 科学/太空工程领域成功,SpaceX/Tesla +- 2025年《福布斯》净资产估值5000亿美元 +- 第9宫和第5宫主星在第12宫 → 研究、空间和工程相关工作倾向 + +**Dasha预测**: +- 2027年10月-2029年4月: Rahu-Moon周期 → 增长和成功,重要项目延迟 +- 2029年4月-2030年4月: 事业将面临艰难和变革性时期 + +**可验证项**: +- [ ] Lagna应为巨蟹座 (Pushya Nakshatra) +- [ ] 火星应在摩羯座擢升 +- [ ] 太阳+水星应在双子座合相 + +--- + +## 案例二:Lionel Messi — 足球运动员 / 世界冠军 + +**来源URL**: https://www.indastro.com/celebrities/lionel-messi-horoscope +**主题**: 职业/体育/健康 + +**出生数据**: +- 日期: 1987-06-24 +- 时间: 20:30 +- 地点: Rosario, Argentina +- 坐标: 约 32°57'S, 60°38'W + +**星盘配置**: +- Lagna: 射手座 (Sagittarius) 12°49' — Mula Nakshatra, Pada 4 +- 太阳: 双子座 (Gemini) 9°16' — Ardra Nakshatra +- 月亮: 金牛座 (Taurus) 25°51' — Mrigasira Nakshatra (擢升) +- 火星: 双子座 (Gemini) 28°50' — Punarvasu Nakshatra +- 水星: 双子座 (Gemini) 22°34' — Punarvasu Nakshatra +- 木星: 白羊座 (Aries) 1°21' — Ashwini Nakshatra (落陷) +- 金星: 金牛座 (Taurus) 23°07' — Rohini Nakshatra +- 土星: 天蝎座 (Scorpio) 23°02' — Jyestha Nakshatra +- 罗睺: 双鱼座 (Pisces) 13°52' — Uttara Bhadrapada Nakshatra +- 计都: 处女座 (Virgo) 13°52' — Hasta Nakshatra + +**主要Yoga格局**: +1. **Durudhara Yoga**: 火星+木星分别在2宫和12宫 → 良好教育,导师指导下获得体育成就 +2. **Voshi Yoga**: 金星在太阳第12宫 → 创造力、自信、国际声誉 +3. **Arishta Yoga**: 水星(6宫主)+太阳(8宫主)合相 → 受伤和健康问题 + +**事件验证**: +- 月亮擢升(金牛座 Mrigasira) → 公众魅力和成功 +- 火星在第6宫(竞争宫) → 竞争力强,体育获胜 +- Arishta Yoga → 11岁诊断生长激素缺乏症 +- Rahu从月亮第11宫 → 全球巨大声誉 + +**可验证项**: +- [ ] Lagna应为射手座 (Mula Nakshatra) +- [ ] 月亮应在金牛座擢升度 +- [ ] 水星+太阳合相形成Arishta Yoga + +--- + +## 案例三:Aishwarya Rai Bachchan — 宝莱坞影星 / 世界小姐 + +**来源URL**: https://www.indastro.com/celebrities/aishwarya-rai-bachchan-horoscope +**主题**: 职业/美貌/婚姻 + +**出生数据**: +- 日期: 1973-11-01 +- 时间: 04:05 +- 地点: Mangalore, Karnataka, India +- 坐标: 约 12°52'N, 74°53'E + +**星盘配置**: +- Lagna: 处女座 (Virgo) 18°02' — Hasta Nakshatra, Pada 3 +- 太阳: 天秤座 (Libra) 15°26' — Swati Nakshatra +- 月亮: 射手座 (Sagittarius) 26°13' — Purva Ashadha Nakshatra +- 火星: 白羊座 (Aries) 5°43' — Ashwini Nakshatra (入庙) +- 水星: 天蝎座 (Scorpio) 2°46' — Vishakha Nakshatra +- 木星: 摩羯座 (Capricorn) 10°37' — Sravana Nakshatra (落陷) +- 金星: 射手座 (Sagittarius) 2°06' — Mula Nakshatra +- 土星: 双子座 (Gemini) 11°02' — Ardra Nakshatra +- 罗睺: 射手座 (Sagittarius) 6°16' — Mula Nakshatra +- 计都: 双子座 (Gemini) 6°16' — Mrigasira Nakshatra + +**主要Yoga格局**: +1. **Vesi Yoga**: 水星在太阳第2宫 → 表演、舞蹈、演艺才能 +2. **Durdhara Yoga**: 木星+水星在月亮第2和第12宫 → 名声和成功电影角色 +3. **Vipreet Raj Yoga**: 火星(8宫主)+太阳(12宫主)相互相位 → 自信和受欢迎 +4. **Rahu+金星合相**: → 海外发展,但爱情婚姻初期不稳定 + +**事件验证**: +- 火星在白羊座入庙 → 竞争力强,成为名人关键 +- Rahu+金星 → 感情初期不稳定但最终与Abhishek Bachchan婚姻稳定 +- 1994年世界小姐冠军 → Vesi/Durdhara Yoga综合效应 +- Vipreet Raj Yoga导致Manglik dosha → 感情关系充满挑战 + +**Dasha信息**: +- 2025年6月-2027年7月: 木星大运 → 新周期开始 +- 2027年2月-4月5日: 将在新领域和生意上取得成功 + +**可验证项**: +- [ ] Lagna应为处女座 (Hasta Nakshatra) +- [ ] 火星应在白羊座入庙(Ashwini) +- [ ] 罗睺+金星应在射手座合相 + +--- + +## 案例四:Rihanna — 音乐人 / 企业家 / 亿万富翁 + +**来源URL**: https://www.indastro.com/celebrities/rihanna-horoscope +**主题**: 职业/财富/艺术 + +**出生数据**: +- 日期: 1988-02-20 +- 时间: 08:50 +- 地点: Bridgetown, Saint Michael, Barbados +- 坐标: 约 13°06'N, 59°37'W + +**星盘配置**: +- Lagna: 双鱼座 (Pisces) 21°50' — Revati Nakshatra, Pada 2 +- 太阳: 水瓶座 (Aquarius) 7°24' — Shatabhisha Nakshatra +- 月亮: 双鱼座 (Pisces) 17°25' — Revati Nakshatra +- 火星: 射手座 (Sagittarius) 5°02' — Mula Nakshatra +- 水星: 摩羯座 (Capricorn) 19°36' — Sravana Nakshatra +- 木星: 白羊座 (Aries) 2°56' — Ashwini Nakshatra +- 金星: 双鱼座 (Pisces) 19°14' — Revati Nakshatra (擢升) +- 土星: 射手座 (Sagittarius) 6°50' — Mula Nakshatra +- 罗睺: 水瓶座 (Aquarius) 29°29' — Purva Bhadrapada Nakshatra +- 计都: 狮子座 (Leo) 29°29' — Uttara Phalguni Nakshatra + +**主要Yoga格局**: +1. **Lagna Yoga (第1宫星群)**: 月亮+金星在双鱼座第1宫 → 艺术魅力、美貌、音乐天赋 +2. **Karma Yoga (第10宫星群)**: 火星+土星+天王星+海王星在射手座第10宫 → 多重职业身份 +3. **日罗合相**: 太阳+罗睺在第12宫水瓶座 → 海外成功、非传统道路 +4. **Gaja Kesari Yoga**: 月亮在Kendra宫(1宫)与木星关联 → 声望、智慧和财富 +5. **金星擢升**: 双鱼座 → 艺术事业成功极强指标 + +**事件验证**: +- 第10宫多行星 → 多重职业:歌手、演员、企业家(Fenty品牌) +- 金星擢升在双鱼座 → 艺术天赋和全球影响力 +- 第2宫主星火星在第10宫 → 财富来自事业 +- Fenty Beauty成为10亿美元品牌 → 第11宫水星在摩羯座的商业头脑 + +**可验证项**: +- [ ] Lagna应为双鱼座 (Revati Nakshatra) +- [ ] 金星应在双鱼座擢升 +- [ ] 月亮+金星应在第1宫合相 + +--- + +## 案例五:Sachin Tendulkar — 板球运动员 / "板球之神" + +**来源URL**: https://www.indastro.com/celebrities/sachin-tendulkar-horoscope +**主题**: 职业/体育/名声 + +**出生数据**: +- 日期: 1973-04-24 +- 时间: 16:28 +- 地点: Mumbai, Maharashtra, India +- 坐标: 约 19°04'N, 72°51'E + +**星盘配置**: +- Lagna: 处女座 (Virgo) 9°13' — Uttara Phalguni Nakshatra, Pada 4 +- 太阳: 白羊座 (Aries) 11°03' — Ashwini Nakshatra (擢升) +- 月亮: 摩羯座 (Capricorn) 1°33' — Uttara Ashadha Nakshatra +- 火星: 摩羯座 (Capricorn) 27°06' — Dhanistha Nakshatra (擢升) +- 水星: 双鱼座 (Pisces) 17°36' — Revati Nakshatra (落陷) +- 木星: 摩羯座 (Capricorn) 16°39' — Sravana Nakshatra (落陷) +- 金星: 白羊座 (Aries) 14°58' — Bharani Nakshatra +- 土星: 金牛座 (Taurus) 24°19' — Mrigasira Nakshatra +- 罗睺: 射手座 (Sagittarius) 16°23' — Purva Ashadha Nakshatra +- 计都: 双子座 (Gemini) 16°23' — Ardra Nakshatra + +**主要Yoga格局**: +1. **Sunapha Yoga**: 木星在月亮第2宫 → 技能卓越、高排名、大量粉丝 +2. **Ubhayachari Yoga**: 水星+金星在太阳两侧 → 强健体魄、承担责任、平衡人生观 +3. **Neechabhanga Raj Yoga**: 水星和木星落陷但获取消 → 挑战后巨大成功 +4. **太阳擢升** (白羊座) + **火星擢升** (摩羯座) → 领导力、运动能力 + +**事件验证**: +- 太阳擢升 → 被称为"板球之神",获Bharat Ratna最高平民奖 +- 火星擢升 → 100个百分纪录、最多国际得分 +- 木星/水星落陷 → 教育中断(未完成大学),16岁国际首秀 +- Grahan Dosha(月亮+罗睺) → 健康挑战(心脏、消化系统) + +**可验证项**: +- [ ] Lagna应为处女座 (Uttara Phalguni) +- [ ] 太阳应在白羊座擢升(Ashwini) +- [ ] 火星应在摩羯座擢升(Dhanistha) + +--- + +## 案例六:Beyoncé — 音乐人 / 格莱美女王 + +**来源URL**: https://www.indastro.com/celebrities/beyonce-horoscope +**主题**: 职业/音乐/财富 +**最后更新**: 2025-10-29 + +**出生数据**: +- 日期: 1981-09-04 +- 时间: 21:47 +- 地点: Houston, Texas, USA +- 坐标: 约 29°45'N, 95°22'W + +**星盘配置**: +- Lagna: 白羊座 (Aries) 11°17' — Ashwini Nakshatra, Pada 4 +- 太阳: 狮子座 (Leo) 18°51' — Purva Phalguni Nakshatra +- 月亮: 天蝎座 (Scorpio) 3°08' — Vishakha Nakshatra (落陷) +- 火星: 巨蟹座 (Cancer) 8°19' — Pushya Nakshatra (落陷) +- 水星: 处女座 (Virgo) 9°40' — Uttara Phalguni Nakshatra (擢升/入庙) +- 木星: 处女座 (Virgo) 18°53' — Hasta Nakshatra +- 金星: 处女座 (Virgo) 27°12' — Chitra Nakshatra (落陷) +- 土星: 处女座 (Virgo) 15°30' — Hasta Nakshatra +- 罗睺: 巨蟹座 (Cancer) 7°12' — Pushya Nakshatra +- 计都: 摩羯座 (Capricorn) 7°12' — Abhijit Nakshatra + +**主要Yoga格局**: +1. **Neechbhanga Raj Yoga**: 金星落陷但水星合相 → 克服挑战,竞争中胜出 +2. **Ubhayachari Yoga**: 金星+水星+木星+土星在太阳第2宫 → 强大个性、迷人声音 +3. **火星-月亮 Parivartan Yoga + Neechbhanga Raj Yoga**: → 多面职业生涯 +4. **Chandra-Buddha Yoga**: 月亮+水星关联 → 歌唱事业成功 +5. 太阳在第10宫(从月亮起) → 成功和高度成就 +6. **水星入庙**在第11宫(从月亮起) → 财务地位提升 + +**事件验证**: +- 35座格莱美奖(女艺人纪录) → 太阳第10宫+水星入庙 +- Destiny's Child到单飞成功 → 多行星第11宫 +- 争议(Cowboy Carter/歌词) → Angarak Dosha(火星+罗睺对10宫相位) +- 与Jay-Z婚姻、三个孩子 → 金星Neechbhanga影响 + +**Dasha预测**: +- 2032年1月-2035年1月: Venus-Rahu周期 → 时尚/慈善/科技领域机遇 + +**可验证项**: +- [ ] Lagna应为白羊座 (Ashwini) +- [ ] 水星应在处女座入庙 +- [ ] 金星+水星+木星+土星应在处女座(第6宫) + +--- + +## 案例七:Brad Pitt — 好莱坞影星 / 制片人 + +**来源URL**: https://www.indastro.com/celebrities/brad-pitt-horoscope +**主题**: 职业/婚姻/名声 + +**出生数据**: +- 日期: 1963-12-18 +- 时间: 06:31 +- 地点: Shawnee, Oklahoma, USA +- 坐标: 约 35°19'N, 96°55'W + +**星盘配置**: +- Lagna: 天蝎座 (Scorpio) 23°02' — Jyestha Nakshatra, Pada 2 +- 太阳: 射手座 (Sagittarius) 2°01' — Mula Nakshatra +- 月亮: 射手座 (Sagittarius) 23°37' — Purva Ashadha Nakshatra +- 火星: 射手座 (Sagittarius) 16°18' — Purva Ashadha Nakshatra +- 水星: 射手座 (Sagittarius) 22°15' — Purva Ashadha Nakshatra +- 木星: 双鱼座 (Pisces) 16°27' — Uttara Bhadrapada Nakshatra (入庙) +- 金星: 射手座 (Sagittarius) 29°31' — Uttara Ashadha Nakshatra +- 土星: 摩羯座 (Capricorn) 25°45' — Dhanistha Nakshatra (入庙) +- 罗睺: 双子座 (Gemini) 17°48' — Ardra Nakshatra +- 计都: 射手座 (Sagittarius) 17°48' — Purva Ashadha Nakshatra + +**主要Yoga格局**: +1. **第2宫星群**: 太阳+月亮+火星+水星+金星5星在射手座第2宫 → 名气、财富、魅力 +2. **Sumukha Yoga**: 月亮+水星在第2宫 → 迷人外表和名声 +3. **Veshi Yoga**: 土星+金星在太阳第2宫 → 善良性格、迷人眼睛 +4. **木星入庙**(双鱼座第5宫) 照射第11宫和第9宫 → 巨大名声、奥斯卡奖项 +5. **Vanchana Chora Bhithi Yoga**: 火星(命主)+计都合相 → 害怕被利用 +6. 日食格局(太阳+计都) → 初期挑战、婚姻问题 + +**事件验证**: +- 五次奥斯卡提名、两次获奖 → 木星入庙+第2宫星群 +- 制片公司Plan B Entertainment成功(《为奴十二年》奥斯卡) → 金星(7宫主)+土星 +- 与Jennifer Aniston和Angelina Jolie高调离婚 → 日食格局 +- 与子女关系挑战 → 计都+太阳+火星组合 + +**Dasha预测**: +- 2026年2月-2029年1月: 土星-金星大运 → 新创意项目 + +**可验证项**: +- [ ] Lagna应为天蝎座 (Jyestha) +- [ ] 木星应在双鱼座入庙 +- [ ] 第2宫应有5星汇聚(射手座) + +--- + +## 案例八:M.S. Dhoni — 板球队长 / "Captain Cool" + +**来源URL**: https://www.indastro.com/celebrities/ms-dhoni-horoscope +**主题**: 职业/领导力/体育 +**最后更新**: 2025-10-01 + +**出生数据**: +- 日期: 1981-07-07 +- 时间: 11:15 +- 地点: Ranchi, Jharkhand, India +- 坐标: 约 23°21'N, 85°20'E + +**星盘配置**: +- Lagna: 处女座 (Virgo) 9°32' — Uttara Phalguni Nakshatra, Pada 4 +- 太阳: 双子座 (Gemini) 21°36' — Punarvasu Nakshatra +- 月亮: 处女座 (Virgo) 2°32' — Uttara Phalguni Nakshatra +- 火星: 金牛座 (Taurus) 28°57' — Mrigasira Nakshatra +- 水星: 双子座 (Gemini) 3°22' — Mrigasira Nakshatra (第10宫) +- 木星: 处女座 (Virgo) 9°11' — Uttara Phalguni Nakshatra +- 金星: 巨蟹座 (Cancer) 15°37' — Pushya Nakshatra +- 土星: 处女座 (Virgo) 10°15' — Hasta Nakshatra +- 罗睺: 巨蟹座 (Cancer) 8°11' — Pushya Nakshatra +- 计都: 摩羯座 (Capricorn) 8°11' — Abhijit Nakshatra + +**主要Yoga格局**: +1. **Gajakesari Yoga**: 月亮+木星在第1宫处女座合相 → 强大、智慧、名声、明智决策 +2. **Parakram Yoga**: 火星相位第3宮 → 非凡勇气、领导团队 +3. **Bhadra Yoga**: 水星在第10宫双子座强势 → 正确时机做出正确决策 +4. **Ubhayachari Yoga**: 金星在太阳第2宫 → 如王者般强健体魄 +5. **Parvata Yoga**: → 知名、幸运、富有、城镇领导者 +6. **Vish Dosha**: 土星+月亮在上升宫 → 过度思考、早期职业挣扎 + +**事件验证**: +- 2007年T20世界杯夺冠 → Bhadra Yoga + 水星第10宫 +- 2011年ODI世界杯夺冠 → Parakram Yoga + Gajakesari Yoga +- 唯一赢得三项ICC锦标赛的队长 → Parvata Yoga +- "Captain Cool"领导风格 → Gajakesari Yoga(月木合相) +- 与管理公司法律纠纷 → Rahu-Venus Grehan Dosha + +**Dasha预测**: +- 2028年10月-2033年8月: 商业计划预期扩展和增长 +- 2027年6月-2027年10月: 需注意健康问题和商业停滞 + +**可验证项**: +- [ ] Lagna应为处女座 (Uttara Phalguni) +- [ ] 月亮+木星应在第1宫合相(Gajakesari Yoga) +- [ ] 水星应在第10宫双子座(Bhadra Yoga) + +--- + +## 案例九:Joe Biden — 美国总统 + +**来源URL**: https://www.indastro.com/celebrities/joe-biden-horoscope +**主题**: 职业/政治/个人悲剧 + +**出生数据**: +- 日期: 1942-11-20 +- 时间: 08:30 +- 地点: Scranton, Pennsylvania, USA +- 坐标: 约 41°24'N, 75°39'W + +**星盘配置**: +- Lagna: 射手座 (Sagittarius) 23°38' — Purva Ashadha Nakshatra, Pada 4 +- 太阳: 天蝎座 (Scorpio) 4°27' — Anuradha Nakshatra +- 月亮: 白羊座 (Aries) 7°24' — Ashwini Nakshatra +- 火星: 天秤座 (Libra) 19°30' — Swati Nakshatra +- 水星: 天秤座 (Libra) 28°24' — Vishakha Nakshatra +- 木星: 巨蟹座 (Cancer) 2°04' — Punarvasu Nakshatra (逆行) +- 金星: 天蝎座 (Scorpio) 5°26' — Anuradha Nakshatra +- 土星: 金牛座 (Taurus) 16°53' — Rohini Nakshatra (逆行) +- 罗睺: 狮子座 (Leo) 7°05' — Magha Nakshatra +- 计都: 水瓶座 (Aquarius) 7°05' — Shatabhisha Nakshatra + +**主要Yoga格局**: +1. **Karmajiva Yoga**: 太阳(10宫主)位于上升 → 事业成功与名望 +2. **Voshi Yoga**: 火星+水星在太阳第12宫 → 国际平台成功 +3. **Parvat Yoga**: 吉星月亮在上升第6宫 → 1972年以冷门之姿当选参议员 +4. **Kapata Yoga**: 计都在第4宫+罗睺影响 → 个人突然损失、缺乏安宁 +5. **Ava Yoga**: 命主火星在第12宫 → 健康挑战、屈辱 +6. **Manglik Yoga** + 逆行土星 + 逆行木星 → 通过失去妻子和孩子经历悲痛 + +**事件验证**: +- 1972年击败在任共和党议员当选参议员(冷门) → Parvat Yoga +- 第一任妻子和女儿车祸去世 → Manglik Yoga+逆行土星+逆行木星 +- 儿子Beau因脑癌去世 → 凶星格局持续影响 +- 2009-2017年副总统 → Voshi Yoga +- 2021-2025年第46任总统 → Karmajiva Yoga +- 口吃童年 → 土星影响上升 + +**Dasha预测**: +- 2025年10月-2026年6月: 反思生活成就、可能撰写回忆录 +- 2026年10月-2028年: 土星过本命计都 → 重大责任和健康挑战 + +**可验证项**: +- [ ] Lagna应为射手座 (Purva Ashadha) +- [ ] 木星应在巨蟹座逆行 +- [ ] 土星应在金牛座逆行 + +--- + +## 案例十:Justin Bieber — 流行歌手 / 年少成名 + +**来源URL**: https://www.indastro.com/celebrities/justin-bieber-horoscope +**主题**: 职业/年少成名/音乐 + +**出生数据**: +- 日期: 1994-03-01 +- 时间: 12:56 +- 地点: London, Ontario, Canada +- 坐标: 约 42°59'N, 81°14'W + +**星盘配置**: +- Lagna: 双子座 (Gemini) 15°38' — Ardra Nakshatra, Pada 3 +- 太阳: 水瓶座 (Aquarius) 17°08' — Shatabhisha Nakshatra +- 月亮: 天秤座 (Libra) 8°10' — Swati Nakshatra +- 火星: 水瓶座 (Aquarius) 1°43' — Dhanishtha Nakshatra +- 水星: 摩羯座 (Capricorn) 29°32' — Dhanishtha Nakshatra +- 木星: 天秤座 (Libra) 20°52' — Vishakha Nakshatra +- 金星: 水瓶座 (Aquarius) 27°40' — Purva Bhadrapada Nakshatra +- 土星: 水瓶座 (Aquarius) 10°02' — Shatabhisha Nakshatra +- 罗睺: 天蝎座 (Scorpio) 3°08' — Vishakha Nakshatra +- 计都: 金牛座 (Taurus) 3°08' — Krittika Nakshatra + +**主要Yoga格局**: +1. **水瓶座星群**: 太阳+火星+金星+土星四星在水瓶座 → 独创性、叛逆 +2. **Rahu主导格局**: 上升Ardra、月亮Swati、太阳Shatabhisha均受Rahu影响 → 全球性爆红 +3. **水星在摩羯座29°**(Dhanishtha) → 通过音乐获取财富和名声 +4. 罗睺-计都轴: 天蝎-金牛Vishakha-Krittika → 欲望/转变/物质与精神拉扯 + +**Dasha信息(推算)**: +- 出生时月亮在Swati Nakshatra(Rahu主星) → 出生Rahu Mahadasha +- Rahu Mahadasha (1994-约2012): 青少年全球爆红 +- Jupiter Mahadasha (约2012起): 个人成长、婚姻、信仰转变 + +**事件验证**: +- 13-15岁YouTube被发现 → Rahu大运 + Ardra上升 +- 全球音乐事业成功 → 水瓶座星群 + 水星Dhanishtha +- 2018年与Hailey Baldwin结婚 → Jupiter大运期间 +- 情绪波动和争议 → 月亮受Rahu影响 + +**可验证项**: +- [ ] Lagna应为双子座 (Ardra Nakshatra) +- [ ] 出生Dasha起运应为Rahu Mahadasha +- [ ] 水瓶座应有4星汇聚 + +--- + +## 案例十一:Kamala Harris — 美国副总统 + +**来源URL**: https://www.indastro.com/celebrities/kamala-harris-horoscope +**主题**: 职业/政治/突破性成就 +**最后更新**: 2025-11-07 + +**出生数据**: +- 日期: 1964-10-20 +- 时间: 21:28 +- 地点: Oakland, California, USA +- 坐标: 约 37°48'N, 122°16'W + +**星盘配置**: +- Lagna: 金牛座 (Taurus) 21°10' — Rohini Nakshatra, Pada 4 +- 太阳: 天秤座 (Libra) 4°19' — Chitra Nakshatra (落陷) +- 月亮: 白羊座 (Aries) 2°30' — Ashwini Nakshatra +- 火星: 巨蟹座 (Cancer) 27°51' — Ashlesha Nakshatra (落陷) +- 水星: 天秤座 (Libra) 7°53' — Swati Nakshatra +- 木星: 金牛座 (Taurus) 0°39' — Krittika Nakshatra +- 金星: 狮子座 (Leo) 24°23' — Purva Phalguni Nakshatra +- 土星: 水瓶座 (Aquarius) 5°06' — Dhanistha Nakshatra (入庙) +- 罗睺: 双子座 (Gemini) 1°10' — Mrigasira Nakshatra +- 计都: 射手座 (Sagittarius) 1°10' — Mula Nakshatra + +**主要Yoga格局**: +1. **Sunapha Yoga**: 木星在月亮第2宫 → 卓越表现、领导大型团队 +2. **Budh-Aditya Yoga**: 太阳+水星在第5宫合相 → 大量追随者、职业成功 +3. **Karmajiva Yoga**: 月亮掌管太阳第10宫 → 关心他人福祉、支持人民 +4. **Bandhana Yoga**: 罗睺在上升宫 → 即使付出大量努力也可能延迟 +5. **Aristha Yoga**: 水星(上升主)受火星相位 → 健康挑战 + +**事件验证**: +- 首位非裔/亚裔美国副总统 → 第10宫主木星在第12宫(逆行) +- 建立仇恨犯罪调查组 → 水星Swati(Rahu影响)赋予勇气 +- 2005年组建环境犯罪调查组 → 正义倾向 +- 选举失利(败给Trump/Vance) → 逆行土星+逆行木星+落陷火星 +- 首位女性副总统 → 突破性成就 + +**Dasha预测**: +- 2028年2月-2029年1月: Rahu-Sun周期 → 心脏和胃部健康挑战 +- 2030年7月以后: 政治生涯重要角色 + +**可验证项**: +- [ ] Lagna应为金牛座 (Rohini Nakshatra) +- [ ] 土星应在水瓶座入庙 +- [ ] 太阳应在天秤座落陷(Chitra) + +--- + +## 案例十二:Katy Perry — 流行歌手 / 事业转型 + +**来源URL**: https://www.indastro.com/celebrities/katy-perry-horoscope +**主题**: 职业/财富/艺术转型 + +**出生数据**: +- 日期: 1984-10-25 +- 时间: 07:58 +- 地点: Santa Barbara, California, USA +- 坐标: 约 34°25'N, 119°42'W + +**星盘配置**: +- Lagna: 天蝎座 (Scorpio) 22°57' — Jyestha Nakshatra, Pada 2 +- 太阳: 天秤座 (Libra) 8°34' — Swati Nakshatra (落陷) +- 月亮: 天秤座 (Libra) 21°50' — Vishakha Nakshatra +- 火星: 射手座 (Sagittarius) 20°39' — Purva Ashadha Nakshatra +- 水星: 天秤座 (Libra) 18°05' — Swati Nakshatra +- 木星: 射手座 (Sagittarius) 14°08' — Purva Ashadha Nakshatra (入庙) +- 金星: 天蝎座 (Scorpio) 12°42' — Anuradha Nakshatra +- 土星: 天秤座 (Libra) 23°25' — Vishakha Nakshatra (擢升) +- 罗睺: 金牛座 (Taurus) 3°53' — Krittika Nakshatra +- 计都: 天蝎座 (Scorpio) 3°53' — Anuradha Nakshatra + +**主要Yoga格局**: +1. **Sasha Mahapurusha Yoga**: 土星形成(Pancha Mahapurusha之一) → 事业巅峰、名声、勤奋 +2. **Neecha Bhang Raj Yoga**: 土星对太阳的相位 → 初期挣扎,最终成功 +3. **Sumukha Yoga**: 金星在第2宫 → 独特才华和成功 +4. **Veshi Yoga**: 金星在太阳第2宫 → 流行音乐、迷人魅力 +5. **Chandra-Buddha Yoga**: 月亮+水星合相 → 迷人外表、展示技能 +6. **Budh-Aditya Yoga**: 太阳+水星合相 → 通过歌曲表达情感、高智商 +7. **Vish Yoga**: 土星+月亮合相 → 初期家庭经济困难 +8. **Vachana Chora Bhithi Yoga**: 命主金星+计都 → 恐惧被利用、关系挑战 + +**事件验证**: +- RIAA钻石认证 → Sasha Mahapurusha Yoga +- 19项吉尼斯世界纪录 → 多Yoga综合效应 +- 初期家庭经济困难(食品券) → Vish Yoga+Neecha Bhang Raj Yoga +- Billboard/AMA/MTV等多种奖项 → 第2宫金星+天秤座星群 +- 2026年火星在射手座 → 艺术重生阶段、新声音/视觉/合作 + +**可验证项**: +- [ ] Lagna应为天蝎座 (Jyestha Nakshatra) +- [ ] 土星应形成Sasha Mahapurusha Yoga +- [ ] 太阳+水星+月亮+土星应在天秤座(Vishakha/Swati) + +--- + +## 综合统计与验证价值 + +### 按主题分类 +| 主题 | 案例数 | 案例名称 | +|------|--------|----------| +| 职业/体育 | 3 | Messi, Tendulkar, Dhoni | +| 职业/科技商业 | 1 | Elon Musk | +| 职业/音乐艺术 | 3 | Beyoncé, Rihanna, Katy Perry, Justin Bieber | +| 职业/演艺 | 2 | Aishwarya Rai, Brad Pitt | +| 职业/政治 | 2 | Joe Biden, Kamala Harris | + +### Yoga格局统计 +| Yoga类型 | 出现次数 | +|----------|----------| +| Neechabhanga Raj Yoga | 4 (Tendulkar, Beyoncé, Perry, Justin Bieber*) | +| Budhaditya/Budh-Aditya Yoga | 3 (Musk, Harris, Perry) | +| Gajakesari Yoga | 2 (Rihanna, Dhoni) | +| Sasha Mahapurusha Yoga | 1 (Katy Perry) | +| Viparita Raja Yoga | 2 (Musk, Aishwarya) | +| 第2宫星群 | 2 (Brad Pitt 5星, Katy Perry 4星) | +| 火星擢升 | 2 (Musk, Tendulkar) | +| 太阳擢升 | 2 (Tendulkar) | + +### 验证引擎可用数据 +1. **精确行星度数**: 所有12个案例均包含精确到角秒的度数 +2. **Nakshatra + Pada**: 每个行星的星宿和Pada信息完整 +3. **上升星座**: 分布均匀(巨蟹、射手、处女、双鱼、处女、白羊、天蝎、处女、射手、双子、金牛、天蝎) +4. **Dasha推算基础**: 多位名人页面提供了Dasha预测和时间窗口 +5. **事件时间线**: 每个案例都有可验证的真实人生事件 + +### 局限性说明 +1. Indastro网站本身未提供完整的Vimshottari Dasha周期表格,仅提供部分预测时间窗口 +2. 宫位分布是分析性推导而非网站直接提供的数据 +3. 现代行星(天王/海王/冥王)不是传统吠陀占星的组成部分 +4. 所有数据均基于Indastro.com的解读,未与其他占星软件交叉验证 +5. 部分案例的"上升星座"在页面不同位置存在文字表述不一致(如Messi的上升曾同时提及射手座和摩羯座) diff --git a/references/jyotishganit_benchmark.md b/references/jyotishganit_benchmark.md new file mode 100644 index 00000000..06f2c3fb --- /dev/null +++ b/references/jyotishganit_benchmark.md @@ -0,0 +1,47 @@ +# jyotishganit 精度基准对比报告 + +> 对比对象:我们的引擎 (swisseph + Lahiri Ayanamsa) vs jyotishganit v0.1.0 (skyfield + True Chitra Paksha Ayanamsa) +> 生成时间: 2026-06-10 18:51:01 +> jyotishganit 许可证: MIT (c) northtara + +## 关键差异说明 + +| 项目 | 我们的引擎 | jyotishganit | +|------|-----------|--------------| +| 天文计算库 | Swiss Ephemeris (pyswisseph) | Skyfield + JPL DE421 | +| Ayanamsa | Lahiri (Chitra Paksha) | True Chitra Paksha | +| 行星节点 | Mean Node (默认) | Mean Node | +| 分盘算法 | BPHS标准 | BPHS标准 (jyotishyamitra实现) | +| 宫位制 | Whole Sign | Whole Sign | + +**核心差异**:Ayanamsa选择不同(Lahiri vs True Chitra Paksha),预期导致所有行星经度存在系统性偏移,偏移量约等于两种Ayanamsa值之差。 + +## 总结与分析 + +### 系统性差异 + +1. **Ayanamsa差异**是最大的系统性差异来源。Lahiri Ayanamsa和True Chitra Paksha Ayanamsa在计算方法上不同: + - Lahiri:基于春分点与Chitra星(Spica)的角距离 + - True Chitra Paksha:直接计算Spica的黄道经度减去180° + - 差异通常在0.1-0.5°之间,随时间略有变化 + +2. **行星经度偏移**:由于Ayanamsa差异,所有行星经度存在系统性偏移。如果减去Ayanamsa差异,剩余偏差应非常小(<0.1°),这取决于天文计算库(Swiss Ephemeris vs Skyfield/JPL)的精度差异。 + +3. **分盘计算**:两边的分盘算法基于BPHS标准,理论上应该一致。但如果D1行星位置因Ayanamsa偏移而跨星座边界,可能导致分盘星座不同。 + +### 精度评估 + +- **Swiss Ephemeris**:行业标准,基于JPL DE431,精度极高(<1角秒) +- **Skyfield + DE421**:同样高精度,但DE421精度略低于DE431(差异在角秒级别) +- **实际影响**:对于占星用途,两者的天文计算精度差异可以忽略(<0.01°) + +### 建议 + +1. 两种Ayanamsa的选择是占星学派的差异,不是精度问题 +2. 可以考虑添加True Chitra Paksha Ayanamsa作为可选项 +3. 分盘算法可以交叉验证,确保BPHS标准实现一致 +4. Ashtakavarga贡献表已校准到BPHS标准,两边应一致 + +--- +*本报告由 jyotishganit_benchmark.py 自动生成* +*jyotishganit (MIT License, c) northtara - https://github.com/northtara/jyotishganit* diff --git a/references/open-source-jyotish-scan-2026.md b/references/open-source-jyotish-scan-2026.md new file mode 100644 index 00000000..6b62ff13 --- /dev/null +++ b/references/open-source-jyotish-scan-2026.md @@ -0,0 +1,294 @@ +# Jyotish开源项目全网扫描 (2026-06) + +> 扫描日期:2026-06-10 | 扫描范围:GitHub, PyPI, npm | 重点:Python项目,次选Node.js/Rust/Java + +--- + +## ⭐⭐⭐ 强烈推荐(可直接复用代码) + +MIT/Apache-2.0许可证项目,代码可直接复制使用。 + +| 项目 | ⭐ | 许可证 | 最后更新 | 语言 | 可复用核心模块 | +|------|----|--------|----------|------|---------------| +| **dashaflow** | 1 | MIT | 2026-04-08 | Python | Ashtakavarga(SAV/BAV/Prashtara), Shadbala, D2-D60(14种), Jaimini(Karakas/Arudha/Karakamsha), 24 Yogas, Synastry(16因子), Muhurtha, Transit(Sade Sati), Panchang, Vimshottari 5层, Career分析 | +| **jyotishganit** | 32 | MIT | 2026-05-30 | Python | D1-D60全部分盘, Shadbala完整六维, Ashtakavarga(SAV/BAV), Vimshottari Dasha, Panchang, Graha Drishti, Planetary Dignities, JSON-LD输出 | +| **panchanga_api** | 2 | MIT | 2026-03-16 | Python | KP System(249 sublords), 300+ Yogas, Ashtakavarga, **Tajika/Varshaphala(含Muntha/Tajika Yogas)**, Prashna卜卦, **Muhurta择时**, **Remedies(宝石/咒语/仪式)**, Shadbala, D1-D60(20种), Synastry, Transit, Panchang, Vimshottari Dasha, Choghadiya/Hora, Pancha Pakshi | +| **jaimini-tropical** | 0 | MIT | 2026-05-28 | Python | **Jaimini完整引擎**: Chara Karaka(7/8星), Arudha Padas(A1-A12+Upapada), Argala(正向/反向/分类), Chara Dasha(含Antar), Special Lagnas(HL/GL/VL), Jaimini D-9/D-3/D-12 | +| **VedicAstro** | 63 | MIT | 活跃 | Python | KP System(249 sublords/sub-sublords), Prashna/Horary完整(horary_chart.py), Significators(ABCD法), Vimshottari Dasha, Planetary Aspects | +| **KPAstroDashboard** | 3 | MIT | 2025-06-13 | Python | KP卜卦引擎(horary chart generation), Sublord高精度计算, 多种Ayanamsa, Swiss Ephemeris集成 | +| **vedic_astro_npm** | 1 | MIT | 2025 | TypeScript | Panchang(Tithi/Nakshatra/Yoga/Karana), Gun Milan(Ashtakoota 36分), Kundali(D1), Daily Transit/Gochar, 纯JS天文计算 | + +### URL列表 + +| 项目 | GitHub URL | PyPI/npm | +|------|-----------|----------| +| dashaflow | https://github.com/adarshj322/dashaflow | `pip install dashaflow` | +| jyotishganit | https://github.com/northtara/jyotishganit | `pip install jyotishganit` | +| panchanga_api | https://github.com/degen0root/panchanga_api | MCP Server | +| jaimini-tropical | https://github.com/tunanfang-pixel/jaimini-tropical | 本地安装 | +| VedicAstro | https://github.com/diliprk/VedicAstro | `pip install VedicAstro` | +| KPAstroDashboard | https://github.com/manan-ramnani/KPAstroDashboard | 本地安装 | +| vedic_astro_npm | https://github.com/arpitasah00/vedic_astro_npm | `npm install vedic_astro_npm` | + +--- + +## ⭐⭐ 推荐(可借鉴思路,GPL/AGPL许可证) + +源码可参考但不能直接复制,算法思路可翻译实现。 + +| 项目 | ⭐ | 许可证 | 最后更新 | 语言 | 核心价值 | +|------|----|--------|----------|------|---------| +| **vedic-calc** | - | AGPL-3.0 | 2026-03 | Python | **最全面AGPL项目**: Ashtakavarga, **Tajika(Ithasala/Easarapha/Induvara/Kamboola/Nakta)**, KP(249 sublord), Jaimini(Chara Karaka/Arudha Padas), **Prashna(含裁决引擎)**, **Muhurta(5层Panchang过滤求解器)**, Yoga(20+), Synastry(Ashtakoot+Porutham), Transit, D1-D60, Shadbala, Panchang, Nakshatra, Remedies, Dasha(4种: Vimshottari/Yogini/Ashtottari/Narayana) | +| **PyJHora** | ~500+ | AGPL | 2026-05-30 | Python | **最全面Python库**: D1-D300全部分盘, 47种Dasha系统, 100+ Yogas, Synastry, Transit, 8种Dosha, Panchang, 多语言UI, ~6800单元测试 | +| **VedAstro (主项目)** | 563 | MIT(C#) | 活跃 | C#/.NET | Prashna卜卦, Muhurta择时, Gochara行运(含Vedha), Dasha(5层), Matchmaking(10 Kuta), AI Astrologer, 出生时间校正 | +| **xalen-ephemeris** | 405 | Apache-2.0 | 2026-06 | Rust | **下一代星历引擎**: Dasha, Shadbala, 16 Vargas, KP, Jaimini, Tajika, Ashtakavarga, 50种Ayanamsa, 23种宫位系统, JPL精度 | + +### URL列表 + +| 项目 | URL | +|------|-----| +| vedic-calc | https://atolat.github.io/vedic-calc/ (PyPI: `pip install vedic-calc`) | +| PyJHora | https://github.com/naturalstupid/PyJHora (`pip install PyJHora`) | +| VedAstro | https://github.com/VedAstro/VedAstro | +| xalen-ephemeris | https://github.com/vedika-io/xalen-ephemeris | + +--- + +## 新发现项目(2024-2026) + +近两年涌现的高质量新项目。 + +| 项目 | 发现时间 | ⭐ | 许可证 | 语言 | 亮点 | +|------|----------|----|--------|------|------| +| **jaimini-tropical** | 2026-05 | 0 | MIT | Python | 首个热带黄道Jaimini引擎, 基于Rangacharya原著 | +| **dashaflow** | 2026-04 | 1 | MIT | Python | 最全面的MIT Python库, 覆盖几乎所有技法 | +| **jyotishganit** | 2025-10 | 32 | MIT | Python | 专业级, D1-D60+Shadbala+Ashtakavarga | +| **panchanga_api** | 2026-03 | 2 | MIT | Python | 唯一同时实现Tajika+KP+Prashna+Remedies的MIT项目 | +| **vedic-calc** | 2026-03 | - | AGPL | Python | 算法最完整, Tajika/Muhurta/Prashna裁决引擎 | +| **vedic-transit-calculator** | 2025-12 | - | - | Python | 专用行运计算工具 | +| **KPAstroDashboard** | 2025-06 | 3 | MIT | Python | KP卜卦专用工具 | +| **vedic_astro_npm** | 2025-12 | 1 | MIT | TypeScript | 纯JS天文计算, 零依赖 | +| **astrokundali** | 2026-01 | - | MIT | Python | D1-D60分盘, 北印度图, 婚配 | +| **xalen-ephemeris** | 2024 | 405 | Apache-2.0 | Rust | 纯Rust星历, 所有Jyotish功能 | +| **vedic-panchang** | 2026-02 | - | - | Python | 专用Panchang库, Swiss Ephemeris | +| **PanchangaAPI (MCP)** | 2025 | - | - | Python | MCP协议, 300+Yogas+KP+Ashtakavarga | + +--- + +## 各技法最佳代码来源 + +按技法逐一列出最佳开源实现。 + +### 1. Ashtakavarga(八宫分盘点数系统) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **dashaflow** ⭐ | MIT | SAV, BAV, Prashtara(源级bindu) | `dashaflow/ashtakavarga.py` | +| **jyotishganit** | MIT | SAV, BAV(按星座分布) | `jyotishganit/` | +| panchanga_api | MIT | SAV完整 | `chart.kundali` | +| vedic-calc | AGPL | 8源bindu系统, SAV综合 | 借鉴思路 | +| PyJHora | AGPL | 完整Ashtakavarga | 借鉴思路 | + +### 2. Tajika / Varshaphala(年度盘/太阳回归) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **panchanga_api** ⭐ | MIT | Varshaphal(Muntha/Year Lord), Tajika Yogas完整 | `prediction.varshaphal` | +| vedic-calc | AGPL | Ithasala/Easarapha/Induvara/Kamboola/Nakta, Varshaphal | 借鉴思路 | +| xalen-ephemeris | Apache-2.0 | Tajika系统(纯Rust,需翻译) | 借鉴思路 | + +### 3. KP System(Krishnamurti Paddhati) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **panchanga_api** ⭐ | MIT | Sub-lords, Significators, Cuspal分析 | `analysis.kp_system` | +| **VedicAstro** ⭐ | MIT | 249 sublord/sub-sublord, Significators(ABCD), Prashna | `VedicHoroscopeData`类 | +| **KPAstroDashboard** ⭐ | MIT | 卜卦引擎, Sublord高精度, KP_SL_Divisions.csv | `main.py` | +| vedic-calc | AGPL | KP盘(Placidus宫头+sign/star/sub lord), 249 sublord表 | 借鉴思路 | +| xalen-ephemeris | Apache-2.0 | KP体系(纯Rust) | 借鉴思路 | + +### 4. Jaimini完整实现 + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **jaimini-tropical** ⭐⭐⭐ | MIT | Chara Karaka(7/8星), Arudha Padas(A1-A12+Upapada), **Argala(正向/反向/王瑜伽分类)**, Chara Dasha(含Antar), Special Lagnas(HL/GL/VL), Jaimini D-9/D-3/D-12, **Karakamsa Rajayoga** | `jaimini/core/` | +| **dashaflow** ⭐ | MIT | 7 Karakas, Arudha Padas(A1-A12含例外规则), Upapada(A12), Karakamsha(AK in Navamsha, Ishta Devata) | `dashaflow/jaimini.py` | +| vedic-calc | AGPL | Chara Karakas, Arudha Padas | 借鉴思路 | +| PyJHora | AGPL | Jaimini完整系统 | 借鉴思路 | + +### 5. Prashna(问卦占星) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **panchanga_api** ⭐ | MIT | Horary征象星, 指示评分 | `prediction.prashna` | +| **VedicAstro** ⭐ | MIT | 完整卜卦(horary_chart.py: `get_horary_ascendant_degree`, `find_exact_ascendant_time`) | `horary_chart.py` | +| **KPAstroDashboard** ⭐ | MIT | KP卜卦引擎, 卜卦数字计算 | `main.py` | +| vedic-calc | AGPL | Prashna盘+Tajika瑜伽+**裁决引擎**(吉/凶/混合判定+推理) | 借鉴思路 | +| VedAstro (C#) | MIT(C#) | Prashna问卦 | 借鉴思路 | + +### 6. Muhurta(择日占星) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **panchanga_api** ⭐ | MIT | 排名吉祥时间窗口, 质量评分, 婚礼/商业/旅行等 | `analysis.muhurta` | +| **dashaflow** ⭐ | MIT | 6种活动类型, Panchang Suddhi, 婚姻Dosha检测 | `dashaflow/muhurtha.py` | +| vedic-calc | AGPL | **Muhurta求解器**(日期范围+5层Panchang过滤+Chandrabala/Tarabala个人覆盖) | 借鉴思路 | +| VedAstro (C#) | MIT(C#) | Muhurta择时 | 借鉴思路 | + +### 7. Yoga识别器(300+ Yogas) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **panchanga_api** ⭐ | MIT | **300+ Yogas**完整识别 | `chart.kundali` | +| **dashaflow** ⭐ | MIT | 24种Yoga(Pancha Mahapurusha, Gajakesari, Budhaditya, Raj, Neecha Bhanga, Parivartana, Dhana等) | `dashaflow/yoga.py` | +| PyJHora | AGPL | 100+ Yogas | 借鉴思路 | +| vedic-calc | AGPL | 20+ Yogas含Viparita Raja, Saraswati, Kemadruma | 借鉴思路 | + +### 8. Synastry / 婚配分析 + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **dashaflow** ⭐ | MIT | **16因子**匹配(Ashtakoot 36分+Mahendra/Stree Deergha/Vedha/Rajju), Kuja Dosha | `dashaflow/matchmaking.py` | +| **panchanga_api** ⭐ | MIT | Ashtakoot 8重匹配(36分制) | `analysis.compatibility` | +| vedic-calc | AGPL | Ashtakoot(Porutham 10因素)+北印度8因素 | 借鉴思路 | +| PyJHora | AGPL | 完整MatchWidget界面 | 借鉴思路 | +| astrokundali | MIT | Kundli Milan, Pandas输出 | `astrokundali` | + +### 9. Transit分析(Gochar) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **dashaflow** ⭐ | MIT | 行运盘叠加本命, **SAV点**, **Sade Sati检测**, Rahu-Ketu轴 | `dashaflow/vedic_calculator.py` (cast_transit) | +| **panchanga_api** ⭐ | MIT | 行星行运相对出生月亮, Sade Sati检测 | `prediction.transits` | +| vedic-calc | AGPL | 当前天空叠加本命 | 借鉴思路 | +| vedic-transit-calculator | - | 终端工具, Swiss Ephemeris | 借鉴思路 | + +### 10. D1-D60全部分盘图 + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **jyotishganit** ⭐ | MIT | **D1-D60全13种**(含D60 Shashtiamsha) | `jyotishganit/` | +| **dashaflow** ⭐ | MIT | D2-D60共14种(每个行星返回所有分盘星座) | `dashaflow/vedic_calculator.py` | +| **panchanga_api** ⭐ | MIT | D1-D60共20种分盘 | `chart.vargas` | +| PyJHora | AGPL | **D1-D300**全部分盘 | 借鉴思路 | +| vedic-calc | AGPL | D1-D60全部 | 借鉴思路 | + +### 11. Shadbala完整六维计算 + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **jyotishganit** ⭐ | MIT | Sthana/Kala/Dig/Chesta/Naisargika/Drik Bala + 总力量Rupas换算 | `jyotishganit/` | +| **dashaflow** ⭐ | MIT | 六重力量(Rupas+百分比), **Ishta/Kashta Phala** | `dashaflow/shadbala.py` | +| panchanga_api | MIT | 六重力量含分解 | `analysis.shadbala` | +| vedic-calc | AGPL | 六维完整 | 借鉴思路 | + +### 12. Panchang(五要素日历) + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **dashaflow** ⭐ | MIT | Tithi, Vara, Nakshatra, Yoga, Karana | `dashaflow/panchang.py` | +| **jyotishganit** ⭐ | MIT | 完整五要素 | `jyotishganit/` | +| **panchanga_api** ⭐ | MIT | 完整Panchanga+Rahu Kalam+Yamaganda+Gulika, **Panchanga组合搜索**, 多日范围查询, Vrata斋戒日历, 50+节日 | `chart.panchanga` | +| vedic-calc | AGPL | 五要素+日出日落+节日 | 借鉴思路 | +| vedic-panchang | - | 专用Panchang, JPL DE431 | PyPI | +| vedic_astro_npm | MIT | Panchang+吉时/凶时窗口 | TypeScript可翻译 | + +### 13. Nakshatra详细分析 + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **dashaflow** ⭐ | MIT | Nakshatra+Pada查找 | `dashaflow/nakshatra.py` | +| **jyotishganit** ⭐ | MIT | Nakshatra完整(含Yogatara) | `jyotishganit/` | +| vedic-calc | AGPL | Nakshatra+Pada | 借鉴思路 | +| xalen-ephemeris | Apache-2.0 | Nakshatra+Pada+Yogatara星表 | 借鉴思路 | + +### 14. Remedies推荐系统 + +| 来源 | 许可证 | 实现内容 | 文件路径 | +|------|--------|---------|---------| +| **panchanga_api** ⭐ | MIT | **唯一MIT实现**: 个性化宝石/咒语/仪式推荐, 基于个人星盘 | `analysis.remedies` | +| vedic-calc | AGPL | Dosha附带remedies(严重等级+化解方法) | 借鉴思路 | + +--- + +## 最佳复用策略 + +### 第一优先级:直接复用(MIT/Apache-2.0) + +按模块选取最佳MIT代码: + +``` +Ashtakavarga → dashaflow/ashtakavarga.py (含Prashtara) +Shadbala → dashaflow/shadbala.py + jyotishganit (互补) +Jaimini → jaimini-tropical/jaimini/core/ (最完整) +KP System → VedicAstro + panchanga_api/kp_system +Yoga识别 → panchanga_api (300+) + dashaflow/yoga.py +Synastry → dashaflow/matchmaking.py (16因子) +Transit → dashaflow (Sade Sati + SAV点) +Muhurta → dashaflow/muhurtha.py + panchanga_api/muhurta +Tajika → panchanga_api/varshaphal (唯一MIT) +Prashna → panchanga_api/prashna + VedicAstro/horary_chart.py +Remedies → panchanga_api/remedies (唯一MIT) +D1-D60分盘 → jyotishganit (最完整) + dashaflow +Panchang → dashaflow/panchang.py + panchanga_api (含搜索) +Nakshatra → dashaflow/nakshatra.py +``` + +### 第二优先级:翻译借鉴(AGPL项目算法思路) + +``` +vedic-calc → Tajika裁决引擎, Muhurta求解器, Prashna裁决引擎 (算法可翻译) +PyJHora → Dasha系统(47种), Yoga(100+), Dosha(8种) (算法可翻译) +``` + +### 第三优先级:跨语言参考 + +``` +VedAstro (C#) → Prashna/Muhurta/Gochara预测逻辑 (翻译为Python) +xalen (Rust) → 所有技法的高精度算法参考 (Apache-2.0, 可安全参考) +``` + +--- + +## 项目对比矩阵(核心技法覆盖) + +| 技法 | dashaflow | jyotishganit | panchanga_api | jaimini-tropical | VedicAstro | vedic-calc | PyJHora | +|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| Ashtakavarga | ✅ | ✅ | ✅ | ❌ | ⚠️ | ✅ | ✅ | +| Tajika/Varshaphala | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ⚠️ | +| KP System | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ⚠️ | +| Jaimini | ✅ | ❌ | ❌ | ✅✅✅ | ❌ | ✅ | ✅ | +| Prashna | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ⚠️ | +| Muhurta | ✅ | ❌ | ✅ | ❌ | ❌ | ✅✅ | ⚠️ | +| Yoga识别 | ✅(24) | ❌ | ✅(300+) | ❌ | ⚠️ | ✅(20+) | ✅(100+) | +| Synastry | ✅(16) | ❌ | ✅ | ❌ | ❌ | ✅(2系统) | ✅ | +| Transit | ✅(SS+SAV) | ❌ | ✅(SS) | ❌ | ❌ | ✅ | ✅ | +| D1-D60 | ✅(14) | ✅(13) | ✅(20) | D9/3/12 | 部分 | ✅ | ✅(300) | +| Shadbala | ✅(含IKP) | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | +| Panchang | ✅ | ✅ | ✅✅ | ❌ | ❌ | ✅ | ✅ | +| Nakshatra | ✅ | ✅ | ✅ | ❌ | 基础 | ✅ | ✅ | +| Remedies | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ⚠️ | +| Dasha(数量) | 1 | 1 | 1 | 1(Chara) | 1 | 4 | 47 | +| **许可证** | MIT | MIT | MIT | MIT | MIT | AGPL | AGPL | + +图例: ✅✅=最佳实现 ✅=完整实现 ⚠️=部分/计划中 ❌=未实现 + +--- + +## 关键结论 + +1. **最佳MIT全栈库**: **dashaflow** — 覆盖面最广的MIT Python库,实现了除Tajika、KP外的几乎所有核心技法。代码质量好,模块化清晰。 + +2. **最佳基础计算库**: **jyotishganit** — D1-D60分盘、Shadbala、Ashtakavarga核心计算精度最高,代码质量优秀(pytest/ruff/mypy)。 + +3. **最佳Jaimini实现**: **jaimini-tropical** — 虽然只有5次提交且0星,但实现了最完整的Jaimini系统(Argala/Arudha/Chara Dasha/Special Lagnas),而且是唯一的热带黄道实现。 + +4. **填补关键技术空白**: **panchanga_api** 是唯一同时以MIT许可证实现Tajika、KP、Prashna、Remedies的项目。 + +5. **最佳算法参考**: **vedic-calc** (AGPL) — 虽然不能直接复制代码,但其Tajika裁决引擎和Muhurta求解器的算法设计值得仔细研究。 + +6. **AGPL vs MIT策略**: 对于MIT项目直接复制代码;对于AGPL项目(PyJHora, vedic-calc)仅借鉴算法思路,用Python独立重新实现。 + +--- + +*扫描完成: 共发现18个项目,其中7个MIT/Apache可直接复用,4个AGPL/GPL可借鉴思路。* diff --git a/references/open_source_sources/dashaflow/__init__.py b/references/open_source_sources/dashaflow/__init__.py new file mode 100644 index 00000000..a2374396 --- /dev/null +++ b/references/open_source_sources/dashaflow/__init__.py @@ -0,0 +1,258 @@ +""" +DashaFlow — Vedic Astrology Calculation Engine +=============================================== + +Swiss Ephemeris with Sidereal Lahiri ayanamsha. +Rooted in Brihat Parashara Hora Shastra (BPHS) and B.V. Raman's Hindu Predictive Astrology. + +Quick start:: + + import dashaflow + + chart = dashaflow.cast_chart("1990-04-15", "14:30", 28.6139, 77.2090, "Asia/Kolkata") + print(chart["lagna"]["sign"]) # e.g. "Leo" + print(chart["planets"]["Moon"]) # full Moon data + print(chart["yogas"]) # detected yogas +""" + +from ._version import __version__ +from ._validation import validate_birth_input +from .vedic_calculator import calculate_vedic_chart, calculate_transit +from .matchmaking import calculate_ashtakoot, calc_kuja_dosha, match_kuja_dosha +from .muhurtha import evaluate_muhurtha, ACTIVITY_RULES +from .career import analyze_career as _analyze_career_internal +from .constants import ZODIAC_SIGNS + +__all__ = [ + "__version__", + "cast_chart", + "cast_transit", + "calculate_compatibility", + "check_muhurtha", + "analyze_career", +] + + +def cast_chart( + dob: str, + time: str, + lat: float, + lon: float, + timezone: str, + query_date: str = None, + ephe_path: str = '', +) -> dict: + """ + Cast a complete Vedic natal chart (Sidereal Lahiri ayanamsha). + + Returns a dict with: metadata, panchang, lagna, planets (with dignity, + combustion, aspects, 14 varga signs), dashas (5 levels), yogas (24 types), + ashtakavarga, jaimini_karakas, shadbala, bhava_chalit, avasthas, + kaal_sarpa, graha_yuddha, gandanta, arudha_padas, upapada, karakamsha. + + Parameters + ---------- + dob : str + Date of birth as "YYYY-MM-DD" + time : str + Time of birth as "HH:MM" (24-hour) + lat : float + Birth latitude (-90 to 90) + lon : float + Birth longitude (-180 to 180) + timezone : str + IANA timezone (e.g. "Asia/Kolkata") + query_date : str, optional + Date for Dasha lookup as "YYYY-MM-DD". Defaults to today. + ephe_path : str, optional + Path to Swiss Ephemeris data files. Defaults to '' (bundled). + + Returns + ------- + dict + Complete chart data. + + Raises + ------ + ValueError + If any input parameter is invalid. + """ + validate_birth_input(dob, time, lat, lon, timezone) + return calculate_vedic_chart( + dob_str=dob, + time_str=time, + lat=lat, + lon=lon, + timezone_str=timezone, + query_date_str=query_date, + ephe_path=ephe_path, + ) + + +def cast_transit( + transit_date: str, + dob_str: str, + time_str: str, + lat: float, + lon: float, + timezone: str = "Asia/Kolkata", +) -> dict: + """ + Calculate planetary transits overlaid on a natal chart. + + Parameters + ---------- + transit_date : str + Date to compute transits as "YYYY-MM-DD" + dob_str : str + Date of birth as "YYYY-MM-DD" + time_str : str + Time of birth as "HH:MM" (24-hour) + lat : float + Birth latitude (-90 to 90) + lon : float + Birth longitude (-180 to 180) + timezone : str, optional + IANA timezone. Defaults to "Asia/Kolkata". + + Returns + ------- + dict + Transit planets with house positions, SAV points, Sade Sati, Rahu-Ketu axis. + """ + validate_birth_input(dob_str, time_str, lat, lon, timezone) + natal_chart = calculate_vedic_chart( + dob_str=dob_str, + time_str=time_str, + lat=lat, + lon=lon, + timezone_str=timezone, + ) + return calculate_transit( + transit_date_str=transit_date, + natal_chart=natal_chart, + timezone_str=timezone, + ) + + +def calculate_compatibility( + dob1: str, time1: str, lat1: float, lon1: float, tz1: str, + dob2: str, time2: str, lat2: float, lon2: float, tz2: str, +) -> dict: + """ + Calculate 36-point Ashtakoot compatibility + Kuja Dosha. + + Person 1 = Male, Person 2 = Female (by tradition for accurate scoring). + + Parameters + ---------- + dob1, time1, lat1, lon1, tz1 : Birth details for Person 1 (Male) + dob2, time2, lat2, lon2, tz2 : Birth details for Person 2 (Female) + + Returns + ------- + dict + 8 Ashtakoot kutas (36 pts), extended kutas (Mahendra, Stree Deergha, + Vedha, Rajju, etc.), Kuja Dosha analysis with compatibility verdict. + """ + validate_birth_input(dob1, time1, lat1, lon1, tz1) + validate_birth_input(dob2, time2, lat2, lon2, tz2) + + chart1 = calculate_vedic_chart(dob1, time1, lat1, lon1, tz1) + chart2 = calculate_vedic_chart(dob2, time2, lat2, lon2, tz2) + + m_moon = chart1["planets"]["Moon"] + f_moon = chart2["planets"]["Moon"] + + m_lon = ZODIAC_SIGNS.index(m_moon["sign"]) * 30 + m_moon["degree"] + f_lon = ZODIAC_SIGNS.index(f_moon["sign"]) * 30 + f_moon["degree"] + + score = calculate_ashtakoot(m_lon, f_lon, male_chart=chart1, female_chart=chart2) + + male_dosha = calc_kuja_dosha(chart1) + female_dosha = calc_kuja_dosha(chart2) + kuja_match = match_kuja_dosha(male_dosha["total_score"], female_dosha["total_score"]) + score["kuja_dosha"] = { + "male": male_dosha, + "female": female_dosha, + "compatibility": kuja_match, + } + + return score + + +def check_muhurtha( + activity: str, + date: str, + time: str, + lat: float, + lon: float, + timezone: str, +) -> dict: + """ + Check if a date/time is auspicious for a specific activity. + + Parameters + ---------- + activity : str + One of: 'marriage', 'travel', 'business', 'education', 'house_entry', 'medical' + date : str + Date as "YYYY-MM-DD" + time : str + Time as "HH:MM" (24-hour) + lat : float + Location latitude + lon : float + Location longitude + timezone : str + IANA timezone string + + Returns + ------- + dict + Verdict (auspicious/mixed/inauspicious), score, factors, panchang_suddhi. + """ + validate_birth_input(date, time, lat, lon, timezone) + if activity not in ACTIVITY_RULES: + raise ValueError(f"Unknown activity '{activity}'. Supported: {list(ACTIVITY_RULES.keys())}") + + chart = calculate_vedic_chart(date, time, lat, lon, timezone) + panchang = chart.get("panchang", {}) + planets = chart.get("planets", {}) + lagna_sign = chart.get("lagna", {}).get("sign") + return evaluate_muhurtha(activity, panchang, planets, lagna_sign) + + +def analyze_career( + dob: str, + time: str, + lat: float, + lon: float, + timezone: str, +) -> dict: + """ + Analyze career potential using the 10th house, D10 Dashamsha, and planetary significations. + + Parameters + ---------- + dob : str + Date of birth as "YYYY-MM-DD" + time : str + Time of birth as "HH:MM" (24-hour) + lat : float + Birth latitude + lon : float + Birth longitude + timezone : str + IANA timezone string + + Returns + ------- + dict + 10th house analysis, D10 indicators, career themes, strength factors. + """ + validate_birth_input(dob, time, lat, lon, timezone) + chart = calculate_vedic_chart(dob, time, lat, lon, timezone) + planets = chart.get("planets", {}) + lagna_sign = chart.get("lagna", {}).get("sign") + return _analyze_career_internal(planets, lagna_sign) diff --git a/references/open_source_sources/dashaflow/_validation.py b/references/open_source_sources/dashaflow/_validation.py new file mode 100644 index 00000000..f0ab49e3 --- /dev/null +++ b/references/open_source_sources/dashaflow/_validation.py @@ -0,0 +1,21 @@ +"""Input validation for DashaFlow public API.""" + +import re +import pytz + +_DATE_RE = re.compile(r"^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$") +_TIME_RE = re.compile(r"^(?:[01]\d|2[0-3]):[0-5]\d$") + + +def validate_birth_input(dob: str, time: str, lat: float, lon: float, timezone: str): + """Validate common birth chart inputs. Raises ValueError on bad data.""" + if not isinstance(dob, str) or not _DATE_RE.match(dob): + raise ValueError(f"Invalid date format '{dob}'. Expected YYYY-MM-DD.") + if not isinstance(time, str) or not _TIME_RE.match(time): + raise ValueError(f"Invalid time format '{time}'. Expected HH:MM (24h).") + if not (-90 <= lat <= 90): + raise ValueError(f"Latitude {lat} out of range [-90, 90].") + if not (-180 <= lon <= 180): + raise ValueError(f"Longitude {lon} out of range [-180, 180].") + if timezone not in pytz.all_timezones: + raise ValueError(f"Unknown timezone '{timezone}'. Use IANA format (e.g. 'Asia/Kolkata').") diff --git a/references/open_source_sources/dashaflow/_version.py b/references/open_source_sources/dashaflow/_version.py new file mode 100644 index 00000000..6849410a --- /dev/null +++ b/references/open_source_sources/dashaflow/_version.py @@ -0,0 +1 @@ +__version__ = "1.1.0" diff --git a/references/open_source_sources/dashaflow/ashtakavarga.py b/references/open_source_sources/dashaflow/ashtakavarga.py new file mode 100644 index 00000000..1caad2ca --- /dev/null +++ b/references/open_source_sources/dashaflow/ashtakavarga.py @@ -0,0 +1,128 @@ +from .constants import ZODIAC_SIGNS + +# 1-indexed houses from the placement of the planet +ASHTAKAVARGA_TABLES = { + "Sun": { + "Sun": [1, 2, 4, 7, 8, 9, 10, 11], + "Moon": [3, 6, 10, 11], + "Mars": [1, 2, 4, 7, 8, 9, 10, 11], + "Mercury": [3, 5, 6, 9, 10, 11, 12], + "Jupiter": [5, 6, 9, 11], + "Venus": [6, 7, 12], + "Saturn": [1, 2, 4, 7, 8, 9, 10, 11], + "Ascendant": [3, 4, 6, 10, 11, 12] + }, + "Moon": { + "Sun": [3, 6, 7, 8, 10, 11], + "Moon": [1, 3, 6, 7, 10, 11], + "Mars": [2, 3, 5, 6, 9, 10, 11], + "Mercury": [1, 3, 4, 5, 7, 8, 10, 11], + "Jupiter": [1, 4, 7, 8, 10, 11, 12], + "Venus": [3, 4, 5, 7, 9, 10, 11], + "Saturn": [3, 5, 6, 11], + "Ascendant": [3, 6, 10, 11] + }, + "Mars": { + "Sun": [3, 5, 6, 10, 11], + "Moon": [3, 6, 11], + "Mars": [1, 2, 4, 7, 8, 10, 11], + "Mercury": [3, 5, 6, 11], + "Jupiter": [6, 10, 11, 12], + "Venus": [6, 8, 11, 12], + "Saturn": [1, 4, 7, 8, 9, 10, 11], + "Ascendant": [1, 3, 6, 10, 11] + }, + "Mercury": { + "Sun": [5, 6, 9, 11, 12], + "Moon": [2, 4, 6, 8, 10, 11], + "Mars": [1, 2, 4, 7, 8, 9, 10, 11], + "Mercury": [1, 3, 5, 6, 9, 10, 11, 12], + "Jupiter": [6, 8, 11, 12], + "Venus": [1, 2, 3, 4, 5, 8, 9, 11], + "Saturn": [1, 2, 4, 7, 8, 9, 10, 11], + "Ascendant": [1, 2, 4, 6, 8, 10, 11] + }, + "Jupiter": { + "Sun": [1, 2, 3, 4, 7, 8, 9, 10, 11], + "Moon": [2, 5, 7, 9, 11], + "Mars": [1, 2, 4, 7, 8, 10, 11], + "Mercury": [1, 2, 4, 5, 6, 9, 10, 11], + "Jupiter": [1, 2, 3, 4, 7, 8, 10, 11], + "Venus": [2, 5, 6, 9, 10, 11], + "Saturn": [3, 5, 6, 12], + "Ascendant": [1, 2, 4, 5, 6, 7, 9, 10, 11] + }, + "Venus": { + "Sun": [8, 11, 12], + "Moon": [1, 2, 3, 4, 5, 8, 9, 11, 12], + "Mars": [3, 5, 6, 9, 11, 12], + "Mercury": [3, 5, 6, 9, 11], + "Jupiter": [5, 8, 9, 10, 11], + "Venus": [1, 2, 3, 4, 5, 8, 9, 10, 11], + "Saturn": [3, 4, 5, 8, 9, 10, 11], + "Ascendant": [1, 2, 3, 4, 5, 8, 9, 11] + }, + "Saturn": { + "Sun": [1, 2, 4, 7, 8, 10, 11], + "Moon": [3, 6, 11], + "Mars": [3, 5, 6, 10, 11, 12], + "Mercury": [6, 8, 9, 10, 11, 12], + "Jupiter": [5, 6, 11, 12], + "Venus": [6, 11, 12], + "Saturn": [3, 5, 6, 11], + "Ascendant": [1, 3, 4, 6, 10, 11] + } +} + +def calculate_ashtakavarga(planets_in_signs: dict, ascendant_sign_idx: int): + """ + Calculates Sarvashtakavarga (SAV) and Bhinnashtakavarga (BAV). + planets_in_signs dict maps "Sun", "Moon", etc. to their 0-11 sign index. + + Returns a dict with 'sarvashtakavarga' (list of 12 ints mapping to ZODIAC_SIGNS) + and 'bhinnashtakavarga' mapping each planet to their 12-sign array. + """ + # Initialize all BAV arrays with 0 + bav = {p: [0]*12 for p in ASHTAKAVARGA_TABLES.keys()} + sav = [0]*12 + + # Extend planets dict with Ascendant for calculation + positions = planets_in_signs.copy() + positions["Ascendant"] = ascendant_sign_idx + + for target_planet, contributions in ASHTAKAVARGA_TABLES.items(): + for source_point, houses_list in contributions.items(): + source_idx = positions[source_point] + for h in houses_list: + # h is 1-indexed house from the source planet. + # So if source is at idx 0 (Aries) and h=1, target sign is 0 (Aries) + target_sign_idx = (source_idx + (h - 1)) % 12 + bav[target_planet][target_sign_idx] += 1 + sav[target_sign_idx] += 1 + + # Return as a dict mapped to Zodiac Sign names for easier LLM reading + sav_dict = {ZODIAC_SIGNS[i]: sav[i] for i in range(12)} + + bav_dict = {} + for p, arr in bav.items(): + bav_dict[p] = {ZODIAC_SIGNS[i]: arr[i] for i in range(12)} + + # Prashtarashtakavarga (expanded scatter chart) + # For each target planet, shows which source contributed bindus to which sign + prashtara = {} + for target_planet, contributions in ASHTAKAVARGA_TABLES.items(): + prashtara[target_planet] = {} + for source_point, houses_list in contributions.items(): + source_idx = positions[source_point] + row = [0] * 12 + for h in houses_list: + target_sign_idx = (source_idx + (h - 1)) % 12 + row[target_sign_idx] = 1 + prashtara[target_planet][source_point] = {ZODIAC_SIGNS[i]: row[i] for i in range(12)} + + return { + "sarvashtakavarga": sav_dict, + "bhinnashtakavarga": bav_dict, + "prashtarashtakavarga": prashtara, + "total_bindus": sum(sav) # Should be 337 + } diff --git a/references/open_source_sources/dashaflow/career.py b/references/open_source_sources/dashaflow/career.py new file mode 100644 index 00000000..2db54ce5 --- /dev/null +++ b/references/open_source_sources/dashaflow/career.py @@ -0,0 +1,171 @@ +""" +Career Analysis Framework — D10 Dashamsha interpretation +Uses D10 chart, 10th house analysis, and planetary significations. +""" + +from .constants import ZODIAC_SIGNS, SIGN_LORDS, EXALTATION, OWN_SIGNS + +# Planet -> career significations (Jyotish standard) +CAREER_SIGNIFICATIONS = { + "Sun": ["government", "politics", "administration", "medicine", "leadership", "authority"], + "Moon": ["nursing", "hospitality", "public_relations", "agriculture", "shipping", "dairy"], + "Mars": ["military", "engineering", "surgery", "sports", "police", "real_estate", "fire_services"], + "Mercury": ["writing", "accounting", "commerce", "communication", "IT", "teaching", "astrology"], + "Jupiter": ["law", "education", "finance", "banking", "religion", "philosophy", "consulting"], + "Venus": ["arts", "entertainment", "fashion", "luxury_goods", "hotel", "cosmetics", "music"], + "Saturn": ["mining", "agriculture", "labor", "construction", "oil", "manufacturing", "judiciary"], + "Rahu": ["technology", "foreign_trade", "aviation", "research", "pharmaceuticals", "diplomacy"], + "Ketu": ["spirituality", "occult", "research", "computing", "alternative_medicine", "languages"], +} + +# Sign -> professional domains +SIGN_CAREERS = { + "Aries": ["military", "sports", "engineering", "entrepreneurship"], + "Taurus": ["banking", "agriculture", "luxury_goods", "arts", "food_industry"], + "Gemini": ["communication", "writing", "media", "IT", "trading"], + "Cancer": ["nursing", "hospitality", "real_estate", "food_industry"], + "Leo": ["government", "entertainment", "leadership", "politics"], + "Virgo": ["healthcare", "accounting", "analysis", "service", "editing"], + "Libra": ["law", "diplomacy", "arts", "fashion", "counseling"], + "Scorpio": ["research", "surgery", "occult", "insurance", "investigation"], + "Sagittarius": ["education", "law", "religion", "publishing", "travel"], + "Capricorn": ["administration", "mining", "construction", "management"], + "Aquarius": ["technology", "social_work", "aviation", "research", "NGO"], + "Pisces": ["spirituality", "arts", "healthcare", "shipping", "charity"], +} + +KENDRA_HOUSES = {1, 4, 7, 10} +TRIKONA_HOUSES = {1, 5, 9} +DUSTHANA_HOUSES = {6, 8, 12} + + +def _is_strong(planet_name, sign): + """Check if planet is exalted or in own sign.""" + if planet_name in EXALTATION and EXALTATION[planet_name][0] == sign: + return True + if planet_name in OWN_SIGNS and sign in OWN_SIGNS[planet_name]: + return True + return False + + +def analyze_career(planets, lagna_sign): + """ + Comprehensive career analysis using 10th house, D10, and planetary influences. + + Parameters + ---------- + planets : dict — Planet data from calculate_vedic_chart (needs house, sign, d10_sign, dignity) + lagna_sign : str — Ascendant sign + + Returns + ------- + dict with career indicators, D10 analysis, and recommendations + """ + lagna_idx = ZODIAC_SIGNS.index(lagna_sign) + tenth_sign = ZODIAC_SIGNS[(lagna_idx + 9) % 12] + tenth_lord = SIGN_LORDS[tenth_sign] + + # D10 analysis + d10_indicators = {} + for p_name, pd in planets.items(): + d10_sign = pd.get("d10_sign") + if d10_sign: + d10_lord = SIGN_LORDS.get(d10_sign) + d10_indicators[p_name] = { + "d10_sign": d10_sign, + "d10_lord": d10_lord, + "d10_strong": _is_strong(p_name, d10_sign), + } + + # Planets in 10th house + tenth_house_planets = [] + for p_name, pd in planets.items(): + if pd.get("house") == 10: + tenth_house_planets.append(p_name) + + # 10th lord analysis + tenth_lord_data = planets.get(tenth_lord, {}) + tenth_lord_house = tenth_lord_data.get("house") + tenth_lord_sign = tenth_lord_data.get("sign", "") + tenth_lord_d10 = tenth_lord_data.get("d10_sign", "") + tenth_lord_dignity = tenth_lord_data.get("dignity", "neutral") + + # Career themes from 10th house planets + career_themes = set() + primary_planets = [] + + # From 10th house occupants + for p_name in tenth_house_planets: + for theme in CAREER_SIGNIFICATIONS.get(p_name, []): + career_themes.add(theme) + primary_planets.append(p_name) + + # From 10th lord's significations + for theme in CAREER_SIGNIFICATIONS.get(tenth_lord, []): + career_themes.add(theme) + if tenth_lord not in primary_planets: + primary_planets.append(tenth_lord) + + # From 10th sign's domains + for theme in SIGN_CAREERS.get(tenth_sign, []): + career_themes.add(theme) + + # From D10 10th lord placement + if tenth_lord in d10_indicators: + d10_info = d10_indicators[tenth_lord] + for theme in SIGN_CAREERS.get(d10_info["d10_sign"], []): + career_themes.add(theme) + + # Strength assessment + strength_factors = [] + if tenth_lord_dignity in ("exalted", "own_sign", "mooltrikona"): + strength_factors.append(f"10th lord {tenth_lord} in {tenth_lord_dignity} — strong career foundation") + if tenth_lord_house in KENDRA_HOUSES: + strength_factors.append(f"10th lord {tenth_lord} in kendra (house {tenth_lord_house}) — career prominence") + if tenth_lord_house in TRIKONA_HOUSES: + strength_factors.append(f"10th lord {tenth_lord} in trikona (house {tenth_lord_house}) — fortune in career") + if tenth_lord_house in DUSTHANA_HOUSES: + strength_factors.append(f"10th lord {tenth_lord} in dusthana (house {tenth_lord_house}) — career challenges") + + for p_name in tenth_house_planets: + pd = planets.get(p_name, {}) + if pd.get("dignity") in ("exalted", "own_sign"): + strength_factors.append(f"{p_name} strong in 10th house — powerful career planet") + if pd.get("is_retrograde"): + strength_factors.append(f"{p_name} retrograde in 10th — unconventional career path") + + # D10 strength check + d10_strong_planets = [p for p, info in d10_indicators.items() if info.get("d10_strong")] + if d10_strong_planets: + strength_factors.append(f"Strong in D10: {', '.join(d10_strong_planets)} — career success in their domains") + + # 6th lord analysis (competition/service) + sixth_sign = ZODIAC_SIGNS[(lagna_idx + 5) % 12] + sixth_lord = SIGN_LORDS[sixth_sign] + sixth_lord_data = planets.get(sixth_lord, {}) + if sixth_lord_data.get("house") == 10: + strength_factors.append(f"6th lord {sixth_lord} in 10th — career in service, healthcare, or competition") + + # 7th lord analysis (business/partnerships) + seventh_sign = ZODIAC_SIGNS[(lagna_idx + 6) % 12] + seventh_lord = SIGN_LORDS[seventh_sign] + seventh_lord_data = planets.get(seventh_lord, {}) + if seventh_lord_data.get("house") == 10 or tenth_lord_house == 7: + strength_factors.append("10th-7th lord connection — career through partnerships or business") + + return { + "tenth_house": { + "sign": tenth_sign, + "lord": tenth_lord, + "lord_house": tenth_lord_house, + "lord_sign": tenth_lord_sign, + "lord_d10": tenth_lord_d10, + "lord_dignity": tenth_lord_dignity, + "occupants": tenth_house_planets, + }, + "d10_indicators": d10_indicators, + "career_themes": sorted(career_themes), + "primary_planets": primary_planets, + "strength_factors": strength_factors, + "d10_strong_planets": d10_strong_planets, + } diff --git a/references/open_source_sources/dashaflow/constants.py b/references/open_source_sources/dashaflow/constants.py new file mode 100644 index 00000000..6232aa3b --- /dev/null +++ b/references/open_source_sources/dashaflow/constants.py @@ -0,0 +1,223 @@ +import swisseph as swe + +PLANETS = { + "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, +} + +ZODIAC_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" +} + +# Nakshatra lord sequence repeats 3 times to cover all 27 +NAKSHATRA_LORD_SEQUENCE = [ + "Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury" +] + +NAKSHATRAS = [ + {"name": "Ashwini", "lord": "Ketu"}, + {"name": "Bharani", "lord": "Venus"}, + {"name": "Krittika", "lord": "Sun"}, + {"name": "Rohini", "lord": "Moon"}, + {"name": "Mrigashira", "lord": "Mars"}, + {"name": "Ardra", "lord": "Rahu"}, + {"name": "Punarvasu", "lord": "Jupiter"}, + {"name": "Pushya", "lord": "Saturn"}, + {"name": "Ashlesha", "lord": "Mercury"}, + {"name": "Magha", "lord": "Ketu"}, + {"name": "Purva Phalguni", "lord": "Venus"}, + {"name": "Uttara Phalguni","lord": "Sun"}, + {"name": "Hasta", "lord": "Moon"}, + {"name": "Chitra", "lord": "Mars"}, + {"name": "Swati", "lord": "Rahu"}, + {"name": "Vishakha", "lord": "Jupiter"}, + {"name": "Anuradha", "lord": "Saturn"}, + {"name": "Jyeshtha", "lord": "Mercury"}, + {"name": "Mula", "lord": "Ketu"}, + {"name": "Purva Ashadha", "lord": "Venus"}, + {"name": "Uttara Ashadha", "lord": "Sun"}, + {"name": "Shravana", "lord": "Moon"}, + {"name": "Dhanishta", "lord": "Mars"}, + {"name": "Shatabhisha", "lord": "Rahu"}, + {"name": "Purva Bhadrapada","lord": "Jupiter"}, + {"name": "Uttara Bhadrapada","lord": "Saturn"}, + {"name": "Revati", "lord": "Mercury"}, +] + +NAK_SPAN = 360.0 / 27.0 # 13.33333... degrees per nakshatra +PADA_SPAN = NAK_SPAN / 4.0 # 3.33333... degrees per pada + +# ========================================== +# VIMSHOTTARI DASHA +# ========================================== +VIMSHOTTARI_YEARS = { + "Ketu": 7, "Venus": 20, "Sun": 6, "Moon": 10, "Mars": 7, + "Rahu": 18, "Jupiter": 16, "Saturn": 19, "Mercury": 17 +} +VIMSHOTTARI_TOTAL = 120.0 + +DASHA_SEQUENCE = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"] + +# ========================================== +# PLANETARY DIGNITY (per BPHS) +# ========================================== + +# (sign_name, deep_exaltation_degree) +EXALTATION = { + "Sun": ("Aries", 10), + "Moon": ("Taurus", 3), + "Mars": ("Capricorn", 28), + "Mercury": ("Virgo", 15), + "Jupiter": ("Cancer", 5), + "Venus": ("Pisces", 27), + "Saturn": ("Libra", 20), + "Rahu": ("Taurus", 20), + "Ketu": ("Scorpio", 20), +} + +DEBILITATION = { + "Sun": ("Libra", 10), + "Moon": ("Scorpio", 3), + "Mars": ("Cancer", 28), + "Mercury": ("Pisces", 15), + "Jupiter": ("Capricorn", 5), + "Venus": ("Virgo", 27), + "Saturn": ("Aries", 20), + "Rahu": ("Scorpio", 20), + "Ketu": ("Taurus", 20), +} + +OWN_SIGNS = { + "Sun": ["Leo"], + "Moon": ["Cancer"], + "Mars": ["Aries", "Scorpio"], + "Mercury": ["Gemini", "Virgo"], + "Jupiter": ["Sagittarius", "Pisces"], + "Venus": ["Taurus", "Libra"], + "Saturn": ["Capricorn", "Aquarius"], + "Rahu": ["Aquarius"], + "Ketu": ["Scorpio"], +} + +# (sign, start_degree, end_degree) -- per BPHS +# Mooltrikona ranges per B.V. Raman (Hindu Predictive Astrology). +# Note: Pure BPHS has slightly narrower ranges for Moon(4-20), Mars(0-12), +# Jupiter(0-10), Venus(0-5). Raman's expanded ranges are used here for +# compatibility with VedAstro and most modern Vedic software. +MOOLTRIKONA = { + "Sun": ("Leo", 0, 20), + "Moon": ("Taurus", 4, 30), + "Mars": ("Aries", 0, 18), + "Mercury": ("Virgo", 16, 20), + "Jupiter": ("Sagittarius", 0, 13), + "Venus": ("Libra", 0, 10), + "Saturn": ("Aquarius", 0, 20), +} + +# ========================================== +# PLANETARY RELATIONSHIPS (BPHS natural/permanent) +# ========================================== +NATURAL_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": ["Mercury", "Venus", "Saturn"], + "Ketu": ["Mars", "Jupiter"], +} + +NATURAL_ENEMIES = { + "Sun": ["Venus", "Saturn"], + "Moon": [], + "Mars": ["Mercury"], + "Mercury": ["Moon"], + "Jupiter": ["Mercury", "Venus"], + "Venus": ["Sun", "Moon"], + "Saturn": ["Sun", "Moon", "Mars"], + "Rahu": ["Sun", "Moon", "Mars"], + "Ketu": ["Mercury", "Venus"], +} + +# Planets not in friends or enemies are neutral +NATURAL_NEUTRALS = { + "Sun": ["Mercury"], + "Moon": ["Mars", "Jupiter", "Venus", "Saturn"], + "Mars": ["Venus", "Saturn"], + "Mercury": ["Mars", "Jupiter", "Saturn"], + "Jupiter": ["Saturn"], + "Venus": ["Mars", "Jupiter"], + "Saturn": ["Jupiter"], + "Rahu": ["Jupiter"], + "Ketu": ["Sun", "Moon", "Saturn"], +} + +# ========================================== +# COMBUSTION ORBS (degrees from Sun) +# ========================================== +COMBUSTION_ORBS = { + "Moon": 12, + "Mars": 17, + "Mercury": {"direct": 14, "retrograde": 12}, + "Jupiter": 11, + "Venus": {"direct": 10, "retrograde": 8}, + "Saturn": 15, +} + +# ========================================== +# PANCHANG NAMES +# ========================================== +TITHI_NAMES = [ + "Pratipada", "Dwitiya", "Tritiya", "Chaturthi", "Panchami", + "Shashthi", "Saptami", "Ashtami", "Navami", "Dashami", + "Ekadashi", "Dwadashi", "Trayodashi", "Chaturdashi", "Purnima", + "Pratipada", "Dwitiya", "Tritiya", "Chaturthi", "Panchami", + "Shashthi", "Saptami", "Ashtami", "Navami", "Dashami", + "Ekadashi", "Dwadashi", "Trayodashi", "Chaturdashi", "Amavasya", +] + +VARA_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] +VARA_LORDS = ["Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Sun"] + +PANCHANG_YOGA_NAMES = [ + "Vishkambha", "Priti", "Ayushman", "Saubhagya", "Shobhana", + "Atiganda", "Sukarma", "Dhriti", "Shula", "Ganda", + "Vriddhi", "Dhruva", "Vyaghata", "Harshana", "Vajra", + "Siddhi", "Vyatipata", "Variyan", "Parigha", "Shiva", + "Siddha", "Sadhya", "Shubha", "Shukla", "Brahma", + "Indra", "Vaidhriti", +] + +KARANA_NAMES = [ + "Bava", "Balava", "Kaulava", "Taitila", "Garija", + "Vanija", "Vishti", "Shakuni", "Chatushpada", "Nagava", "Kimstughna" +] + +# ========================================== +# DIGBALA (directional strength) +# Houses where planets gain Digbala (1-indexed) +# ========================================== +DIGBALA_HOUSES = { + "Jupiter": 1, + "Mercury": 1, + "Sun": 10, + "Mars": 10, + "Saturn": 7, + "Moon": 4, + "Venus": 4, +} diff --git a/references/open_source_sources/dashaflow/dasha.py b/references/open_source_sources/dashaflow/dasha.py new file mode 100644 index 00000000..e61ab6b5 --- /dev/null +++ b/references/open_source_sources/dashaflow/dasha.py @@ -0,0 +1,185 @@ +import datetime +from .constants import VIMSHOTTARI_YEARS, DASHA_SEQUENCE, NAK_SPAN +from .nakshatra import get_nakshatra + + +def _add_years_days(dt, years, days): + """Add fractional years (as whole years + remaining days) to a datetime.""" + total_days = years * 365.2425 + days + return dt + datetime.timedelta(days=total_days) + + +def _build_sub_periods(start_dt, total_days, starting_lord): + """ + Build sub-periods (Antardasha or Pratyantardasha) within a parent period. + The sub-period sequence starts from the parent lord and cycles through + the Dasha sequence. + """ + seq_start = DASHA_SEQUENCE.index(starting_lord) + periods = [] + cursor = start_dt + + for i in range(9): + lord = DASHA_SEQUENCE[(seq_start + i) % 9] + proportion = VIMSHOTTARI_YEARS[lord] / 120.0 + sub_days = total_days * proportion + end = cursor + datetime.timedelta(days=sub_days) + periods.append({ + "planet": lord, + "start": cursor.strftime("%Y-%m-%d"), + "end": end.strftime("%Y-%m-%d"), + "days": round(sub_days, 2), + }) + cursor = end + + return periods + + +def calculate_dashas(moon_longitude, birth_dt, query_dt=None): + """ + Compute Vimshottari Dasha timeline from Moon's sidereal longitude at birth. + + Parameters + ---------- + moon_longitude : float + Sidereal longitude of the Moon at birth (0-360). + birth_dt : datetime.datetime + Birth datetime (timezone-aware or naive). + query_dt : datetime.datetime, optional + Date to find active Maha/Antar/Pratyantar for. Defaults to today. + + Returns + ------- + dict with keys: maha, antar, pratyantar, timeline + """ + if query_dt is None: + query_dt = datetime.datetime.now() + if hasattr(birth_dt, 'tzinfo') and birth_dt.tzinfo: + birth_dt = birth_dt.replace(tzinfo=None) + if hasattr(query_dt, 'tzinfo') and query_dt.tzinfo: + query_dt = query_dt.replace(tzinfo=None) + + nak_info = get_nakshatra(moon_longitude) + nak_lord = nak_info["lord"] + + elapsed_fraction = nak_info["degree_in_nakshatra"] / NAK_SPAN + remaining_fraction = 1.0 - elapsed_fraction + + seq_start = DASHA_SEQUENCE.index(nak_lord) + + # Build Mahadasha timeline starting from birth + timeline = [] + cursor = birth_dt + + first_maha_years = VIMSHOTTARI_YEARS[nak_lord] * remaining_fraction + first_maha_days = first_maha_years * 365.2425 + first_end = cursor + datetime.timedelta(days=first_maha_days) + timeline.append({ + "planet": nak_lord, + "start": cursor.strftime("%Y-%m-%d"), + "end": first_end.strftime("%Y-%m-%d"), + "years": round(first_maha_years, 4), + "days": round(first_maha_days, 2), + }) + cursor = first_end + + # Remaining 8 full cycles, then repeat to cover 120+ years + for cycle in range(2): + start_offset = 1 if cycle == 0 else 0 + for i in range(start_offset, 9): + lord = DASHA_SEQUENCE[(seq_start + i) % 9] + years = VIMSHOTTARI_YEARS[lord] + days = years * 365.2425 + end = cursor + datetime.timedelta(days=days) + timeline.append({ + "planet": lord, + "start": cursor.strftime("%Y-%m-%d"), + "end": end.strftime("%Y-%m-%d"), + "years": float(years), + "days": days, + }) + cursor = end + + active_maha = None + active_antar = None + active_pratyantar = None + active_sukshma = None + active_prana = None + + for period in timeline: + p_start = datetime.datetime.strptime(period["start"], "%Y-%m-%d") + p_end = datetime.datetime.strptime(period["end"], "%Y-%m-%d") + if p_start <= query_dt < p_end: + active_maha = period + break + + if active_maha: + maha_start = datetime.datetime.strptime(active_maha["start"], "%Y-%m-%d") + maha_days = active_maha["days"] + antars = _build_sub_periods(maha_start, maha_days, active_maha["planet"]) + + for antar in antars: + a_start = datetime.datetime.strptime(antar["start"], "%Y-%m-%d") + a_end = datetime.datetime.strptime(antar["end"], "%Y-%m-%d") + if a_start <= query_dt < a_end: + active_antar = antar + break + + if active_antar: + antar_start = datetime.datetime.strptime(active_antar["start"], "%Y-%m-%d") + antar_days = active_antar["days"] + pratyantars = _build_sub_periods(antar_start, antar_days, active_antar["planet"]) + + for prat in pratyantars: + pr_start = datetime.datetime.strptime(prat["start"], "%Y-%m-%d") + pr_end = datetime.datetime.strptime(prat["end"], "%Y-%m-%d") + if pr_start <= query_dt < pr_end: + active_pratyantar = prat + break + + # Level 4: Sukshma Dasha + if active_pratyantar: + prat_start = datetime.datetime.strptime(active_pratyantar["start"], "%Y-%m-%d") + prat_days = active_pratyantar["days"] + sukshmas = _build_sub_periods(prat_start, prat_days, active_pratyantar["planet"]) + + for suk in sukshmas: + s_start = datetime.datetime.strptime(suk["start"], "%Y-%m-%d") + s_end = datetime.datetime.strptime(suk["end"], "%Y-%m-%d") + if s_start <= query_dt < s_end: + active_sukshma = suk + break + + # Level 5: Prana Dasha + if active_sukshma: + suk_start = datetime.datetime.strptime(active_sukshma["start"], "%Y-%m-%d") + suk_days = active_sukshma["days"] + pranas = _build_sub_periods(suk_start, suk_days, active_sukshma["planet"]) + + for pra in pranas: + pra_start = datetime.datetime.strptime(pra["start"], "%Y-%m-%d") + pra_end = datetime.datetime.strptime(pra["end"], "%Y-%m-%d") + if pra_start <= query_dt < pra_end: + active_prana = pra + break + + # Trim timeline to a reasonable window (birth to ~120 years) + compact_timeline = [] + for t in timeline: + compact_timeline.append({ + "planet": t["planet"], + "start": t["start"], + "end": t["end"], + }) + t_end = datetime.datetime.strptime(t["end"], "%Y-%m-%d") + if t_end > birth_dt + datetime.timedelta(days=120 * 365.2425): + break + + return { + "maha": active_maha, + "antar": active_antar, + "pratyantar": active_pratyantar, + "sukshma": active_sukshma, + "prana": active_prana, + "timeline": compact_timeline, + } diff --git a/references/open_source_sources/dashaflow/dignity.py b/references/open_source_sources/dashaflow/dignity.py new file mode 100644 index 00000000..d429c10e --- /dev/null +++ b/references/open_source_sources/dashaflow/dignity.py @@ -0,0 +1,148 @@ +from .constants import ( + EXALTATION, DEBILITATION, OWN_SIGNS, MOOLTRIKONA, + SIGN_LORDS, NATURAL_FRIENDS, NATURAL_ENEMIES, NATURAL_NEUTRALS, + COMBUSTION_ORBS, ZODIAC_SIGNS, +) + + +def get_dignity(planet_name, sign, degree_in_sign, planets_in_signs=None): + """ + Determine a planet's dignity in a given sign per BPHS. + Returns one of: exalted, mooltrikona, own_sign, friend, neutral, enemy, debilitated. + Rahu/Ketu get a simplified check (exalt/debilit/own or neutral). + """ + if planet_name in EXALTATION and EXALTATION[planet_name][0] == sign: + if planet_name in MOOLTRIKONA: + mt_sign, mt_start, mt_end = MOOLTRIKONA[planet_name] + if mt_sign == sign and mt_start <= degree_in_sign <= mt_end: + return "mooltrikona" + return "exalted" + + if planet_name in DEBILITATION and DEBILITATION[planet_name][0] == sign: + return "debilitated" + + if planet_name in OWN_SIGNS and sign in OWN_SIGNS[planet_name]: + if planet_name in MOOLTRIKONA: + mt_sign, mt_start, mt_end = MOOLTRIKONA[planet_name] + if mt_sign == sign and mt_start <= degree_in_sign <= mt_end: + return "mooltrikona" + return "own_sign" + + if planet_name in MOOLTRIKONA: + mt_sign, mt_start, mt_end = MOOLTRIKONA[planet_name] + if mt_sign == sign and mt_start <= degree_in_sign <= mt_end: + return "mooltrikona" + + if planet_name in ("Rahu", "Ketu"): + return "neutral" + + sign_lord = SIGN_LORDS.get(sign) + if not sign_lord: + return "neutral" + + if planets_in_signs: + return get_compound_relationship(planet_name, sign, planets_in_signs) + + if sign_lord in NATURAL_FRIENDS.get(planet_name, []): + return "friend" + if sign_lord in NATURAL_ENEMIES.get(planet_name, []): + return "enemy" + return "neutral" + + +def get_compound_relationship(planet_name, sign, planets_in_signs): + """ + Compute Panchadha Maitri (5-fold compound relationship). + Combines natural relationship with temporary friendship. + Temporary friend = any planet in signs 2,3,4,10,11,12 from the planet. + Temporary enemy = any planet in signs 1,5,6,7,8,9 (same sign counts as 1). + + Parameters + ---------- + planet_name : str + sign : str - the sign the planet occupies + planets_in_signs : dict - {planet_name: sign_index} for all planets + + Returns one of: great_friend, friend, neutral, enemy, great_enemy + """ + sign_lord = SIGN_LORDS.get(sign) + if not sign_lord or planet_name in ("Rahu", "Ketu"): + return "neutral" + + natural = _natural_relationship(planet_name, sign_lord) + + planet_sign_idx = ZODIAC_SIGNS.index(sign) + sign_lord_idx = None + for p, idx in planets_in_signs.items(): + if p == sign_lord: + sign_lord_idx = idx + break + if sign_lord_idx is None: + return natural + + dist = (sign_lord_idx - planet_sign_idx) % 12 + # Houses 2,3,4,10,11,12 from planet are temporary friends (BPHS) + # dist values {1,2,3,9,10,11} map to these houses (0-indexed) + temp_friend_positions = {1, 2, 3, 9, 10, 11} + is_temp_friend = dist in temp_friend_positions + + compound = _combine_relationships(natural, is_temp_friend) + return compound + + +def _natural_relationship(planet, other): + if other in NATURAL_FRIENDS.get(planet, []): + return "friend" + if other in NATURAL_ENEMIES.get(planet, []): + return "enemy" + return "neutral" + + +def _combine_relationships(natural, is_temp_friend): + """Panchadha Maitri combination table.""" + if natural == "friend" and is_temp_friend: + return "great_friend" + if natural == "friend" and not is_temp_friend: + return "neutral" + if natural == "neutral" and is_temp_friend: + return "friend" + if natural == "neutral" and not is_temp_friend: + return "enemy" + if natural == "enemy" and is_temp_friend: + return "neutral" + if natural == "enemy" and not is_temp_friend: + return "great_enemy" + return "neutral" + + +def check_combustion(planet_name, planet_lon, sun_lon, is_retrograde=False): + """ + Check if a planet is combust (too close to the Sun). + Sun, Rahu, Ketu cannot be combust. + """ + if planet_name in ("Sun", "Rahu", "Ketu"): + return False + + orb_data = COMBUSTION_ORBS.get(planet_name) + if orb_data is None: + return False + + if isinstance(orb_data, dict): + orb = orb_data["retrograde"] if is_retrograde else orb_data["direct"] + else: + orb = orb_data + + angular_dist = abs(planet_lon - sun_lon) + if angular_dist > 180: + angular_dist = 360 - angular_dist + + return angular_dist <= orb + + +def get_digbala(planet_name, house): + """Check if a planet has directional strength in its current house.""" + from .constants import DIGBALA_HOUSES + ideal_house = DIGBALA_HOUSES.get(planet_name) + if ideal_house is None: + return False + return house == ideal_house diff --git a/references/open_source_sources/dashaflow/jaimini.py b/references/open_source_sources/dashaflow/jaimini.py new file mode 100644 index 00000000..a3c76a59 --- /dev/null +++ b/references/open_source_sources/dashaflow/jaimini.py @@ -0,0 +1,243 @@ +""" +Jaimini Karakas — Chara Karaka System +Based on BPHS Jaimini Sutras: The planet with the highest degree +in its sign (excluding Rahu/Ketu) becomes the Atmakaraka (soul significator). +The 8-karaka scheme is used (includes Rahu as the 8th). +""" + +from .constants import ZODIAC_SIGNS, SIGN_LORDS + +KARAKA_NAMES = [ + "Atmakaraka", # Soul, self (highest degree) — most important planet in chart + "Amatyakaraka", # Career/profession, minister + "Bhratrikaraka", # Siblings, courage + "Matrikaraka", # Mother, education, property + "Putrakaraka", # Children, creativity, intelligence + "Gnatikaraka", # Enemies, diseases, obstacles + "Darakaraka", # Spouse (lowest degree among 7 planets) +] + +KARAKA_DESCRIPTIONS = { + "Atmakaraka": "The King of the chart. Represents the soul's deepest desire and the primary life lesson. The house it sits in (Karakamsha in D9) reveals the soul's ultimate direction.", + "Amatyakaraka": "The Minister. Represents career, profession, and the means through which one earns and contributes to society. Check its D10 position for career specifics.", + "Bhratrikaraka": "Significator of siblings, courage, and personal initiative. Its strength shows the native's ability to take bold action.", + "Matrikaraka": "Significator of mother, formal education, property, and emotional comfort.", + "Putrakaraka": "Significator of children, intelligence, creativity, and past-life merit (Purva Punya). Check D7 for progeny details.", + "Gnatikaraka": "Significator of enemies, diseases, and obstacles. Its Dasha can bring confrontations but also the strength to overcome them.", + "Darakaraka": "Significator of spouse and marriage partner. The planet with the LOWEST degree becomes the Darakaraka. Its sign, dignity, and D9 position describe the spouse's nature.", +} + +# Arudha Pada names for all 12 houses +ARUDHA_NAMES = { + 1: "Arudha Lagna (AL)", + 2: "Dhana Pada (A2)", + 3: "Vikrama Pada (A3)", + 4: "Sukha Pada (A4)", + 5: "Mantra Pada (A5)", + 6: "Roga Pada (A6)", + 7: "Dara Pada (A7)", + 8: "Mrithyu Pada (A8)", + 9: "Dharma Pada (A9)", + 10: "Karma Pada (A10)", + 11: "Labha Pada (A11)", + 12: "Upapada (UL)", +} + + +def calculate_jaimini_karakas(planets_data: dict): + """ + Calculates the 7 Chara Karakas from the natal chart planet data. + + Parameters + ---------- + planets_data : dict + The 'planets' dict from calculate_vedic_chart output. + Each planet entry must have 'degree' (0-30 within sign). + + Returns + ------- + dict with karaka assignments and descriptions. + """ + # Only the 7 visible planets participate (Rahu/Ketu excluded from standard 7-karaka scheme) + eligible = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] + + # Build list of (planet_name, degree_in_sign) + planet_degrees = [] + for name in eligible: + if name in planets_data: + deg = planets_data[name]["degree"] + planet_degrees.append((name, deg)) + + # Sort by degree DESCENDING — highest degree = Atmakaraka + planet_degrees.sort(key=lambda x: x[1], reverse=True) + + karakas = {} + for i, (planet_name, degree) in enumerate(planet_degrees): + if i < len(KARAKA_NAMES): + karaka_name = KARAKA_NAMES[i] + karakas[karaka_name] = { + "planet": planet_name, + "degree": degree, + "description": KARAKA_DESCRIPTIONS[karaka_name], + "sign": planets_data[planet_name]["sign"], + "house": planets_data[planet_name]["house"], + "d9_sign": planets_data[planet_name].get("d9_sign", ""), + } + + return karakas + + +def _sign_idx(sign_name): + """Get index of a sign (0-11).""" + return ZODIAC_SIGNS.index(sign_name) + + +def _house_count(from_idx, to_idx): + """Count houses from one sign index to another (1-based).""" + return ((to_idx - from_idx) % 12) + 1 + + +def calculate_arudha_padas(lagna_sign, planets_data): + """ + Calculate Arudha Padas for all 12 houses per Jaimini system. + + Arudha Pada of a house = count from house sign to its lord's placement, + then count the same distance from the lord's sign. + + Exception: If the Arudha falls in the same sign as the house or the 7th from it, + move it to the 10th sign from the house instead. + + Parameters + ---------- + lagna_sign : str — Ascendant sign name + planets_data : dict — planet data from calculate_vedic_chart + + Returns + ------- + dict: {house_num: {"sign": str, "name": str, "sign_index": int}} + """ + lagna_idx = _sign_idx(lagna_sign) + + # Build planet sign lookup + planet_signs = {} + for name, pd in planets_data.items(): + planet_signs[name] = _sign_idx(pd["sign"]) + + arudha_padas = {} + + for house_num in range(1, 13): + house_sign_idx = (lagna_idx + house_num - 1) % 12 + house_sign = ZODIAC_SIGNS[house_sign_idx] + + # Lord of this house + lord = SIGN_LORDS[house_sign] + + if lord not in planet_signs: + continue + + lord_sign_idx = planet_signs[lord] + + # Count from house sign to lord's sign + distance = _house_count(house_sign_idx, lord_sign_idx) + + # Arudha = same distance counted from lord's sign + arudha_idx = (lord_sign_idx + distance - 1) % 12 + + # Exception rule: if arudha falls in the house itself or 7th from it + seventh_from_house = (house_sign_idx + 6) % 12 + if arudha_idx == house_sign_idx or arudha_idx == seventh_from_house: + # Move to 10th from the house + arudha_idx = (house_sign_idx + 9) % 12 + + arudha_padas[house_num] = { + "sign": ZODIAC_SIGNS[arudha_idx], + "sign_index": arudha_idx, + "name": ARUDHA_NAMES.get(house_num, f"A{house_num}"), + } + + return arudha_padas + + +def calculate_upapada(lagna_sign, planets_data): + """ + Calculate Upapada Lagna (UL) — the Arudha of the 12th house. + Critical for spouse analysis in Jaimini. + + The sign of the Upapada and planets in/aspecting it describe the spouse. + The 2nd from Upapada shows longevity of marriage. + + Returns + ------- + dict: {"sign": str, "sign_index": int, "lord": str, "second_from_ul": str} + """ + arudha_padas = calculate_arudha_padas(lagna_sign, planets_data) + + ul = arudha_padas.get(12) + if not ul: + return None + + ul_idx = ul["sign_index"] + second_idx = (ul_idx + 1) % 12 + + return { + "sign": ul["sign"], + "sign_index": ul_idx, + "lord": SIGN_LORDS[ul["sign"]], + "second_from_ul": ZODIAC_SIGNS[second_idx], + "description": f"Upapada in {ul['sign']} — spouse characteristics shaped by {SIGN_LORDS[ul['sign']]}. 2nd from UL ({ZODIAC_SIGNS[second_idx]}) indicates marriage longevity.", + } + + +def calculate_karakamsha(karakas, planets_data, lagna_sign): + """ + Calculate Karakamsha — Atmakaraka's sign in D9 (Navamsha). + The Karakamsha sign becomes a reference lagna for soul-level analysis. + + Also computes the 12th from Karakamsha → Ishta Devata (personal deity). + + Parameters + ---------- + karakas : dict — output from calculate_jaimini_karakas + planets_data : dict — planets output with d9_sign + lagna_sign : str — D1 ascendant sign + + Returns + ------- + dict: Karakamsha analysis + """ + ak = karakas.get("Atmakaraka") + if not ak: + return None + + ak_planet = ak["planet"] + ak_d9_sign = ak.get("d9_sign", "") + + if not ak_d9_sign: + return None + + ak_d9_idx = _sign_idx(ak_d9_sign) + lagna_idx = _sign_idx(lagna_sign) + + # Karakamsha house from Lagna + karakamsha_house = _house_count(lagna_idx, ak_d9_idx) + + # 12th from Karakamsha → Ishta Devata sign + ishta_devata_sign_idx = (ak_d9_idx - 1) % 12 + ishta_devata_sign = ZODIAC_SIGNS[ishta_devata_sign_idx] + ishta_devata_lord = SIGN_LORDS[ishta_devata_sign] + + # Check which planets occupy the Karakamsha sign in D9 + planets_in_karakamsha = [] + for name, pd in planets_data.items(): + if pd.get("d9_sign") == ak_d9_sign: + planets_in_karakamsha.append(name) + + return { + "atmakaraka": ak_planet, + "karakamsha_sign": ak_d9_sign, + "karakamsha_house_from_lagna": karakamsha_house, + "planets_in_karakamsha": planets_in_karakamsha, + "ishta_devata_sign": ishta_devata_sign, + "ishta_devata_lord": ishta_devata_lord, + "description": f"Atmakaraka {ak_planet} in D9 {ak_d9_sign} (house {karakamsha_house} from Lagna) — soul's deepest direction. Ishta Devata indicated by {ishta_devata_lord} ({ishta_devata_sign}).", + } diff --git a/references/open_source_sources/dashaflow/matchmaking.py b/references/open_source_sources/dashaflow/matchmaking.py new file mode 100644 index 00000000..e04d5e5e --- /dev/null +++ b/references/open_source_sources/dashaflow/matchmaking.py @@ -0,0 +1,508 @@ +from .constants import ZODIAC_SIGNS, SIGN_LORDS, NATURAL_FRIENDS, NATURAL_ENEMIES, EXALTATION, DEBILITATION, OWN_SIGNS +from .nakshatra import get_nakshatra +import math + +# --- Data Tables --- +# 0-26 indexed Nakshatras +YONI_ANIMALS = [ + "Horse", "Elephant", "Sheep", "Serpent", "Serpent", "Dog", "Cat", "Sheep", "Cat", # 0-8 + "Rat", "Rat", "Cow", "Buffalo", "Tiger", "Buffalo", "Tiger", "Deer", "Deer", # 9-17 + "Dog", "Monkey", "Mongoose", "Monkey", "Lion", "Horse", "Lion", "Cow", "Elephant" # 18-26 +] + +YONI_ENEMIES = { + "Horse": "Buffalo", "Buffalo": "Horse", + "Elephant": "Lion", "Lion": "Elephant", + "Sheep": "Monkey", "Monkey": "Sheep", + "Serpent": "Mongoose", "Mongoose": "Serpent", + "Dog": "Deer", "Deer": "Dog", + "Cat": "Rat", "Rat": "Cat", + "Cow": "Tiger", "Tiger": "Cow" +} + +GANA = [ + "Deva", "Manushya", "Rakshasa", "Manushya", "Deva", "Manushya", "Deva", "Deva", "Rakshasa", + "Rakshasa", "Manushya", "Manushya", "Deva", "Rakshasa", "Deva", "Rakshasa", "Deva", "Rakshasa", + "Rakshasa", "Manushya", "Manushya", "Deva", "Rakshasa", "Rakshasa", "Manushya", "Manushya", "Deva" +] + +NADI = [ + "Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya", + "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", + "Adi", "Madhya", "Antya", "Antya", "Madhya", "Adi", "Adi", "Madhya", "Antya" +] + +VARNA = { + "Cancer": 1, "Scorpio": 1, "Pisces": 1, # Brahmin + "Aries": 2, "Leo": 2, "Sagittarius": 2, # Kshatriya + "Taurus": 3, "Virgo": 3, "Capricorn": 3, # Vaishya + "Gemini": 4, "Libra": 4, "Aquarius": 4 # Shudra +} + +# 1. Varna (1 point) +def calc_varna(m_sign, f_sign): + m_varna = VARNA[m_sign] + f_varna = VARNA[f_sign] + return 1.0 if m_varna <= f_varna else 0.0 + +# 2. Vashya (2 points) — Full BPHS classification +# Sign categories: Chatushpada (quadruped), Manava (human), Jalachara (water), +# Vanachara (wild/forest), Keet (insect/reptile) +VASHYA_TYPE = { + "Aries": "Chatushpada", "Taurus": "Chatushpada", + "Leo": "Vanachara", "Sagittarius": "Chatushpada", # 2nd half is Manava + "Capricorn": "Chatushpada", # 1st half is Chatushpada + "Gemini": "Manava", "Virgo": "Manava", "Libra": "Manava", + "Aquarius": "Manava", # 1st half is Manava + "Cancer": "Jalachara", "Pisces": "Jalachara", + "Scorpio": "Keet", +} + +# Vashya compatibility scoring matrix +VASHYA_MATRIX = { + ("Chatushpada", "Chatushpada"): 2.0, + ("Manava", "Manava"): 2.0, + ("Jalachara", "Jalachara"): 2.0, + ("Vanachara", "Vanachara"): 2.0, + ("Keet", "Keet"): 2.0, + ("Chatushpada", "Manava"): 0.5, + ("Manava", "Chatushpada"): 0.5, + ("Manava", "Jalachara"): 1.0, + ("Jalachara", "Manava"): 1.0, + ("Chatushpada", "Jalachara"): 0.5, + ("Jalachara", "Chatushpada"): 0.5, + ("Vanachara", "Chatushpada"): 0.0, # Wild eats quadruped + ("Chatushpada", "Vanachara"): 0.0, + ("Keet", "Chatushpada"): 0.0, + ("Chatushpada", "Keet"): 0.0, + ("Vanachara", "Manava"): 0.5, + ("Manava", "Vanachara"): 0.5, + ("Keet", "Manava"): 0.5, + ("Manava", "Keet"): 0.5, + ("Vanachara", "Jalachara"): 0.5, + ("Jalachara", "Vanachara"): 0.5, + ("Keet", "Jalachara"): 1.0, + ("Jalachara", "Keet"): 1.0, + ("Vanachara", "Keet"): 1.0, + ("Keet", "Vanachara"): 1.0, +} + +def calc_vashya(m_sign, f_sign): + if m_sign == f_sign: + return 2.0 + m_type = VASHYA_TYPE.get(m_sign, "Manava") + f_type = VASHYA_TYPE.get(f_sign, "Manava") + return VASHYA_MATRIX.get((m_type, f_type), 1.0) + +# 3. Tara (3 points) +def calc_tara(m_nak_idx, f_nak_idx): + m_to_f = (f_nak_idx - m_nak_idx) % 9 + f_to_m = (m_nak_idx - f_nak_idx) % 9 + pts = 0.0 + if m_to_f not in (2, 4, 6): pts += 1.5 + if f_to_m not in (2, 4, 6): pts += 1.5 + return pts + +# 4. Yoni (4 points) +def calc_yoni(m_nak_idx, f_nak_idx): + m_yoni = YONI_ANIMALS[m_nak_idx] + f_yoni = YONI_ANIMALS[f_nak_idx] + if m_yoni == f_yoni: + return 4.0 + if YONI_ENEMIES.get(m_yoni) == f_yoni: + return 0.0 + return 2.0 # Neutral + +# 5. Graha Maitri (5 points) +def check_friendship(p1, p2): + if p1 == p2: + return 1.0 # Same lord + if p2 in NATURAL_FRIENDS.get(p1, []): + return 1.0 + if p2 in NATURAL_ENEMIES.get(p1, []): + return 0.0 + return 0.5 # Neutral + +def calc_graha_maitri(m_sign, f_sign): + m_lord = SIGN_LORDS[m_sign] + f_lord = SIGN_LORDS[f_sign] + m_to_f = check_friendship(m_lord, f_lord) + f_to_m = check_friendship(f_lord, m_lord) + + total = m_to_f + f_to_m + if total == 2.0: return 5.0 + if total == 1.5: return 4.0 + if total == 1.0: return 3.0 + if total == 0.5: return 1.0 + return 0.0 + +# 6. Gana (6 points) +def calc_gana(m_nak_idx, f_nak_idx): + m_gana = GANA[m_nak_idx] + f_gana = GANA[f_nak_idx] + if m_gana == f_gana: return 6.0 + if m_gana == "Deva" and f_gana == "Manushya": return 6.0 + if m_gana == "Manushya" and f_gana == "Deva": return 5.0 + if m_gana == "Rakshasa" and f_gana == "Manushya": return 0.0 + if f_gana == "Rakshasa" and m_gana == "Manushya": return 0.0 + if m_gana == "Rakshasa" and f_gana == "Deva": return 1.0 + if f_gana == "Rakshasa" and m_gana == "Deva": return 0.0 + return 0.0 + +# 7. Bhakoot (7 points) +def calc_bhakoot(m_sign, f_sign): + m_idx = ZODIAC_SIGNS.index(m_sign) + f_idx = ZODIAC_SIGNS.index(f_sign) + diff = (f_idx - m_idx) % 12 + 1 + if diff in (1, 7, 3, 11, 4, 10): + return 7.0 + return 0.0 + +# 8. Nadi (8 points) +def calc_nadi(m_nak_idx, f_nak_idx): + m_nadi = NADI[m_nak_idx] + f_nadi = NADI[f_nak_idx] + if m_nadi != f_nadi: + return 8.0 + return 0.0 # Nadi Dosha + + +# ========================================== +# ADDITIONAL KUTAS (beyond 36-point Ashtakoot) +# ========================================== + +# 9. Mahendra Kuta — longevity and well-being +def calc_mahendra(m_nak_idx, f_nak_idx): + """Male's nakshatra counted from female's. Auspicious if 4,7,10,13,16,19,22,25.""" + count = ((m_nak_idx - f_nak_idx) % 27) + 1 + return "good" if count in (4, 7, 10, 13, 16, 19, 22, 25) else "bad" + + +# 10. Stree Deergha — husband's longevity +def calc_stree_deergha(m_nak_idx, f_nak_idx): + """Male's nakshatra must be >= 9 nakshatras from female's (counted f→m).""" + count = ((m_nak_idx - f_nak_idx) % 27) + 1 + return "good" if count >= 9 else "bad" + + +# 11. Vedha Kuta — obstruction pairs +VEDHA_PAIRS = [ + (0, 17), # Ashwini - Jyeshtha + (1, 16), # Bharani - Anuradha + (2, 15), # Krittika - Vishakha + (3, 14), # Rohini - Swati + (5, 21), # Ardra - Shravana + (6, 20), # Punarvasu - Uttara Ashadha + (7, 19), # Pushya - Purva Ashadha + (8, 18), # Ashlesha - Mula + (9, 26), # Magha - Revati + (10, 25), # Purva Phalguni - Uttara Bhadrapada + (11, 24), # Uttara Phalguni - Purva Bhadrapada + (12, 23), # Hasta - Shatabhisha + (4, 22), # Mrigashira - Dhanishta +] + +def calc_vedha(m_nak_idx, f_nak_idx): + """Check if male and female nakshatras form a hostile Vedha pair.""" + for a, b in VEDHA_PAIRS: + if (m_nak_idx == a and f_nak_idx == b) or (m_nak_idx == b and f_nak_idx == a): + return "bad" + return "good" + + +# 12. Kuja Dosha (Manglik) — Mars affliction analysis +_DOSHA_HOUSES = {2, 4, 7, 8, 12} +_HIGH_SEVERITY_HOUSES = {7, 8} + +_MARS_EXCEPTIONS = { + 2: {"Gemini", "Virgo"}, + 12: {"Taurus", "Libra"}, + 4: {"Aries", "Scorpio"}, + 7: {"Capricorn", "Cancer"}, + 8: {"Sagittarius", "Pisces"}, +} +_MARS_EXEMPT_SIGNS = {"Aquarius", "Leo"} + + +def _planet_dignity_level(planet_name, sign): + """Return dignity level for Kuja Dosha scoring.""" + if planet_name in EXALTATION and EXALTATION[planet_name][0] == sign: + return "exalted" + if planet_name in OWN_SIGNS and sign in OWN_SIGNS[planet_name]: + return "own" + lord = SIGN_LORDS.get(sign) + if lord and planet_name in NATURAL_FRIENDS.get(lord, []): + return "friendly" + if lord and planet_name in NATURAL_ENEMIES.get(lord, []): + return "enemy" + if planet_name in DEBILITATION and DEBILITATION[planet_name][0] == sign: + return "debilitated" + return "neutral" + + +_DOSHA_SCORES_HIGH = { + "Mars": {"debilitated": 100, "enemy": 90, "neutral": 80, "friendly": 70, "own": 60, "exalted": 50}, + "Saturn": {"debilitated": 75, "enemy": 67.5, "neutral": 60, "friendly": 52.5, "own": 45, "exalted": 37.5}, + "Sun": {"debilitated": 50, "enemy": 45, "neutral": 40, "friendly": 35, "own": 30, "exalted": 25}, +} +_DOSHA_SCORES_LOW = { + "Mars": {"debilitated": 50, "enemy": 45, "neutral": 40, "friendly": 35, "own": 30, "exalted": 25}, + "Saturn": {"debilitated": 37.5, "enemy": 33.75, "neutral": 30, "friendly": 26.25, "own": 22.5, "exalted": 18.75}, + "Sun": {"debilitated": 25, "enemy": 22.5, "neutral": 20, "friendly": 17.5, "own": 15, "exalted": 12.5}, +} + + +def _calc_dosha_score(planet_name, house, sign): + """Calculate Kuja Dosha score for a single planet placement.""" + if house not in _DOSHA_HOUSES: + return 0.0 + # Mars-specific exceptions + if planet_name == "Mars": + if sign in _MARS_EXEMPT_SIGNS: + return 0.0 + if house in _MARS_EXCEPTIONS and sign in _MARS_EXCEPTIONS[house]: + return 0.0 + + dig = _planet_dignity_level(planet_name, sign) + score_planet = planet_name if planet_name in _DOSHA_SCORES_HIGH else "Saturn" # Rahu/Ketu use Saturn table + + if house in _HIGH_SEVERITY_HOUSES: + return _DOSHA_SCORES_HIGH.get(score_planet, _DOSHA_SCORES_HIGH["Saturn"]).get(dig, 60) + else: + return _DOSHA_SCORES_LOW.get(score_planet, _DOSHA_SCORES_LOW["Saturn"]).get(dig, 30) + + +def calc_kuja_dosha(chart): + """ + Calculate total Kuja Dosha score for a chart. + Checks Mars, Saturn, Rahu, Ketu, Sun in houses 2,4,7,8,12. + chart: output from calculate_vedic_chart (needs planets with house and sign). + Returns dict with total score and per-planet breakdown. + """ + planets = chart.get("planets", {}) + total = 0.0 + breakdown = {} + for p_name in ("Mars", "Saturn", "Rahu", "Ketu", "Sun"): + pd = planets.get(p_name) + if not pd: + continue + score = _calc_dosha_score(p_name, pd["house"], pd["sign"]) + if score > 0: + breakdown[p_name] = {"house": pd["house"], "sign": pd["sign"], "score": score} + total += score + return {"total_score": round(total, 2), "breakdown": breakdown, "is_manglik": total > 0} + + +def match_kuja_dosha(male_score, female_score): + """ + Compare Kuja Dosha between male and female. + |diff| <= 5: good. Female > male by > 5: bad. Male > female by > 5: check 25% threshold. + """ + diff = male_score - female_score + if abs(diff) <= 5: + return {"result": "good", "description": "Kuja Dosha balanced between partners."} + if diff < -5: + return {"result": "bad", "description": "Female has significantly more Kuja Dosha."} + # male > female by > 5 + if female_score > 0 and diff < female_score * 0.25: + return {"result": "acceptable", "description": "Male has more Kuja Dosha but within tolerance."} + return {"result": "bad", "description": "Male has significantly more Kuja Dosha."} + + +# 13. Rajju Kuta — marital longevity based on nakshatra body-part group +RAJJU_GROUPS = { + "Pada": {0, 8, 9, 17, 18, 26}, # Ashwini, Ashlesha, Magha, Jyeshtha, Mula, Revati + "Kati": {1, 7, 10, 16, 25, 19}, # Bharani, Pushya, P.Phalguni, Anuradha, U.Bhadra, P.Ashadha + "Udara": {2, 6, 11, 15, 20, 24}, # Krittika, Punarvasu, U.Phalguni, Vishakha, U.Ashadha, P.Bhadra + "Kanta": {3, 5, 12, 14, 21, 23}, # Rohini, Ardra, Hasta, Swati, Shravana, Shatabhisha + "Sira": {4, 13, 22}, # Mrigashira, Chitra, Dhanishta +} + +RAJJU_EFFECTS = { + "Sira": "head — risk to husband's longevity", + "Kanta": "neck — risk to wife's longevity", + "Udara": "stomach — risk to children", + "Kati": "waist — poverty may ensue", + "Pada": "foot — couple may be always wandering", +} + +def _get_rajju_group(nak_idx): + for group, indices in RAJJU_GROUPS.items(): + if nak_idx in indices: + return group + return None + +def calc_rajju(m_nak_idx, f_nak_idx): + """Same Rajju group = bad; different = good.""" + m_group = _get_rajju_group(m_nak_idx) + f_group = _get_rajju_group(f_nak_idx) + if m_group and f_group and m_group == f_group: + return {"result": "bad", "group": m_group, "effect": RAJJU_EFFECTS.get(m_group, "")} + return {"result": "good", "group": None, "effect": ""} + + +# 14. Bad Constellations — specific nakshatra quarters considered destructive +def calc_bad_constellations(m_nak_idx, m_pada, f_nak_idx, f_pada): + """ + Only first pada of Moola/Ashlesha/Jyeshtha and 4th pada of Vishakha are bad. + Ashlesha/Jyeshtha/Vishakha destructive only for females. + """ + issues = [] + if m_nak_idx == 18 and m_pada == 1: + issues.append("Male born in Moola 1st pada — risk to father-in-law.") + if f_nak_idx == 18 and f_pada == 1: + issues.append("Female born in Moola 1st pada — risk to father-in-law.") + if f_nak_idx == 8 and f_pada == 1: + issues.append("Female born in Ashlesha 1st pada — risk to husband's mother.") + if f_nak_idx == 17 and f_pada == 1: + issues.append("Female born in Jyeshtha 1st pada — risk to husband's elder brother.") + if f_nak_idx == 15 and f_pada == 4: + issues.append("Female born in Vishakha 4th pada — risk to husband's younger brother.") + return {"result": "bad" if issues else "good", "issues": issues} + + +# 15. Lagna and House 7 — cross-Lagna compatibility +def calc_lagna_house7(chart1, chart2): + """ + Good if female's Moon sign = male's Lagna OR male's Moon sign = female's Lagna, + OR if 7th house lords are exchanged. + """ + m_lagna = chart1.get("lagna", {}).get("sign") + f_lagna = chart2.get("lagna", {}).get("sign") + m_moon = chart1.get("planets", {}).get("Moon", {}).get("sign") + f_moon = chart2.get("planets", {}).get("Moon", {}).get("sign") + + if (f_moon and m_lagna and f_moon == m_lagna) or (m_moon and f_lagna and m_moon == f_lagna): + return {"result": "good", "description": "Moon-Lagna cross match — mutual understanding and affection."} + + m_lagna_idx = ZODIAC_SIGNS.index(m_lagna) if m_lagna else None + f_lagna_idx = ZODIAC_SIGNS.index(f_lagna) if f_lagna else None + if m_lagna_idx is not None and f_lagna_idx is not None: + m_7th_sign = ZODIAC_SIGNS[(m_lagna_idx + 6) % 12] + f_7th_sign = ZODIAC_SIGNS[(f_lagna_idx + 6) % 12] + m_7th_lord = SIGN_LORDS[m_7th_sign] + f_7th_lord = SIGN_LORDS[f_7th_sign] + m_7lord_sign = chart1.get("planets", {}).get(m_7th_lord, {}).get("sign") + f_7lord_sign = chart2.get("planets", {}).get(f_7th_lord, {}).get("sign") + if m_7lord_sign == f_lagna or f_7lord_sign == m_lagna: + return {"result": "good", "description": "7th house lord cross-placement — marriage stability."} + + return {"result": "neutral", "description": "No special Lagna-7th house connection found."} + + +# 16. Sex Energy — based on planets in 7th house +def calc_sex_energy(chart1, chart2): + """ + Mars/Venus in 7th = strong sex drive. Mercury/Jupiter in 7th = moderate. + Mismatch between partners = potential incompatibility. + """ + def _classify(chart): + planets = chart.get("planets", {}) + strong = any(planets.get(p, {}).get("house") == 7 for p in ("Mars", "Venus")) + moderate = any(planets.get(p, {}).get("house") == 7 for p in ("Mercury", "Jupiter")) + if strong and not moderate: + return "strong" + if moderate and not strong: + return "moderate" + if strong and moderate: + return "mixed" + return "unknown" + + m_type = _classify(chart1) + f_type = _classify(chart2) + if m_type in ("unknown", "mixed") or f_type in ("unknown", "mixed"): + return {"result": "neutral", "male": m_type, "female": f_type, + "description": "Insufficient data for sex energy assessment."} + if m_type == f_type: + return {"result": "good", "male": m_type, "female": f_type, + "description": f"Both partners have {m_type} sex energy — compatible."} + return {"result": "bad", "male": m_type, "female": f_type, + "description": f"Male has {m_type} and female has {f_type} sex energy — potential mismatch."} + + +def calculate_ashtakoot(male_moon_lon: float, female_moon_lon: float, + male_chart=None, female_chart=None): + """ + Calculates the 36-point Ashtakoot compatibility matching + plus additional kutas (Mahendra, Stree Deergha, Vedha, Rajju, etc.). + """ + m_nak = get_nakshatra(male_moon_lon) + f_nak = get_nakshatra(female_moon_lon) + m_nak_idx = m_nak["index"] + f_nak_idx = f_nak["index"] + + m_sign_idx = int((male_moon_lon % 360) / 30) + f_sign_idx = int((female_moon_lon % 360) / 30) + m_sign = ZODIAC_SIGNS[m_sign_idx] + f_sign = ZODIAC_SIGNS[f_sign_idx] + + scores = { + "Varna": calc_varna(m_sign, f_sign), + "Vashya": calc_vashya(m_sign, f_sign), + "Tara": calc_tara(m_nak_idx, f_nak_idx), + "Yoni": calc_yoni(m_nak_idx, f_nak_idx), + "GrahaMaitri": calc_graha_maitri(m_sign, f_sign), + "Gana": calc_gana(m_nak_idx, f_nak_idx), + "Bhakoot": calc_bhakoot(m_sign, f_sign), + "Nadi": calc_nadi(m_nak_idx, f_nak_idx), + } + + total_score = sum(scores.values()) + + # Additional kutas + rajju_result = calc_rajju(m_nak_idx, f_nak_idx) + additional_kutas = { + "Mahendra": calc_mahendra(m_nak_idx, f_nak_idx), + "StreeDeergha": calc_stree_deergha(m_nak_idx, f_nak_idx), + "Vedha": calc_vedha(m_nak_idx, f_nak_idx), + "Rajju": rajju_result, + "BadConstellations": calc_bad_constellations( + m_nak_idx, m_nak.get("pada", 0), f_nak_idx, f_nak.get("pada", 0)), + } + + # Chart-dependent kutas (need full chart data) + if male_chart and female_chart: + additional_kutas["LagnaHouse7"] = calc_lagna_house7(male_chart, female_chart) + additional_kutas["SexEnergy"] = calc_sex_energy(male_chart, female_chart) + + # Exception logic (per VedAstro/BPHS): + exceptions = [] + + # 1. Bad Nadi neutralized if Bhakoot + Rajju both good + if scores["Nadi"] == 0: + if scores["Bhakoot"] > 0 and rajju_result["result"] == "good": + exceptions.append("Nadi Dosha mitigated by good Bhakoot and Rajju.") + + # 2. Bad Rajju neutralized if GrahaMaitri + Bhakoot + Tara + Mahendra all good + if rajju_result["result"] == "bad": + if (scores["GrahaMaitri"] >= 4.0 and scores["Bhakoot"] > 0 and + scores["Tara"] >= 1.5 and additional_kutas["Mahendra"] == "good"): + exceptions.append("Rajju Dosha mitigated by good Graha Maitri, Bhakoot, Tara, and Mahendra.") + + # 3. Bad Stree Deergha neutralized if Bhakoot + GrahaMaitri both good + if additional_kutas["StreeDeergha"] == "bad": + if scores["Bhakoot"] > 0 and scores["GrahaMaitri"] >= 4.0: + exceptions.append("Stree Deergha Dosha mitigated by good Bhakoot and Graha Maitri.") + + return { + "male_details": { + "moon_sign": m_sign, + "nakshatra": m_nak["name"], + "gana": GANA[m_nak_idx], + "nadi": NADI[m_nak_idx], + "yoni": YONI_ANIMALS[m_nak_idx] + }, + "female_details": { + "moon_sign": f_sign, + "nakshatra": f_nak["name"], + "gana": GANA[f_nak_idx], + "nadi": NADI[f_nak_idx], + "yoni": YONI_ANIMALS[f_nak_idx] + }, + "scores": scores, + "total_score": total_score, + "max_score": 36.0, + "additional_kutas": additional_kutas, + "exceptions": exceptions, + "is_match_approved": total_score >= 18.0 and (scores["Nadi"] > 0 or len(exceptions) > 0) + } diff --git a/references/open_source_sources/dashaflow/muhurtha.py b/references/open_source_sources/dashaflow/muhurtha.py new file mode 100644 index 00000000..1e1d7c28 --- /dev/null +++ b/references/open_source_sources/dashaflow/muhurtha.py @@ -0,0 +1,236 @@ +""" +Muhurtha — Electional Astrology (BPHS) +Evaluates auspiciousness of a given date/time for specific activities. +Uses Panchang elements, planetary positions, and classical rules. +""" + +from .constants import ZODIAC_SIGNS, SIGN_LORDS + +# ========================================== +# UNIVERSAL AVOIDANCE RULES +# ========================================== + +# Inauspicious Panchang Yoga indices (0-indexed) +BAD_YOGAS = {0, 5, 8, 9, 12, 14, 16, 18, 26} + +# Universally avoided lunar days (tithis) +BAD_TITHIS = {4, 6, 8, 12, 14, 30} + +# Universally avoided nakshatras +BAD_NAKSHATRAS = {"Bharani", "Krittika"} + + +# ========================================== +# ACTIVITY-SPECIFIC RULES +# ========================================== + +MARRIAGE_RULES = { + "good_nakshatras": {"Rohini", "Mrigashira", "Magha", "Uttara Phalguni", "Hasta", + "Swati", "Anuradha", "Moola", "Uttara Ashadha", "Uttara Bhadrapada", "Revati"}, + "good_tithis": {2, 3, 5, 7, 10, 11, 13}, + "good_lagnas": {"Taurus", "Gemini", "Cancer", "Virgo", "Libra", "Sagittarius"}, +} + +TRAVEL_RULES = { + "good_nakshatras": {"Ashwini", "Mrigashira", "Punarvasu", "Pushya", "Hasta", + "Anuradha", "Shravana", "Dhanishta", "Revati"}, + "good_tithis": {2, 3, 5, 7, 10, 11, 13}, + "good_lagnas": {"Aries", "Taurus", "Cancer", "Leo", "Libra", "Sagittarius"}, +} + +BUSINESS_RULES = { + "good_nakshatras": {"Ashwini", "Rohini", "Punarvasu", "Pushya", "Uttara Phalguni", + "Hasta", "Chitra", "Swati", "Anuradha", "Shravana", "Dhanishta", "Revati"}, + "good_tithis": {2, 3, 5, 7, 10, 11, 13}, + "good_weekdays": {"Monday", "Wednesday", "Thursday", "Friday"}, + "moon_signs": {"Taurus", "Cancer", "Virgo", "Libra", "Sagittarius", "Pisces"}, +} + +EDUCATION_RULES = { + "good_nakshatras": {"Ashwini", "Punarvasu", "Pushya", "Hasta", "Chitra", + "Swati", "Shravana", "Dhanishta", "Shatabhisha", "Revati"}, + "good_tithis": {2, 3, 5, 7, 10, 11, 13}, + "good_lagnas": {"Gemini", "Virgo", "Sagittarius", "Pisces"}, +} + +HOUSE_ENTRY_RULES = { + "good_nakshatras": {"Rohini", "Uttara Phalguni", "Uttara Ashadha", "Uttara Bhadrapada", + "Shravana", "Dhanishta", "Revati", "Ashwini", "Mrigashira"}, + "good_tithis": {2, 3, 5, 7, 10, 11, 13}, + "good_weekdays": {"Monday", "Wednesday", "Thursday", "Friday"}, +} + +MEDICAL_RULES = { + "good_nakshatras": {"Ashwini", "Rohini", "Mrigashira", "Pushya", "Hasta", + "Chitra", "Swati", "Anuradha", "Shravana", "Revati"}, + "good_tithis": {2, 3, 5, 7, 10, 11, 13}, + "good_weekdays": {"Saturday", "Monday"}, +} + +ACTIVITY_RULES = { + "marriage": MARRIAGE_RULES, + "travel": TRAVEL_RULES, + "business": BUSINESS_RULES, + "education": EDUCATION_RULES, + "house_entry": HOUSE_ENTRY_RULES, + "medical": MEDICAL_RULES, +} + + +def _check_panchanga_suddhi(panchang): + """Check universal Panchang purity (5-fold). Returns list of issues.""" + issues = [] + + tithi_num = panchang.get("tithi", {}).get("number", 0) + if tithi_num in BAD_TITHIS: + issues.append(f"Inauspicious tithi: {panchang['tithi'].get('name', tithi_num)}") + + nak_name = panchang.get("nakshatra", {}).get("name", "") + if nak_name in BAD_NAKSHATRAS: + issues.append(f"Inauspicious nakshatra: {nak_name}") + + yoga_idx = panchang.get("yoga", {}).get("index", -1) + if yoga_idx in BAD_YOGAS: + issues.append(f"Inauspicious yoga: {panchang['yoga'].get('name', yoga_idx)}") + + return issues + + +def _check_marriage_doshas(planets): + """Check marriage-specific rejection doshas (per BPHS).""" + doshas = [] + + # Sagraha Dosha: Moon conjunct any planet + moon = planets.get("Moon", {}) + moon_sign = moon.get("sign", "") + for p_name, pd in planets.items(): + if p_name != "Moon" and pd.get("sign") == moon_sign: + doshas.append(f"Sagraha Dosha: Moon conjunct {p_name} in {moon_sign}") + break + + # Moon in 6th, 8th, or 12th + moon_house = moon.get("house", 0) + if moon_house in (6, 8, 12): + doshas.append(f"Shashtashta Dosha: Moon in house {moon_house}") + + # Venus in 6th + venus = planets.get("Venus", {}) + if venus.get("house") == 6: + doshas.append("Bhrigupta Shatka: Venus in 6th house") + + # Mars in 8th + mars = planets.get("Mars", {}) + if mars.get("house") == 8: + doshas.append("Kujaasthama: Mars in 8th house") + + return doshas + + +def evaluate_muhurtha(activity, panchang, planets=None, lagna_sign=None): + """ + Evaluate auspiciousness of a moment for a given activity. + + Parameters + ---------- + activity : str + One of: 'marriage', 'travel', 'business', 'education', 'house_entry', 'medical' + panchang : dict + Panchang data from calculate_panchang() + planets : dict, optional + Planet positions (for marriage dosha checks) + lagna_sign : str, optional + Rising sign at the moment + + Returns + ------- + dict with verdict, reasons, and score + """ + rules = ACTIVITY_RULES.get(activity) + if not rules: + return {"verdict": "error", "reason": f"Unknown activity: {activity}", + "supported_activities": list(ACTIVITY_RULES.keys())} + + positive = [] + negative = [] + + # 1. Universal Panchanga Suddhi + panchang_issues = _check_panchanga_suddhi(panchang) + negative.extend(panchang_issues) + + # 2. Activity-specific nakshatra + nak_name = panchang.get("nakshatra", {}).get("name", "") + good_naks = rules.get("good_nakshatras", set()) + if nak_name in good_naks: + positive.append(f"Auspicious nakshatra for {activity}: {nak_name}") + elif nak_name and nak_name not in BAD_NAKSHATRAS: + negative.append(f"Nakshatra {nak_name} is not ideal for {activity}") + + # 3. Activity-specific tithi + tithi_num = panchang.get("tithi", {}).get("number", 0) + good_tithis = rules.get("good_tithis", set()) + if tithi_num in good_tithis: + positive.append(f"Auspicious tithi: {panchang['tithi'].get('name', tithi_num)}") + + # 4. Weekday check + good_weekdays = rules.get("good_weekdays") + if good_weekdays: + vara = panchang.get("vara", {}).get("name", "") + if vara in good_weekdays: + positive.append(f"Auspicious weekday: {vara}") + else: + negative.append(f"Weekday {vara} is not ideal for {activity}") + + # 5. Lagna check + good_lagnas = rules.get("good_lagnas") + if good_lagnas and lagna_sign: + if lagna_sign in good_lagnas: + positive.append(f"Auspicious Lagna: {lagna_sign}") + else: + negative.append(f"Lagna {lagna_sign} is not ideal for {activity}") + + # 6. Moon sign check (for business) + moon_signs = rules.get("moon_signs") + if moon_signs and planets: + moon_sign = planets.get("Moon", {}).get("sign", "") + if moon_sign in moon_signs: + positive.append(f"Moon in auspicious sign for {activity}: {moon_sign}") + else: + negative.append(f"Moon in {moon_sign} is not ideal for {activity}") + + # 7. Marriage-specific dosha checks + if activity == "marriage" and planets: + doshas = _check_marriage_doshas(planets) + for d in doshas: + negative.append(f"DOSHA: {d}") + + # 8th house check (should be empty for marriage, medical, house_entry) + if activity in ("marriage", "medical", "house_entry") and planets: + for p_name, pd in planets.items(): + if p_name not in ("Rahu", "Ketu") and pd.get("house") == 8: + negative.append(f"Planet in 8th house: {p_name}") + break + + # Scoring + score = len(positive) * 10 - len(negative) * 15 + has_hard_reject = any("DOSHA:" in n for n in negative) + + if has_hard_reject: + verdict = "inauspicious" + elif len(negative) == 0 and len(positive) >= 2: + verdict = "auspicious" + elif len(positive) > len(negative): + verdict = "mixed_favorable" + elif len(negative) > len(positive): + verdict = "inauspicious" + else: + verdict = "mixed" + + return { + "activity": activity, + "verdict": verdict, + "score": max(0, score), + "positive_factors": positive, + "negative_factors": negative, + "total_positive": len(positive), + "total_negative": len(negative), + } diff --git a/references/open_source_sources/dashaflow/nakshatra.py b/references/open_source_sources/dashaflow/nakshatra.py new file mode 100644 index 00000000..73f08ec0 --- /dev/null +++ b/references/open_source_sources/dashaflow/nakshatra.py @@ -0,0 +1,24 @@ +from .constants import NAKSHATRAS, NAK_SPAN, PADA_SPAN + + +def get_nakshatra(longitude): + """ + Returns Nakshatra details for a given sidereal longitude (0-360). + """ + longitude = longitude % 360.0 + nak_idx = int(longitude / NAK_SPAN) + if nak_idx >= 27: + nak_idx = 26 + degree_in_nak = longitude - (nak_idx * NAK_SPAN) + pada = int(degree_in_nak / PADA_SPAN) + 1 + if pada > 4: + pada = 4 + + nak = NAKSHATRAS[nak_idx] + return { + "name": nak["name"], + "pada": pada, + "lord": nak["lord"], + "index": nak_idx, + "degree_in_nakshatra": round(degree_in_nak, 4), + } diff --git a/references/open_source_sources/dashaflow/panchang.py b/references/open_source_sources/dashaflow/panchang.py new file mode 100644 index 00000000..7e0e0fa0 --- /dev/null +++ b/references/open_source_sources/dashaflow/panchang.py @@ -0,0 +1,97 @@ +import math +from .constants import TITHI_NAMES, VARA_NAMES, VARA_LORDS, PANCHANG_YOGA_NAMES, KARANA_NAMES +from .nakshatra import get_nakshatra + + +def calculate_panchang(jd, sun_lon, moon_lon, lat=None, lon=None): + """ + Calculate the five Panchang elements for a given moment. + + Parameters + ---------- + jd : float + Julian Day number + sun_lon : float + Sidereal longitude of the Sun (0-360) + moon_lon : float + Sidereal longitude of the Moon (0-360) + + Returns + ------- + dict with tithi, vara, nakshatra, yoga, karana + """ + # --- Tithi --- + diff = (moon_lon - sun_lon) % 360.0 + tithi_num = int(diff / 12.0) # 0-29 + paksha = "Shukla" if tithi_num < 15 else "Krishna" + tithi_name = TITHI_NAMES[tithi_num] + + # --- Vara (weekday) --- + # Julian Day 0 = Monday in many conventions; swe.julday for J2000 epoch + # JD 2451545.0 (2000-01-01 12:00 UT) was a Saturday + # weekday = (JD + 1.5) % 7 => 0=Mon, 1=Tue, ... 6=Sun (Julian convention) + day_idx = int(math.floor(jd + 0.5)) % 7 + + # Correct for local sunrise if lat/lon provided + if lat is not None and lon is not None: + try: + import swisseph as swe + swe.set_topo(lon, lat, 0) + # Find the sunrise for the current civil day UT + jd_midnight = math.floor(jd - 0.5) + 0.5 + res, tret = swe.rise_trans(jd_midnight, swe.SUN, "", swe.CALC_RISE, (lon, lat, 0.0)) + sunrise_jd = tret[0] + # If born before today's sunrise, the Vedic day is yesterday + if jd < sunrise_jd: + day_idx = (day_idx - 1) % 7 + except Exception: + pass + + # JD 0 = Monday (Julian proleptic). Map: 0=Mon,1=Tue,...6=Sun + vara = VARA_NAMES[day_idx] + vara_lord = VARA_LORDS[day_idx] + + # --- Nakshatra (Moon's) --- + nak = get_nakshatra(moon_lon) + + # --- Yoga (Panchang Yoga) --- + yoga_val = (sun_lon + moon_lon) % 360.0 + yoga_idx = int(yoga_val / (360.0 / 27.0)) + if yoga_idx >= 27: + yoga_idx = 26 + yoga_name = PANCHANG_YOGA_NAMES[yoga_idx] + + # --- Karana --- + # Each tithi has 2 karanas (half-tithi = 6 degrees of Sun-Moon distance) + karana_num = int(diff / 6.0) # 0-59 + if karana_num == 0: + karana_name = KARANA_NAMES[10] # Kimstughna (first half of Shukla Pratipada) + elif karana_num >= 57: + # Last 3 are fixed karanas: Shakuni(57), Chatushpada(58), Nagava(59) + fixed_idx = karana_num - 57 + 7 # maps to indices 7,8,9 + karana_name = KARANA_NAMES[fixed_idx] + else: + # Repeating cycle of first 7 karanas (Bava through Vishti) + karana_name = KARANA_NAMES[(karana_num - 1) % 7] + + return { + "tithi": { + "number": tithi_num + 1, + "name": tithi_name, + "paksha": paksha, + }, + "vara": { + "name": vara, + "lord": vara_lord, + }, + "nakshatra": { + "name": nak["name"], + "pada": nak["pada"], + "lord": nak["lord"], + }, + "yoga": { + "index": yoga_idx, + "name": yoga_name, + }, + "karana": karana_name, + } diff --git a/references/open_source_sources/dashaflow/shadbala.py b/references/open_source_sources/dashaflow/shadbala.py new file mode 100644 index 00000000..160b95e2 --- /dev/null +++ b/references/open_source_sources/dashaflow/shadbala.py @@ -0,0 +1,435 @@ +""" +Shadbala — Six-fold Planetary Strength System (BPHS) +Calculates a numerical strength score for each planet based on: +1. Sthana Bala (Positional Strength) — Uchcha, Saptavargaja, Ojayugmarasyamsha, Kendra, Drekkana +2. Dig Bala (Directional Strength) — Based on house position +3. Kala Bala (Temporal Strength) — Day/night birth, hora lord, etc. (simplified) +4. Chesta Bala (Motional Strength) — Based on speed/retrograde +5. Naisargika Bala (Natural Strength) — Fixed hierarchy Sun > Moon > Venus > Jupiter > Mercury > Mars > Saturn +6. Drik Bala (Aspectual Strength) — Based on aspects received (simplified) + +All values are in Shashtiamshas (60ths of a Rupa). 1 Rupa = 60 Shashtiamshas. +""" + +from .constants import ( + ZODIAC_SIGNS, EXALTATION, DEBILITATION, OWN_SIGNS, + MOOLTRIKONA, NATURAL_FRIENDS, NATURAL_ENEMIES, NATURAL_NEUTRALS, + DIGBALA_HOUSES, SIGN_LORDS +) +import math + + +# ============================================================ +# 1. STHANA BALA (Positional Strength) +# ============================================================ + +def _uchcha_bala(planet_name, planet_lon): + """ + Exaltation strength. Max 60 Shashtiamshas at exact exaltation degree, + 0 at exact debilitation degree. Linear interpolation. + """ + if planet_name not in EXALTATION: + return 0.0 + + exalt_sign, exalt_deg = EXALTATION[planet_name] + exalt_sign_idx = ZODIAC_SIGNS.index(exalt_sign) + exalt_lon = exalt_sign_idx * 30 + exalt_deg + + # Angular distance from exaltation point + diff = abs(planet_lon - exalt_lon) + if diff > 180: + diff = 360 - diff + + # Max strength at 0 diff (exalted), min at 180 (debilitated) + bala = (180 - diff) / 180.0 * 60.0 + return round(bala, 2) + + +def _varga_dignity(planet_name, varga_sign): + """ + Determine a planet's dignity in a given varga chart sign. + Returns a score: exalted=30, mooltrikona=22.5, own=20, friend=15, neutral=10, enemy=5, debilitated=2. + """ + if not varga_sign or planet_name in ("Rahu", "Ketu"): + return 10.0 + # Exaltation check + if planet_name in EXALTATION and EXALTATION[planet_name][0] == varga_sign: + return 30.0 + # Debilitation check + if planet_name in DEBILITATION and DEBILITATION[planet_name][0] == varga_sign: + return 2.0 + # Mooltrikona check + if planet_name in MOOLTRIKONA and MOOLTRIKONA[planet_name][0] == varga_sign: + return 22.5 + # Own sign check + if planet_name in OWN_SIGNS and varga_sign in OWN_SIGNS[planet_name]: + return 20.0 + # Relationship with sign lord + sign_lord = SIGN_LORDS.get(varga_sign) + if sign_lord and sign_lord != planet_name: + if planet_name in NATURAL_FRIENDS and sign_lord in NATURAL_FRIENDS[planet_name]: + return 15.0 + if planet_name in NATURAL_ENEMIES and sign_lord in NATURAL_ENEMIES[planet_name]: + return 5.0 + return 10.0 + + +def _saptavargaja_bala(planet_name, dignity, planet_data=None): + """ + Strength based on dignity in the Saptavarga (7 divisional charts): + D1 (Rasi), D2 (Hora), D3 (Drekkana), D7 (Saptamsha), + D9 (Navamsha), D12 (Dwadashamsha), D30 (Trimshamsha). + Each varga contributes up to 30 points; total is averaged. + """ + dignity_scores = { + "exalted": 30.0, + "mooltrikona": 22.5, + "own_sign": 20.0, + "friend": 15.0, + "neutral": 10.0, + "enemy": 5.0, + "debilitated": 2.0, + } + d1_score = dignity_scores.get(dignity, 10.0) + + if not planet_data: + return d1_score + + varga_keys = ["d2_sign", "d3_sign", "d7_sign", "d9_sign", "d12_sign", "d30_sign"] + scores = [d1_score] + for key in varga_keys: + varga_sign = planet_data.get(key) + scores.append(_varga_dignity(planet_name, varga_sign)) + + return round(sum(scores) / len(scores), 2) + + +def _ojayugmarasyamsha_bala(planet_name, sign_idx): + """ + Strength from odd/even sign placement. + Sun, Mars, Jupiter, Saturn gain strength in odd signs. + Moon, Venus, Mercury (and Rahu/Ketu) gain in even signs. + """ + is_odd_sign = (sign_idx % 2 == 0) # Aries=0 is odd (index 0) + odd_planets = ["Sun", "Mars", "Jupiter", "Saturn"] + if planet_name in odd_planets: + return 15.0 if is_odd_sign else 0.0 + else: + return 15.0 if not is_odd_sign else 0.0 + + +def _kendra_bala(house): + """ + Planets in Kendras (1,4,7,10) get 60, Panapara (2,5,8,11) get 30, + Apoklima (3,6,9,12) get 15. + """ + if house in [1, 4, 7, 10]: + return 60.0 + elif house in [2, 5, 8, 11]: + return 30.0 + else: + return 15.0 + + +def _drekkana_bala(planet_name, degree_in_sign): + """ + Male planets (Sun, Mars, Jupiter) strong in 1st drekkana (0-10°), + Neutral planets (Mercury, Saturn) in 2nd drekkana (10-20°), + Female planets (Moon, Venus) in 3rd drekkana (20-30°). + """ + if degree_in_sign < 10: + drekkana = 1 + elif degree_in_sign < 20: + drekkana = 2 + else: + drekkana = 3 + + male = ["Sun", "Mars", "Jupiter"] + female = ["Moon", "Venus"] + + if planet_name in male and drekkana == 1: + return 15.0 + elif planet_name in female and drekkana == 3: + return 15.0 + elif planet_name not in male and planet_name not in female and drekkana == 2: + return 15.0 + return 0.0 + + +def _sthana_bala(planet_name, planet_lon, dignity, sign_idx, house, degree, planet_data=None): + uchcha = _uchcha_bala(planet_name, planet_lon) + saptavarga = _saptavargaja_bala(planet_name, dignity, planet_data) + ojayugma = _ojayugmarasyamsha_bala(planet_name, sign_idx) + kendra = _kendra_bala(house) + drekkana = _drekkana_bala(planet_name, degree) + total = uchcha + saptavarga + ojayugma + kendra + drekkana + return { + "uchcha": uchcha, + "saptavargaja": saptavarga, + "ojayugmarasyamsha": ojayugma, + "kendra": kendra, + "drekkana": drekkana, + "total": round(total, 2) + } + + +# ============================================================ +# 2. DIG BALA (Directional Strength) +# ============================================================ + +def _dig_bala(planet_name, house): + """ + Max 60 when planet is in its Digbala house, 0 when opposite. + Linear interpolation based on house distance. + """ + if planet_name not in DIGBALA_HOUSES: + return 0.0 + + ideal_house = DIGBALA_HOUSES[planet_name] + # House distance (1-indexed, circular) + dist = abs(house - ideal_house) + if dist > 6: + dist = 12 - dist + # Max at 0 distance, 0 at 6 houses away + bala = (6 - dist) / 6.0 * 60.0 + return round(bala, 2) + + +# ============================================================ +# 3. KALA BALA (Temporal Strength) — Simplified +# ============================================================ + +def _kala_bala(planet_name, is_day_birth=True, moon_lon=None, sun_lon=None): + """ + Kala Bala with three sub-components: + 1. Natonnata Bala (day/night strength) + 2. Paksha Bala (lunar phase strength) + 3. Ayana Bala (Sun's declination / seasonal strength) + """ + # 1. Natonnata Bala (day/night) + day_planets = ["Sun", "Jupiter", "Venus"] + night_planets = ["Moon", "Mars", "Saturn"] + if planet_name == "Mercury": + natonnata = 30.0 + elif is_day_birth and planet_name in day_planets: + natonnata = 60.0 + elif not is_day_birth and planet_name in night_planets: + natonnata = 60.0 + else: + natonnata = 0.0 + + # 2. Paksha Bala (Moon phase — benefics strong in Shukla Paksha, malefics in Krishna) + paksha = 0.0 + if moon_lon is not None and sun_lon is not None: + tithi_angle = (moon_lon - sun_lon) % 360 + is_shukla = tithi_angle < 180 + # Strength proportional to how close to Full/New Moon + if is_shukla: + phase_ratio = tithi_angle / 180.0 + else: + phase_ratio = (360 - tithi_angle) / 180.0 + benefics = ["Jupiter", "Venus", "Mercury", "Moon"] + if planet_name in benefics: + paksha = phase_ratio * 60.0 # benefics strong near Full Moon + else: + paksha = (1 - phase_ratio) * 60.0 # malefics strong near New Moon + + # 3. Ayana Bala (seasonal — based on Sun's longitude) + ayana = 0.0 + if sun_lon is not None: + # Sun 0-180° = Uttarayana (northern), 180-360° = Dakshinayana (southern) + # Benefics strong in Uttarayana, malefics in Dakshinayana + sun_norm = sun_lon % 360 + benefics = ["Jupiter", "Venus", "Mercury", "Moon"] + if sun_norm < 180: + ratio = (180 - abs(sun_norm - 90)) / 180.0 + else: + ratio = (180 - abs(sun_norm - 270)) / 180.0 + if planet_name in benefics: + ayana = ratio * 30.0 if sun_norm < 180 else (1 - ratio) * 30.0 + else: + ayana = (1 - ratio) * 30.0 if sun_norm < 180 else ratio * 30.0 + + return round(natonnata + paksha + ayana, 2) + + +# ============================================================ +# 4. CHESTA BALA (Motional Strength) +# ============================================================ + +# Average daily speeds in degrees (approximate) +_AVG_SPEEDS = { + "Mars": 0.524, + "Mercury": 1.383, + "Jupiter": 0.083, + "Venus": 1.200, + "Saturn": 0.034, +} + + +def _chesta_bala(planet_name, speed, is_retrograde): + """ + Motional strength based on actual speed. + Retrograde=60, Stationary(very slow)=45, Direct uses speed ratio. + Sun=Chesta from longitude, Moon=Paksha-based (set in Kala Bala). + """ + if planet_name in ("Sun", "Moon"): + return 30.0 + if planet_name in ("Rahu", "Ketu"): + return 0.0 + if is_retrograde: + return 60.0 + avg = _AVG_SPEEDS.get(planet_name, 1.0) + abs_speed = abs(speed) + if abs_speed < avg * 0.1: + return 45.0 # near-stationary + # Scale linearly: 0 speed → 45, avg speed → 30, 2x avg → 15 + ratio = min(abs_speed / avg, 2.0) + return round(60.0 - ratio * 15.0, 2) + + +# ============================================================ +# 5. NAISARGIKA BALA (Natural Strength) — Fixed +# ============================================================ + +NAISARGIKA_BALA = { + "Sun": 60.0, + "Moon": 51.43, + "Venus": 42.86, + "Jupiter": 34.29, + "Mercury": 25.71, + "Mars": 17.14, + "Saturn": 8.57, +} + + +# ============================================================ +# 6. DRIK BALA (Aspectual Strength) — Simplified +# ============================================================ + +# BPHS partial aspect strengths (house distance → fraction) +_ASPECT_STRENGTH = { + 3: 0.25, 4: 0.75, 5: 0.50, 7: 1.00, 8: 0.75, 9: 0.50, 10: 0.25, +} +# Special full aspects override +_SPECIAL_ASPECTS = { + "Mars": {4: 1.00, 8: 1.00}, + "Jupiter": {5: 1.00, 9: 1.00}, + "Saturn": {3: 1.00, 10: 1.00}, +} + + +def _drik_bala(planet_name, house, planets_data): + """ + Aspectual strength using BPHS weighted partial aspects. + Benefics aspecting add strength; malefics reduce it. + Aspect weight varies by house distance and special aspects. + """ + benefics = ["Jupiter", "Venus", "Mercury", "Moon"] + malefics = ["Saturn", "Mars", "Sun", "Rahu", "Ketu"] + + target_sign_idx = ZODIAC_SIGNS.index(planets_data[planet_name]["sign"]) + score = 0.0 + + for name, data in planets_data.items(): + if name == planet_name or name in ("Rahu", "Ketu"): + continue + aspector_sign_idx = ZODIAC_SIGNS.index(data["sign"]) + house_dist = ((target_sign_idx - aspector_sign_idx) % 12) + 1 + if house_dist == 1: + continue # conjunction, not aspect + + # Check if this planet aspects at this distance + special = _SPECIAL_ASPECTS.get(name, {}) + strength = special.get(house_dist, _ASPECT_STRENGTH.get(house_dist, 0.0)) + if strength <= 0: + continue + + if name in benefics: + score += strength * 15.0 + elif name in malefics: + score -= strength * 15.0 + + # Normalize to [0, 60] range with 30 as midpoint + return round(max(0.0, min(60.0, score + 30.0)), 2) + + +# ============================================================ +# MAIN CALCULATOR +# ============================================================ + +# Minimum required Shadbala (in Rupas) per BPHS +REQUIRED_SHADBALA = { + "Sun": 6.5, + "Moon": 6.0, + "Mars": 5.0, + "Mercury": 7.0, + "Jupiter": 6.5, + "Venus": 5.5, + "Saturn": 5.0, +} + + +def calculate_shadbala(planets_data: dict, raw_planets: dict, is_day_birth: bool = True): + """ + Calculates the six-fold strength for all 7 planets. + + Parameters + ---------- + planets_data : dict — The enriched 'planets' output from calculate_vedic_chart + raw_planets : dict — The raw planet data with 'lon', 'sign_idx', 'speed' + is_day_birth : bool — Whether birth occurred during daytime + + Returns + ------- + dict: Shadbala breakdown for each planet with total in Shashtiamshas and Rupas. + """ + result = {} + + # Extract Sun and Moon longitudes for Kala Bala sub-components + sun_lon = raw_planets.get("Sun", {}).get("lon") + moon_lon = raw_planets.get("Moon", {}).get("lon") + + for planet_name in ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]: + if planet_name not in planets_data or planet_name not in raw_planets: + continue + + pd = planets_data[planet_name] + rp = raw_planets[planet_name] + + sthana = _sthana_bala(planet_name, rp["lon"], pd["dignity"], rp["sign_idx"], pd["house"], pd["degree"], pd) + dig = _dig_bala(planet_name, pd["house"]) + kala = _kala_bala(planet_name, is_day_birth, moon_lon, sun_lon) + chesta = _chesta_bala(planet_name, rp["speed"], pd["is_retrograde"]) + naisargika = NAISARGIKA_BALA.get(planet_name, 0.0) + drik = _drik_bala(planet_name, pd["house"], planets_data) + + total_shashtiamshas = sthana["total"] + dig + kala + chesta + naisargika + drik + total_rupas = round(total_shashtiamshas / 60.0, 2) + required = REQUIRED_SHADBALA.get(planet_name, 5.0) + is_strong = total_rupas >= required + + # Ishta Phala and Kashta Phala (BPHS) + # Ishta = sqrt(Uchcha Bala * Chesta Bala) + # Kashta = sqrt((60 - Uchcha Bala) * (60 - Chesta Bala)) + uchcha = sthana["uchcha"] + ishta_phala = round(math.sqrt(max(0, uchcha * chesta)), 2) + kashta_phala = round(math.sqrt(max(0, (60.0 - uchcha) * (60.0 - chesta))), 2) + + result[planet_name] = { + "sthana_bala": sthana, + "dig_bala": dig, + "kala_bala": kala, + "chesta_bala": chesta, + "naisargika_bala": naisargika, + "drik_bala": drik, + "total_shashtiamshas": round(total_shashtiamshas, 2), + "total_rupas": total_rupas, + "required_rupas": required, + "is_strong": is_strong, + "strength_ratio": round(total_rupas / required, 2), + "ishta_phala": ishta_phala, + "kashta_phala": kashta_phala, + } + + return result diff --git a/references/open_source_sources/dashaflow/vedic_calculator.py b/references/open_source_sources/dashaflow/vedic_calculator.py new file mode 100644 index 00000000..1efa87f3 --- /dev/null +++ b/references/open_source_sources/dashaflow/vedic_calculator.py @@ -0,0 +1,694 @@ +import swisseph as swe +import datetime +import pytz +import json + +from .constants import PLANETS, ZODIAC_SIGNS, SIGN_LORDS, OWN_SIGNS +from .nakshatra import get_nakshatra +from .dasha import calculate_dashas +from .dignity import get_dignity, check_combustion, get_digbala +from .yoga import detect_yogas, detect_kaal_sarpa, detect_graha_yuddha, detect_gandanta +from .panchang import calculate_panchang +from .ashtakavarga import calculate_ashtakavarga +from .jaimini import calculate_jaimini_karakas, calculate_arudha_padas, calculate_upapada, calculate_karakamsha +from .shadbala import calculate_shadbala + +swe.set_ephe_path('') + + +def get_sign_and_degree(longitude): + """Converts 360-degree longitude to Zodiac Sign and degree within that sign.""" + longitude = longitude % 360.0 + sign_idx = int(longitude / 30) + if sign_idx >= 12: + sign_idx = 11 + degree = longitude % 30 + return ZODIAC_SIGNS[sign_idx], round(degree, 2), sign_idx + + +def calculate_navamsha(longitude): + """Calculates D9 (Navamsha) sign based on absolute longitude.""" + navamsha_absolute = (longitude * 9) % 360 + sign_idx = int(navamsha_absolute / 30) + return ZODIAC_SIGNS[sign_idx] + + +def calculate_d2_hora(longitude): + """Calculates D2 (Hora) sign — Wealth. + Odd signs: 0-15° → Leo, 15-30° → Cancer. + Even signs: 0-15° → Cancer, 15-30° → Leo. + Validated against VedAstro Vargas.cs HoraTable.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + is_odd = (sign_idx + 1) % 2 != 0 + if is_odd: + return "Leo" if degree < 15 else "Cancer" + else: + return "Cancer" if degree < 15 else "Leo" + + +def calculate_dashamsha(longitude): + """Calculates D10 (Dashamsha) sign per BPHS Parashari method.""" + sign_idx = int(longitude / 30) + degree_in_sign = longitude % 30 + part = int(degree_in_sign / 3.0) + + if (sign_idx + 1) % 2 != 0: # odd sign (1-indexed) + d10_idx = (sign_idx + part) % 12 + else: + d10_idx = (sign_idx + 8 + part) % 12 + + return ZODIAC_SIGNS[d10_idx] + +def calculate_d3_drekkana(longitude): + """Calculates D3 (Drekkana) sign.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 10.0) + if part == 0: + d3_idx = sign_idx + elif part == 1: + d3_idx = (sign_idx + 4) % 12 + else: + d3_idx = (sign_idx + 8) % 12 + return ZODIAC_SIGNS[d3_idx] + +def calculate_d4_chaturthamsha(longitude): + """Calculates D4 (Chaturthamsha) sign.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 7.5) + d4_idx = (sign_idx + (part * 3)) % 12 + return ZODIAC_SIGNS[d4_idx] + +def calculate_d7_saptamsha(longitude): + """Calculates D7 (Saptamsha) sign.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / (30.0 / 7.0)) + if (sign_idx + 1) % 2 != 0: # Odd sign + d7_idx = (sign_idx + part) % 12 + else: # Even sign + d7_idx = (sign_idx + 6 + part) % 12 + return ZODIAC_SIGNS[d7_idx] + +def calculate_d12_dwadashamsha(longitude): + """Calculates D12 (Dwadashamsha) sign.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 2.5) + d12_idx = (sign_idx + part) % 12 + return ZODIAC_SIGNS[d12_idx] + + +def calculate_d16_shodashamsha(longitude): + """Calculates D16 (Shodashamsha) sign — Vehicles, Comforts, Happiness. + 16 equal parts of 1.875° each. + Start sign = (sign_idx * 4) % 12 counted from Aries. + Validated against VedAstro Vargas.cs ShodashamshaTable.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 1.875) + if part >= 16: + part = 15 + start_idx = (sign_idx * 4) % 12 + d16_idx = (start_idx + part) % 12 + return ZODIAC_SIGNS[d16_idx] + + +def calculate_d20_vimshamsha(longitude): + """Calculates D20 (Vimshamsha) sign — Spiritual Progress, Worship. + 20 equal parts of 1.5° each. + Start sign = (sign_idx * 8) % 12 counted from Aries. + Validated against VedAstro Vargas.cs VimshamshaTable.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 1.5) + if part >= 20: + part = 19 + start_idx = (sign_idx * 8) % 12 + d20_idx = (start_idx + part) % 12 + return ZODIAC_SIGNS[d20_idx] + + +def calculate_d27_bhamsha(longitude): + """Calculates D27 (Bhamsha / Saptavimshamsha) sign — Strength, Courage. + 27 equal parts of ~1.1111° each. + Fire signs (Aries,Leo,Sag) start from Aries. + Earth signs (Taurus,Virgo,Cap) start from Cancer. + Air signs (Gemini,Libra,Aqua) start from Libra. + Water signs (Cancer,Scorpio,Pisces) start from Capricorn. + Validated against VedAstro Vargas.cs BhamshaTable.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / (30.0 / 27.0)) + if part >= 27: + part = 26 + # Determine element of the sign (0=Fire, 1=Earth, 2=Air, 3=Water) + element = sign_idx % 4 + if element == 0: # Fire signs: Aries(0), Leo(4), Sagittarius(8) + start_idx = 0 # Aries + elif element == 1: # Earth signs: Taurus(1), Virgo(5), Capricorn(9) + start_idx = 3 # Cancer + elif element == 2: # Air signs: Gemini(2), Libra(6), Aquarius(10) + start_idx = 6 # Libra + else: # Water signs: Cancer(3), Scorpio(7), Pisces(11) + start_idx = 9 # Capricorn + d27_idx = (start_idx + part) % 12 + return ZODIAC_SIGNS[d27_idx] + +def calculate_d24_chaturvimshamsha(longitude): + """Calculates D24 (Chaturvimshamsha / Siddhamsha) sign — Education & Learning. + Odd signs: count from Leo. Even signs: count from Cancer.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / (30.0 / 24.0)) + if (sign_idx + 1) % 2 != 0: # Odd sign + d24_idx = (4 + part) % 12 # Leo = index 4 + else: + d24_idx = (3 + part) % 12 # Cancer = index 3 + return ZODIAC_SIGNS[d24_idx] + +def calculate_d30_trimshamsha(longitude): + """Calculates D30 (Trimshamsha) sign — Misfortunes & Diseases. + Uses the BPHS unequal division: 5°, 5°, 8°, 7°, 5° for odd signs + and reversed for even signs.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + is_odd = (sign_idx + 1) % 2 != 0 + + if is_odd: + # Odd: Mars(5), Saturn(5), Jupiter(8), Mercury(7), Venus(5) + if degree < 5: lord = "Mars" + elif degree < 10: lord = "Saturn" + elif degree < 18: lord = "Jupiter" + elif degree < 25: lord = "Mercury" + else: lord = "Venus" + else: + # Even: Venus(5), Mercury(7), Jupiter(8), Saturn(5), Mars(5) + if degree < 5: lord = "Venus" + elif degree < 12: lord = "Mercury" + elif degree < 20: lord = "Jupiter" + elif degree < 25: lord = "Saturn" + else: lord = "Mars" + + # D30 sign = the sign owned by the lord + return OWN_SIGNS[lord][0] # Return the first own sign + +def calculate_d60_shashtiamsha(longitude): + """Calculates D60 (Shashtiamsha) sign. + BPHS: Odd signs count from self, Even signs count from opposite (7th).""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 0.5) + if (sign_idx + 1) % 2 != 0: # Odd sign + d60_idx = (sign_idx + part) % 12 + else: # Even sign + d60_idx = (sign_idx + 6 + part) % 12 + return ZODIAC_SIGNS[d60_idx] + + +def calculate_d40_khavedamsha(longitude): + """Calculates D40 (Khavedamsha / Akshavedamsha) sign — Auspicious/Inauspicious effects. + 40 equal parts of 0.75° each. + Odd signs start from Aries, Even signs start from Libra. + Per K.S. Charak / VedAstro Vargas.cs AkshavedamshaTable.""" + sign_idx = int(longitude / 30) + degree = longitude % 30 + part = int(degree / 0.75) + if part >= 40: + part = 39 + if (sign_idx + 1) % 2 != 0: # Odd sign + start_idx = 0 # Aries + else: # Even sign + start_idx = 6 # Libra + d40_idx = (start_idx + part) % 12 + return ZODIAC_SIGNS[d40_idx] + +def get_vedic_aspects(planet_name, sign_idx): + """ + Calculates the signs aspected by a planet based on BPHS rules. + Standard Parashari: only Mars, Jupiter, Saturn have special aspects. + Rahu/Ketu get only the universal 7th aspect. + """ + aspected_indices = [(sign_idx + 6) % 12] + + if planet_name == "Mars": + aspected_indices.extend([(sign_idx + 3) % 12, (sign_idx + 7) % 12]) + elif planet_name == "Jupiter": + aspected_indices.extend([(sign_idx + 4) % 12, (sign_idx + 8) % 12]) + elif planet_name == "Saturn": + aspected_indices.extend([(sign_idx + 2) % 12, (sign_idx + 9) % 12]) + + return [ZODIAC_SIGNS[idx] for idx in sorted(set(aspected_indices))] + + +def _house_from_lagna(planet_sign_idx, lagna_sign_idx): + """Whole-sign house number (1-12) from the Ascendant sign.""" + return ((planet_sign_idx - lagna_sign_idx) % 12) + 1 + + +def _to_jd(dob_str, time_str, timezone_str): + """Convert local date/time to Julian Day and return (jd, birth_dt_local).""" + local_tz = pytz.timezone(timezone_str) + naive_dt = datetime.datetime.strptime(f"{dob_str} {time_str}", "%Y-%m-%d %H:%M") + local_dt = local_tz.localize(naive_dt) + utc_dt = local_dt.astimezone(pytz.utc) + + year, month, day = utc_dt.year, utc_dt.month, utc_dt.day + hour = utc_dt.hour + utc_dt.minute / 60.0 + utc_dt.second / 3600.0 + jd = swe.julday(year, month, day, hour) + return jd, local_dt + + +def calculate_bhava_chalit(asc_lon, raw_planets): + """ + Calculate Bhava Chalit (Equal House from Lagna midpoint). + + In Bhava Chalit, house cusps are at 15° before and after the Lagna degree. + Bhava 1 midpoint = Lagna. Cusp 1 starts at (Lagna - 15°). + Each house spans exactly 30°. + + A planet's Bhava house may differ from its Rashi (whole-sign) house + when it's near a sign boundary. + + Returns + ------- + dict: {planet_name: {"bhava_house": int, "rashi_house": int, "shifted": bool}} + """ + # Bhava 1 midpoint is at the Lagna degree + # Cusp of house 1 starts at asc_lon - 15° + cusp_start = (asc_lon - 15.0) % 360.0 + + asc_sign_idx = int(asc_lon / 30) % 12 + + result = {} + for name, rp in raw_planets.items(): + planet_lon = rp["lon"] + + # Rashi house (whole-sign) + rashi_house = ((rp["sign_idx"] - asc_sign_idx) % 12) + 1 + + # Bhava house (equal house from lagna midpoint) + diff = (planet_lon - cusp_start) % 360.0 + bhava_house = int(diff / 30.0) + 1 + if bhava_house > 12: + bhava_house = 12 + + result[name] = { + "bhava_house": bhava_house, + "rashi_house": rashi_house, + "shifted": bhava_house != rashi_house, + } + + return result + + +def calculate_avasthas(planets_data, raw_planets): + """ + Calculate Planetary Avasthas (age states) per BPHS. + + Five states based on degree in sign: + - Bala (Infant): 0-6° — weak, dependent, immature results + - Kumara (Adolescent): 6-12° — growing, partially effective + - Yuva (Youth): 12-18° — full strength, best results + - Vriddha (Old): 18-24° — declining, delayed results + - Mrita (Dead): 24-30° — very weak, negligible results + + Returns + ------- + dict: {planet_name: {"avastha": str, "degree": float, "strength_factor": float, "description": str}} + """ + AVASTHA_TABLE = [ + ("Bala", 0, 6, 0.25, "Infant state — immature, dependent, weak delivery of results."), + ("Kumara", 6, 12, 0.50, "Adolescent state — growing potential, partially effective."), + ("Yuva", 12, 18, 1.00, "Youth state — full vigor, maximum capacity to deliver results."), + ("Vriddha", 18, 24, 0.50, "Old state — declining energy, delayed or reduced results."), + ("Mrita", 24, 30, 0.125, "Dead state — exhausted, negligible capacity to deliver results."), + ] + + # For odd signs: Bala→Kumara→Yuva→Vriddha→Mrita (normal order) + # For even signs: Mrita→Vriddha→Yuva→Kumara→Bala (reverse order) + + result = {} + for name, rp in raw_planets.items(): + if name in ("Rahu", "Ketu"): + continue + + degree = rp["degree"] + sign_idx = rp["sign_idx"] + is_odd_sign = (sign_idx % 2) == 0 # Aries=0 (odd), Taurus=1 (even), etc. + + if is_odd_sign: + table = AVASTHA_TABLE + else: + table = list(reversed(AVASTHA_TABLE)) + + avastha_name = "Yuva" + strength_factor = 1.0 + description = "" + + for avastha, start, end, factor, desc in table: + if start <= degree < end or (end == 30 and degree >= 24): + avastha_name = avastha + strength_factor = factor + description = desc + break + + result[name] = { + "avastha": avastha_name, + "degree": round(degree, 2), + "strength_factor": strength_factor, + "description": description, + } + + return result + + +def calculate_vedic_chart(dob_str: str, time_str: str, lat: float, lon: float, timezone_str: str, + query_date_str: str = None, ephe_path: str = ''): + """ + Calculates a comprehensive Vedic Astrological Chart (Sidereal Lahiri). + + Parameters + ---------- + dob_str : str "YYYY-MM-DD" + time_str : str "HH:MM" (24-hour format) + lat : float Latitude (e.g., 28.6139 for Delhi) + lon : float Longitude (e.g., 77.2090 for Delhi) + timezone_str : str (e.g., "Asia/Kolkata") + query_date_str : str, optional "YYYY-MM-DD" for Dasha lookup. Defaults to today. + ephe_path : str, optional Path to Swiss Ephemeris data files. Defaults to '' (bundled). + + Returns + ------- + dict: Full chart data including planets, nakshatra, dasha, yogas, panchang. + """ + swe.set_ephe_path(ephe_path) + swe.set_sid_mode(swe.SIDM_LAHIRI) + jd, local_dt = _to_jd(dob_str, time_str, timezone_str) + flags = swe.FLG_SIDEREAL | swe.FLG_SPEED + + ayanamsha_val = swe.get_ayanamsa_ut(jd) + + # --- Ascendant (Lagna) --- + cusps, ascmc = swe.houses_ex(jd, lat, lon, b'W', flags) + asc_lon = ascmc[0] + asc_sign, asc_deg, asc_sign_idx = get_sign_and_degree(asc_lon) + asc_nak = get_nakshatra(asc_lon) + + # --- Planetary positions --- + raw_planets = {} + sun_lon = None + + for name, planet_id in PLANETS.items(): + res, _ = swe.calc_ut(jd, planet_id, flags) + planet_lon = res[0] + speed = res[3] + + sign, deg, sign_idx = get_sign_and_degree(planet_lon) + + if name in ("Rahu", "Ketu"): + is_retrograde = True + elif name in ("Sun", "Moon"): + is_retrograde = False + else: + is_retrograde = speed < 0 + + if name == "Sun": + sun_lon = planet_lon + + raw_planets[name] = { + "lon": planet_lon, + "sign": sign, + "degree": deg, + "sign_idx": sign_idx, + "speed": speed, + "is_retrograde": is_retrograde, + } + + # Ketu = Rahu + 180 + rahu_data = raw_planets["Rahu"] + ketu_lon = (rahu_data["lon"] + 180) % 360 + k_sign, k_deg, k_sign_idx = get_sign_and_degree(ketu_lon) + raw_planets["Ketu"] = { + "lon": ketu_lon, + "sign": k_sign, + "degree": k_deg, + "sign_idx": k_sign_idx, + "speed": -abs(rahu_data["speed"]), + "is_retrograde": True, + } + + # --- Build enriched planet data --- + planets_output = {} + planets_for_yoga = {} + planets_in_signs = {name: rp["sign_idx"] for name, rp in raw_planets.items()} + + for name, rp in raw_planets.items(): + house = _house_from_lagna(rp["sign_idx"], asc_sign_idx) + nak = get_nakshatra(rp["lon"]) + dignity = get_dignity(name, rp["sign"], rp["degree"], planets_in_signs) + is_combust = check_combustion(name, rp["lon"], sun_lon, rp["is_retrograde"]) if sun_lon is not None else False + has_digbala = get_digbala(name, house) + + planet_entry = { + "sign": rp["sign"], + "degree": rp["degree"], + "house": house, + "nakshatra": nak["name"], + "pada": nak["pada"], + "nakshatra_lord": nak["lord"], + "is_retrograde": rp["is_retrograde"], + "is_combust": is_combust, + "dignity": dignity, + "has_digbala": has_digbala, + "d2_sign": calculate_d2_hora(rp["lon"]), + "d3_sign": calculate_d3_drekkana(rp["lon"]), + "d4_sign": calculate_d4_chaturthamsha(rp["lon"]), + "d7_sign": calculate_d7_saptamsha(rp["lon"]), + "d9_sign": calculate_navamsha(rp["lon"]), + "d10_sign": calculate_dashamsha(rp["lon"]), + "d12_sign": calculate_d12_dwadashamsha(rp["lon"]), + "d16_sign": calculate_d16_shodashamsha(rp["lon"]), + "d20_sign": calculate_d20_vimshamsha(rp["lon"]), + "d24_sign": calculate_d24_chaturvimshamsha(rp["lon"]), + "d27_sign": calculate_d27_bhamsha(rp["lon"]), + "d30_sign": calculate_d30_trimshamsha(rp["lon"]), + "d40_sign": calculate_d40_khavedamsha(rp["lon"]), + "d60_sign": calculate_d60_shashtiamsha(rp["lon"]), + "aspects": get_vedic_aspects(name, rp["sign_idx"]), + } + planets_output[name] = planet_entry + planets_for_yoga[name] = { + "sign": rp["sign"], + "sign_idx": rp["sign_idx"], + "house": house, + "dignity": dignity, + "is_combust": is_combust, + } + + # --- Dasha --- + moon_lon = raw_planets["Moon"]["lon"] + birth_dt_naive = local_dt.replace(tzinfo=None) + + query_dt = None + if query_date_str: + query_dt = datetime.datetime.strptime(query_date_str, "%Y-%m-%d") + else: + query_dt = datetime.datetime.now() + + dasha_data = calculate_dashas(moon_lon, birth_dt_naive, query_dt) + + # --- Yogas --- + yogas = detect_yogas(planets_for_yoga, asc_sign) + + # --- Panchang --- + panchang_data = calculate_panchang(jd, sun_lon, moon_lon, lat, lon) + + # --- Ashtakavarga --- + sav_planets = {name: rp["sign_idx"] for name, rp in raw_planets.items() if name in ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]} + ashtakavarga_data = calculate_ashtakavarga(sav_planets, asc_sign_idx) + + # --- Assemble output --- + jaimini_karakas = calculate_jaimini_karakas(planets_output) + + chart_data = { + "metadata": { + "dob": dob_str, + "time": time_str, + "coordinates": {"lat": lat, "lon": lon}, + "timezone": timezone_str, + "ayanamsha": "Lahiri", + "ayanamsha_degrees": round(ayanamsha_val, 4), + "query_date": query_dt.strftime("%Y-%m-%d"), + }, + "panchang": panchang_data, + "lagna": { + "sign": asc_sign, + "degree": asc_deg, + "nakshatra": asc_nak["name"], + "pada": asc_nak["pada"], + "d2_sign": calculate_d2_hora(asc_lon), + "d3_sign": calculate_d3_drekkana(asc_lon), + "d4_sign": calculate_d4_chaturthamsha(asc_lon), + "d7_sign": calculate_d7_saptamsha(asc_lon), + "d9_sign": calculate_navamsha(asc_lon), + "d10_sign": calculate_dashamsha(asc_lon), + "d12_sign": calculate_d12_dwadashamsha(asc_lon), + "d16_sign": calculate_d16_shodashamsha(asc_lon), + "d20_sign": calculate_d20_vimshamsha(asc_lon), + "d24_sign": calculate_d24_chaturvimshamsha(asc_lon), + "d27_sign": calculate_d27_bhamsha(asc_lon), + "d30_sign": calculate_d30_trimshamsha(asc_lon), + "d40_sign": calculate_d40_khavedamsha(asc_lon), + "d60_sign": calculate_d60_shashtiamsha(asc_lon), + }, + "planets": planets_output, + "dashas": dasha_data, + "yogas": yogas, + "ashtakavarga": ashtakavarga_data, + "jaimini_karakas": jaimini_karakas, + "shadbala": calculate_shadbala(planets_output, raw_planets, is_day_birth=((asc_lon - sun_lon + 360) % 360) < 180), + "bhava_chalit": calculate_bhava_chalit(asc_lon, raw_planets), + "avasthas": calculate_avasthas(planets_output, raw_planets), + "kaal_sarpa": detect_kaal_sarpa(raw_planets), + "graha_yuddha": detect_graha_yuddha(raw_planets), + "gandanta": detect_gandanta(raw_planets, asc_lon), + "arudha_padas": calculate_arudha_padas(asc_sign, planets_output), + "upapada": calculate_upapada(asc_sign, planets_output), + "karakamsha": calculate_karakamsha(jaimini_karakas, planets_output, asc_sign), + } + + return chart_data + + +def calculate_transit(transit_date_str: str, natal_chart: dict, timezone_str: str = "Asia/Kolkata"): + """ + Calculate current planetary transit positions and overlay on natal chart. + + Parameters + ---------- + transit_date_str : str "YYYY-MM-DD" + natal_chart : dict Output from calculate_vedic_chart() + timezone_str : str + + Returns + ------- + dict with transit planets, house placements from natal Lagna/Moon, and Sade Sati status. + """ + swe.set_sid_mode(swe.SIDM_LAHIRI) + flags = swe.FLG_SIDEREAL | swe.FLG_SPEED + + # Transit at noon on the given date + dt = datetime.datetime.strptime(transit_date_str, "%Y-%m-%d") + local_tz = pytz.timezone(timezone_str) + local_noon = local_tz.localize(dt.replace(hour=12)) + utc_noon = local_noon.astimezone(pytz.utc) + jd = swe.julday(utc_noon.year, utc_noon.month, utc_noon.day, + utc_noon.hour + utc_noon.minute / 60.0) + + natal_lagna_sign = natal_chart["lagna"]["sign"] + natal_lagna_idx = ZODIAC_SIGNS.index(natal_lagna_sign) + + natal_moon_sign = natal_chart["planets"]["Moon"]["sign"] + natal_moon_idx = ZODIAC_SIGNS.index(natal_moon_sign) + + transit_planets = {} + transit_sun_lon = None + + for name, planet_id in PLANETS.items(): + res, _ = swe.calc_ut(jd, planet_id, flags) + planet_lon = res[0] + speed = res[3] + sign, deg, sign_idx = get_sign_and_degree(planet_lon) + + if name in ("Rahu", "Ketu"): + is_retro = True + elif name in ("Sun", "Moon"): + is_retro = False + else: + is_retro = speed < 0 + + if name == "Sun": + transit_sun_lon = planet_lon + + house_from_lagna = _house_from_lagna(sign_idx, natal_lagna_idx) + house_from_moon = _house_from_lagna(sign_idx, natal_moon_idx) + + transit_planets[name] = { + "sign": sign, + "degree": deg, + "is_retrograde": is_retro, + "nakshatra": get_nakshatra(planet_lon)["name"], + "house_from_lagna": house_from_lagna, + "house_from_moon": house_from_moon, + "sav_points": natal_chart.get("ashtakavarga", {}).get("sarvashtakavarga", {}).get(sign, 0) + } + + # Ketu + rahu_lon = transit_planets["Rahu"] + rahu_raw_lon = swe.calc_ut(jd, swe.MEAN_NODE, flags)[0][0] + ketu_lon = (rahu_raw_lon + 180) % 360 + k_sign, k_deg, k_sign_idx = get_sign_and_degree(ketu_lon) + transit_planets["Ketu"] = { + "sign": k_sign, + "degree": k_deg, + "is_retrograde": True, + "nakshatra": get_nakshatra(ketu_lon)["name"], + "house_from_lagna": _house_from_lagna(k_sign_idx, natal_lagna_idx), + "house_from_moon": _house_from_lagna(k_sign_idx, natal_moon_idx), + "sav_points": natal_chart.get("ashtakavarga", {}).get("sarvashtakavarga", {}).get(k_sign, 0) + } + + # --- Sade Sati detection --- + saturn_sign_idx = ZODIAC_SIGNS.index(transit_planets["Saturn"]["sign"]) + sade_sati_active = False + sade_sati_phase = None + dist = (saturn_sign_idx - natal_moon_idx) % 12 + if dist == 11: + sade_sati_active = True + sade_sati_phase = "rising (12th from Moon)" + elif dist == 0: + sade_sati_active = True + sade_sati_phase = "peak (over Moon)" + elif dist == 1: + sade_sati_active = True + sade_sati_phase = "setting (2nd from Moon)" + + # --- Rahu-Ketu transit axis --- + rahu_house_lagna = transit_planets["Rahu"]["house_from_lagna"] + ketu_house_lagna = transit_planets["Ketu"]["house_from_lagna"] + + return { + "transit_date": transit_date_str, + "planets": transit_planets, + "sade_sati": { + "active": sade_sati_active, + "phase": sade_sati_phase, + "saturn_transit_sign": transit_planets["Saturn"]["sign"], + "natal_moon_sign": natal_moon_sign, + }, + "rahu_ketu_axis": { + "rahu_house_from_lagna": rahu_house_lagna, + "ketu_house_from_lagna": ketu_house_lagna, + "rahu_sign": transit_planets["Rahu"]["sign"], + "ketu_sign": transit_planets["Ketu"]["sign"], + }, + } + + +if __name__ == "__main__": + data = calculate_vedic_chart( + dob_str="1990-04-15", + time_str="14:30", + lat=28.6139, + lon=77.2090, + timezone_str="Asia/Kolkata" + ) + print("=== NATAL CHART ===") + print(json.dumps(data, indent=2)) + + print("\n=== TRANSIT ===") + transit = calculate_transit("2026-02-28", data) + print(json.dumps(transit, indent=2)) diff --git a/references/open_source_sources/dashaflow/yoga.py b/references/open_source_sources/dashaflow/yoga.py new file mode 100644 index 00000000..c77d61f3 --- /dev/null +++ b/references/open_source_sources/dashaflow/yoga.py @@ -0,0 +1,559 @@ +from .constants import ZODIAC_SIGNS, SIGN_LORDS, EXALTATION, OWN_SIGNS + + +KENDRA_HOUSES = {1, 4, 7, 10} +TRIKONA_HOUSES = {1, 5, 9} +DUSTHANA_HOUSES = {6, 8, 12} +BENEFICS = {"Jupiter", "Venus", "Mercury"} +MAHAPURUSHA_PLANETS = {"Mars", "Mercury", "Jupiter", "Venus", "Saturn"} +MAHAPURUSHA_NAMES = { + "Mars": "Ruchaka Yoga", + "Mercury": "Bhadra Yoga", + "Jupiter": "Hamsa Yoga", + "Venus": "Malavya Yoga", + "Saturn": "Shasha Yoga", +} + +# Gandanta junctions: last nakshatra of water sign → first nakshatra of fire sign +# Water signs: Cancer(3), Scorpio(7), Pisces(11) Fire signs: Leo(4), Sagittarius(8), Aries(0) +# Gandanta zones: last 3°20' of water sign + first 3°20' of fire sign +GANDANTA_JUNCTIONS = [ + (3, 4), # Cancer → Leo + (7, 8), # Scorpio → Sagittarius + (11, 0), # Pisces → Aries +] +GANDANTA_ORB = 3.3333 # 3°20' = one pada + + +def _house_from(base_sign_idx, planet_sign_idx): + return ((planet_sign_idx - base_sign_idx) % 12) + 1 + + +def _sign_idx(sign_name): + return ZODIAC_SIGNS.index(sign_name) + + +def _lord_of_house(lagna_sign_idx, house_num): + sign_idx = (lagna_sign_idx + house_num - 1) % 12 + return SIGN_LORDS[ZODIAC_SIGNS[sign_idx]] + + +def _is_exalted_or_own(planet_name, sign): + if planet_name in EXALTATION and EXALTATION[planet_name][0] == sign: + return True + if planet_name in OWN_SIGNS and sign in OWN_SIGNS[planet_name]: + return True + return False + + +def detect_yogas(planets, lagna_sign): + """ + Detect key Vedic yogas from chart data. + + Parameters + ---------- + planets : dict + {planet_name: {"sign": str, "sign_idx": int, "house": int, "dignity": str, ...}} + lagna_sign : str + + Returns + ------- + list of dict: [{"name": str, "formed_by": list, "description": str}, ...] + """ + yogas = [] + lagna_idx = _sign_idx(lagna_sign) + + moon_data = planets.get("Moon", {}) + moon_idx = moon_data.get("sign_idx", 0) + + # --- Pancha Mahapurusha Yogas --- + for p in MAHAPURUSHA_PLANETS: + pd = planets.get(p) + if not pd: + continue + if pd.get("house") in KENDRA_HOUSES and _is_exalted_or_own(p, pd["sign"]): + yogas.append({ + "name": MAHAPURUSHA_NAMES[p], + "formed_by": [p], + "description": f"{p} in own/exalted sign in house {pd['house']} from Lagna.", + }) + + # --- Gajakesari Yoga: Jupiter in kendra from Moon --- + jup = planets.get("Jupiter") + if jup and moon_data: + house_from_moon = _house_from(moon_idx, jup["sign_idx"]) + if house_from_moon in KENDRA_HOUSES: + yogas.append({ + "name": "Gajakesari Yoga", + "formed_by": ["Jupiter", "Moon"], + "description": f"Jupiter in house {house_from_moon} from Moon (kendra).", + }) + + # --- Budhaditya Yoga: Sun + Mercury in same sign --- + sun_d = planets.get("Sun") + mer_d = planets.get("Mercury") + if sun_d and mer_d and sun_d["sign"] == mer_d["sign"]: + if mer_d.get("dignity") != "debilitated" and not mer_d.get("is_combust"): + yogas.append({ + "name": "Budhaditya Yoga", + "formed_by": ["Sun", "Mercury"], + "description": f"Sun and Mercury conjoined in {sun_d['sign']}.", + }) + + # --- Chandra-Mangal Yoga: Moon + Mars in same sign --- + mars_d = planets.get("Mars") + if moon_data and mars_d and moon_data["sign"] == mars_d["sign"]: + yogas.append({ + "name": "Chandra-Mangal Yoga", + "formed_by": ["Moon", "Mars"], + "description": f"Moon and Mars conjoined in {moon_data['sign']}.", + }) + + # --- Kemadruma Yoga: No planet in 2nd or 12th from Moon --- + if moon_data: + sign_2nd = (moon_idx + 1) % 12 + sign_12th = (moon_idx - 1) % 12 + has_support = False + for p_name, pd in planets.items(): + if p_name in ("Sun", "Moon", "Rahu", "Ketu"): + continue + if pd["sign_idx"] in (sign_2nd, sign_12th): + has_support = True + break + if not has_support: + yogas.append({ + "name": "Kemadruma Yoga", + "formed_by": ["Moon"], + "description": "No planet (except Sun/nodes) in 2nd or 12th from Moon.", + }) + + # --- Adhi Yoga: Benefics in 6th, 7th, 8th from Moon --- + if moon_data: + target_houses = {6, 7, 8} + adhi_planets = [] + for p_name in ("Mercury", "Jupiter", "Venus"): + pd = planets.get(p_name) + if pd: + h = _house_from(moon_idx, pd["sign_idx"]) + if h in target_houses: + adhi_planets.append(p_name) + if len(adhi_planets) >= 2: + yogas.append({ + "name": "Adhi Yoga", + "formed_by": adhi_planets, + "description": f"Benefics ({', '.join(adhi_planets)}) in 6/7/8 from Moon.", + }) + + # --- Raj Yoga: Lord of kendra + Lord of trikona conjoined --- + kendra_lords = set() + trikona_lords = set() + for h in KENDRA_HOUSES: + kendra_lords.add(_lord_of_house(lagna_idx, h)) + for h in TRIKONA_HOUSES: + trikona_lords.add(_lord_of_house(lagna_idx, h)) + + dual_lords = kendra_lords & trikona_lords + for lord_name in dual_lords: + pd = planets.get(lord_name) + if pd and pd.get("house") in KENDRA_HOUSES | TRIKONA_HOUSES: + yogas.append({ + "name": "Raj Yoga", + "formed_by": [lord_name], + "description": f"{lord_name} is lord of both kendra and trikona, placed in house {pd['house']}.", + }) + + pure_kendra = kendra_lords - dual_lords + pure_trikona = trikona_lords - dual_lords + for kl in pure_kendra: + for tl in pure_trikona: + kl_data = planets.get(kl) + tl_data = planets.get(tl) + if kl_data and tl_data and kl_data["sign"] == tl_data["sign"]: + yogas.append({ + "name": "Raj Yoga", + "formed_by": [kl, tl], + "description": f"Kendra lord {kl} conjoined with trikona lord {tl} in {kl_data['sign']}.", + }) + + # --- Viparita Raj Yoga: Lord of 6/8/12 in another dusthana --- + dusthana_lords = {} + for h in DUSTHANA_HOUSES: + lord = _lord_of_house(lagna_idx, h) + dusthana_lords[h] = lord + + for h, lord in dusthana_lords.items(): + pd = planets.get(lord) + if pd and pd.get("house") in DUSTHANA_HOUSES and pd["house"] != h: + yogas.append({ + "name": "Viparita Raj Yoga", + "formed_by": [lord], + "description": f"Lord of house {h} ({lord}) placed in house {pd['house']} (dusthana in dusthana).", + }) + + # --- Neecha Bhanga Raja Yoga --- + for p_name, pd in planets.items(): + if pd.get("dignity") != "debilitated": + continue + sign = pd["sign"] + cancellation = False + cancel_reason = "" + + dispositor = SIGN_LORDS.get(sign) + if dispositor: + disp_data = planets.get(dispositor) + if disp_data: + if disp_data.get("house") in KENDRA_HOUSES: + cancellation = True + cancel_reason = f"Dispositor {dispositor} in kendra from Lagna." + elif moon_data and _house_from(moon_idx, disp_data["sign_idx"]) in KENDRA_HOUSES: + cancellation = True + cancel_reason = f"Dispositor {dispositor} in kendra from Moon." + + if not cancellation and p_name in EXALTATION: + exalt_sign = EXALTATION[p_name][0] + exalt_lord = SIGN_LORDS.get(exalt_sign) + if exalt_lord: + el_data = planets.get(exalt_lord) + if el_data: + if el_data.get("house") in KENDRA_HOUSES: + cancellation = True + cancel_reason = f"Lord of exaltation sign ({exalt_lord}) in kendra from Lagna." + elif moon_data and _house_from(moon_idx, el_data["sign_idx"]) in KENDRA_HOUSES: + cancellation = True + cancel_reason = f"Lord of exaltation sign ({exalt_lord}) in kendra from Moon." + + if cancellation: + yogas.append({ + "name": "Neecha Bhanga Raja Yoga", + "formed_by": [p_name], + "description": f"Debilitated {p_name} in {sign} with cancellation: {cancel_reason}", + }) + + # --- Parivartana Yoga: Mutual exchange of signs --- + checked_pairs = set() + for p1, d1 in planets.items(): + if p1 in ("Rahu", "Ketu"): + continue + lord_of_p1_sign = SIGN_LORDS.get(d1["sign"]) + if lord_of_p1_sign and lord_of_p1_sign != p1: + d2 = planets.get(lord_of_p1_sign) + if d2: + lord_of_p2_sign = SIGN_LORDS.get(d2["sign"]) + if lord_of_p2_sign == p1: + pair = tuple(sorted([p1, lord_of_p1_sign])) + if pair not in checked_pairs: + checked_pairs.add(pair) + h1, h2 = d1["house"], d2["house"] + # Classify exchange type + if {h1, h2} <= DUSTHANA_HOUSES: + ptype = "Dainya" + elif {h1, h2} & DUSTHANA_HOUSES: + ptype = "Khala" + else: + ptype = "Maha" + yogas.append({ + "name": f"Parivartana Yoga ({ptype})", + "formed_by": list(pair), + "description": f"{pair[0]} in {d1['sign'] if pair[0] == p1 else d2['sign']} and " + f"{pair[1]} in {d2['sign'] if pair[1] == lord_of_p1_sign else d1['sign']} — mutual sign exchange.", + }) + + # --- Dhana Yogas: Wealth combinations --- + lord_2 = _lord_of_house(lagna_idx, 2) + lord_5 = _lord_of_house(lagna_idx, 5) + lord_9 = _lord_of_house(lagna_idx, 9) + lord_11 = _lord_of_house(lagna_idx, 11) + + good_houses = KENDRA_HOUSES | TRIKONA_HOUSES + # Lord of 2nd and 11th both in kendra/trikona + l2d = planets.get(lord_2) + l11d = planets.get(lord_11) + if l2d and l11d and l2d.get("house") in good_houses and l11d.get("house") in good_houses: + yogas.append({ + "name": "Dhana Yoga", + "formed_by": [lord_2, lord_11], + "description": f"Lord of 2nd ({lord_2}) in house {l2d['house']} and lord of 11th ({lord_11}) in house {l11d['house']}.", + }) + # Lord of 5th and 9th conjoined or in mutual kendra + l5d = planets.get(lord_5) + l9d = planets.get(lord_9) + if l5d and l9d: + if l5d["sign"] == l9d["sign"]: + yogas.append({ + "name": "Dhana Yoga", + "formed_by": [lord_5, lord_9], + "description": f"Lord of 5th ({lord_5}) and lord of 9th ({lord_9}) conjoined in {l5d['sign']}.", + }) + elif _house_from(l5d["sign_idx"], l9d["sign_idx"]) in KENDRA_HOUSES: + yogas.append({ + "name": "Dhana Yoga", + "formed_by": [lord_5, lord_9], + "description": f"Lord of 5th ({lord_5}) and lord of 9th ({lord_9}) in mutual kendra.", + }) + + # --- Sunapha / Anapha / Durudhura Yogas (Moon-based) --- + if moon_data: + sign_2nd_m = (moon_idx + 1) % 12 + sign_12th_m = (moon_idx - 1) % 12 + sunapha_planets = [] + anapha_planets = [] + for p_name, pd in planets.items(): + if p_name in ("Sun", "Moon", "Rahu", "Ketu"): + continue + if pd["sign_idx"] == sign_2nd_m: + sunapha_planets.append(p_name) + if pd["sign_idx"] == sign_12th_m: + anapha_planets.append(p_name) + + if sunapha_planets and anapha_planets: + yogas.append({ + "name": "Durudhura Yoga", + "formed_by": sunapha_planets + anapha_planets, + "description": f"Planets in 2nd ({', '.join(sunapha_planets)}) and 12th ({', '.join(anapha_planets)}) from Moon — wealth and fame.", + }) + elif sunapha_planets: + yogas.append({ + "name": "Sunapha Yoga", + "formed_by": sunapha_planets, + "description": f"{', '.join(sunapha_planets)} in 2nd from Moon — self-made wealth.", + }) + elif anapha_planets: + yogas.append({ + "name": "Anapha Yoga", + "formed_by": anapha_planets, + "description": f"{', '.join(anapha_planets)} in 12th from Moon — good character and comfort.", + }) + + # --- Amala Yoga: Natural benefic in 10th from Lagna or Moon --- + for base_name, base_idx in [("Lagna", lagna_idx), ("Moon", moon_idx)]: + for b_name in BENEFICS: + bd = planets.get(b_name) + if bd and _house_from(base_idx, bd["sign_idx"]) == 10: + yogas.append({ + "name": "Amala Yoga", + "formed_by": [b_name], + "description": f"{b_name} in 10th from {base_name} — virtuous deeds and lasting fame.", + }) + break # one benefic is enough per base + + # --- Saraswati Yoga: Jupiter, Venus, Mercury in kendra/trikona/2nd --- + saraswati_houses = KENDRA_HOUSES | TRIKONA_HOUSES | {2} + sara_ok = [] + for p_name in ("Jupiter", "Venus", "Mercury"): + pd = planets.get(p_name) + if pd and pd.get("house") in saraswati_houses: + sara_ok.append(p_name) + if len(sara_ok) == 3: + jup_d = planets.get("Jupiter") + if jup_d and (jup_d.get("dignity") in ("own_sign", "exalted", "mooltrikona") or jup_d.get("house") in KENDRA_HOUSES): + yogas.append({ + "name": "Saraswati Yoga", + "formed_by": sara_ok, + "description": "Jupiter, Venus, Mercury in kendra/trikona/2nd with strong Jupiter — learning and wisdom.", + }) + + # --- Lakshmi Yoga: Lord of 9th in own/exalted + Venus in own/exalted kendra --- + ven_d = planets.get("Venus") + if l9d and ven_d: + lord9_strong = l9d.get("dignity") in ("own_sign", "exalted", "mooltrikona") + venus_strong = (ven_d.get("house") in KENDRA_HOUSES and + _is_exalted_or_own("Venus", ven_d["sign"])) + if lord9_strong and venus_strong: + yogas.append({ + "name": "Lakshmi Yoga", + "formed_by": [lord_9, "Venus"], + "description": f"Lord of 9th ({lord_9}) in dignity and Venus in own/exalted kendra — great wealth.", + }) + + # --- Voshi Yoga / Veshi Yoga: Planet in 2nd/12th from Sun --- + if sun_d: + sun_idx = sun_d["sign_idx"] + sign_2nd_s = (sun_idx + 1) % 12 + sign_12th_s = (sun_idx - 1) % 12 + voshi_planets = [] # 12th from Sun + veshi_planets = [] # 2nd from Sun + for p_name, pd in planets.items(): + if p_name in ("Sun", "Moon", "Rahu", "Ketu"): + continue + if pd["sign_idx"] == sign_2nd_s: + veshi_planets.append(p_name) + if pd["sign_idx"] == sign_12th_s: + voshi_planets.append(p_name) + + if veshi_planets and voshi_planets: + yogas.append({ + "name": "Ubhayachari Yoga", + "formed_by": veshi_planets + voshi_planets, + "description": f"Planets in 2nd ({', '.join(veshi_planets)}) and 12th ({', '.join(voshi_planets)}) from Sun — balanced fame.", + }) + elif veshi_planets: + yogas.append({ + "name": "Veshi Yoga", + "formed_by": veshi_planets, + "description": f"{', '.join(veshi_planets)} in 2nd from Sun — industrious nature.", + }) + elif voshi_planets: + yogas.append({ + "name": "Voshi Yoga", + "formed_by": voshi_planets, + "description": f"{', '.join(voshi_planets)} in 12th from Sun — charitable nature.", + }) + + return yogas + + +def detect_kaal_sarpa(raw_planets): + """ + Detect Kaal Sarpa Dosha: all 7 planets hemmed between Rahu-Ketu axis. + + Parameters + ---------- + raw_planets : dict — raw planet data with 'sign_idx' for each planet + + Returns + ------- + dict or None — dosha details if present, None otherwise + """ + rahu_idx = raw_planets["Rahu"]["sign_idx"] + ketu_idx = raw_planets["Ketu"]["sign_idx"] + + # The 7 planets (Sun through Saturn) must all be on one side of the Rahu-Ketu axis + seven = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] + + # Check if all planets fall in the arc from Rahu to Ketu (going forward) + def _in_arc(planet_idx, start_idx, end_idx): + """Check if planet_idx is in the arc from start_idx to end_idx (exclusive of nodes).""" + if start_idx == end_idx: + return False + if start_idx < end_idx: + return start_idx < planet_idx < end_idx + else: # wraps around + return planet_idx > start_idx or planet_idx < end_idx + + # Arc from Rahu to Ketu + all_rahu_to_ketu = all(_in_arc(raw_planets[p]["sign_idx"], rahu_idx, ketu_idx) for p in seven) + # Arc from Ketu to Rahu + all_ketu_to_rahu = all(_in_arc(raw_planets[p]["sign_idx"], ketu_idx, rahu_idx) for p in seven) + + if all_rahu_to_ketu or all_ketu_to_rahu: + # Determine type: Ascending (Rahu leads) or Descending (Ketu leads) + if all_rahu_to_ketu: + kaal_type = "Ascending (planets move toward Ketu)" + else: + kaal_type = "Descending (planets move toward Rahu)" + + return { + "present": True, + "type": kaal_type, + "rahu_sign": ZODIAC_SIGNS[rahu_idx], + "ketu_sign": ZODIAC_SIGNS[ketu_idx], + "description": "All 7 planets hemmed between Rahu-Ketu axis — karmic restriction pattern affecting life direction.", + } + + # Check partial Kaal Sarpa (one planet outside — still significant) + for direction, checker in [("Rahu→Ketu", lambda p: _in_arc(raw_planets[p]["sign_idx"], rahu_idx, ketu_idx)), + ("Ketu→Rahu", lambda p: _in_arc(raw_planets[p]["sign_idx"], ketu_idx, rahu_idx))]: + outside = [p for p in seven if not checker(p)] + if len(outside) == 1: + return { + "present": True, + "type": f"Partial ({outside[0]} outside)", + "rahu_sign": ZODIAC_SIGNS[rahu_idx], + "ketu_sign": ZODIAC_SIGNS[ketu_idx], + "description": f"Near-complete Kaal Sarpa — only {outside[0]} escapes the nodal axis. Karmic themes still dominant.", + } + + return None + + +def detect_graha_yuddha(raw_planets): + """ + Detect Planetary War (Graha Yuddha): two planets within 1° of each other. + Only applies to Mars, Mercury, Jupiter, Venus, Saturn (not Sun, Moon, Rahu, Ketu). + The planet with higher longitude wins; the loser is weakened. + + Returns + ------- + list of dict — each war detected + """ + war_planets = ["Mars", "Mercury", "Jupiter", "Venus", "Saturn"] + wars = [] + + for i in range(len(war_planets)): + for j in range(i + 1, len(war_planets)): + p1, p2 = war_planets[i], war_planets[j] + lon1 = raw_planets[p1]["lon"] + lon2 = raw_planets[p2]["lon"] + + # Angular separation (handle wrap-around at 360°) + diff = abs(lon1 - lon2) + if diff > 180: + diff = 360 - diff + + if diff <= 1.0: + # Planet with higher latitude wins (simplified: brighter/larger planet wins) + # Traditional: planet with higher longitude in the same sign wins + # Simplified: we report both and let interpretation handle it + winner = p1 if lon1 > lon2 else p2 + loser = p2 if winner == p1 else p1 + wars.append({ + "planet1": p1, + "planet2": p2, + "separation_degrees": round(diff, 4), + "winner": winner, + "loser": loser, + "description": f"{p1} and {p2} in planetary war ({diff:.2f}° apart) — {loser} is weakened, {winner} gains strength.", + }) + + return wars + + +def detect_gandanta(raw_planets, asc_lon=None): + """ + Detect Gandanta: planets or Lagna at water-fire sign junctions (last/first 3°20'). + These are inauspicious knot points where nakshatra and sign boundaries overlap. + + Returns + ------- + list of dict — each gandanta point detected + """ + gandanta_points = [] + + def _check_gandanta(name, longitude): + sign_idx = int(longitude / 30) % 12 + degree = longitude % 30 + + for water_idx, fire_idx in GANDANTA_JUNCTIONS: + # Last 3°20' of water sign + if sign_idx == water_idx and degree >= (30 - GANDANTA_ORB): + return { + "planet": name, + "sign": ZODIAC_SIGNS[sign_idx], + "degree": round(degree, 2), + "junction": f"{ZODIAC_SIGNS[water_idx]}-{ZODIAC_SIGNS[fire_idx]}", + "position": "end_of_water_sign", + "description": f"{name} at {degree:.1f}° {ZODIAC_SIGNS[sign_idx]} — Gandanta zone (karmic knot, spiritual transformation).", + } + # First 3°20' of fire sign + if sign_idx == fire_idx and degree <= GANDANTA_ORB: + return { + "planet": name, + "sign": ZODIAC_SIGNS[sign_idx], + "degree": round(degree, 2), + "junction": f"{ZODIAC_SIGNS[water_idx]}-{ZODIAC_SIGNS[fire_idx]}", + "position": "start_of_fire_sign", + "description": f"{name} at {degree:.1f}° {ZODIAC_SIGNS[sign_idx]} — Gandanta zone (karmic knot, spiritual transformation).", + } + return None + + for name, rp in raw_planets.items(): + result = _check_gandanta(name, rp["lon"]) + if result: + gandanta_points.append(result) + + if asc_lon is not None: + result = _check_gandanta("Lagna", asc_lon) + if result: + gandanta_points.append(result) + + return gandanta_points diff --git a/references/open_source_sources/jaimini-tropical/.gitignore b/references/open_source_sources/jaimini-tropical/.gitignore new file mode 100644 index 00000000..f1c646b3 --- /dev/null +++ b/references/open_source_sources/jaimini-tropical/.gitignore @@ -0,0 +1,53 @@ +# Python +__pycache__/ +*.py[cod] +*.so +*.egg-info/ +dist/ +build/ + +# Virtual environments +venv/ +.venv/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Project temp files +progress/ +output/ +*.png +*.bat + +# Skyfield ephemeris cache (auto-downloaded, ~17MB) +jaimini/data/*.bsp +*.bsp +*.bsp.gz + +# PDF pipeline (separate project) +pipeline.py +run.bat +pdf_translate.py +Jaimini占星文件/ +古典占星著作/ + +# Original micrograd files (separate project) +test.py +run.sh + +# Claude internal +.claude/ +CLAUDE.md + +# Test outputs +jaimini/tests/test_output.txt +jaimini/tests/test_full.txt diff --git a/references/open_source_sources/jaimini-tropical/Jaimini.spec b/references/open_source_sources/jaimini-tropical/Jaimini.spec new file mode 100644 index 00000000..a7fa550d --- /dev/null +++ b/references/open_source_sources/jaimini-tropical/Jaimini.spec @@ -0,0 +1,100 @@ +# -*- mode: python ; coding: utf-8 -*- + +import sys +from pathlib import Path + +block_cipher = None + +PROJECT_ROOT = Path(SPECPATH) + +a = Analysis( + [str(PROJECT_ROOT / 'jaimini_web_launcher.py')], + pathex=[str(PROJECT_ROOT)], + binaries=[], + datas=[ + (str(PROJECT_ROOT / 'jaimini' / 'data' / 'de421.bsp'), 'jaimini/data'), + (str(PROJECT_ROOT / 'jaimini' / 'web' / 'templates'), 'jaimini/web/templates'), + (str(PROJECT_ROOT / 'jaimini' / 'web' / 'static'), 'jaimini/web/static'), + ], + hiddenimports=[ + # Skyfield + numpy + 'skyfield', + 'skyfield.timelib', + 'skyfield.data', + 'numpy', + # Jaimini engine + 'jaimini', + 'jaimini.engine', + 'jaimini.engine.ephemeris', + 'jaimini.engine.time_utils', + 'jaimini.engine.houses', + # Jaimini core + 'jaimini.core', + 'jaimini.core.karakas', + 'jaimini.core.dashas', + 'jaimini.core.padas', + 'jaimini.core.lagnas', + 'jaimini.core.divisions', + 'jaimini.core.argala', + # Jaimini panchanga (NEW) + 'jaimini.panchanga', + 'jaimini.panchanga.panchanga', + # Jaimini chart + 'jaimini.chart', + 'jaimini.chart.chart', + # Jaimini CLI + 'jaimini.cli', + 'jaimini.cli.main', + # Jaimini web (NEW) + 'jaimini.web', + 'jaimini.web.app', + # Web server dependencies + 'uvicorn', + 'uvicorn.loops', + 'uvicorn.loops.auto', + 'uvicorn.protocols', + 'uvicorn.protocols.http', + 'uvicorn.protocols.http.h11_impl', + 'fastapi', + 'starlette', + 'jinja2', + 'jinja2.ext', + # AnyIO (uvicorn dependency) + 'anyio', + 'anyio._backends', + 'anyio._backends._asyncio', + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='Jaimini', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=None, +) diff --git a/references/open_source_sources/jaimini-tropical/LICENSE b/references/open_source_sources/jaimini-tropical/LICENSE new file mode 100644 index 00000000..b77bf2ab --- /dev/null +++ b/references/open_source_sources/jaimini-tropical/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/references/open_source_sources/jaimini-tropical/README.md b/references/open_source_sources/jaimini-tropical/README.md new file mode 100644 index 00000000..61986323 --- /dev/null +++ b/references/open_source_sources/jaimini-tropical/README.md @@ -0,0 +1,138 @@ +# Jaimini Tropical Astrology Engine + +A high-precision **Jaimini astrology** computation engine using the **Tropical Zodiac** (Sayana). Built from scratch based on Iranganti Rangacharya's authoritative works — *Jaimini Sutramritam* and *Jyotish-Prasana*. + +**Key differentiator**: All existing Jaimini/Vedic libraries use the Sidereal zodiac (Lahiri Ayanamsa). This is the first engine built for the **Tropical zodiac**, making Jaimini techniques compatible with classical Western astrology (Hermetic Lots, etc.). + +## Design Principles + +1. **Purely Jaimini** — No Parashara/BPHS contamination. No Vimshottari Dasha, no Shadbala, no planetary aspects. Jaimini is treated as a self-contained, independent system per Iranganti Rangacharya's teachings. + +2. **Tropical (Sayana)** — Default tropical zodiac for compatibility with classical Western techniques. No ayanamsa applied. + +3. **Whole Sign Houses** — Per Brhat Jataka 1.4: Rasi = Bhava. One sign = one house. + +4. **High Precision** — NASA JPL DE421 ephemeris via Skyfield. Sun position accurate to <1 arcsecond, Moon to <10 arcseconds. + +5. **Data over Graphics** — Output is precise numerical tables, not visual chart wheels. Every calculation is transparent and reproducible. + +## Features + +### Layer 1: Planetary Positions (JPL DE421) +- All 7 classical planets + Rahu/Ketu + outer planets +- Tropical longitude to 6 decimal places +- Speed (deg/day), retrograde detection, ecliptic latitude + +### Layer 2: Chara Karaka & Arudha Pada +- **7 Chara Karakas** (Atmakaraka through Darakaraka) by degree-within-sign ranking +- **12 Arudha Padas** (A1-A12) with standard exception rules +- **Upapada** (UL) for spouse analysis + +### Layer 3: Special Lagnas (Ghati-based time differentiation) +- **Hora Lagna** (HL / Yin-Yang Fortune) — ~1 hour sensitivity +- **Ghatika Lagna** (GL / Five-Element Fortune) — 24 minute sensitivity +- **Varnada Lagna** (VL / Palace Origin) — derived from Ascendant + +### Layer 4: Divisional Charts +- **D-9 Navamsa** — Jaimini's Yang-forward/Yin-reverse mapping +- **D-3 Drekkana** — Parivrittitraya method +- **D-12 Dwadashamsha** + +### Argala Analysis (Jaimini Judgment System) +- Primary Argala (houses 2, 4, 11) — positive planetary intervention +- Virodhargala obstruction (houses 12, 10, 3) +- Specific Argala (house 3 with 2+ malefics) +- Secondary Argala (houses 5, 9) +- Argala Rajayoga classification (Poorna/Tripada/Ardha/Padargala) +- Karakamsa Rajayoga (AK in Navamsa analysis) + +### Chara Dasha (Variable-Period System) +- Full 12-sign Mahadasha timeline from birth +- Prakriti Chakra sign sequence +- Antar (Bhukti) sub-periods with proportional year distribution + +## Quick Start + +```bash +pip install skyfield numpy +python run_jaimini.py "1949-10-01" "15:00:00" "+8" "39.907" "116.397" +``` + +### Input Format +``` +python run_jaimini.py