diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d9afdb..cb2d35ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # 印度占星 Skill 更新日志 +## v6.1.7(2026-06-08)—— Yoga F1 首次突破 95% 与 Sankha 误报收紧 + +> **目标**:继续 Yoga 精度冲刺,但只发布源码语义明确、能减少误报且不牺牲召回的规则修复。 + +### 关键改进 +- `full-reading` 与 `yoga` 子命令补齐 D9/D60/Panchanga 上下文注入:出生信息路径会从 `varga-full` 构造 YogaContext,让依赖 Navamsa / Shashtiamsa 的 Yoga 规则在主链路中实际生效。 +- `yoga --context-json` 支持显式传入包含 `d9`、`d60`、`panchanga` 的上下文,便于验证脚本和外部 API 复用同一 Yoga 引擎。 +- 收紧 `sankha_yoga` 第二分支:`L1 and L10 together in a movable sign` 必须是两个不同宫主实际同宫;当 `L1 == L10`(双子/处女/射手/双鱼上升常见)时,不再把共享宫主误判为“1宫主与10宫主同宫”。 +- 修复效果:移除 Stephen Hawking、Max Planck、Cristiano Ronaldo 三个 Sankha Yoga false positive 中的 2 个(整体 FP 38→36),FN 保持 67 不变。 +- 保持“准确率优先”:未为了短期召回扩大 Thrikaala / Dharidhra / Nishkapata 等高风险规则。 + +### 验证 +```bash +python3 -m py_compile scripts/yoga_engine.py scripts/validate_logic_v2.py +python3 scripts/validate_logic_v2.py +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 +``` +- 60 张测试图,82 条可对比规则 +- FP=36,FN=67 +- Precision=96.46%,Recall=93.61%,F1=95.02% +- full-reading smoke:`summary.status=complete`,`errors=[]`,`modules=47`,`modules.yoga.context_layers=['d60','d9','panchanga']` + +--- + ## v6.1.6(2026-06-07)—— full-reading 五系统推运收敛与接口修复 > **目标**:继续“合并隐藏模块→打通引擎→验证可用”的优化路线,将 Ashtottari / Yogini / Kalachakra 从独立模块进一步接入 `full-reading` 主链路。 diff --git a/SKILL.md b/SKILL.md index ca0f81f4..cec0f2e9 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,6 @@ --- name: jyotish-vedic-astrology -version: 6.1.6 +version: 6.1.7 description: 印度占星(Jyotish)专业解盘与推运系统。核心能力:PDF星盘输入→严谨解盘→精确推运应期输出。触发词:印度占星、吠陀占星、Jyotish、解盘、推运、星盘分析、Dasha、Transit、Nakshatra、Yoga、出生时间矫正、PDF星盘、读取PDF、分析PDF星盘、现代解读、误判纠错、Varga分盘、综合分析、过境分析、合盘、婚姻匹配、年运盘、Prashna、Argala、Jaimini、Shadbala、Ashtakavarga、HTML报告、深度解盘。 --- @@ -10,19 +10,19 @@ 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.1.6 | **详细变更**:`CHANGELOG.md` +> **版本**:v6.1.7 | **详细变更**:`CHANGELOG.md` ## Yoga 逻辑验证指标 -| 指标 | v6.0.45(旧基线) | v6.0.47(当前) | -|---|---|---| -| Precision | 83.26% | **92.50%** | -| Recall | 91.52% | **91.71%** | -| **F1 Score** | 87.19% | **92.10%** | -| FP | 193 | **78** | -| FN | 89 | **87** | +| 指标 | v6.0.45(旧基线) | v6.1.7(当前) | +|---|---:|---:| +| Precision | 83.26% | **96.46%** | +| Recall | 91.52% | **93.61%** | +| **F1 Score** | 87.19% | **95.02%** | +| FP | 193 | **36** | +| FN | 89 | **67** | -> v6.0.46: 修复 6 条规则(FP 193→78)。v6.0.47: 修复 dharidhra bidirectional associated() → one-way aspect(),FP 未进一步下降因残余根因在 lord 解析(co-lord vs 古典宫主)和 rasi/house 差异。 +> v6.1.7 当前真实指标以 `references/validation_logic_report.json` 为准:60张测试图、82条可对比规则,FP=36、FN=67、F1=95.02%。关键提升来自 aspect 方向修正、D9/D60 上下文注入、Thrikaala Gnana 收紧、Dharidhra/Navamsa 方法恢复、五系统 Dasha Convergence 接入、full-reading YogaContext 桥接,以及 Sankha Yoga 共享宫主误报收紧。 --- @@ -313,9 +313,9 @@ $PYTHON $SCRIPT <子命令> [参数] --- -**版本**:v6.1.6-full-reading-five-dasha-convergence +**版本**:v6.1.7-yoga-f1-95-sankha-tightening **创建日期**:2026-04-20 -**最后更新**: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` 为准。) +**最后更新**:2026-06-08(v6.1.7 在 v6.1.6 五系统推运收敛基础上,收紧 Sankha Yoga 第二分支:L1/L10 共享宫主不再视为两宫主同宫,Yoga F1 首次达到 95.02%。Yoga 当前以 `references/validation_logic_report.json` 为准。) --- diff --git a/references/validation_logic_report.json b/references/validation_logic_report.json index 9218848a..9d2a9686 100644 --- a/references/validation_logic_report.json +++ b/references/validation_logic_report.json @@ -2,7 +2,7 @@ "summary": { "charts_tested": 60, "comparable_rules": 82, - "skill_total": 1020, + "skill_total": 1018, "pyjhora_total": 1049, "unmapped_pyjhora": 718, "missing_mappings": { @@ -99,27 +99,22 @@ ] }, "agreements": 982, - "false_positives": 38, + "false_positives": 36, "false_negatives": 67, - "precision": 0.9627, + "precision": 0.9646, "recall": 0.9361, - "f1": 0.9493 + "f1": 0.9502 }, "false_positives": [ - { - "chart": "Albert Einstein", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra 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": "Albert Einstein", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga" }, { "chart": "Nikola Tesla", @@ -127,9 +122,9 @@ "rule_name": "Krisanga Yoga" }, { - "chart": "Stephen Hawking", - "rule_id": "sankha_yoga", - "rule_name": "Sankha Yoga" + "chart": "Nikola Tesla", + "rule_id": "bvr_bhratruvriddhi_precise", + "rule_name": "Bhratruvriddhi Yoga" }, { "chart": "Charles Darwin", @@ -153,13 +148,13 @@ }, { "chart": "Richard Feynman", - "rule_id": "bvr_018_ubhayachara_precise", - "rule_name": "Ubhayachara Yoga" + "rule_id": "bvr_nishkapata_precise", + "rule_name": "Nishkapata Yoga" }, { "chart": "Richard Feynman", - "rule_id": "bvr_nishkapata_precise", - "rule_name": "Nishkapata Yoga" + "rule_id": "bvr_018_ubhayachara_precise", + "rule_name": "Ubhayachara Yoga" }, { "chart": "Mahatma Gandhi", @@ -188,13 +183,13 @@ }, { "chart": "Barack Obama", - "rule_id": "bvr_yukthi_samanwithavagmi_yoga", - "rule_name": "Yukthi Samanwithavagmi Yoga" + "rule_id": "bvr_bandhubhisthyaktha_precise", + "rule_name": "Bandhubhisthyaktha Yoga" }, { "chart": "Barack Obama", - "rule_id": "bvr_bandhubhisthyaktha_precise", - "rule_name": "Bandhubhisthyaktha Yoga" + "rule_id": "bvr_yukthi_samanwithavagmi_yoga", + "rule_name": "Yukthi Samanwithavagmi Yoga" }, { "chart": "Pablo Picasso", @@ -216,20 +211,15 @@ "rule_id": "adhi_yoga", "rule_name": "Adhi Yoga" }, - { - "chart": "MS Dhoni", - "rule_id": "bvr_karma_malika_precise", - "rule_name": "Karma Malika Yoga" - }, { "chart": "MS Dhoni", "rule_id": "bvr_mathibhramana_yoga", "rule_name": "Mathibhramana Yoga" }, { - "chart": "Lionel Messi", - "rule_id": "brahma_yoga", - "rule_name": "Brahma Yoga" + "chart": "MS Dhoni", + "rule_id": "bvr_karma_malika_precise", + "rule_name": "Karma Malika Yoga" }, { "chart": "Lionel Messi", @@ -237,14 +227,14 @@ "rule_name": "Vosi Yoga" }, { - "chart": "Cristiano Ronaldo", - "rule_id": "bvr_dwadasa_sahodara_yoga", - "rule_name": "Dwadasa Sahodara Yoga" + "chart": "Lionel Messi", + "rule_id": "brahma_yoga", + "rule_name": "Brahma Yoga" }, { "chart": "Cristiano Ronaldo", - "rule_id": "sankha_yoga", - "rule_name": "Sankha Yoga" + "rule_id": "bvr_dwadasa_sahodara_yoga", + "rule_name": "Dwadasa Sahodara Yoga" }, { "chart": "Pele", @@ -273,13 +263,13 @@ }, { "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": "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": "J. Krishnamurti", @@ -288,13 +278,13 @@ }, { "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" }, { "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" } ], "false_negatives": [ @@ -307,14 +297,6 @@ "bahu_puthra_yoga_221" ] }, - { - "chart": "Marie Curie", - "rule_id": "parvata_yoga", - "rule_name": "Parvata Yoga", - "pyjhora_names": [ - "parvata_yoga" - ] - }, { "chart": "Marie Curie", "rule_id": "bvr_grihanasa_yoga", @@ -332,6 +314,14 @@ "thrikaala_gnana_yoga" ] }, + { + "chart": "Marie Curie", + "rule_id": "parvata_yoga", + "rule_name": "Parvata Yoga", + "pyjhora_names": [ + "parvata_yoga" + ] + }, { "chart": "Nikola Tesla", "rule_id": "bvr_thrikaala_gnana_yoga", @@ -370,14 +360,6 @@ "parannabhojana_yoga" ] }, - { - "chart": "Mahatma Gandhi", - "rule_id": "bvr_pisacha_grastha_yoga", - "rule_name": "Pisacha Grastha Yoga", - "pyjhora_names": [ - "pisacha_grastha_yoga" - ] - }, { "chart": "Mahatma Gandhi", "rule_id": "brahma_yoga", @@ -395,6 +377,14 @@ "matrudeerghayur_yoga_197" ] }, + { + "chart": "Mahatma Gandhi", + "rule_id": "bvr_pisacha_grastha_yoga", + "rule_name": "Pisacha Grastha Yoga", + "pyjhora_names": [ + "pisacha_grastha_yoga" + ] + }, { "chart": "Narendra Modi", "rule_id": "bvr_nishkapata_precise", @@ -412,14 +402,6 @@ "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", @@ -429,6 +411,14 @@ "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", @@ -475,6 +465,15 @@ "matru_satrutwa_yoga" ] }, + { + "chart": "Margaret Thatcher", + "rule_id": "bvr_nishkapata_precise", + "rule_name": "Nishkapata Yoga", + "pyjhora_names": [ + "nishkapata_yoga_205", + "nishkapata_yoga_206" + ] + }, { "chart": "Margaret Thatcher", "rule_id": "bvr_dharidhra_11_precise", @@ -489,15 +488,6 @@ "dharidhra_yoga_152" ] }, - { - "chart": "Margaret Thatcher", - "rule_id": "bvr_nishkapata_precise", - "rule_name": "Nishkapata Yoga", - "pyjhora_names": [ - "nishkapata_yoga_205", - "nishkapata_yoga_206" - ] - }, { "chart": "Rabindranath Tagore", "rule_id": "bvr_kapata_yoga", @@ -534,6 +524,14 @@ "kapata_yoga_204" ] }, + { + "chart": "Wolfgang Mozart", + "rule_id": "bvr_theevrabuddhi_yoga", + "rule_name": "Theevrabuddhi Yoga", + "pyjhora_names": [ + "theevrabuddhi_yoga" + ] + }, { "chart": "Wolfgang Mozart", "rule_id": "bvr_raja_bhanga_yoga", @@ -543,14 +541,6 @@ "raja_bhanga_yoga_299" ] }, - { - "chart": "Wolfgang Mozart", - "rule_id": "bvr_theevrabuddhi_yoga", - "rule_name": "Theevrabuddhi Yoga", - "pyjhora_names": [ - "theevrabuddhi_yoga" - ] - }, { "chart": "Wolfgang Mozart", "rule_id": "bvr_pittharoga_yoga", @@ -645,20 +635,6 @@ "bahu_sthree_yoga" ] }, - { - "chart": "Azim Premji", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga", - "pyjhora_names": [ - "dharidhra_yoga_144", - "dharidhra_yoga_147", - "dharidhra_yoga_148", - "dharidhra_yoga_149", - "dharidhra_yoga_150", - "dharidhra_yoga_151", - "dharidhra_yoga_152" - ] - }, { "chart": "Azim Premji", "rule_id": "bvr_ayatna_griha_prapta_yoga", @@ -686,6 +662,20 @@ "nishkapata_yoga_206" ] }, + { + "chart": "Azim Premji", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga", + "pyjhora_names": [ + "dharidhra_yoga_144", + "dharidhra_yoga_147", + "dharidhra_yoga_148", + "dharidhra_yoga_149", + "dharidhra_yoga_150", + "dharidhra_yoga_151", + "dharidhra_yoga_152" + ] + }, { "chart": "Muhammad Ali", "rule_id": "pushkala_yoga", @@ -713,11 +703,11 @@ }, { "chart": "Lionel Messi", - "rule_id": "bvr_matrunasa_precise", - "rule_name": "Matrunasa Yoga", + "rule_id": "bvr_matrudeerghayur_yoga", + "rule_name": "Matrudeerghayur Yoga", "pyjhora_names": [ - "matrunasa_yoga_198", - "matrunasa_yoga_199" + "matrudeerghayur_yoga_196", + "matrudeerghayur_yoga_197" ] }, { @@ -730,6 +720,15 @@ "kapata_yoga_204" ] }, + { + "chart": "Lionel Messi", + "rule_id": "bvr_matrunasa_precise", + "rule_name": "Matrunasa Yoga", + "pyjhora_names": [ + "matrunasa_yoga_198", + "matrunasa_yoga_199" + ] + }, { "chart": "Lionel Messi", "rule_id": "bvr_dharidhra_11_precise", @@ -744,15 +743,6 @@ "dharidhra_yoga_152" ] }, - { - "chart": "Lionel Messi", - "rule_id": "bvr_matrudeerghayur_yoga", - "rule_name": "Matrudeerghayur Yoga", - "pyjhora_names": [ - "matrudeerghayur_yoga_196", - "matrudeerghayur_yoga_197" - ] - }, { "chart": "Cristiano Ronaldo", "rule_id": "bvr_matru_sneha_yoga", @@ -810,6 +800,14 @@ "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", @@ -819,11 +817,11 @@ ] }, { - "chart": "Dalai Lama", - "rule_id": "bvr_utthama_graha_yoga", - "rule_name": "Utthama Griha Yoga", + "chart": "Pope Francis", + "rule_id": "bvr_bhratruvriddhi_precise", + "rule_name": "Bhratruvriddhi Yoga", "pyjhora_names": [ - "utthama_graha_yoga" + "bhratruvriddhi_yoga" ] }, { @@ -836,11 +834,12 @@ ] }, { - "chart": "Pope Francis", - "rule_id": "bvr_bhratruvriddhi_precise", - "rule_name": "Bhratruvriddhi Yoga", + "chart": "J. Krishnamurti", + "rule_id": "bvr_grihanasa_yoga", + "rule_name": "Grihanasa Yoga", "pyjhora_names": [ - "bhratruvriddhi_yoga" + "grihanasa_yoga_191", + "grihanasa_yoga_192" ] }, { @@ -851,15 +850,6 @@ "vanchana_chora_bheethi_yoga" ] }, - { - "chart": "J. Krishnamurti", - "rule_id": "bvr_grihanasa_yoga", - "rule_name": "Grihanasa Yoga", - "pyjhora_names": [ - "grihanasa_yoga_191", - "grihanasa_yoga_192" - ] - }, { "chart": "Ramana Maharshi", "rule_id": "bvr_pittharoga_yoga", @@ -868,6 +858,14 @@ "pittharoga_yoga" ] }, + { + "chart": "Paramahansa Yogananda", + "rule_id": "bvr_bandhubhisthyaktha_precise", + "rule_name": "Bandhubhisthyaktha Yoga", + "pyjhora_names": [ + "bandhubhisthyaktha_yoga" + ] + }, { "chart": "Paramahansa Yogananda", "rule_id": "bvr_matrunasa_precise", @@ -877,14 +875,6 @@ "matrunasa_yoga_199" ] }, - { - "chart": "Paramahansa Yogananda", - "rule_id": "bvr_bandhubhisthyaktha_precise", - "rule_name": "Bandhubhisthyaktha Yoga", - "pyjhora_names": [ - "bandhubhisthyaktha_yoga" - ] - }, { "chart": "Paramahansa Yogananda", "rule_id": "bvr_dattha_puthra_yoga", diff --git a/references/yoga_rules.json b/references/yoga_rules.json index d156c040..53976667 100644 --- a/references/yoga_rules.json +++ b/references/yoga_rules.json @@ -1288,7 +1288,7 @@ "category": "auspicious", "logic": { "type": "custom", - "expr": "\n# PyJHora/BVR-12: (L1 strong and L5/L6 in mutual kendras) OR\n# (L1 and L10 together in a movable sign and L9 strong). PyJHora strong() uses\n# house_strength >= FRIEND, not generic kendra/trikona strength.\nl1, l5, l6, l9, l10 = [lord(h) for h in [1, 5, 6, 9, 10]]\nif not all(p in ctx.planets for p in [l1, l5, l6, l9, l10]):\n False\nelse:\n l1h, l5h, l6h, l9h, l10h = [house_of(p) for p in [l1, l5, l6, l9, l10]]\n cond1 = strong(l1) and offset(l5h, l6h) in [0, 3, 6, 9] and offset(l6h, l5h) in [0, 3, 6, 9]\n cond2 = strong(l9) and house_of(l1) == house_of(l10) and movable_house(l1h)\n cond1 or cond2\n", + "expr": "\n# PyJHora/BVR-12: (L1 strong and L5/L6 in mutual kendras) OR\n# (L1 and L10 together in a movable sign and L9 strong). PyJHora strong() uses\n# house_strength >= FRIEND, not generic kendra/trikona strength.\nl1, l5, l6, l9, l10 = [lord(h) for h in [1, 5, 6, 9, 10]]\nif not all(p in ctx.planets for p in [l1, l5, l6, l9, l10]):\n False\nelse:\n l1h, l5h, l6h, l9h, l10h = [house_of(p) for p in [l1, l5, l6, l9, l10]]\n cond1 = strong(l1) and offset(l5h, l6h) in [0, 3, 6, 9] and offset(l6h, l5h) in [0, 3, 6, 9]\n # The second branch requires actual conjunction of two distinct lords; if L1==L10\n # (common for Gemini/Virgo/Sagittarius/Pisces ascendants), PyJHora does not count\n # the shared lord as \"L1 and L10 together\" for Sankha Yoga.\n cond2 = l1 != l10 and strong(l9) and house_of(l1) == house_of(l10) and movable_house(l1h)\n cond1 or cond2\n", "combo_template": "上升主强且5/6宫主互为Kendra,或1/10宫主合于变动星座且9宫主强" }, "effects": [ @@ -1299,7 +1299,8 @@ "strength": "强", "enabled": true, "source_reference": "BVR-12: L1 strong, L5/L6 in mutual Kendras", - "bvr_chapter": "Chapter 2" + "bvr_chapter": "Chapter 2", + "accuracy_note": "v6.1.7: tightened movable-sign branch to require distinct L1/L10 lords; removes shared-lord false positives for dual ascendants." }, { "id": "bheri_yoga", diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 3c34d686..7c9175c8 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -418,6 +418,60 @@ def _add_chart_args(p): p.add_argument('--node-mode', default='mean', choices=['mean', 'true'], help='Rahu/Ketu节点口径:mean=Mean Node(默认,JHora常用/Swiss direct baseline),true=True Node(PyJHora默认)') +def _varga_chart_to_yoga_context(varga_chart): + """Convert varga.py output into yoga_engine context chart shape.""" + if not isinstance(varga_chart, dict): + return {} + asc = varga_chart.get('Ascendant', {}) + asc_sign = asc.get('sign') if isinstance(asc, dict) else None + asc_idx = SIGNS.index(asc_sign) if asc_sign in SIGNS else None + planets_ctx = {} + for pn, pd in varga_chart.items(): + if pn in ('Ascendant', '_meta', '_dignity', '_d9_analysis', '_d60_analysis'): + continue + if not isinstance(pd, dict) or 'sign' not in pd: + continue + sign = pd.get('sign') + sign_idx = pd.get('sign_idx') + if sign_idx is None and sign in SIGNS: + sign_idx = SIGNS.index(sign) + house = None + if asc_idx is not None and sign_idx is not None: + house = ((int(sign_idx) - asc_idx) % 12) + 1 + planets_ctx[pn] = { + 'sign': sign, + 'house': house, + 'degree': pd.get('degree_in_sign', pd.get('degree')), + 'degree_in_sign': pd.get('degree_in_sign', pd.get('degree')), + 'sign_idx': sign_idx, + } + return { + 'ascendant': asc_sign, + 'ascendant_degree': asc.get('degree_in_sign') if isinstance(asc, dict) else None, + 'planets': planets_ctx, + } + + +def _build_yoga_context_from_vargas(varga_result, planet_lons=None): + """Build optional YogaContext payload with D9/D60 and basic panchanga.""" + context = {} + if isinstance(varga_result, dict): + d9 = varga_result.get('D9_Navamsa') or {} + d60 = varga_result.get('D60_Shashtiamsa') or varga_result.get('D60_Shashtyamsa') or {} + if d9: + context['d9'] = _varga_chart_to_yoga_context(d9) + if d60: + context['d60'] = _varga_chart_to_yoga_context(d60) + if isinstance(planet_lons, dict) and 'Moon' in planet_lons and 'Sun' in planet_lons: + tithi_no = int(((planet_lons.get('Moon', 0) - planet_lons.get('Sun', 0)) % 360) / 12) + 1 + context['panchanga'] = { + 'tithi': tithi_no, + 'paksha': 'waxing' if 1 <= tithi_no <= 15 else 'waning', + 'is_waning_moon': tithi_no > 15, + } + return context + + # ============================================================================ # 公共星盘计算(供 chart/shadbala/ashtakavarga 共用,v3.4提取) # ============================================================================ @@ -608,6 +662,8 @@ def cmd_yoga(args): planets = {} asc = args.ascendant or "Aries" + yoga_context = None + if args.planets: for item in args.planets.split(','): parts = item.strip().split(':') @@ -619,6 +675,12 @@ def cmd_yoga(args): except ValueError: pass planets[parts[0].strip()] = pdata + context_json = getattr(args, 'context_json', None) + if context_json: + try: + yoga_context = json.loads(context_json) + except Exception: + yoga_context = None else: required_birth_fields = ["year", "month", "day", "hour", "minute", "lat", "lon"] has_birth_input = all(getattr(args, field, None) is not None for field in required_birth_fields) @@ -630,6 +692,7 @@ def cmd_yoga(args): if chart is None: return {"error": "swisseph未安装"} asc = chart.get("ascendant", {}).get("sign", asc) + planet_lons_for_varga = {} for pname, pd in chart.get("planets", {}).items(): if isinstance(pd, dict) and "sign" in pd and "house" in pd: planets[pname] = { @@ -637,13 +700,25 @@ def cmd_yoga(args): "house": pd["house"], "degree": pd.get("degree_in_sign", pd.get("degree")), } + if pd.get("degree_raw") is not None: + planet_lons_for_varga[pname] = pd.get("degree_raw") + try: + from varga import calc_all_vargas + asc_deg_raw = chart.get("ascendant", {}).get("degree_raw") + if asc_deg_raw is not None and planet_lons_for_varga: + yoga_context = _build_yoga_context_from_vargas( + calc_all_vargas(planet_lons_for_varga, asc_deg_raw, [9, 60]), + planet_lons_for_varga, + ) + except Exception: + yoga_context = None ai = SIGNS.index(asc) if asc in SIGNS else 0 kl = list(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 4, 7, 10]])) tl = list(set([SIGN_LORDS[SIGNS[(ai + h - 1) % 12]] for h in [1, 5, 9]])) # 调用数据驱动引擎(yoga_engine.py) - yogas = detect_yogas(planets, asc) + yogas = detect_yogas(planets, asc, context=yoga_context) return { "ascendant": asc, @@ -3076,6 +3151,32 @@ def cmd_full_reading(args): if d1_data: varga_result["D1_Rashi"] = d1_data report['modules']['varga_full'] = varga_result + + # v6.1.7: Re-run Yoga with D9/D60 context after varga-full is available. + # The earlier Step 3 remains a D1-only fallback for backward compatibility. + try: + yoga_context = _build_yoga_context_from_vargas(varga_result, planet_lons) + yoga_planets = {} + for pn, pd in planets.items(): + if isinstance(pd, dict) and 'sign' in pd and 'house' in pd: + yoga_planets[pn] = { + 'sign': pd['sign'], + 'house': pd['house'], + 'degree': pd.get('degree_in_sign', pd.get('degree')), + } + yoga_result = cmd_yoga(type('Args', (), { + 'ascendant': asc_sign, + 'planets': ','.join([ + f"{k}:{v['sign']}:{v['house']}" + (f":{v['degree']}" if v.get('degree') is not None else "") + for k, v in yoga_planets.items() + ]), + 'context_json': json.dumps(yoga_context, ensure_ascii=False), + })()) + yoga_result['context_layers'] = sorted(k for k, v in yoga_context.items() if v) + report['modules']['yoga'] = yoga_result + except Exception as yoga_ctx_e: + report['errors'].append(f"yoga-context-injection: {yoga_ctx_e}") + report['modules']['vargottama'] = _calc_vargottama(planets, varga_result) report['modules']['pushkara'] = _calc_pushkara_flags(planets) except Exception as e: @@ -3901,6 +4002,7 @@ def main(): _add_chart_args(p) p.add_argument('--ascendant', default=None) p.add_argument('--planets', default=None, help="格式: 'Sun:Aries:9[:10.5],Moon:Aquarius:7[:15.2],...';若提供则优先使用并忽略出生信息") + p.add_argument('--context-json', default=None, help='可选:传入包含 d9/d60/panchanga 的 YogaContext JSON') for action in p._actions: if action.dest in ['year', 'month', 'day', 'hour', 'minute', 'lat', 'lon']: action.required = False