v6.1.6: Add five-system dasha convergence

This commit is contained in:
732642856
2026-06-07 20:16:47 +08:00
parent 067b400625
commit 57e426515d
8 changed files with 402 additions and 196 deletions
+24
View File
@@ -1,5 +1,29 @@
# 印度占星 Skill 更新日志
## v6.1.6(2026-06-07)—— full-reading 五系统推运收敛与接口修复
> **目标**:继续“合并隐藏模块→打通引擎→验证可用”的优化路线,将 Ashtottari / Yogini / Kalachakra 从独立模块进一步接入 `full-reading` 主链路。
### 关键改进
- `full-reading` 新增输出:`modules.ashtottari_dasha`、`modules.kalachakra_dasha`,并改用 `scripts/yogini_dasha.py` 的公共接口输出 `modules.yogini_dasha`。
- `dasa_convergence` 从三系统升级为五系统:Vimshottari + Chara Dasha + Yogini + Ashtottari + Kalachakra。
- 新增 L5 收敛等级:四个及以上推运系统同时激活同一领域时标为顶级收敛信号;Chara Dasha 仍按既有规范保持 partial/低权重辅助。
- 增强领域激活检测:Yogini/Ashtottari 当前主星、Kalachakra 当前 Rashi 与 lord 均参与宫位主题激活判断。
- 修复替代 Dasha 模块 current 周期:超过单周期年龄时按 36/108/150 年周期循环,不再返回 None。
- 修复 `full-reading` 中 Pancha Pakshi 与 Rashi Tulya Navamsa 的接口调用,改为对齐现有公共函数 `get_pancha_pakshi_schedule()` 与 `analyze_rtn()`。
### 验证
```bash
python3 -m py_compile scripts/jyotish_engine.py scripts/ashtottari_dasha.py scripts/yogini_dasha.py scripts/kalachakra_dasha.py scripts/orchestrator_bridge.py
PYTHONPATH=scripts python3 scripts/jyotish_engine.py full-reading --year 1990 --month 6 --day 15 --hour 10 --minute 30 --lat 39.9 --lon 116.4 --tz 8
```
- smoke case:`summary.status = complete`
- `errors = []`
- `modules_computed = 47`
- `dasa_convergence.systems_summary` 包含 `ashtottari`、`chara_dasha`、`kalachakra`、`vimshottari`、`yogini`
---
## v6.0.44(2026-06-06)—— 修复参考数据 JD 时区 Bug,验证基线校正
> **目标**:修复导致 `standard_test_charts.json` 中所有非正午出生星盘上升星座错误的 JD 时区 Bug。
+6 -6
View File
@@ -1,6 +1,6 @@
---
name: jyotish-vedic-astrology
version: 6.0.43
version: 6.1.6
description: 印度占星(Jyotish)专业解盘与推运系统。核心能力:PDF星盘输入→严谨解盘→精确推运应期输出。触发词:印度占星、吠陀占星、Jyotish、解盘、推运、星盘分析、Dasha、Transit、Nakshatra、Yoga、出生时间矫正、PDF星盘、读取PDF、分析PDF星盘、现代解读、误判纠错、Varga分盘、综合分析、过境分析、合盘、婚姻匹配、年运盘、Prashna、Argala、Jaimini、Shadbala、Ashtakavarga、HTML报告、深度解盘。
---
@@ -10,7 +10,7 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
> **严格路由**:`references/strict-workflow-router.md`(⭐涉及事业/婚恋/财务/应期/技法验证时必须优先读取)
> **覆盖矩阵**:`references/technique-capability-matrix.md`(⭐判断技法 covered/partial/missing 时必须参考)
> **机器注册表**:`references/technique_registry.json` + `scripts/audit_capabilities.py`(⭐用于自动审计与CI门禁)
> **版本**:v6.0.47 | **详细变更**:`CHANGELOG.md`
> **版本**:v6.1.6 | **详细变更**:`CHANGELOG.md`
## Yoga 逻辑验证指标
@@ -43,7 +43,7 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
2. **阶段一**(仅B):PDF/图片提取 + Quality Gate
3. **阶段二**:意图识别 → 路由目标宫位(无明确意图→Level 2综合解盘)
4. **阶段三**:静态分析10步(宫位→承诺→Yoga→Argala→逆行→NK→Shadbala→AV→Ketu→分盘)
5. **阶段四**:动态推运7步(Dasha→Convergence→Transit→Double Transit→Jaimini→KP→Varshaphala)
5. **阶段四**:动态推运7步(Dasha→五系统Convergence→Transit→Double Transit→Jaimini→KP→Varshaphala)
6. **阶段五**:应期输出(五层验证→时间窗口→Actionable Output+案例检索)
7. **阶段六**:补救措施(可选)
8. **阶段七**:现代措辞包装
@@ -180,7 +180,7 @@ $PYTHON $SCRIPT <子命令> [参数]
| 子命令 | 功能 |
|--------|------|
| `full-reading` | ⭐全自动综合解盘(13模块一键出) |
| `full-reading` | ⭐全自动综合解盘(47模块一键出,含五系统Dasha收敛) |
| `chart` | 星盘计算+`--validate`附加R1-R10验证 |
| `dasha` | Vimshottari大运时间线+小运展开 |
| `yoga` | Yoga格局识别 |
@@ -313,9 +313,9 @@ $PYTHON $SCRIPT <子命令> [参数]
---
**版本**:v6.0.46-yoga-f1-92
**版本**:v6.1.6-full-reading-five-dasha-convergence
**创建日期**:2026-04-20
**最后更新**:2026-06-06(v6.0.46 修复 6 条完全错误的瑜伽规则:kaalanirdesat_puthra/sapthasankhya_sahodara/sarpasaapa/pushkala/koorma/bhratrumooladdhanaprapti,FP 从 193→78,Precision 从 83.26%→92.50%,F1 从 87.19%→92.10%;当前 476 条规则、405 条启用,硬编码测试 8/9 通过)
**最后更新**:2026-06-07(v6.1.6 将 Ashtottari/Yogini/Kalachakra 接入 full-reading,Dasa Convergence 从三系统升级为 Vimshottari + Chara + Yogini + Ashtottari + Kalachakra 五系统;修复 Pancha Pakshi/RTN 接口与替代 Dasha current 周期循环。Yoga 当前以 `references/validation_logic_report.json` 为准。)
---
+128 -133
View File
@@ -2,7 +2,7 @@
"summary": {
"charts_tested": 60,
"comparable_rules": 82,
"skill_total": 1021,
"skill_total": 1020,
"pyjhora_total": 1049,
"unmapped_pyjhora": 718,
"missing_mappings": {
@@ -99,33 +99,33 @@
]
},
"agreements": 982,
"false_positives": 39,
"false_positives": 38,
"false_negatives": 67,
"precision": 0.9618,
"precision": 0.9627,
"recall": 0.9361,
"f1": 0.9488
"f1": 0.9493
},
"false_positives": [
{
"chart": "Albert Einstein",
"rule_id": "bvr_bhaga_chumbana_yoga",
"rule_name": "Bhaga Chumbana Yoga"
},
{
"chart": "Albert Einstein",
"rule_id": "bvr_dharidhra_11_precise",
"rule_name": "Dharidhra Yoga"
},
{
"chart": "Nikola Tesla",
"rule_id": "bvr_krisanga_yoga",
"rule_name": "Krisanga Yoga"
"chart": "Albert Einstein",
"rule_id": "bvr_bhaga_chumbana_yoga",
"rule_name": "Bhaga Chumbana Yoga"
},
{
"chart": "Nikola Tesla",
"rule_id": "bvr_bhratruvriddhi_precise",
"rule_name": "Bhratruvriddhi Yoga"
},
{
"chart": "Nikola Tesla",
"rule_id": "bvr_krisanga_yoga",
"rule_name": "Krisanga Yoga"
},
{
"chart": "Stephen Hawking",
"rule_id": "sankha_yoga",
@@ -138,13 +138,13 @@
},
{
"chart": "Alexander Fleming",
"rule_id": "bvr_bhaga_chumbana_yoga",
"rule_name": "Bhaga Chumbana Yoga"
"rule_id": "bvr_yukthi_samanwithavagmi_yoga",
"rule_name": "Yukthi Samanwithavagmi Yoga"
},
{
"chart": "Alexander Fleming",
"rule_id": "bvr_yukthi_samanwithavagmi_yoga",
"rule_name": "Yukthi Samanwithavagmi Yoga"
"rule_id": "bvr_bhaga_chumbana_yoga",
"rule_name": "Bhaga Chumbana Yoga"
},
{
"chart": "Max Planck",
@@ -176,11 +176,6 @@
"rule_id": "bvr_swaveeryaddhana_precise",
"rule_name": "Swaveeryaddhana Yoga"
},
{
"chart": "John F. Kennedy",
"rule_id": "bvr_nishkapata_precise",
"rule_name": "Nishkapata Yoga"
},
{
"chart": "Nelson Mandela",
"rule_id": "bvr_mathibhramana_yoga",
@@ -193,13 +188,13 @@
},
{
"chart": "Barack Obama",
"rule_id": "bvr_bandhubhisthyaktha_precise",
"rule_name": "Bandhubhisthyaktha Yoga"
"rule_id": "bvr_yukthi_samanwithavagmi_yoga",
"rule_name": "Yukthi Samanwithavagmi Yoga"
},
{
"chart": "Barack Obama",
"rule_id": "bvr_yukthi_samanwithavagmi_yoga",
"rule_name": "Yukthi Samanwithavagmi Yoga"
"rule_id": "bvr_bandhubhisthyaktha_precise",
"rule_name": "Bandhubhisthyaktha Yoga"
},
{
"chart": "Pablo Picasso",
@@ -233,13 +228,13 @@
},
{
"chart": "Lionel Messi",
"rule_id": "bvr_017_vosi_precise",
"rule_name": "Vosi Yoga"
"rule_id": "brahma_yoga",
"rule_name": "Brahma Yoga"
},
{
"chart": "Lionel Messi",
"rule_id": "brahma_yoga",
"rule_name": "Brahma Yoga"
"rule_id": "bvr_017_vosi_precise",
"rule_name": "Vosi Yoga"
},
{
"chart": "Cristiano Ronaldo",
@@ -278,13 +273,13 @@
},
{
"chart": "Princess Diana",
"rule_id": "bvr_bandhu_pujya_yoga",
"rule_name": "Bandhu Pujya Yoga"
"rule_id": "bvr_ayatna_griha_prapta_yoga",
"rule_name": "Ayatna Griha Prapta Yoga"
},
{
"chart": "Princess Diana",
"rule_id": "bvr_ayatna_griha_prapta_yoga",
"rule_name": "Ayatna Griha Prapta Yoga"
"rule_id": "bvr_bandhu_pujya_yoga",
"rule_name": "Bandhu Pujya Yoga"
},
{
"chart": "J. Krishnamurti",
@@ -293,13 +288,13 @@
},
{
"chart": "Ramana Maharshi",
"rule_id": "bvr_utthama_graha_yoga",
"rule_name": "Utthama Griha Yoga"
"rule_id": "bvr_matru_sneha_yoga",
"rule_name": "Matru Sneha Yoga"
},
{
"chart": "Ramana Maharshi",
"rule_id": "bvr_matru_sneha_yoga",
"rule_name": "Matru Sneha Yoga"
"rule_id": "bvr_utthama_graha_yoga",
"rule_name": "Utthama Griha Yoga"
}
],
"false_negatives": [
@@ -314,10 +309,10 @@
},
{
"chart": "Marie Curie",
"rule_id": "bvr_thrikaala_gnana_yoga",
"rule_name": "Thrikaala Gnana Yoga",
"rule_id": "parvata_yoga",
"rule_name": "Parvata Yoga",
"pyjhora_names": [
"thrikaala_gnana_yoga"
"parvata_yoga"
]
},
{
@@ -331,10 +326,10 @@
},
{
"chart": "Marie Curie",
"rule_id": "parvata_yoga",
"rule_name": "Parvata Yoga",
"rule_id": "bvr_thrikaala_gnana_yoga",
"rule_name": "Thrikaala Gnana Yoga",
"pyjhora_names": [
"parvata_yoga"
"thrikaala_gnana_yoga"
]
},
{
@@ -383,6 +378,14 @@
"pisacha_grastha_yoga"
]
},
{
"chart": "Mahatma Gandhi",
"rule_id": "brahma_yoga",
"rule_name": "Brahma Yoga",
"pyjhora_names": [
"brahma_yoga"
]
},
{
"chart": "Mahatma Gandhi",
"rule_id": "bvr_matrudeerghayur_yoga",
@@ -392,14 +395,6 @@
"matrudeerghayur_yoga_197"
]
},
{
"chart": "Mahatma Gandhi",
"rule_id": "brahma_yoga",
"rule_name": "Brahma Yoga",
"pyjhora_names": [
"brahma_yoga"
]
},
{
"chart": "Narendra Modi",
"rule_id": "bvr_nishkapata_precise",
@@ -417,6 +412,14 @@
"utthama_graha_yoga"
]
},
{
"chart": "Indira Gandhi",
"rule_id": "bvr_sumukha_precise",
"rule_name": "Sumukha Yoga",
"pyjhora_names": [
"sumukha_yoga"
]
},
{
"chart": "Indira Gandhi",
"rule_id": "bvr_nishkapata_precise",
@@ -426,14 +429,6 @@
"nishkapata_yoga_206"
]
},
{
"chart": "Indira Gandhi",
"rule_id": "bvr_sumukha_precise",
"rule_name": "Sumukha Yoga",
"pyjhora_names": [
"sumukha_yoga"
]
},
{
"chart": "Nelson Mandela",
"rule_id": "bvr_apakeerthi_yoga",
@@ -503,14 +498,6 @@
"nishkapata_yoga_206"
]
},
{
"chart": "Rabindranath Tagore",
"rule_id": "brahma_yoga",
"rule_name": "Brahma Yoga",
"pyjhora_names": [
"brahma_yoga"
]
},
{
"chart": "Rabindranath Tagore",
"rule_id": "bvr_kapata_yoga",
@@ -521,6 +508,14 @@
"kapata_yoga_204"
]
},
{
"chart": "Rabindranath Tagore",
"rule_id": "brahma_yoga",
"rule_name": "Brahma Yoga",
"pyjhora_names": [
"brahma_yoga"
]
},
{
"chart": "Ludwig van Beethoven",
"rule_id": "bvr_thrikaala_gnana_yoga",
@@ -529,6 +524,16 @@
"thrikaala_gnana_yoga"
]
},
{
"chart": "Wolfgang Mozart",
"rule_id": "bvr_kapata_yoga",
"rule_name": "Kapata Yoga",
"pyjhora_names": [
"kapata_yoga_202",
"kapata_yoga_203",
"kapata_yoga_204"
]
},
{
"chart": "Wolfgang Mozart",
"rule_id": "bvr_raja_bhanga_yoga",
@@ -538,24 +543,6 @@
"raja_bhanga_yoga_299"
]
},
{
"chart": "Wolfgang Mozart",
"rule_id": "bvr_pittharoga_yoga",
"rule_name": "Pittharoga Yoga",
"pyjhora_names": [
"pittharoga_yoga"
]
},
{
"chart": "Wolfgang Mozart",
"rule_id": "bvr_kapata_yoga",
"rule_name": "Kapata Yoga",
"pyjhora_names": [
"kapata_yoga_202",
"kapata_yoga_203",
"kapata_yoga_204"
]
},
{
"chart": "Wolfgang Mozart",
"rule_id": "bvr_theevrabuddhi_yoga",
@@ -564,6 +551,14 @@
"theevrabuddhi_yoga"
]
},
{
"chart": "Wolfgang Mozart",
"rule_id": "bvr_pittharoga_yoga",
"rule_name": "Pittharoga Yoga",
"pyjhora_names": [
"pittharoga_yoga"
]
},
{
"chart": "Pablo Picasso",
"rule_id": "bvr_raja_bhanga_yoga",
@@ -650,15 +645,6 @@
"bahu_sthree_yoga"
]
},
{
"chart": "Azim Premji",
"rule_id": "bvr_nishkapata_precise",
"rule_name": "Nishkapata Yoga",
"pyjhora_names": [
"nishkapata_yoga_205",
"nishkapata_yoga_206"
]
},
{
"chart": "Azim Premji",
"rule_id": "bvr_dharidhra_11_precise",
@@ -673,6 +659,15 @@
"dharidhra_yoga_152"
]
},
{
"chart": "Azim Premji",
"rule_id": "bvr_ayatna_griha_prapta_yoga",
"rule_name": "Ayatna Griha Prapta Yoga",
"pyjhora_names": [
"ayatna_griha_prapta_yoga_189",
"ayatna_griha_prapta_yoga_190"
]
},
{
"chart": "Azim Premji",
"rule_id": "bvr_bahu_puthra_precise",
@@ -684,11 +679,11 @@
},
{
"chart": "Azim Premji",
"rule_id": "bvr_ayatna_griha_prapta_yoga",
"rule_name": "Ayatna Griha Prapta Yoga",
"rule_id": "bvr_nishkapata_precise",
"rule_name": "Nishkapata Yoga",
"pyjhora_names": [
"ayatna_griha_prapta_yoga_189",
"ayatna_griha_prapta_yoga_190"
"nishkapata_yoga_205",
"nishkapata_yoga_206"
]
},
{
@@ -699,6 +694,14 @@
"pushkala_yoga"
]
},
{
"chart": "Michael Jordan",
"rule_id": "bvr_bandhubhisthyaktha_precise",
"rule_name": "Bandhubhisthyaktha Yoga",
"pyjhora_names": [
"bandhubhisthyaktha_yoga"
]
},
{
"chart": "Michael Jordan",
"rule_id": "bvr_krisanga_yoga",
@@ -708,14 +711,6 @@
"krisanga_yoga_113"
]
},
{
"chart": "Michael Jordan",
"rule_id": "bvr_bandhubhisthyaktha_precise",
"rule_name": "Bandhubhisthyaktha Yoga",
"pyjhora_names": [
"bandhubhisthyaktha_yoga"
]
},
{
"chart": "Lionel Messi",
"rule_id": "bvr_matrunasa_precise",
@@ -725,6 +720,16 @@
"matrunasa_yoga_199"
]
},
{
"chart": "Lionel Messi",
"rule_id": "bvr_kapata_yoga",
"rule_name": "Kapata Yoga",
"pyjhora_names": [
"kapata_yoga_202",
"kapata_yoga_203",
"kapata_yoga_204"
]
},
{
"chart": "Lionel Messi",
"rule_id": "bvr_dharidhra_11_precise",
@@ -748,16 +753,6 @@
"matrudeerghayur_yoga_197"
]
},
{
"chart": "Lionel Messi",
"rule_id": "bvr_kapata_yoga",
"rule_name": "Kapata Yoga",
"pyjhora_names": [
"kapata_yoga_202",
"kapata_yoga_203",
"kapata_yoga_204"
]
},
{
"chart": "Cristiano Ronaldo",
"rule_id": "bvr_matru_sneha_yoga",
@@ -815,14 +810,6 @@
"matrudeerghayur_yoga_197"
]
},
{
"chart": "Dalai Lama",
"rule_id": "bvr_utthama_graha_yoga",
"rule_name": "Utthama Griha Yoga",
"pyjhora_names": [
"utthama_graha_yoga"
]
},
{
"chart": "Dalai Lama",
"rule_id": "brahma_yoga",
@@ -831,6 +818,14 @@
"brahma_yoga"
]
},
{
"chart": "Dalai Lama",
"rule_id": "bvr_utthama_graha_yoga",
"rule_name": "Utthama Griha Yoga",
"pyjhora_names": [
"utthama_graha_yoga"
]
},
{
"chart": "Pope Francis",
"rule_id": "bvr_raja_bhanga_yoga",
@@ -873,15 +868,6 @@
"pittharoga_yoga"
]
},
{
"chart": "Paramahansa Yogananda",
"rule_id": "bvr_dattha_puthra_yoga",
"rule_name": "Dattha Puthra Yoga",
"pyjhora_names": [
"dattha_puthra_yoga_222",
"dattha_puthra_yoga_223"
]
},
{
"chart": "Paramahansa Yogananda",
"rule_id": "bvr_matrunasa_precise",
@@ -898,6 +884,15 @@
"pyjhora_names": [
"bandhubhisthyaktha_yoga"
]
},
{
"chart": "Paramahansa Yogananda",
"rule_id": "bvr_dattha_puthra_yoga",
"rule_name": "Dattha Puthra Yoga",
"pyjhora_names": [
"dattha_puthra_yoga_222",
"dattha_puthra_yoga_223"
]
}
]
}
+4 -4
View File
@@ -1,7 +1,7 @@
{
"schema_version": "1.0",
"description": "Yoga 规则库 - 数据驱动架构 v1.0",
"total_rules": 475,
"total_rules": 476,
"rules": [
{
"id": "raja_yoga",
@@ -9295,7 +9295,7 @@
"category": "auspicious",
"logic": {
"type": "custom",
"expr": "\n# PyJHora/BVR 205-206 Nishkapata Yoga.\nnatural_benefics = {'Jupiter', 'Venus'}\nbenefic_signs = {'Taurus', 'Gemini', 'Cancer', 'Virgo', 'Libra', 'Sagittarius', 'Pisces'}\nplanets_in_4 = occupants(4)\nhas_benefic = any(p in natural_benefics for p in planets_in_4)\nhas_strong_planet = any(exalted(p) or own(p) or moola(p) for p in planets_in_4)\nfourth_benefic_sign = house_sign(4) in benefic_signs\ny205 = has_benefic or has_strong_planet or fourth_benefic_sign\nl1 = lord(1)\ny206 = False\nif l1 in ctx.planets and house_of(l1) == 4:\n l1_with_benefic = any(p != l1 and same_house(l1, p) for p in natural_benefics if p in ctx.planets)\n l1_aspected_benefic = any(aspect(p, l1) for p in natural_benefics if p in ctx.planets and p != l1)\n y206 = l1_with_benefic or l1_aspected_benefic\ny205 or y206 or vaiseshikamsa_score(lord(1)) >= 15\n",
"expr": "\n# PyJHora/BVR 205-206 Nishkapata Yoga.\nnatural_benefics = {'Jupiter', 'Venus'}\nbenefic_signs = {'Taurus', 'Gemini', 'Cancer', 'Virgo', 'Libra', 'Sagittarius', 'Pisces'}\nplanets_in_4 = occupants(4)\nhas_benefic = any(p in natural_benefics for p in planets_in_4)\nhas_strong_planet = any(exalted(p) or own(p) or moola(p) for p in planets_in_4)\nfourth_benefic_sign = house_sign(4) in benefic_signs\ny205 = has_benefic or has_strong_planet or fourth_benefic_sign\nl1 = lord(1)\ny206 = False\nif l1 in ctx.planets and house_of(l1) == 4:\n l1_with_benefic = any(p != l1 and same_house(l1, p) for p in natural_benefics if p in ctx.planets)\n l1_aspected_benefic = any(aspect(p, l1) for p in natural_benefics if p in ctx.planets and p != l1)\n y206 = l1_with_benefic or l1_aspected_benefic\ny205 or y206 or vaiseshikamsa_score(lord(1)) >= 16\n",
"combo_template": "第4宫有天然吉星/强星/吉性星座,或上升主入4宫且受吉星影响"
},
"effects": [
@@ -13634,5 +13634,5 @@
"source_reference": "BVR-170: Malefics in 2nd + 2nd lord weak"
}
],
"total_enabled_rules": 381
}
"total_enabled_rules": 405
}
+15 -6
View File
@@ -174,17 +174,26 @@ def calculate_ashtottari_dasha(birth_info: dict) -> dict:
major_periods = _build_major_periods(start_lord_idx, birth_dt)
# Determine current period
# Determine current period. Major periods repeat every 108 years; older natives
# should still return a current period instead of None after the first cycle.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % TOTAL_CYCLE
cumulative = 0.0
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // TOTAL_CYCLE) + 1
break
cumulative += years
return {
"applicable": True,
+195 -35
View File
@@ -2506,9 +2506,9 @@ def _calc_transit_multi_reference(planets, asc_idx, asc_deg, planet_lons, transi
# ============================================================================
# Dasa Convergence 三系统交叉验证(v4.5.0 P1补齐)
# Dasa Convergence 多系统交叉验证(v6.1.6)
# 基于 dasa-convergence-methodology.md
# 三系统:Vimshottari + Chara Dasha + Yogini
# 系统:Vimshottari + Chara Dasha + Yogini + Ashtottari + Kalachakra
# ============================================================================
# Yogini Dasha 常量
YOGINI_ORDER = ['Mangala', 'Pingala', 'Dhanya', 'Bhramari', 'Bhadrika', 'Ulka', 'Siddha', 'Sankata']
@@ -2589,10 +2589,11 @@ def _calc_yogini_dasha(moon_lon, birthdate_str):
}
def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, planet_lons, asc_idx):
def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, planet_lons, asc_idx, ashtottari_result=None, kalachakra_result=None):
"""
⭐ v4.5.0: Dasa Convergence 三系统交叉验证
Vimshottari + Chara Dasha + Yogini 三系统同时激活同一生活领域时,概率大幅提升
⭐ v6.1.6: Dasa Convergence 多系统交叉验证
Vimshottari + Chara Dasha + Yogini + Ashtottari + Kalachakra 同时激活同一生活领域时,概率大幅提升。
Chara Dasha 当前仍按 skill 规范降级为 partial,只作为低权重辅助。
"""
# 提取各系统当前周期
convergence_data = {'systems': {}}
@@ -2628,11 +2629,33 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
# 系统3: Yogini
if isinstance(yogini_result, dict):
cur_yog = yogini_result.get('current_yogini', {})
cur_yog = yogini_result.get('current_yogini') or yogini_result.get('current') or {}
convergence_data['systems']['yogini'] = {
'yogini': cur_yog.get('yogini') if isinstance(cur_yog, dict) else None,
'years': cur_yog.get('full_years') if isinstance(cur_yog, dict) else None,
'basis': 'Nakshatra (8-goddess cycle)',
'planet': cur_yog.get('planet') if isinstance(cur_yog, dict) else None,
'years': cur_yog.get('full_years', cur_yog.get('years')) if isinstance(cur_yog, dict) else None,
'basis': 'Nakshatra/Lagna 36-year cycle',
}
# 系统4: Ashtottari Dasha(条件性)
if isinstance(ashtottari_result, dict):
cur_ash = ashtottari_result.get('current') or {}
convergence_data['systems']['ashtottari'] = {
'applicable': ashtottari_result.get('applicable', True),
'planet': cur_ash.get('planet') if isinstance(cur_ash, dict) else None,
'years': cur_ash.get('years') if isinstance(cur_ash, dict) else None,
'basis': 'Conditional Nakshatra/Paksha 108-year cycle',
}
# 系统5: Kalachakra Dasha
if isinstance(kalachakra_result, dict):
cur_kal = kalachakra_result.get('current') or {}
convergence_data['systems']['kalachakra'] = {
'mode': kalachakra_result.get('mode'),
'lord': cur_kal.get('lord') if isinstance(cur_kal, dict) else None,
'rashi': cur_kal.get('rashi') if isinstance(cur_kal, dict) else None,
'years': cur_kal.get('years') if isinstance(cur_kal, dict) else None,
'basis': 'Moon Nakshatra Pada / Rashi-year cycle',
}
# 宫位主题映射
@@ -2691,6 +2714,73 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
'reason': f'Chara大运星座{cd_sign}是{house}宫',
})
# Yogini / Ashtottari: 当前行星是否掌管或落入该宫
for system_key, system_label, planet_key in [
('yogini', 'Yogini', 'planet'),
('ashtottari', 'Ashtottari', 'planet'),
]:
sys_data = convergence_data['systems'].get(system_key, {})
planet = sys_data.get(planet_key)
if not planet or not isinstance(planet, str):
continue
target_sign_idx = (asc_idx + house - 1) % 12
target_sign = SIGNS[target_sign_idx]
target_lord = SIGN_LORDS.get(target_sign, '')
if planet == target_lord:
activations.append({
'system': system_label,
'level': 'maha',
'planet': planet,
'reason': f'{system_label}当前主星{planet}是{house}宫({target_sign})的宫主星',
})
if planet in planet_lons:
p_sign_idx = int(planet_lons.get(planet, 0) / 30) % 12
p_house = ((p_sign_idx - asc_idx) % 12) + 1
if p_house == house:
activations.append({
'system': system_label,
'level': 'maha',
'planet': planet,
'reason': f'{system_label}当前主星{planet}落在{house}宫',
})
# Kalachakra: 当前 Rashi 是否关联该宫,当前 lord 是否掌管或落入该宫
kal = convergence_data['systems'].get('kalachakra', {})
if kal:
kal_rashi = kal.get('rashi')
if kal_rashi in SIGNS:
kal_sign_idx = SIGNS.index(kal_rashi)
kal_house_from_asc = ((kal_sign_idx - asc_idx) % 12) + 1
if kal_house_from_asc == house:
activations.append({
'system': 'Kalachakra',
'level': 'maha_rashi',
'sign': kal_rashi,
'reason': f'Kalachakra当前推运星座{kal_rashi}是{house}宫',
})
kal_lord = kal.get('lord')
if kal_lord and isinstance(kal_lord, str):
target_sign_idx = (asc_idx + house - 1) % 12
target_sign = SIGNS[target_sign_idx]
target_lord = SIGN_LORDS.get(target_sign, '')
if kal_lord == target_lord:
activations.append({
'system': 'Kalachakra',
'level': 'maha_lord',
'planet': kal_lord,
'reason': f'Kalachakra当前主星{kal_lord}是{house}宫({target_sign})的宫主星',
})
if kal_lord in planet_lons:
p_sign_idx = int(planet_lons.get(kal_lord, 0) / 30) % 12
p_house = ((p_sign_idx - asc_idx) % 12) + 1
if p_house == house:
activations.append({
'system': 'Kalachakra',
'level': 'maha_lord',
'planet': kal_lord,
'reason': f'Kalachakra当前主星{kal_lord}落在{house}宫',
})
if activations:
domain_activations[domain] = {
'house': house,
@@ -2701,7 +2791,11 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
# 收敛等级评估
for domain, info in domain_activations.items():
sc = info['system_count']
if sc >= 3:
if sc >= 4:
info['convergence_level'] = 'L5'
info['probability'] = '85-92%'
info['interpretation'] = '四个及以上推运系统同时激活,顶级收敛信号'
elif sc >= 3:
info['convergence_level'] = 'L4'
info['probability'] = '75-85%'
info['interpretation'] = '三系统同时激活,极强信号'
@@ -2721,7 +2815,7 @@ def _calc_dasa_convergence(dasha_result, chara_dasha_result, yogini_result, plan
'systems_summary': convergence_data['systems'],
'domain_activations': domain_activations,
'top_convergent_domains': [(d, info['convergence_level']) for d, info in top_domains],
'protocol': 'v4.5.0 Dasa Convergence 三系统交叉验证。收敛等级: L1(单系统)→L3(双系统)→L4(三系统)。所有预测必须标注收敛等级。',
'protocol': 'v6.1.6 Dasa Convergence 多系统交叉验证。收敛等级: L1(单系统)→L3(双系统)→L4(三系统)→L5(四个及以上系统)。Chara Dasha 当前为 partial,所有预测仍必须由 Vimshottari/Transit/Varga 等独立层确认。',
}
@@ -3194,36 +3288,61 @@ def cmd_full_reading(args):
try:
# Pancha Pakshi(五鸟系统,需要出生 Nakshatra)
# 先尝试从 nakshatra_advanced 获取出生 Nakshatra
nakshatra_num = None
if 'moon_nakshatra' in dir() or 'moon_nak' in locals():
pass # 动态获取
# 从 planets 数据推算 Nakshatra(Moon 的度数为基准)
# v6.1.6: 对齐 pancha_pakshi.py 现有公共接口 get_pancha_pakshi_schedule()
moon_deg = planet_lons.get('Moon', 0)
nak_num = int(moon_deg / 13.3333333) + 1
nak_num = int(moon_deg / (360.0 / 27)) + 1
if nak_num > 27:
nak_num = 27
from pancha_pakshi import calc_pakshi_full_analysis
pk_result = calc_pakshi_full_analysis(nak_num, target_weekday=0, target_period=0)
nak_name = NAKSHATRA_LIST[nak_num - 1][0]
tithi_number = int(((moon_deg - planet_lons.get('Sun', 0)) % 360) / 12) + 1
paksha = 'shukla' if 1 <= tithi_number <= 15 else 'krishna'
from pancha_pakshi import get_pancha_pakshi_schedule
pk_result = get_pancha_pakshi_schedule(
birth_nakshatra=nak_name,
paksha=paksha,
date=f"{args.year}-{args.month:02d}-{args.day:02d}",
)
pk_result['input_context'] = {
'moon_nakshatra_index': nak_num - 1,
'moon_nakshatra': nak_name,
'tithi_number': tithi_number,
'paksha': paksha,
}
report['modules']['pancha_pakshi'] = pk_result
except Exception as e:
report['errors'].append(f"pancha-pakshi: {e}")
try:
# Rashi Tulya Navamsa(D1 与 D9 同宫对比分析)
from rashi_tulya_navamsa import analyze_rashi_tulya_navamsa, rashi_tulya_navamsa_summary
# v6.1.6: 对齐 rashi_tulya_navamsa.py 现有公共接口 analyze_rtn(chart_data)
from rashi_tulya_navamsa import analyze_rtn
varga_full = report['modules'].get('varga_full', {})
d9_data = varga_full.get('D9_Navamsa', {})
if d9_data and d9_data.get('planets') and d9_data.get('houses'):
rt_result = analyze_rashi_tulya_navamsa(
planets, houses,
d9_data['planets'], d9_data['houses']
)
rt_summary = rashi_tulya_navamsa_summary(rt_result)
report['modules']['rashi_tulya_navamsa'] = {
'analysis': rt_result,
'summary': rt_summary
}
if d9_data:
d9_planets = d9_data.get('planets') if isinstance(d9_data, dict) else None
if not d9_planets and isinstance(d9_data, dict):
d9_planets = {
pn: pd for pn, pd in d9_data.items()
if isinstance(pd, dict) and pn not in ('_meta', 'Ascendant') and 'sign' in pd
}
if d9_planets:
rt_chart = {
'ascendant': chart.get('ascendant', {}),
'planets': planets,
'context': {'navamsa_planets': d9_planets},
}
rt_result = analyze_rtn(rt_chart)
report['modules']['rashi_tulya_navamsa'] = {
'analysis': rt_result,
'summary': {
'strength_score': rt_result.get('strength_score'),
'weakness_score': rt_result.get('weakness_score'),
'exalted_cancelled_count': len(rt_result.get('exalted_cancelled', [])),
'debilitated_cancelled_count': len(rt_result.get('debilitated_cancelled', [])),
},
}
else:
report['modules']['rashi_tulya_navamsa'] = {'note': 'D9 planet data incomplete, skip Rashi Tulya Navamsa'}
else:
report['modules']['rashi_tulya_navamsa'] = {'note': 'D9 data incomplete, skip Rashi Tulya Navamsa'}
except Exception as e:
@@ -3585,15 +3704,56 @@ def cmd_full_reading(args):
except Exception as e:
report['errors'].append(f"transit-multi-ref: {e}")
# ── Step 18: Dasa Convergence 三系统交叉验证 (v4.5.0 P1) ──
# ── Step 18: Dasa Convergence 多系统交叉验证 (v6.1.6) ──
try:
dasha_data = report['modules'].get('dasha', {})
jaimini_data = report['modules'].get('jaimini', {})
chara_dasha_data = jaimini_data.get('chara_dasha', {}) if isinstance(jaimini_data, dict) else {}
birthdate_str = f"{args.year}-{args.month:02d}-{args.day:02d}"
yogini_data = _calc_yogini_dasha(planet_lons.get('Moon', 0), birthdate_str)
report['modules']['yogini_dasha'] = yogini_data
convergence = _calc_dasa_convergence(dasha_data, chara_dasha_data, yogini_data, planet_lons, asc_idx)
moon_lon = planet_lons.get('Moon', 0)
moon_nakshatra_index = int(moon_lon / (360 / 27)) % 27
moon_pada = int((moon_lon % (360 / 27)) / (360 / 108)) + 1
tithi_number = int(((moon_lon - planet_lons.get('Sun', 0)) % 360) / 12) + 1
birth_info_for_alt_dasha = {
'birth_datetime': datetime(args.year, args.month, args.day, args.hour, args.minute),
'moon_nakshatra_index': moon_nakshatra_index,
'moon_pada': moon_pada,
'is_shukla_paksha': 1 <= tithi_number <= 15,
'lagna_rashi_index': asc_idx,
}
try:
from yogini_dasha import calculate_yogini_dasha
yogini_data = calculate_yogini_dasha(birth_info_for_alt_dasha)
report['modules']['yogini_dasha'] = yogini_data
except Exception as alt_e:
yogini_data = {'error': str(alt_e)}
report['modules']['yogini_dasha'] = yogini_data
try:
from ashtottari_dasha import calculate_ashtottari_dasha
ashtottari_data = calculate_ashtottari_dasha(birth_info_for_alt_dasha)
report['modules']['ashtottari_dasha'] = ashtottari_data
except Exception as alt_e:
ashtottari_data = {'error': str(alt_e)}
report['modules']['ashtottari_dasha'] = ashtottari_data
try:
from kalachakra_dasha import calculate_kalachakra_dasha
kalachakra_data = calculate_kalachakra_dasha(birth_info_for_alt_dasha)
report['modules']['kalachakra_dasha'] = kalachakra_data
except Exception as alt_e:
kalachakra_data = {'error': str(alt_e)}
report['modules']['kalachakra_dasha'] = kalachakra_data
convergence = _calc_dasa_convergence(
dasha_data,
chara_dasha_data,
yogini_data,
planet_lons,
asc_idx,
ashtottari_result=ashtottari_data,
kalachakra_result=kalachakra_data,
)
report['modules']['dasa_convergence'] = convergence
except Exception as e:
report['errors'].append(f"dasa-convergence: {e}")
@@ -3651,7 +3811,7 @@ def cmd_full_reading(args):
'modules_computed': module_count,
'errors': error_count,
'status': 'complete' if error_count == 0 else f'{error_count} errors',
'next_step': '⭐ v4.5.0: P1缺口已补齐。新增 transit_multi_reference(四参考点) + dasa_convergence(三系统交叉) + yogini_dasha + d9_navamsa_expanded(逐行星尊严展开)。AI必须使用四参考点分析Transit,Dasa预测必须标注收敛等级。',
'next_step': '⭐ v6.1.6: full-reading 已输出 transit_multi_reference(四参考点) + dasa_convergence(五系统交叉) + yogini_dasha + ashtottari_dasha + kalachakra_dasha + d9_navamsa_expanded。AI必须使用四参考点分析Transit,Dasa预测必须标注多系统收敛等级。',
}
return report
+15 -6
View File
@@ -217,17 +217,26 @@ def calculate_kalachakra_dasha(birth_info: dict) -> dict:
starting_lord = major_periods[0]["lord"] if major_periods else None
starting_rashi = major_periods[0]["rashi"] if major_periods else None
# Determine current period
# Determine current period. Kalachakra cycles through the generated Rashi-year
# sequence repeatedly; older natives should still return a current period.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % total_cycle if total_cycle else age_years
cumulative = 0.0
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // total_cycle) + 1 if total_cycle else 1
break
cumulative += years
return {
"mode": mode,
+15 -6
View File
@@ -136,17 +136,26 @@ def calculate_yogini_dasha(birth_info: dict) -> dict:
major_periods = _build_major_periods(start_idx, birth_dt)
# Determine current period
# Determine current period. Yogini repeats every 36 years; older natives
# should still return a current period instead of None after the first cycle.
now = datetime.now()
current_period = None
age_years = max((now - birth_dt).days / 365.25, 0)
current_in_cycle = age_years % TOTAL_CYCLE
cumulative = 0.0
for p in major_periods:
p_start = datetime.fromisoformat(p["start_date"])
p_end = datetime.fromisoformat(p["end_date"])
if p_start <= now < p_end:
years = p["years"]
if cumulative <= current_in_cycle < cumulative + years:
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
cycle_end = cycle_start + timedelta(days=years * 365.25)
current_period = p.copy()
current_period["elapsed_years"] = (now - p_start).days / 365.25
current_period["remaining_years"] = (p_end - now).days / 365.25
current_period["start_date"] = cycle_start.isoformat()
current_period["end_date"] = cycle_end.isoformat()
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
current_period["remaining_years"] = (cycle_end - now).days / 365.25
current_period["cycle_number"] = int(age_years // TOTAL_CYCLE) + 1
break
cumulative += years
return {
"major": major_periods,