Release v6.0.39 Yoga source parity

This commit is contained in:
732642856
2026-06-05 22:03:06 +08:00
parent 58fc615b16
commit 94be318d56
5 changed files with 580 additions and 1230 deletions
+47
View File
@@ -1,5 +1,52 @@
# 印度占星 Skill 更新日志
## v6.0.392026-06-05)—— Yoga F1 90% 源码口径冲刺
> **目标**:继续用户要求的“准确率优先”路线,不为指标盲目调参;只在确认 PyJHora 源码口径后修改规则或 helper,使 D1/Rasi Yoga 逻辑验证首次突破 F1 90%。
### 引擎与 helper 修复
- `scripts/yoga_engine.py` 新增 PyJHora-style Scorpio/Aquarius 动态双主星 resolver
- Scorpio 由 `Mars` / `Ketu` 动态择强;
- Aquarius 由 `Saturn` / `Rahu` 动态择强;
- `_house_lords``_d9_house_lords``is_own_sign()``navamsa_dispositor()` 与 custom `house_strength()` 均接入动态主星口径。
- 新增 `graha_aspects_house(p, h)`,用于显式判断 Graha Drishti 对宫位的影响。
- 新增 `pyjhora_planets_aspecting_raasi(p, h)`,复刻 PyJHora `house.planets_aspecting_the_raasi()` 在 Bandhu Pujya Yoga 194 中的实际源码行为,避免用常规 Rasi Drishti 语义替代源码 oracle。
### 高影响规则对齐
- `bheri_yoga`:对齐 PyJHora/BVR-45,改为“9宫主有力且 1/2/7/12 宫均有行星,或 Jupiter/Venus/Lagna lord 三者互为 Kendra”。
- `bvr_sada_sanchara_precise`:对齐 PyJHora/BVR-117,改为“Lagna lord 在 movable sign,或 Lagna lord 的 dispositor 在 movable sign”,不再错误纳入 Moon movable 条件。
- `bvr_bandhu_pujya_yoga`:对齐 PyJHora/BVR 193-194,修复 Jupiter 与 4宫/4宫主关联判断:
- 保留 193benefic L4 被另一吉星相位且 Mercury 在 Lagna
- 修复 194Jupiter 在4宫、PyJHora-style 影响4宫 rasi、Jupiter 本身为动态4宫主、与 L4 同宫或相位 L4。
### 验证结果
- `scripts/validate_logic_v2.py`
- Precision **93.82%**v6.0.38 为 88.37%
- Recall **86.70%**v6.0.38 为 81.09%
- F1 **90.12%**v6.0.38 为 84.57%
- FP61FN142
- `scripts/run_quality_gate.py --skip-yoga-logic`:通过。
- pytest **35 passed**
- BPHS/Ashtakavarga 不变量 **18/18**
- golden case 通过
- capability audit valid0 problem / 0 warning
### 规则库状态
- JSON 规则总数:**475**
- 启用规则:**381**
- 当前验证报告:`references/validation_logic_report.json`
### 后续优化点
- 下一轮 Top residual 仍建议只做源码确认后再改:`bvr_vanchana_chora_bheethi_yoga``bvr_kaalanirdesat_puthranaasa_yoga``bvr_kapata_yoga``bvr_bhaga_chumbana_yoga``bvr_mathibhramana_yoga``bvr_amala_precise``bvr_sumukha_precise`
- 建议继续把 PyJHora dynamic benefics/malefics、Upagraha/Gulika house-index、以及 `planets_aspecting_the_raasi()` 的边界行为抽象为可复用 helper,减少 custom 规则重复。
---
## v6.0.382026-06-05)—— Yoga 源码口径对齐与 F1 深水区提升
> **目标**:继续 v6.0.37 的 context-aware 路线,但不盲目扩规则;优先对齐 PyJHora 源码中已经明确的 house-strength、Navamsa dispositor、watery sign/planet、以及验证脚本路径口径,减少残余 Top FP/FN。
+4 -4
View File
@@ -1,6 +1,6 @@
---
name: jyotish-vedic-astrology
version: 6.0.38
version: 6.0.39
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.38-yoga-source-alignment | **详细变更**`CHANGELOG.md`
> **版本**v6.0.39-yoga-f1-90-source-parity | **详细变更**`CHANGELOG.md`
---
@@ -301,9 +301,9 @@ $PYTHON $SCRIPT <子命令> [参数]
---
**版本**v6.0.38-yoga-source-alignment
**版本**v6.0.39-yoga-f1-90-source-parity
**创建日期**2026-04-20
**最后更新**2026-06-05v6.0.38 对齐 PyJHora house-strength 口径,修复 `sankha_yoga``kahala_yoga``bvr_kaahala_yoga``bvr_rogagrastha_precise``bvr_swaveeryaddhana_precise``bvr_bahu_puthra_precise``bvr_dehasthoulya_yoga`残余高影响规则,并固定 `validate_logic_v2.py` 从任意 cwd 运行都读写 skill 内部 references;当前 475 条 JSON 规则、381 条启用规则,D1/Rasi 逻辑验证 Precision=88.37%、Recall=81.09%、F1=84.57%,报告以 `references/validation_logic_report.json` 为准)
**最后更新**2026-06-05v6.0.39 继续对齐 PyJHora Yoga 源码口径,实现 Scorpio/Aquarius 动态 co-lord resolver,修复 `bheri_yoga``bvr_sada_sanchara_precise``bvr_bandhu_pujya_yoga` 等高影响规则;当前 475 条 JSON 规则、381 条启用规则,D1/Rasi 逻辑验证 Precision=93.82%、Recall=86.70%、F1=90.12%,质量门禁通过,报告以 `references/validation_logic_report.json` 为准)
---
File diff suppressed because it is too large Load Diff
+21 -100
View File
@@ -1300,8 +1300,8 @@
"category": "auspicious",
"logic": {
"type": "custom",
"expr": "\nlord_9 = lord(9)\nif lord_9 not in ctx.planets or house_of(lord_9) != 2:\n False\nelse:\n jv_in_kendra = [p for p in ['Jupiter', 'Venus'] if p in ctx.planets and kendra(house_of(p))]\n bool(jv_in_kendra)\n",
"combo_template": "9宫主在2宫+木星/金星在角宫"
"expr": "\n# PyJHora/BVR-45 Bheri Yoga. 9th lord strong and either 1/2/7/12 occupied,\n# or Jupiter, Venus and Lagna lord are pairwise in mutual kendras.\nl1 = lord(1)\nl9 = lord(9)\nif l1 not in ctx.planets or l9 not in ctx.planets:\n False\nelse:\n ninth_strong = strong(l9, include_neutral=True)\n path_a = all(bool(occupants(h)) for h in [1, 2, 7, 12])\n def mutual_kendra_planets(a, b):\n return a in ctx.planets and b in ctx.planets and offset(house_of(a), house_of(b)) in [0, 3, 6, 9] and offset(house_of(b), house_of(a)) in [0, 3, 6, 9]\n path_b = mutual_kendra_planets('Jupiter', 'Venus') and mutual_kendra_planets('Jupiter', l1) and mutual_kendra_planets('Venus', l1)\n ninth_strong and (path_a or path_b)\n",
"combo_template": "PyJHora aligned Bheri Yoga: 9宫主强且满足占宫/互为角宫条件"
},
"effects": [
"品德高尚",
@@ -1322,7 +1322,7 @@
"category": "auspicious",
"logic": {
"type": "custom",
"expr": "\nasc_lord = SIGN_LORDS.get(ctx.ascendant)\nif asc_lord not in ctx.planets:\n False\nelse:\n asc_strong = exalted(asc_lord) or own(asc_lord)\n other_strong = [p for p in ctx.planets if p != asc_lord and p not in ['Rahu', 'Ketu'] and (exalted(p) or own(p))]\n asc_strong and bool(other_strong)\n",
"expr": "\n# PyJHora/BVR-46: planets with strength > FRIEND in both quadrants and trines,\n# and Lagna lord strength > FRIEND. Strength > FRIEND means own/exalted only.\nl1 = lord(1)\nif l1 not in ctx.planets:\n False\nelse:\n strong_kendra = any(p in ctx.planets and house_of(p) in [1, 4, 7, 10] and house_strength(p) is not None and house_strength(p) > 3 for p in PLANET_INDEX)\n strong_trine = any(p in ctx.planets and house_of(p) in [1, 5, 9] and house_strength(p) is not None and house_strength(p) > 3 for p in PLANET_INDEX)\n strong_kendra and strong_trine and house_strength(l1) is not None and house_strength(l1) > 3\n",
"combo_template": "上升主星有力+其他行星有力"
},
"effects": [
@@ -8217,15 +8217,8 @@
"bvr_id": "BVR-2",
"category": "chandra",
"logic": {
"type": "has_planet_in_house_from",
"planets": "all",
"from": "Moon",
"houses": [
2
],
"exclude": [
"Sun"
],
"type": "custom",
"expr": "\n# PyJHora/BVR-2 Sunapha: target 2nd from Moon has at least one planet and Sun is absent.\nmoon_h = house_of('Moon')\nif moon_h is None:\n False\nelse:\n target = ((moon_h - 1 + 1) % 12) + 1\n ps = occupants(target)\n bool(ps) and 'Sun' not in ps\n",
"combo_template": "{planets}在月亮的第2宫"
},
"effects": [
@@ -8248,15 +8241,8 @@
"bvr_id": "BVR-3",
"category": "chandra",
"logic": {
"type": "has_planet_in_house_from",
"planets": "all",
"from": "Moon",
"houses": [
12
],
"exclude": [
"Sun"
],
"type": "custom",
"expr": "\n# PyJHora/BVR-3 Anapha: target 12th from Moon has at least one planet and Sun is absent.\nmoon_h = house_of('Moon')\nif moon_h is None:\n False\nelse:\n target = ((moon_h - 1 + 11) % 12) + 1\n ps = occupants(target)\n bool(ps) and 'Sun' not in ps\n",
"combo_template": "{planets}在月亮的第12宫"
},
"effects": [
@@ -8279,33 +8265,9 @@
"bvr_id": "BVR-4",
"category": "chandra",
"logic": {
"type": "and",
"conditions": [
{
"type": "has_planet_in_house_from",
"planets": "all",
"from": "Moon",
"houses": [
2
],
"exclude": [
"Sun"
],
"combo_template": "{planets}在月亮的第2宫"
},
{
"type": "has_planet_in_house_from",
"planets": "all",
"from": "Moon",
"houses": [
12
],
"exclude": [
"Sun"
],
"combo_template": "{planets}在月亮的第12宫"
}
]
"type": "custom",
"expr": "\n# PyJHora/BVR-4 Duradhara: both Sunapha and Anapha conditions are true.\nmoon_h = house_of('Moon')\nif moon_h is None:\n False\nelse:\n h2 = ((moon_h - 1 + 1) % 12) + 1\n h12 = ((moon_h - 1 + 11) % 12) + 1\n ps2 = occupants(h2)\n ps12 = occupants(h12)\n bool(ps2) and 'Sun' not in ps2 and bool(ps12) and 'Sun' not in ps12\n",
"combo_template": "PyJHora Chandra yoga logic"
},
"effects": [
"月亮前后均有支撑",
@@ -9194,7 +9156,7 @@
"category": "travel",
"logic": {
"type": "custom",
"expr": "\n# Lagna lord or Moon in movable sign (Chara Rashi)\nchara_signs = {'Aries', 'Cancer', 'Libra', 'Capricorn'}\nasc_lord = lord(1)\nin_chara = False\nif asc_lord in ctx.planets:\n if ctx.planets[asc_lord].get('sign') in chara_signs:\n in_chara = True\nif 'Moon' in ctx.planets and ctx.planets['Moon'].get('sign') in chara_signs:\n in_chara = True\nin_chara\n",
"expr": "\n# PyJHora/BVR-117 Sada Sanchara: Lagna lord OR the dispositor of Lagna lord is in a movable sign.\nl1 = lord(1)\nif l1 not in ctx.planets:\n False\nelse:\n l1_h = house_of(l1)\n l1_dispositor = lord(l1_h) if l1_h else None\n sign_of(l1) in MOVABLE_SIGNS or (l1_dispositor in ctx.planets and sign_of(l1_dispositor) in MOVABLE_SIGNS)\n",
"combo_template": "上升主或月亮在变动星座"
},
"effects": [
@@ -9259,7 +9221,7 @@
"category": "auspicious",
"logic": {
"type": "custom",
"expr": "\n# PyJHora/BVR-177: 3rd lord, Mars, or 3rd house joined/aspected by benefics.\nlord_3 = lord(3)\nhouse_3 = 3\nbenefics = [p for p in BENEFICS if p in ctx.planets]\ndef benefic_link_to_planet(p):\n return p in ctx.planets and strong(p) and any(b != p and (same_house(b, p) or aspect(b, p)) for b in benefics)\nhouse_joined = any(house_of(b) == house_3 for b in benefics)\nhouse_aspected = any(aspects_house(b, house_3) for b in benefics)\nbenefic_link_to_planet(lord_3) or benefic_link_to_planet('Mars') or house_joined or house_aspected\n",
"expr": "\n# PyJHora/BVR-177: 3rd lord, Mars, or 3rd house joined/aspected by benefics and strong.\n# Planet-to-house aspect uses rasi drishti; planet-to-planet uses graha drishti.\nlord_3 = lord(3)\nbenefics = [p for p in BENEFICS if p in ctx.planets]\ndef benefic_link_to_planet(p):\n return p in ctx.planets and strong(p) and any(b != p and (same_house(b, p) or aspect(b, p)) for b in benefics)\nhouse_joined = any(house_of(b) == 3 for b in benefics)\nhouse_aspected = any(rasi_aspects_house(b, 3) for b in benefics)\nbenefic_link_to_planet(lord_3) or benefic_link_to_planet('Mars') or house_joined or house_aspected\n",
"combo_template": "3宫主、火星或3宫受吉星连接"
},
"effects": [
@@ -9301,7 +9263,7 @@
"category": "durbhaga",
"logic": {
"type": "custom",
"expr": "\n# PyJHora/BVR 198-199.\nif 'Moon' not in ctx.planets:\n False\nelse:\n evil = set(MALEFICS)\n moon_h = house_of('Moon')\n prev_h = ((moon_h - 2) % 12) + 1 if moon_h else None\n next_h = (moon_h % 12) + 1 if moon_h else None\n hemmed = bool(prev_h and next_h and house_has_malefic(prev_h) and house_has_malefic(next_h))\n associated = any(same_house('Moon', p) for p in evil if p in ctx.planets)\n aspected = any(aspect(p, 'Moon') for p in evil if p in ctx.planets)\n y198 = hemmed or associated or aspected\n l4 = lord(4)\n nav_lord = navamsa_dispositor(l4) if l4 else None\n final_planet = navamsa_dispositor(nav_lord) if nav_lord else None\n y199 = final_planet in ctx.planets and house_of(final_planet) in [6, 8, 12]\n y198 or y199\n",
"expr": "\n# PyJHora/BVR 198-199. Yoga 198 uses natural malefics and rasi drishti on Moon's rasi.\nif 'Moon' not in ctx.planets:\n False\nelse:\n moon_h = house_of('Moon')\n prev_h = ((moon_h - 2) % 12) + 1 if moon_h else None\n next_h = (moon_h % 12) + 1 if moon_h else None\n hemmed = bool(prev_h and next_h and house_has_malefic(prev_h) and house_has_malefic(next_h))\n associated = any(same_house('Moon', p) for p in MALEFICS if p in ctx.planets)\n aspected = any(rasi_aspects_house(p, moon_h) for p in MALEFICS if p in ctx.planets)\n y198 = hemmed or associated or aspected\n l4 = lord(4)\n nav_lord = navamsa_dispositor(l4) if l4 else None\n final_planet = navamsa_dispositor(nav_lord) if nav_lord else None\n y199 = final_planet in ctx.planets and house_of(final_planet) in [6, 8, 12]\n y198 or y199\n",
"combo_template": "月亮受凶星夹制/合相/相位,或4宫主Navamsa链最终落6/8/12"
},
"effects": [
@@ -9755,24 +9717,9 @@
"bvr_id": null,
"category": "kalatra",
"logic": {
"type": "compound_conditions",
"conditions": [
{
"type": "compound",
"operator": "AND",
"conditions": [
{
"type": "lords_in_mutual_conjunction_or_aspect",
"lords": [
1,
7
],
"note": "Lords of Lagna and 7th in conjunction or aspect with each other"
}
]
}
],
"note": ""
"type": "custom",
"expr": "\n# PyJHora/BVR-238: Lords of Lagna and 7th in conjunction or mutual graha aspect.\nl1 = lord(1)\nl7 = lord(7)\nif l1 not in ctx.planets or l7 not in ctx.planets:\n False\nelse:\n same_house(l1, l7) or (aspect(l1, l7) and aspect(l7, l1))\n",
"combo_template": "PyJHora aligned source logic"
},
"effects": [
"Association with many women"
@@ -9835,35 +9782,9 @@
"bvr_id": null,
"category": "special",
"logic": {
"type": "compound_conditions",
"conditions": [
{
"type": "compound",
"operator": "OR",
"conditions": [
{
"type": "compound",
"operator": "AND",
"conditions": [
{
"type": "4th_lord_is_benefic_aspected_by_benefic",
"note": "Benefic 4th lord aspected by another benefic"
},
{
"type": "planet_in_lagna",
"planet": "Mercury",
"note": "Mercury in Lagna"
}
]
},
{
"type": "4th_house_or_lord_with_aspect_of_jupiter",
"note": "4th house or 4th lord has association/aspect of Jupiter"
}
]
}
],
"note": ""
"type": "custom",
"expr": "\n# PyJHora/BVR 193-194. Yoga 194 treats Jupiter associated with the 4th house/lord.\n# Source parity note: PyJHora uses planets_aspecting_the_raasi() for the 4th-rasi clause,\n# and regards Jupiter as conjoining L4 when Jupiter itself is the dynamic 4th lord.\nl4 = lord(4)\nif l4 not in ctx.planets:\n False\nelse:\n benefics = [p for p in BENEFICS if p in ctx.planets]\n y193 = l4 in BENEFICS and house_of('Mercury') == 1 and any(b != l4 and aspect(b, l4) for b in benefics)\n y194 = 'Jupiter' in ctx.planets and (house_of('Jupiter') == 4 or pyjhora_planets_aspecting_raasi('Jupiter', 4) or l4 == 'Jupiter' or same_house('Jupiter', l4) or aspect('Jupiter', l4))\n y193 or y194\n",
"combo_template": "PyJHora aligned source logic"
},
"effects": [
"Respected and honored by relatives"
@@ -11237,7 +11158,7 @@
"category": "putra",
"logic": {
"type": "custom",
"expr": "\n# PyJHora 229/230 D1-safe logic. Variant 229: Rahu in 5th, L5 joined\n# malefic, Jupiter debilitated. Variant 230: malefics occupy/aspect both\n# 5th from Lagna and 5th from Jupiter.\nl5 = lord(5)\nif l5 not in ctx.planets or 'Jupiter' not in ctx.planets:\n False\nelse:\n cond229 = house_of('Rahu') == 5 and debil('Jupiter') and any(m in ctx.planets and same_house(l5, m) for m in MALEFICS if m != l5)\n j5 = ((house_of('Jupiter') - 1 + 4) % 12) + 1\n mal_lagna5 = any(m in ctx.planets and (house_of(m) == 5 or aspects_house(m, 5)) for m in MALEFICS)\n mal_jupiter5 = any(m in ctx.planets and (house_of(m) == j5 or aspects_house(m, j5)) for m in MALEFICS)\n cond229 or (mal_lagna5 and mal_jupiter5)\n",
"expr": "\n# PyJHora 229/230. Variant 229: Rahu in 5th, L5 joined malefic, Jupiter debilitated.\n# Variant 230: natural malefics occupy or rasi-aspect both 5th from Lagna and 5th from Jupiter.\nl5 = lord(5)\nif l5 not in ctx.planets or 'Jupiter' not in ctx.planets:\n False\nelse:\n cond229 = house_of('Rahu') == 5 and debil('Jupiter') and any(m in ctx.planets and same_house(l5, m) for m in MALEFICS)\n j5 = ((house_of('Jupiter') - 1 + 4) % 12) + 1\n mal_lagna5 = any(m in ctx.planets and (house_of(m) == 5 or rasi_aspects_house(m, 5)) for m in MALEFICS)\n mal_jupiter5 = any(m in ctx.planets and (house_of(m) == j5 or rasi_aspects_house(m, j5)) for m in MALEFICS)\n cond229 or (mal_lagna5 and mal_jupiter5)\n",
"combo_template": "Rahu/L5/木星或凶星影响Lagna与木星起算第5宫"
},
"effects": [
@@ -13795,7 +13716,7 @@
"category": "durbhaga",
"logic": {
"type": "custom",
"expr": "\n# PyJHora/BVR 202-204 Kapata Yoga.\nevil = set(MALEFICS)\nl4 = lord(4)\nl10 = lord(10)\nmaandi_h = maandi_house()\nif l4 not in ctx.planets or l10 not in ctx.planets:\n False\nelse:\n # 202: 4th joined by a malefic; 4th lord joined/aspected/hemmed by malefics.\n malefic_in_4 = any(house_of(m) == 4 for m in evil if m in ctx.planets)\n l4_h = house_of(l4)\n l4_joined_malefic = any(m != l4 and same_house(l4, m) for m in evil if m in ctx.planets)\n l4_aspected_malefic = any(aspect(m, l4) for m in evil if m in ctx.planets)\n prev_h = ((l4_h - 2) % 12) + 1 if l4_h else None\n next_h = (l4_h % 12) + 1 if l4_h else None\n l4_hemmed = bool(prev_h and next_h and house_has_malefic(prev_h) and house_has_malefic(next_h))\n yoga202 = malefic_in_4 and (l4_joined_malefic or l4_aspected_malefic or l4_hemmed)\n # 203: Saturn, Mars, Rahu in 4th; malefic 10th lord aspected by malefics.\n yoga203 = all(house_of(p) == 4 for p in ['Saturn', 'Mars', 'Rahu']) and l10 in evil and any(aspect(m, l10) for m in evil if m in ctx.planets and m != l10)\n # 204: 4th lord joins Saturn, Mandi, Rahu and is aspected by malefics.\n yoga204 = bool(maandi_h and house_of(l4) == house_of('Saturn') == house_of('Rahu') == maandi_h and any(aspect(m, l4) for m in evil if m in ctx.planets and m != l4))\n yoga202 or yoga203 or yoga204\n",
"expr": "\n# PyJHora/BVR 202-204 Kapata Yoga; house aspects use rasi drishti, planet aspects use graha drishti.\nl4 = lord(4)\nl10 = lord(10)\nmaandi_h = maandi_house()\nif l4 not in ctx.planets or l10 not in ctx.planets:\n False\nelse:\n malefic_in_4 = any(house_of(m) == 4 for m in MALEFICS if m in ctx.planets)\n l4_h = house_of(l4)\n l4_joined_malefic = any(same_house(l4, m) for m in MALEFICS if m in ctx.planets)\n l4_aspected_malefic = any(aspect(m, l4) for m in MALEFICS if m in ctx.planets)\n prev_h = ((l4_h - 2) % 12) + 1 if l4_h else None\n next_h = (l4_h % 12) + 1 if l4_h else None\n l4_hemmed = bool(prev_h and next_h and house_has_malefic(prev_h) and house_has_malefic(next_h))\n yoga202 = malefic_in_4 and (l4_joined_malefic or l4_aspected_malefic or l4_hemmed)\n yoga203 = all(house_of(p) == 4 for p in ['Saturn', 'Mars', 'Rahu']) and l10 in MALEFICS and any(aspect(m, l10) for m in MALEFICS if m in ctx.planets)\n yoga204 = bool(maandi_h and house_of('Saturn') == maandi_h == house_of('Rahu') == l4_h and any(aspect(m, l4) for m in MALEFICS if m in ctx.planets))\n yoga202 or yoga203 or yoga204\n",
"combo_template": "4宫/4宫主受凶星、Rahu、Maandi/Mandi 等严重影响"
},
"effects": [
+163 -7
View File
@@ -45,8 +45,11 @@ PLANET_CN = {
BENEFICS = ['Jupiter', 'Venus', 'Mercury', 'Moon']
MALEFICS = ['Mars', 'Saturn', 'Sun', 'Rahu', 'Ketu']
MOVABLE_SIGNS = ['Aries', 'Cancer', 'Libra', 'Capricorn']
FIXED_SIGNS = ['Taurus', 'Leo', 'Scorpio', 'Aquarius']
DUAL_SIGNS = ['Gemini', 'Virgo', 'Sagittarius', 'Pisces']
ALL_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu']
PLANET_INDEX = {planet: idx for idx, planet in enumerate(ALL_PLANETS)}
RAHU_KETU_OWNED_SIGN_INDEX = {'Rahu': 10, 'Ketu': 7}
# PyJHora const.house_strengths_of_planets, copied to make Yoga rule alignment deterministic.
# Strength scale: 5 own/lord, 4 exalted, 3 friend, 2 neutral, 1 enemy, 0 debilitated.
HOUSE_STRENGTHS = {
@@ -90,11 +93,13 @@ class YogaContext:
self.panchanga = self.context.get("panchanga", {}) if isinstance(self.context, dict) else {}
self.upagraha = self.context.get("upagraha", {}) if isinstance(self.context, dict) else {}
# 预计算 D1 宫主星
# 预计算 D1 宫主星。v6.0.39: Scorpio/Aquarius follow PyJHora's dynamic co-lord resolver
# (Scorpio: stronger of Mars/Ketu; Aquarius: stronger of Saturn/Rahu), while SIGN_LORDS stays
# as the classical fixed fallback for dignity tables and non-Jaimini contexts.
self._house_lords: Dict[int, str] = {}
for h in range(1, 13):
sign = SIGNS[(self.asc_idx + h - 1) % 12]
self._house_lords[h] = SIGN_LORDS[sign]
self._house_lords[h] = self._resolve_sign_lord(sign)
# 预计算 D9 宫主星(用于依赖 Navamsa 的 B.V. Raman Yoga
d9_asc = self.d9.get("ascendant")
@@ -103,7 +108,104 @@ class YogaContext:
if d9_asc_idx is not None:
for h in range(1, 13):
sign = SIGNS[(d9_asc_idx + h - 1) % 12]
self._d9_house_lords[h] = SIGN_LORDS[sign]
self._d9_house_lords[h] = self._resolve_sign_lord(sign)
def _sign_index_of_planet(self, planet: str) -> Optional[int]:
sign = self.sign_of(planet)
return SIGNS.index(sign) if sign in SIGNS else None
def _rasi_drishti_sign_indices_from(self, sign_idx: int) -> List[int]:
sign_name = SIGNS[sign_idx]
if sign_name in MOVABLE_SIGNS:
return [i for i in [1, 4, 7, 10] if i not in ((sign_idx + 1) % 12, (sign_idx - 1) % 12)]
if sign_name in FIXED_SIGNS:
return [i for i in [0, 3, 6, 9] if i not in ((sign_idx + 1) % 12, (sign_idx - 1) % 12)]
return [SIGNS.index(s) for s in DUAL_SIGNS if SIGNS.index(s) != sign_idx]
def _stronger_co_lord(self, planet1: str, planet2: str) -> str:
"""Approximate PyJHora stronger_planet_from_planet_positions() for Sc/Aq co-lords."""
h1 = self._sign_index_of_planet(planet1)
h2 = self._sign_index_of_planet(planet2)
if h1 is None:
return planet2
if h2 is None:
return planet1
node_owned_sign = RAHU_KETU_OWNED_SIGN_INDEX.get(planet1) or RAHU_KETU_OWNED_SIGN_INDEX.get(planet2)
if node_owned_sign is not None:
if h1 == node_owned_sign and h2 != node_owned_sign:
return planet2
if h2 == node_owned_sign and h1 != node_owned_sign:
return planet1
planet1_co_count = sum(1 for p in self.planets if self._sign_index_of_planet(p) == h1) - 1
planet2_co_count = sum(1 for p in self.planets if self._sign_index_of_planet(p) == h2) - 1
if planet1_co_count > planet2_co_count:
return planet1
if planet2_co_count > planet1_co_count:
return planet2
dispositor1 = self._resolve_sign_lord(SIGNS[h1], dynamic_co_lords=False)
dispositor2 = self._resolve_sign_lord(SIGNS[h2], dynamic_co_lords=False)
support1 = sum(
self._sign_index_of_planet(p) == h1
for p in ['Mercury', 'Jupiter', dispositor1]
if p in self.planets
)
support1 += sum(
p in self.planets and self._sign_index_of_planet(p) in self._rasi_drishti_sign_indices_from(h1)
for p in ['Mercury', 'Jupiter', dispositor1]
)
support2 = sum(
self._sign_index_of_planet(p) == h2
for p in ['Mercury', 'Jupiter', dispositor2]
if p in self.planets
)
support2 += sum(
p in self.planets and self._sign_index_of_planet(p) in self._rasi_drishti_sign_indices_from(h2)
for p in ['Mercury', 'Jupiter', dispositor2]
)
if support1 > support2:
return planet1
if support2 > support1:
return planet2
strength1 = HOUSE_STRENGTHS.get(planet1, [None] * 12)[h1]
strength2 = HOUSE_STRENGTHS.get(planet2, [None] * 12)[h2]
if strength1 == 4 and strength2 is not None and strength1 > strength2:
return planet1
if strength2 == 4 and strength1 is not None and strength2 > strength1:
return planet2
def modality_rank(sign_idx: int) -> int:
sign_name = SIGNS[sign_idx]
if sign_name in DUAL_SIGNS:
return 3
if sign_name in FIXED_SIGNS:
return 2
return 1
rank1 = modality_rank(h1)
rank2 = modality_rank(h2)
if rank1 > rank2:
return planet1
if rank2 > rank1:
return planet2
degree1 = self.degree_of(planet1)
degree2 = self.degree_of(planet2)
if degree1 is not None and degree2 is not None:
return planet1 if degree1 > degree2 else planet2
return planet1
def _resolve_sign_lord(self, sign: str, dynamic_co_lords: bool = True) -> Optional[str]:
if not dynamic_co_lords:
return SIGN_LORDS.get(sign)
if sign == 'Scorpio':
return self._stronger_co_lord('Mars', 'Ketu')
if sign == 'Aquarius':
return self._stronger_co_lord('Saturn', 'Rahu')
return SIGN_LORDS.get(sign)
# --- 基础查询 ---
def house_of(self, planet: str) -> Optional[int]:
@@ -133,7 +235,7 @@ class YogaContext:
def navamsa_dispositor(self, planet: str) -> Optional[str]:
sign = self.d9_sign_of(planet)
return SIGN_LORDS.get(sign) if sign else None
return self._resolve_sign_lord(sign) if sign else None
def tithi(self) -> Optional[int]:
return self.panchanga.get("tithi")
@@ -156,7 +258,7 @@ class YogaContext:
def is_own_sign(self, planet: str) -> bool:
s = self.sign_of(planet)
return s is not None and SIGN_LORDS.get(s) == planet
return s is not None and self._resolve_sign_lord(s) == planet
def is_moolatrikona(self, planet: str) -> bool:
s = self.sign_of(planet)
@@ -962,6 +1064,48 @@ class YogaEngine:
return False
return h == ctx.house_of(p) or offset(ctx.house_of(p), h) in (6, 3 if p == 'Mars' else -1, 7 if p == 'Mars' else -1, 4 if p == 'Jupiter' else -1, 8 if p == 'Jupiter' else -1, 2 if p == 'Saturn' else -1, 9 if p == 'Saturn' else -1)
def graha_aspects_house(p, h):
if p not in ctx.planets or h is None:
return False
return offset(ctx.house_of(p), h) in (6, 3 if p == 'Mars' else -1, 7 if p == 'Mars' else -1, 4 if p == 'Jupiter' else -1, 8 if p == 'Jupiter' else -1, 2 if p == 'Saturn' else -1, 9 if p == 'Saturn' else -1)
def pyjhora_planets_aspecting_raasi(p, h):
"""Replicate PyJHora house.planets_aspecting_the_raasi() behavior for source parity."""
if p not in ctx.planets or h is None:
return False
sign_name = ctx.sign_of(p)
if sign_name not in SIGNS:
return False
target_rasi_idx = (ctx.asc_idx + h - 1) % 12
aspected_signs = rasi_drishti_signs_from(sign_name)
planet_ids_in_aspected_signs = [
PLANET_INDEX[q]
for q in ctx.planets
if q in PLANET_INDEX and ctx.sign_of(q) in aspected_signs
]
return target_rasi_idx in planet_ids_in_aspected_signs
def rasi_drishti_signs_from(sign_name):
if sign_name not in SIGNS:
return []
sign_idx = SIGNS.index(sign_name)
if sign_name in MOVABLE_SIGNS:
return [SIGNS[i] for i in [1, 4, 7, 10] if i not in ((sign_idx + 1) % 12, (sign_idx - 1) % 12)]
if sign_name in FIXED_SIGNS:
return [SIGNS[i] for i in [0, 3, 6, 9] if i not in ((sign_idx + 1) % 12, (sign_idx - 1) % 12)]
return [s for s in DUAL_SIGNS if s != sign_name]
def rasi_aspects_house(p, h):
if p not in ctx.planets or h is None:
return False
return house_sign(h) in rasi_drishti_signs_from(ctx.sign_of(p))
def rasi_aspects(a, b):
return a in ctx.planets and b in ctx.planets and rasi_aspects_house(a, ctx.house_of(b))
def rasi_aspected_by_planets(h):
return [p for p in ctx.planets if rasi_aspects_house(p, h)]
def sign_index_of_planet(p):
sign_name = ctx.sign_of(p)
return SIGNS.index(sign_name) if sign_name in SIGNS else None
@@ -970,7 +1114,13 @@ class YogaEngine:
sign_idx = sign_index_of_planet(p)
if sign_idx is None or p not in HOUSE_STRENGTHS:
return None
return HOUSE_STRENGTHS[p][sign_idx]
strength = HOUSE_STRENGTHS[p][sign_idx]
sign_name = SIGNS[sign_idx]
# v6.0.39: PyJHora dynamic co-lord owner strength for Scorpio/Aquarius.
# Without this, Ketu/Rahu can become house lords but still look non-owner in strength checks.
if sign_name in ('Scorpio', 'Aquarius') and ctx._resolve_sign_lord(sign_name) == p:
return max(strength, 5)
return strength
def strong(p, include_neutral=False):
strength = house_strength(p)
@@ -1061,10 +1211,11 @@ class YogaEngine:
"list": list, "bool": bool,
"ctx": ctx, "bindings": bindings, "rule": rule,
"SIGNS": SIGNS, "SIGN_LORDS": SIGN_LORDS,
"RAHU_KETU_OWNED_SIGN_INDEX": RAHU_KETU_OWNED_SIGN_INDEX,
"EXALTATION": EXALTATION, "DEBILITATION": DEBILITATION,
"PLANET_CN": PLANET_CN, "BENEFICS": BENEFICS, "MALEFICS": MALEFICS,
"MOVABLE_SIGNS": MOVABLE_SIGNS, "FIXED_SIGNS": FIXED_SIGNS, "DUAL_SIGNS": DUAL_SIGNS,
"PLANET_INDEX": PLANET_INDEX, "HOUSE_STRENGTHS": HOUSE_STRENGTHS,
"MOVABLE_SIGNS": MOVABLE_SIGNS,
"ALL_PLANETS": ALL_PLANETS, "MOOLATRIKONA_SIGN": MOOLATRIKONA_SIGN,
# 基础查询
"house_of": house_of, "sign_of": sign_of, "deg_of": deg_of,
@@ -1086,6 +1237,11 @@ class YogaEngine:
"offset": offset,
# v6.0.32: 同宫与相位检查(custom规则常用)
"same_house": same_house, "aspect": aspect, "aspects_house": aspects_house,
"graha_aspects_house": graha_aspects_house,
"pyjhora_planets_aspecting_raasi": pyjhora_planets_aspecting_raasi,
"rasi_drishti_signs_from": rasi_drishti_signs_from,
"rasi_aspects_house": rasi_aspects_house, "rasi_aspects": rasi_aspects,
"rasi_aspected_by_planets": rasi_aspected_by_planets,
"house_strength": house_strength, "strong": strong, "weak": weak,
"associated": associated, "temporal_friend": temporal_friend,
"occupants": occupants, "only_benefics_in_house": only_benefics_in_house,