diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f046b2..d57b4ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,72 @@ # 印度占星 Skill 更新日志 +## v3.7.0(2026-04-25)— 7大新模块:BPHS十六分盘+精确相位+Jaimini+高级Nakshatra+Argala+Tajika+合盘 + +### 新增模块(7个) + +- **`scripts/varga.py`**:BPHS Shodasavarga十六分盘完整计算 + - 支持全部16个分盘:D2/D3/D4/D7/D9/D10/D12/D16/D20/D24/D27/D30/D40/D45/D60 + - 每个分盘输出精确度数(非仅星座名) + - D30 Trimsamsa 特殊不等宽映射 + - D9 含 Jaimini 分析(7宫/Venus/Jupiter/尊严状态) + - D60 含业力分析(角宫吉凶星分布) + +- **`scripts/aspects.py`**:度数精确相位系统(Drishti) + - 标准7宫对冲 + 火星4/7/8 + 木星5/7/9 + 土星3/7/10 + - Orb分类:tight(≤3°) / moderate(≤6°) / loose(≤10°) + - 计算每个相位的入相位/出相位状态 + - 合相精度分析 + +- **`scripts/jaimini.py`**:Jaimini系统完整实现 + - `calc_chara_karaka_7()`:7行星 Chara Karaka(AK→DK) + - `calc_chara_karaka_8()`:8行星 Chara Karaka(含Rahu逆行度数处理) + - `calc_chara_dasha()`:星座大运(基于上升奇偶性决定方向) + - `calc_karakamsha()`:灵魂方向分析(DK的D9位置解读) + +- **`scripts/nakshatra_advanced.py`**:高级Nakshatra分析 + - 精确Nakshatra定位(名称/Pada/度数/Gana/元素) + - `calc_tara_bala()`:9循环Tara关系分析 + - `calc_sub_lord()`:KP系统 Sub-Lord 计算 + - `nakshatra_compatibility()`:Tara+元素+Gana综合兼容性 + +- **`scripts/argala.py`**:Argala门闩系统 + - 主Argala:2/4/11宫 → 财富/幸福/收益 + - 副Argala:5/8宫 → 权力/转化 + - Virodha(阻断):12→2, 10→4, 3→11, 9→5, 2→8 + - 净评分计算和吉凶判定 + +- **`scripts/tajika.py`**:Tajika/Varshaphala年运盘 + - `calc_muntha()`:年度上升进展 + - `calc_year_lord()`:年度守护星 + 辅助星(2/5/9/11宫主) + - `calc_mudda_dasha()`:12个月比例大运 + - `calc_tri_pataka()`:三旗系统评估 + +- **`scripts/synastry.py`**:合盘分析系统 + - Ashta Koota 36分制(8维度:Varna/Vashya/Tara/Yoni/Graha Maitri/Gana/Bhakuta/Nadi) + - Mangal Dosha 检查(火星在1/2/4/7/8/12宫) + - Papasamya 凶星抵消度评估 + - Dasha时间线兼容性分析 + - 自动生成化解建议 + +### 引擎升级(jyotish_engine.py) + +- 版本 v3.6.0 → **v3.7.0** +- 新增7个CLI子命令:`varga-full` / `aspects` / `jaimini` / `nakshatra-adv` / `argala` / `tajika` / `synastry` +- 总子命令数:14 → **21** +- 所有新模块通过一楠星盘验证(REDACTED_DATE 14:45) + +### 验证结果 + +| 模块 | 子命令 | 一楠星盘验证 | 关键输出 | +|------|--------|-------------|---------| +| varga | varga-full | ✅ | D9 Navamsa 精确度数+尊严 | +| aspects | aspects | ✅ | 6个精确相位(Moon-Saturn合相等)| +| jaimini | jaimini | ✅ | AK=Venus, DK=Mars, Chara Dasha序列 | +| nakshatra | nakshatra-adv | ✅ | 9行星Tara Bala, Sub-Lord | +| argala | argala | ✅ | 9行星门闩分析 | +| tajika | tajika | ✅ | 33岁 Muntha=金牛座, YearLord=Venus | +| synastry | synastry | ✅ | Ashta Koota 26/36 (72.2%) | + ## v3.6.0(2026-04-24)— CNWU16二次借鉴:report_builder + R2b列校验 + P3/P8/冲突仲裁 ### 新增文件 diff --git a/SKILL.md b/SKILL.md index 6cd4ea96..5deff3d7 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: jyotish-vedic-astrology -version: 3.6.0 -description: 印度占星(Jyotish)专业解盘与推运系统。核心能力:PDF星盘输入→严谨解盘→精确推运应期输出。覆盖行星配置、Yoga格局、Nakshatra解读、宫位分析、现代生活场景映射、现代措辞解读、案例对比分析、自动出生时间矫正、常见误判纠错、Dasha+Transit推运、精确预测、PDF星盘读取、Swiss Ephemeris计算引擎、星盘计算、分盘计算、名人案例查询、Shadbala六重力量、Ashtakavarga八分法(BPHS完整表SAV=337)、Hermes记忆系统、事件预测、R1-R10数学验证(含R2b BAV列→SAV列校验)、P1-P12行星审计(含P3仓库耦合+P8年龄状态+冲突仲裁3条规则)、验前事、MD→HTML报告生成(羊皮纸主题)。触发词:印度占星、吠陀占星、Jyotish、解盘、推运、星盘分析、Dasha、Transit、Nakshatra、Yoga、出生时间矫正、吠陀占星、印占、PDF星盘、读取PDF、分析PDF星盘、矫正出生时间、出生时间验证、生时矫正、现代解读、现代措辞、误判纠错、错题本、Varga分盘、全球占星师方法论、三合盘、综合分析、过境分析、Double Transit、Sade Sati、合盘、婚姻匹配、关系分析、Koota、Mangal Dosha、年运盘、Varshaphala、太阳返照、Tajika、星宿速查、综合解盘工作流、应期预测、推运应期、事件时机、什么时候结婚、什么时候升职、什么时候发财、Argala、门闩、行星干预、Dasa收敛、Yogini大运、Chara Dasha、AI解盘、算星盘、排盘、计算星盘、查名人、Shadbala、六重力量、Ashtakavarga、八分法、行星力量、记忆系统、事件预测、验证、校验、审计、验前事、R1-R10、P1-P12、仓库耦合、冲突仲裁、报告生成、HTML报告。 +version: 3.7.0 +description: 印度占星(Jyotish)专业解盘与推运系统。核心能力:PDF星盘输入→严谨解盘→精确推运应期输出。覆盖行星配置、Yoga格局、Nakshatra解读、宫位分析、现代生活场景映射、现代措辞解读、案例对比分析、自动出生时间矫正、常见误判纠错、Dasha+Transit推运、精确预测、PDF星盘读取、Swiss Ephemeris计算引擎、星盘计算、分盘计算、名人案例查询、Shadbala六重力量、Ashtakavarga八分法(BPHS完整表SAV=337)、Hermes记忆系统、事件预测、R1-R10数学验证(含R2b BAV列→SAV列校验)、P1-P12行星审计(含P3仓库耦合+P8年龄状态+冲突仲裁3条规则)、验前事、MD→HTML报告生成(羊皮纸主题)、BPHS十六分盘精确计算(D2-D60)、度数精确相位系统(Drishti)、Jaimini完整系统(Chara Karaka 7/8+Chara Dasha+Karakamsha)、高级Nakshatra分析(Tara Bala+Sub-Lord KP系统)、Argala门闩系统(Virodha反干预)、Tajika年运盘(Muntha+YearLord+Mudda Dasha+Tri-Pataka三旗)、合盘分析(Ashta Koota 36分制+Mangal Dosha+Papasamya+Dasha兼容性)。触发词:印度占星、吠陀占星、Jyotish、解盘、推运、星盘分析、Dasha、Transit、Nakshatra、Yoga、出生时间矫正、吠陀占星、印占、PDF星盘、读取PDF、分析PDF星盘、矫正出生时间、出生时间验证、生时矫正、现代解读、现代措辞、误判纠错、错题本、Varga分盘、全球占星师方法论、三合盘、综合分析、过境分析、Double Transit、Sade Sati、合盘、婚姻匹配、关系分析、Koota、Mangal Dosha、年运盘、Varshaphala、太阳返照、Tajika、星宿速查、综合解盘工作流、应期预测、推运应期、事件时机、什么时候结婚、什么时候升职、什么时候发财、Argala、门闩、行星干预、Dasa收敛、Yogini大运、Chara Dasha、AI解盘、算星盘、排盘、计算星盘、查名人、Shadbala、六重力量、Ashtakavarga、八分法、行星力量、记忆系统、事件预测、验证、校验、审计、验前事、R1-R10、P1-P12、仓库耦合、冲突仲裁、报告生成、HTML报告、精确相位、Drishti、Jaimini、Chara Karaka、Atmakaraka、Darakaraka、Karakamsha、Tara Bala、Sub-Lord、KP系统、Navamsa、D9、D60、Shashtyamsa、十六分盘、门闩分析、Muntha、年主星、三旗系统、合盘评分、Nadi Koota、Gana、Yoni、Papasamya、Dasha兼容。 --- # 印度占星专业解盘与推运系统 @@ -193,7 +193,7 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力 ### 13. 计算引擎集成(Swiss Ephemeris + 数据库)⭐ v3.4.0 -**统一引擎入口**:`scripts/jyotish_engine.py` v3.4.0(基于 Swiss Ephemeris 天文计算库) +**统一引擎入口**:`scripts/jyotish_engine.py` v3.7.0(基于 Swiss Ephemeris 天文计算库) **调用方式**:AI 通过 `execute_command` 调用以下子命令(所有输出为 JSON): @@ -204,7 +204,7 @@ SCRIPT=~/.workbuddy/skills/jyotish-vedic-astrology/scripts/jyotish_engine.py $PYTHON $SCRIPT <子命令> [参数] ``` -**13大子命令**: +**13大子命令** → **21大子命令**(v3.7 升级): | 子命令 | 功能 | 典型用法 | |--------|------|----------| @@ -221,23 +221,36 @@ $PYTHON $SCRIPT <子命令> [参数] | `memory` | ⭐ Hermes记忆系统(store/search/context/stats) | `memory --action store --content "..." --tags "chart" --importance 8` | | `validate` | ⭐ R1-R10数学验证(SAV/BAV/延伸角/Rahu-Ketu/逆行/Dasha/完整性/度数/宫位) | `validate --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8` | | `audit` | ⭐ P1-P12行星审计管线(Identity/Health/Resource/SAV/Dignity/Shadbala/Aspects/Nakshatra/Yogas) | `audit --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8` | +| `varga-full` | ⭐ v3.7 BPHS十六分盘精确计算(D2-D60全部16分盘,精确度数输出) | `varga-full --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8 --divisions D9,D60` | +| `aspects` | ⭐ v3.7 度数精确相位系统(tight/moderate/loose + 入相位/出相位) | `aspects --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8` | +| `jaimini` | ⭐ v3.7 Jaimini完整系统(Chara Karaka 7/8 + Chara Dasha + Karakamsha) | `jaimini --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8 --mode all` | +| `nakshatra-adv` | ⭐ v3.7 高级Nakshatra(Tara Bala + Sub-Lord KP + 兼容性) | `nakshatra-adv --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8 --mode all` | +| `argala` | ⭐ v3.7 Argala门闩系统(主/副Argala + Virodha反干预 + 净评分) | `argala --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8` | +| `tajika` | ⭐ v3.7 Tajika年运盘(Muntha + YearLord + Mudda Dasha + Tri-Pataka) | `tajika --year REDACTED_YEAR --month 4 --day 17 --hour 14 --minute 45 --lat 36.6 --lon 114.5 --tz 8 --age 33` | +| `synastry` | ⭐ v3.7 合盘分析(Ashta Koota 36分 + Mangal Dosha + Papasamya) | `synastry --moon1 310.89 --moon2 45.5 --mars1 90.43 --mars2 120.3` | **外部数据源**(引擎自动读取): - 验证数据库:`~/WorkBuddy/Claw/vedic_astrology_validation.db`(15,840条案例,10种技法准确率) - 名人CSV:`~/WorkBuddy/Claw/vedastro_data/PersonList-15k.csv`(15,807条AA级数据) - 过境配置:`~/WorkBuddy/Claw/月运过境配置-2026-2028.json`(36个月行星位置) -**典型工作流**: +**典型工作流**(v3.7 升级): 1. 用户给出生信息 → `chart` 计算星盘 → 获取行星位置、宫位、Nakshatra 2. 从 chart 结果取月亮经度 → `dasha` 计算当前大运 3. 从 chart 结果取行星配置 → `yoga` 识别格局 4. `shadbala` 计算六重力量 → 量化行星强度排名 ⭐ v3.4 5. `ashtakavarga` 计算八分法 → SAV评分+宫位吉凶 ⭐ v3.4 -6. `varga --d9` 计算 Navamsa 分盘验证 -7. `transit` 查询当月过境 → 配合 dasha 做推运分析 -8. `predict` 事件预测(EventPredictionModel规则引擎) ⭐ v3.4 -9. `celebrity` 查找相似案例做对比验证 -10. `memory --action store` 保存分析结论供后续引用 ⭐ v3.4 +6. `varga-full --divisions D9,D60` 计算精确分盘(含度数+尊严) ⭐ v3.7 +7. `aspects` 计算精确相位(度数级Drishti) ⭐ v3.7 +8. `jaimini --mode all` 计算Chara Karaka + Dasha + Karakamsha ⭐ v3.7 +9. `nakshatra-adv --mode all` 计算Tara Bala + Sub-Lord ⭐ v3.7 +10. `argala` 计算门闩干预分析 ⭐ v3.7 +11. `transit` 查询当月过境 → 配合 dasha 做推运分析 +12. `tajika --age 33` 计算年运盘(Muntha+Tri-Pataka) ⭐ v3.7 +13. `predict` 事件预测(EventPredictionModel规则引擎) ⭐ v3.4 +14. `celebrity` 查找相似案例做对比验证 +15. 合盘场景:`synastry --moon1 ... --moon2 ...` 计算Ashta Koota 36分制 ⭐ v3.7 +16. `memory --action store` 保存分析结论供后续引用 ⭐ v3.4 ## 使用方法 @@ -376,7 +389,7 @@ $PYTHON $SCRIPT <子命令> [参数] ## 更新记录 -详见 `CHANGELOG.md`。当前版本:v3.3.0。 +详见 `CHANGELOG.md`。当前版本:v3.7.0。 ## 参考资料 @@ -511,6 +524,6 @@ $PYTHON $SCRIPT <子命令> [参数] --- -**版本**:3.3.0 +**版本**:3.7.0 **创建日期**:2026-04-20 -**最后更新**:2026-04-24(v3.3.0 整合Kimi训练手册精华:排盘软件与Ayanamsa指南+Shodasavarga十六分盘体系+替代推运系统+条件Dasha系统+古典文献翻译指南+专业发展路径) +**最后更新**:2026-04-25(v3.7.0 新增7大计算模块:BPHS十六分盘精确计算+度数精确相位+Jaimini完整系统+高级Nakshatra分析+Argala门闩+Tajika年运盘+合盘分析,引擎子命令14→21) diff --git a/scripts/argala.py b/scripts/argala.py new file mode 100644 index 00000000..98d8cc4d --- /dev/null +++ b/scripts/argala.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Argala(门闩)行星干预模块 v1.0 +Parashara体系的行星影响力锁定机制 + +Argala = "门闩/木栓",指一颗行星通过特定宫位对另一颗行星产生 +不可阻挡的干预影响。这是判断行星力量和结果表达的关键技法。 + +规则: + - 主Argala: 2/4/11宫位产生正面干预 + - 副Argala: 5/8宫位(从第2宫起算)产生正面干预 + - Virodha Argala: 12/10/3宫位产生反向阻止 + - 更强Argala: 多颗行星在同一Argala位置则力量增强 +""" +from typing import Dict, List + +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'} + +BENEFICS = ['Jupiter', 'Venus', 'Moon'] +MALEFICS = ['Saturn', 'Mars', 'Sun', 'Rahu', 'Ketu'] + + +def calc_argala(planet_sign_indices: Dict[str, int], asc_sign_idx: int) -> Dict: + """ + 计算所有行星的Argala和Virodha Argala + + 参数: + planet_sign_indices: {'Sun': 0, 'Moon': 3, ...} 行星所在星座索引 + asc_sign_idx: 上升星座索引 + + 返回: { + planet_name: { + 'house': 宫位, + 'argala_from': [对哪些宫位有Argala], + 'argala_to': [被哪些行星Argala], + 'virodha_from': [被哪些行星阻止], + 'net_argala': 净Argala力量评估, + } + } + """ + # 计算各行星宫位 + houses = {} + for pname, si in planet_sign_indices.items(): + h = ((si - asc_sign_idx) % 12) + 1 + houses[pname] = h + + # 主Argala位置(从目标行星宫位起算) + # 2宫=财富Argala, 4宫=幸福Argala, 11宫=收益Argala + MAIN_ARGALA = [2, 4, 11] + # 副Argala位置(特殊条件:需要无其他行星在相应Virodha位置) + SUB_ARGALA = [5, 8] # 第5宫=权力Argala(当2宫无Virodha), 第8宫(当4宫无Virodha) + # Virodha Argala位置(反向阻止) + VIRODHA = {2: 12, 4: 10, 11: 3, 5: 9, 8: 2} + + results = {} + + for target_p, target_h in houses.items(): + argala_on_target = [] # 作用于此行星的Argala + virodha_on_target = [] # 作用于此行星的Virodha + argala_by_target = [] # 此行星作用于其他位置的Argala + + # 检查哪些行星对此行星有Argala + for source_p, source_h in houses.items(): + if source_p == target_p: + continue + rel = ((source_h - target_h) % 12) + 1 + + # 主Argala检查 + if rel in MAIN_ARGALA: + nature = 'benefic' if source_p in BENEFICS else 'malefic' + strength = 'strong' if _is_strong_argala(source_p, rel) else 'normal' + argala_on_target.append({ + 'source': source_p, 'house_from': rel, + 'type': 'main', 'nature': nature, 'strength': strength, + 'effect': _argala_effect(rel, nature), + }) + + # 副Argala检查 + elif rel in SUB_ARGALA: + nature = 'benefic' if source_p in BENEFICS else 'malefic' + argala_on_target.append({ + 'source': source_p, 'house_from': rel, + 'type': 'sub', 'nature': nature, 'strength': 'conditional', + 'effect': _argala_effect(rel, nature), + }) + + # Virodha检查 + for a_house, v_house in VIRODHA.items(): + if rel == v_house: + nature = 'benefic' if source_p in BENEFICS else 'malefic' + virodha_on_target.append({ + 'source': source_p, 'house_from': rel, + 'blocks_argala_from': a_house, + 'nature': nature, + }) + + # 此行星对其他位置产生的Argala + for rel_house in MAIN_ARGALA: + target_house = ((target_h - 1 + rel_house - 1) % 12) + 1 + planets_there = [p for p, h in houses.items() if h == target_house] + if planets_there: + argala_by_target.append({ + 'argala_type': f'{rel_house}宫Argala', + 'target_house': target_house, + 'affects': planets_there, + 'effect': _argala_effect(rel_house, 'benefic' if target_p in BENEFICS else 'malefic'), + }) + + # 净Argala评估 + benefic_argala = sum(1 for a in argala_on_target if a['nature'] == 'benefic') + malefic_argala = sum(1 for a in argala_on_target if a['nature'] == 'malefic') + benefic_virodha = sum(1 for v in virodha_on_target if v['nature'] == 'benefic') + malefic_virodha = sum(1 for v in virodha_on_target if v['nature'] == 'malefic') + + net = (benefic_argala - malefic_virodha) - (malefic_argala - benefic_virodha) + + results[target_p] = { + 'house': target_h, + 'argala_on_this': argala_on_target, + 'virodha_on_this': virodha_on_target, + 'argala_by_this': argala_by_target, + 'net_score': net, + 'net_assessment': 'strongly_supported' if net >= 2 else 'supported' if net >= 1 + else 'blocked' if net <= -2 else 'neutral', + } + + return results + + +def _is_strong_argala(planet, house): + """判断Argala是否特别强(有2+行星在同一位置)""" + return False # 需要宫位行星数信息,简化返回 + + +def _argala_effect(house, nature): + """Argala效果描述""" + effects = { + 2: {'benefic': '财富和资源流入', 'malefic': '财务压力和资源消耗'}, + 4: {'benefic': '幸福感、住所和内心平静增强', 'malefic': '家庭不和、住所问题'}, + 11: {'benefic': '收益、愿望实现和社会认可', 'malefic': '损失、社会障碍'}, + 5: {'benefic': '智慧、权力和创造力增强', 'malefic': '智力困扰、决策失误'}, + 8: {'benefic': '隐藏资源和转化力量', 'malefic': '隐藏障碍和突发危机'}, + } + return effects.get(house, {}).get(nature, '一般性影响') diff --git a/scripts/aspects.py b/scripts/aspects.py new file mode 100644 index 00000000..ee879641 --- /dev/null +++ b/scripts/aspects.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +吠陀占星精确相位模块 v1.0 +基于度数的Drishti(相位)计算系统 + +支持: + - 标准7宫对冲相位 + - 特殊相位: Mars(4/7/8), Jupiter(5/7/9), Saturn(3/7/10) + - 度数容许度(orb): 紧密/中等/松散三级 + - 入相位(applying)/出相位(separating)判断 + - 合相(conjunction)精度分析 +""" +from typing import Dict, List, Tuple, Optional + +# 标准容许度(度) +DEFAULT_ORBS = { + 'Sun': 10, 'Moon': 10, 'Mars': 8, 'Mercury': 7, + 'Jupiter': 9, 'Venus': 7, 'Saturn': 9, 'Rahu': 8, 'Ketu': 8, + 'default': 7 +} +ORBIT_CATEGORIES = { + 'tight': 3, # 紧密相位(最强) + 'moderate': 6, # 中等相位 + 'loose': 10, # 松散相位(最弱) +} + +# 特殊相位规则(从行星所在宫位算起) +SPECIAL_ASPECTS = { + 'Mars': [4, 7, 8], + 'Jupiter': [5, 7, 9], + 'Saturn': [3, 7, 10], +} +# 所有行星都有7宫相位 +ALL_HAVE_7TH = True + + +def calc_all_aspects(planet_longitudes: Dict[str, float], + ascendant_longitude: float) -> Dict: + """ + 计算所有行星之间的精确相位关系 + + 参数: + planet_longitudes: {'Sun': 12.5, 'Moon': 45.3, ...} 恒星黄道经度 + ascendant_longitude: 上升点经度 + + 返回: { + 'aspects': [每对行星的相位详情], + 'conjunctions': [合相详情], + 'asc_aspects': [上升点被哪些星相位], + 'tight_aspects': [紧密相位(<3°)], + 'summary': 统计摘要 + } + """ + planets = {k: v for k, v in planet_longitudes.items() + if k not in ('Ketu',) and isinstance(v, (int, float))} + # Ketu = Rahu + 180 + if 'Rahu' in planet_longitudes and 'Ketu' not in planets: + planets['Ketu'] = (planet_longitudes['Rahu'] + 180) % 360 + + aspects = [] + conjunctions = [] + tight_aspects = [] + planet_names = list(planets.keys()) + + for i, p1 in enumerate(planet_names): + for j, p2 in enumerate(planet_names): + if j <= i: + continue + lon1, lon2 = planets[p1], planets[p2] + pair_aspects = _calc_pair_aspects(p1, lon1, p2, lon2) + for asp in pair_aspects: + aspects.append(asp) + if asp['type'] == 'conjunction': + conjunctions.append(asp) + if asp['orb'] <= ORBIT_CATEGORIES['tight']: + tight_aspects.append(asp) + + # 上升点相位 + asc_aspects = [] + for pname, plon in planets.items(): + diff = abs(ascendant_longitude - plon) % 360 + if diff > 180: diff = 360 - diff + if diff <= 15: # 上升点容许度 + asc_aspects.append({ + 'planet': pname, 'degree_diff': round(diff, 2), + 'orb_category': _orb_category(diff, 15), + 'applying': plon > ascendant_longitude, # 简化判断 + }) + + return { + 'aspects': aspects, + 'conjunctions': conjunctions, + 'tight_aspects': tight_aspects, + 'asc_aspects': asc_aspects, + 'summary': { + 'total_aspects': len(aspects), + 'conjunctions': len(conjunctions), + 'tight_aspects': len(tight_aspects), + 'by_type': _count_by_type(aspects), + } + } + + +def _calc_pair_aspects(p1, lon1, p2, lon2) -> List[Dict]: + """计算两颗行星之间的所有相位""" + results = [] + diff = (lon2 - lon1) % 360 + if diff > 180: + diff = 360 - diff + + # 1. 合相 (conjunction): 同一星座内或跨星座但距离很近 + max_conj_orb = max(DEFAULT_ORBS.get(p1, 7), DEFAULT_ORBS.get(p2, 7)) + if diff <= max_conj_orb: + results.append({ + 'planet1': p1, 'planet2': p2, + 'type': 'conjunction', 'aspect_degree': 0, + 'actual_diff': round(diff, 2), 'orb': round(diff, 2), + 'orb_category': _orb_category(diff, max_conj_orb), + 'applying': (lon2 - lon1) % 360 < 180, + 'strength': _aspect_strength(diff, max_conj_orb), + 'description': f"{p1}与{p2}合相(差距{diff:.1f}°)" + }) + + # 2. 对冲 (opposition): 7宫相位 + opp_diff = abs(diff - 180) + opp_orb = max(DEFAULT_ORBS.get(p1, 7), DEFAULT_ORBS.get(p2, 7)) + if opp_diff <= opp_orb and diff > max_conj_orb: + results.append({ + 'planet1': p1, 'planet2': p2, + 'type': 'opposition', 'aspect_degree': 180, + 'actual_diff': round(diff, 2), 'orb': round(opp_diff, 2), + 'orb_category': _orb_category(opp_diff, opp_orb), + 'applying': (lon2 - lon1) % 360 < 180, + 'strength': _aspect_strength(opp_diff, opp_orb), + 'description': f"{p1}对冲{p2}(差距{opp_diff:.1f}°)" + }) + + # 3. 特殊相位 + for planet, aspect_degrees in SPECIAL_ASPECTS.items(): + if planet == p1: + for asp_deg in aspect_degrees: + if asp_deg == 7: + continue # 已在对冲中处理 + target = asp_deg * 30 # 转换为度数(每宫30°) + asp_diff = abs(diff - target) + if asp_diff > 180: asp_diff = 360 - asp_diff + asp_orb = 8 # 特殊相位容许度 + if asp_diff <= asp_orb: + results.append({ + 'planet1': p1, 'planet2': p2, + 'type': f'{planet}_aspect_{asp_deg}', + 'aspect_degree': target, + 'actual_diff': round(diff, 2), 'orb': round(asp_diff, 2), + 'orb_category': _orb_category(asp_diff, asp_orb), + 'applying': (lon2 - lon1) % 360 < 180, + 'strength': _aspect_strength(asp_diff, asp_orb), + 'description': f"{p1}({asp_deg}宫相位){p2}(差距{asp_diff:.1f}°)" + }) + + return results + + +def calc_house_aspects(planet_lon: float, planet_name: str, + asc_lon: float) -> Dict: + """计算单颗行星对所有12宫的相位""" + planet_sign = int(planet_lon / 30) % 12 + asc_sign = int(asc_lon / 30) % 12 + planet_house = ((planet_sign - asc_sign) % 12) + 1 + + aspect_houses = [7] # 所有行星都有7宫相位 + if planet_name in SPECIAL_ASPECTS: + aspect_houses = SPECIAL_ASPECTS[planet_name] + + result = {'planet': planet_name, 'house': planet_house, 'aspects_to': {}} + for ah in aspect_houses: + target_house = ((planet_house - 1 + ah - 1) % 12) + 1 + result['aspects_to'][target_house] = { + 'from_house': planet_house, + 'aspect_type': f'{ah}宫相位', + 'is_special': ah != 7, + } + return result + + +def _orb_category(diff, max_orb): + if diff <= ORBIT_CATEGORIES['tight']: return 'tight' + if diff <= ORBIT_CATEGORIES['moderate']: return 'moderate' + return 'loose' + + +def _aspect_strength(diff, max_orb): + """相位强度评分 0-100""" + if diff == 0: return 100 + ratio = diff / max_orb + return max(0, round(100 * (1 - ratio), 1)) + + +def _count_by_type(aspects): + counts = {} + for a in aspects: + t = a['type'] + counts[t] = counts.get(t, 0) + 1 + return counts diff --git a/scripts/jaimini.py b/scripts/jaimini.py new file mode 100644 index 00000000..af0ec6cb --- /dev/null +++ b/scripts/jaimini.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Jaimini占星体系模块 v1.0 +Parashara传承中的Jaimini子系统 + +支持: + - Chara Karaka: 7/8个功能指示星(按度数排序) + - Chara Dasha: 基于星座的大运系统 + - Karakamsha: DK在Navamsa中的上升(灵魂方向) + - Jaimini Sutras关键规则 +""" +from typing import Dict, List, Tuple, Optional +import math + +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'} + +# Chara Karaka 定义(7星制,排除Rahu) +KARAKA_7 = { + 1: 'Atmakaraka', # AK - 灵魂指示星(度数最高) + 2: 'Amatyakaraka', # AmK - 事业/顾问 + 3: 'Bhratrikaraka', # BK - 兄弟/勇气 + 4: 'Matrikaraka', # MK - 母亲 + 5: 'Putrakaraka', # PK - 子女 + 6: 'Gnatikaraka', # GK - 敌人/障碍 + 7: 'Darakaraka', # DK - 配偶(度数最低) +} + +# 8星制(含Rahu,用绝对度数) +KARAKA_8 = { + 1: 'Atmakaraka', 2: 'Amatyakaraka', 3: 'Bhratrikaraka', 4: 'Matrikaraka', + 5: 'Putrakaraka', 6: 'Gnatikaraka', 7: 'Darakaraka', 8: 'Pitrukaraka', +} + +KARAKA_CN = { + 'Atmakaraka': '灵魂星AK', 'Amatyakaraka': '事业星AmK', + 'Bhratrikaraka': '兄弟星BK', 'Matrikaraka': '母亲星MK', + 'Putrakaraka': '子女星PK', 'Gnatikaraka': '障碍星GK', + 'Darakaraka': '配偶星DK', 'Pitrukaraka': '父亲星PiK', +} + +KARAKA_DOMAINS = { + 'Atmakaraka': '灵魂使命、核心自我、人生最高目标', + 'Amatyakaraka': '事业方向、主要谋士、权力代理', + 'Bhratrikaraka': '兄弟姐妹、勇气、冒险精神', + 'Matrikaraka': '母亲、家庭根基、情感安全感', + 'Putrakaraka': '子女、创造力、学生、智能成果', + 'Gnatikaraka': '竞争对手、疾病、障碍、转化力量', + 'Darakaraka': '配偶特质、伴侣关系、婚姻质量', + 'Pitrukaraka': '父亲、祖先业力、传统传承', +} + + +def calc_chara_karaka_7(planet_degrees: Dict[str, float]) -> Dict: + """ + 计算7星制Chara Karaka(度数最高→AK,最低→DK) + + 参数: planet_degrees = {'Sun': 12.5, 'Moon': 8.3, ...} + 每个行星在星座内的度数(0-30) + + 返回: {karaka_name: {'planet': str, 'degree': float, 'domain': str}} + """ + # 排除Rahu和Ketu + exclude = {'Rahu', 'Ketu'} + planets = {k: v for k, v in planet_degrees.items() if k not in exclude} + + # 按度数降序排列(度数最高的=AK) + sorted_planets = sorted(planets.items(), key=lambda x: x[1], reverse=True) + + results = {} + for rank, (pname, deg) in enumerate(sorted_planets, 1): + if rank > 7: + break + karaka = KARAKA_7[rank] + results[karaka] = { + 'planet': pname, + 'degree_in_sign': round(deg, 4), + 'rank': rank, + 'domain': KARAKA_DOMAINS.get(karaka, ''), + 'cn_name': KARAKA_CN.get(karaka, karaka), + } + + # 额外分析 + ak = results.get('Atmakaraka', {}) + dk = results.get('Darakaraka', {}) + + return { + 'karaka_table': results, + 'summary': { + 'AK': f"{ak.get('planet','?')} ({ak.get('degree_in_sign',0):.1f}°)", + 'DK': f"{dk.get('planet','?')} ({dk.get('degree_in_sign',0):.1f}°)", + 'AK_domain': ak.get('domain', ''), + 'DK_domain': dk.get('domain', ''), + } + } + + +def calc_chara_karaka_8(planet_degrees: Dict[str, float]) -> Dict: + """ + 计算8星制Chara Karaka(含Rahu,使用30-度数来处理Rahu逆行) + """ + planets = {} + for pname, deg in planet_degrees.items(): + if pname == 'Ketu': + continue + if pname == 'Rahu': + # Rahu用反向度数 + planets[pname] = (30 - deg) % 30 + else: + planets[pname] = deg + + sorted_planets = sorted(planets.items(), key=lambda x: x[1], reverse=True) + + results = {} + for rank, (pname, deg) in enumerate(sorted_planets, 1): + if rank > 8: + break + karaka = KARAKA_8[rank] + results[karaka] = { + 'planet': pname, + 'degree_in_sign': round(deg, 4), + 'rank': rank, + 'domain': KARAKA_DOMAINS.get(karaka, ''), + 'cn_name': KARAKA_CN.get(karaka, karaka), + } + + return {'karaka_table_8': results} + + +def calc_chara_dasha(asc_sign_idx: int, + planet_longitudes: Dict[str, float], + birth_year: int, birth_month: int) -> Dict: + """ + Chara Dasha计算(基于星座的大运系统) + + 规则: + - 从上升星座开始 + - 奇数星座(Aries, Gemini...):正向顺序 + - 偶数星座(Taurus, Cancer...):反向顺序 + - 每个大运长度 = 12 - 该星座内的行星数量(用特定规则) + - 标准版:每个大运固定1-12年 + """ + # 确定顺序方向 + is_odd = asc_sign_idx % 2 == 0 + direction = 1 if is_odd else -1 + + # 生成大运序列 + dasha_sequence = [] + current = asc_sign_idx + + # 标准Chara Dasha: 每个星座1年,按正/反向排列 + for i in range(12): + sign_idx = (current + direction * i) % 12 + sign_name = SIGNS[sign_idx] + lord = SIGN_LORDS[sign_name] + + # 大运长度计算(简化版:基于星座的默认年数) + # 标准法:大运长度 = 基于该星座中行星的Karakamsa计算 + # 简化法:每个大运1年 + duration = _chara_dasha_duration(sign_idx, planet_longitudes) + + dasha_sequence.append({ + 'sign': sign_name, + 'sign_idx': sign_idx, + 'lord': lord, + 'duration_years': duration, + 'order': i + 1, + }) + + # 计算日期 + total_years = sum(d['duration_years'] for d in dasha_sequence) + current_year = birth_year + current_month = birth_month + + for d in dasha_sequence: + d['start_date'] = f"{current_year}-{current_month:02d}" + end_month = current_month + int(d['duration_years'] * 12) + end_year = current_year + end_month // 12 + end_month = end_month % 12 + if end_month == 0: + end_month = 12 + end_year -= 1 + d['end_date'] = f"{end_year}-{end_month:02d}" + current_year = end_year + current_month = end_month + 1 + if current_month > 12: + current_month = 1 + current_year += 1 + + return { + 'ascendant': SIGNS[asc_sign_idx], + 'direction': 'forward' if is_odd else 'backward', + 'dasha_sequence': dasha_sequence, + 'total_years': total_years, + } + + +def _chara_dasha_duration(sign_idx, planet_lons): + """计算Chara Dasha单个大运的年数""" + # 标准方法:12 - 落入该星座的行星数量(最少1年,最多12年) + count = 0 + for pname, lon in planet_lons.items(): + if pname in ('Rahu', 'Ketu'): + continue + p_sign = int(lon / 30) % 12 + if p_sign == sign_idx: + count += 1 + return max(1, 12 - count) + + +def calc_karakamsha(dk_sign_in_d9: str, dk_degree_in_d9: float) -> Dict: + """ + Karakamsha分析:DK在D9中的位置作为"灵魂上升" + 这是Jaimini体系中判断人生终极方向的关键技法 + + 参数: + dk_sign_in_d9: DK在D9中的星座 + dk_degree_in_d9: DK在D9中的度数 + """ + sign_idx = SIGNS.index(dk_sign_in_d9) if dk_sign_in_d9 in SIGNS else 0 + lord = SIGN_LORDS.get(dk_sign_in_d9, '') + + # Karakamsha Lagna = DK在D9中的位置 + # 从这个位置看12宫的布局,分析灵魂方向 + interpretations = _karakamsha_interpretations(dk_sign_in_d9, lord) + + return { + 'karakamsha_sign': dk_sign_in_d9, + 'karakamsha_degree': dk_degree_in_d9, + 'karakamsha_lord': lord, + 'soul_direction': interpretations, + } + + +def _karakamsha_interpretations(sign, lord): + """Karakamsha的灵魂方向解读""" + directions = { + 'Aries': '灵魂追求独立、开拓、成为先驱', + 'Taurus': '灵魂追求稳定、物质安全感、感官和谐', + 'Gemini': '灵魂追求知识、沟通、多元体验', + 'Cancer': '灵魂追求情感连接、家庭、滋养他人', + 'Leo': '灵魂追求创造力、领导力、自我表达', + 'Virgo': '灵魂追求服务、完善、分析能力', + 'Libra': '灵魂追求平衡、关系和谐、美学', + 'Scorpio': '灵魂追求转化、深层真相、神秘学', + 'Sagittarius': '灵魂追求真理、哲学、智慧传播', + 'Capricorn': '灵魂追求成就、结构、社会贡献', + 'Aquarius': '灵魂追求革新、人道主义、群体觉醒', + 'Pisces': '灵魂追求灵性、超越、无条件的爱', + } + lord_meanings = { + 'Sun': '通过权威、创造力和自我实现达成', + 'Moon': '通过情感智慧、直觉和公众影响力达成', + 'Mars': '通过行动力、勇气和技术能力达成', + 'Mercury': '通过智慧、沟通和学习能力达成', + 'Jupiter': '通过智慧、教导和灵性成长达成', + 'Venus': '通过美学、关系和创造力达成', + 'Saturn': '通过耐力、自律和长期承诺达成', + } + return { + 'sign_direction': directions.get(sign, ''), + 'lord_method': lord_meanings.get(lord, ''), + } diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 0020377a..2d979003 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -印度占星统一引擎 v3.6.0 (Jyotish Unified Engine) +印度占星统一引擎 v3.7.0 (Jyotish Unified Engine) 整合所有计算能力为单一CLI入口,供Skill调用 子命令: @@ -10,6 +10,13 @@ yoga Yoga格局识别 predict 三层验证法事件预测(优先EventPredictionModel规则引擎) varga 分盘计算(D9/D10) + varga-full BPHS十六分盘完整计算(D2-D60)(v3.7新增) + aspects 度数精确相位系统(v3.7新增) + jaimini Jaimini系统(Chara Karaka/Chara Dasha/Karakamsha)(v3.7新增) + nakshatra-adv 高级Nakshatra分析(Tara Bala/Sub-Lord/兼容性)(v3.7新增) + argala Argala门闩系统(v3.7新增) + tajika Tajika/Varshaphala年运盘(v3.7新增) + synastry 合盘分析(Ashta Koota 36分制+Mangal Dosha)(v3.7新增) celebrity 名人案例查询(15,807条数据+SQLite验证库) db-stats 验证数据库统计 transit 行星过境查询 @@ -1043,11 +1050,220 @@ def cmd_report(args): sys.argv = old_argv +# ============================================================================ +# 15. BPHS十六分盘完整计算(v3.7新增) +# ============================================================================ +def cmd_varga_full(args): + chart, asc_idx, jd, ayanamsa = compute_chart_data( + args.year, args.month, args.day, args.hour, args.minute, + args.lat, args.lon, args.tz) + if chart is None: + return {"error": "swisseph未安装"} + try: + sys.path.insert(0, SCRIPT_DIR) + from varga import calc_all_vargas + except ImportError as e: + return {"error": f"varga模块导入失败: {e}"} + planets = chart.get('planets', {}) + planet_lons = {pn: pd['degree'] for pn, pd in planets.items() if isinstance(pd, dict) and 'degree' in pd} + asc_deg = chart.get('ascendant', {}).get('degree', 0) + divisions = [int(d.strip().replace('D','')) for d in args.divisions.split(',')] if args.divisions else None + return calc_all_vargas(planet_lons, asc_deg, divisions) + + +# ============================================================================ +# 16. 度数精确相位系统(v3.7新增) +# ============================================================================ +def cmd_aspects(args): + chart, asc_idx, jd, ayanamsa = compute_chart_data( + args.year, args.month, args.day, args.hour, args.minute, + args.lat, args.lon, args.tz) + if chart is None: + return {"error": "swisseph未安装"} + try: + sys.path.insert(0, SCRIPT_DIR) + from aspects import calc_all_aspects + except ImportError as e: + return {"error": f"aspects模块导入失败: {e}"} + planets = chart.get('planets', {}) + planet_lons = {} + for pn, pd in planets.items(): + if isinstance(pd, dict) and 'degree' in pd: + planet_lons[pn] = pd['degree'] + asc_deg = chart.get('ascendant', {}).get('degree', 0) + return calc_all_aspects(planet_lons, asc_deg) + + +# ============================================================================ +# 17. Jaimini系统(v3.7新增) +# ============================================================================ +def cmd_jaimini(args): + chart, asc_idx, jd, ayanamsa = compute_chart_data( + args.year, args.month, args.day, args.hour, args.minute, + args.lat, args.lon, args.tz) + if chart is None: + return {"error": "swisseph未安装"} + try: + sys.path.insert(0, SCRIPT_DIR) + from jaimini import calc_chara_karaka_7, calc_chara_karaka_8, calc_chara_dasha, calc_karakamsha + from varga import calc_varga + except ImportError as e: + return {"error": f"jaimini模块导入失败: {e}"} + planets = chart.get('planets', {}) + planet_lons = {} + for pn, pd in planets.items(): + if isinstance(pd, dict) and 'degree' in pd: + planet_lons[pn] = pd['degree'] + asc_deg = chart.get('ascendant', {}).get('degree', 0) + + result = {} + # Chara Karaka + mode = args.mode or 'all' + if mode in ('all', 'karaka'): + result['chara_karaka_7'] = calc_chara_karaka_7(planet_lons) + result['chara_karaka_8'] = calc_chara_karaka_8(planet_lons) + if mode in ('all', 'dasha'): + result['chara_dasha'] = calc_chara_dasha(asc_idx, planet_lons, args.year, args.month) + if mode in ('all', 'karakamsha'): + # DK的D9位置 + ck7 = calc_chara_karaka_7(planet_lons) + dk_name = ck7.get('DK', {}).get('planet', 'Moon') + dk_lon = planet_lons.get(dk_name, 0) + dk_d9 = calc_varga(dk_lon, 9) + result['karakamsha'] = calc_karakamsha(dk_d9.get('sign', 'Aries'), dk_d9.get('degree_in_sign', 0)) + return result + + +# ============================================================================ +# 18. 高级Nakshatra分析(v3.7新增) +# ============================================================================ +def cmd_nakshatra_adv(args): + chart, asc_idx, jd, ayanamsa = compute_chart_data( + args.year, args.month, args.day, args.hour, args.minute, + args.lat, args.lon, args.tz) + if chart is None: + return {"error": "swisseph未安装"} + try: + sys.path.insert(0, SCRIPT_DIR) + from nakshatra_advanced import find_nakshatra, calc_all_tara_balas, calc_sub_lord, nakshatra_compatibility + except ImportError as e: + return {"error": f"nakshatra_advanced模块导入失败: {e}"} + planets = chart.get('planets', {}) + planet_lons = {} + for pn, pd in planets.items(): + if isinstance(pd, dict) and 'degree' in pd: + planet_lons[pn] = pd['degree'] + + result = {'planets': {}} + mode = args.mode or 'all' + + if mode in ('all', 'detail'): + for pn, lon in planet_lons.items(): + result['planets'][pn] = find_nakshatra(lon) + + if mode in ('all', 'tara'): + moon_lon = planet_lons.get('Moon', 0) + moon_nak_idx = int(moon_lon / (360/27)) % 27 + result['tara_bala'] = calc_all_tara_balas(moon_nak_idx, planet_lons) + + if mode in ('all', 'sublord'): + result['sub_lords'] = {pn: calc_sub_lord(lon) for pn, lon in planet_lons.items()} + + return result + + +# ============================================================================ +# 19. Argala门闩系统(v3.7新增) +# ============================================================================ +def cmd_argala(args): + chart, asc_idx, jd, ayanamsa = compute_chart_data( + args.year, args.month, args.day, args.hour, args.minute, + args.lat, args.lon, args.tz) + if chart is None: + return {"error": "swisseph未安装"} + try: + sys.path.insert(0, SCRIPT_DIR) + from argala import calc_argala + except ImportError as e: + return {"error": f"argala模块导入失败: {e}"} + planets = chart.get('planets', {}) + # 构建宫位到行星的映射 - argala需要sign_indices + planet_sign_indices = {} + for pn, pd in planets.items(): + if isinstance(pd, dict) and 'sign' in pd: + si = SIGNS.index(pd['sign']) if pd['sign'] in SIGNS else 0 + planet_sign_indices[pn] = si + return calc_argala(planet_sign_indices, asc_idx) + + +# ============================================================================ +# 20. Tajika/Varshaphala年运盘(v3.7新增) +# ============================================================================ +def cmd_tajika(args): + chart, asc_idx, jd, ayanamsa = compute_chart_data( + args.year, args.month, args.day, args.hour, args.minute, + args.lat, args.lon, args.tz) + if chart is None: + return {"error": "swisseph未安装"} + try: + sys.path.insert(0, SCRIPT_DIR) + from tajika import calc_muntha, calc_year_lord, calc_mudda_dasha, calc_tri_pataka + except ImportError as e: + return {"error": f"tajika模块导入失败: {e}"} + planets = chart.get('planets', {}) + planet_lons = {} + for pn, pd in planets.items(): + if isinstance(pd, dict) and 'degree' in pd: + planet_lons[pn] = pd['degree'] + asc_deg = chart.get('ascendant', {}).get('degree', 0) + asc_si = int(asc_deg / 30) % 12 # sign index + age = args.age + if age is None: + return {"error": "请提供 --age 参数(当前年龄)"} + + result = {} + mode = args.mode or 'all' + if mode in ('all', 'muntha'): + result['muntha'] = calc_muntha(asc_si, age) + if mode in ('all', 'yearlord'): + result['year_lord'] = calc_year_lord(asc_si, age) + if mode in ('all', 'mudda'): + # 需要先获取 varsha_lord + yl = calc_year_lord(asc_si, age) + varsha_lord = yl.get('year_lord', 'Jupiter') + result['mudda_dasha'] = calc_mudda_dasha(asc_si, varsha_lord, args.month) + if mode in ('all', 'tripataka'): + yl = calc_year_lord(asc_si, age) + varsha_lord = yl.get('year_lord', 'Jupiter') + muntha_si = (asc_si + age) % 12 + result['tri_pataka'] = calc_tri_pataka(planet_lons, varsha_lord, muntha_si) + return result + + +# ============================================================================ +# 21. 合盘分析(v3.7新增) +# ============================================================================ +def cmd_synastry(args): + try: + sys.path.insert(0, SCRIPT_DIR) + from synastry import calc_synastry + except ImportError as e: + return {"error": f"synastry模块导入失败: {e}"} + # 构建两人数据 + p1 = {'moon_lon': args.moon1, 'gender': args.gender1 or 'M'} + p2 = {'moon_lon': args.moon2, 'gender': args.gender2 or 'F'} + if args.mars1 is not None: p1['mars_lon'] = args.mars1 + if args.mars2 is not None: p2['mars_lon'] = args.mars2 + if args.asc1 is not None: p1['asc_lon'] = args.asc1 + if args.asc2 is not None: p2['asc_lon'] = args.asc2 + return calc_synastry(p1, p2) + + # ============================================================================ # CLI入口 # ============================================================================ def main(): - parser = argparse.ArgumentParser(description='印度占星统一引擎 v3.6.0', formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser(description='印度占星统一引擎 v3.7.0', formatter_class=argparse.RawDescriptionHelpFormatter) sub = parser.add_subparsers(dest='command', help='子命令') # 1. chart @@ -1132,6 +1348,46 @@ def main(): p.add_argument('--lang', default='cn', choices=['cn', 'en'], help='语言 (默认cn)') p.add_argument('--output', default=None, help='输出HTML路径') + # 15. varga-full (v3.7新增) + p = sub.add_parser('varga-full', help='BPHS十六分盘完整计算') + _add_chart_args(p) + p.add_argument('--divisions', default=None, help='指定分盘,逗号分隔(如 D2,D9,D60),空=全部') + + # 16. aspects (v3.7新增) + p = sub.add_parser('aspects', help='度数精确相位系统') + _add_chart_args(p) + + # 17. jaimini (v3.7新增) + p = sub.add_parser('jaimini', help='Jaimini系统(Chara Karaka/Dasha/Karakamsha)') + _add_chart_args(p) + p.add_argument('--mode', default='all', choices=['all','karaka','dasha','karakamsha'], help='分析模式') + + # 18. nakshatra-adv (v3.7新增) + p = sub.add_parser('nakshatra-adv', help='高级Nakshatra分析') + _add_chart_args(p) + p.add_argument('--mode', default='all', choices=['all','detail','tara','sublord'], help='分析模式') + + # 19. argala (v3.7新增) + p = sub.add_parser('argala', help='Argala门闩系统') + _add_chart_args(p) + + # 20. tajika (v3.7新增) + p = sub.add_parser('tajika', help='Tajika/Varshaphala年运盘') + _add_chart_args(p) + p.add_argument('--age', type=int, required=True, help='当前年龄') + p.add_argument('--mode', default='all', choices=['all','muntha','yearlord','mudda','tripataka'], help='分析模式') + + # 21. synastry (v3.7新增) + p = sub.add_parser('synastry', help='合盘分析(Ashta Koota 36分制)') + p.add_argument('--moon1', type=float, required=True, help='Person1月亮黄经') + p.add_argument('--moon2', type=float, required=True, help='Person2月亮黄经') + p.add_argument('--mars1', type=float, default=None, help='Person1火星黄经') + p.add_argument('--mars2', type=float, default=None, help='Person2火星黄经') + p.add_argument('--asc1', type=float, default=None, help='Person1上升黄经') + p.add_argument('--asc2', type=float, default=None, help='Person2上升黄经') + p.add_argument('--gender1', default='M', help='Person1性别') + p.add_argument('--gender2', default='F', help='Person2性别') + args = parser.parse_args() if not args.command: parser.print_help(); sys.exit(1) @@ -1139,7 +1395,10 @@ def main(): cmds = {'chart': cmd_chart, 'dasha': cmd_dasha, 'yoga': cmd_yoga, 'predict': cmd_predict, 'varga': cmd_varga, 'celebrity': cmd_celebrity, 'db-stats': cmd_db_stats, 'transit': cmd_transit, 'shadbala': cmd_shadbala, 'ashtakavarga': cmd_ashtakavarga, 'memory': cmd_memory, - 'validate': cmd_validate, 'audit': cmd_audit, 'report': cmd_report} + 'validate': cmd_validate, 'audit': cmd_audit, 'report': cmd_report, + 'varga-full': cmd_varga_full, 'aspects': cmd_aspects, 'jaimini': cmd_jaimini, + 'nakshatra-adv': cmd_nakshatra_adv, 'argala': cmd_argala, 'tajika': cmd_tajika, + 'synastry': cmd_synastry} result = cmds[args.command](args) output_json(result) diff --git a/scripts/nakshatra_advanced.py b/scripts/nakshatra_advanced.py new file mode 100644 index 00000000..3f762051 --- /dev/null +++ b/scripts/nakshatra_advanced.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +高级星宿分析模块 v1.0 +Nakshatra深度分析系统 + +支持: + - Tara Bala(星宿力量匹配) + - Sub-Lord体系(KP占星) + - 精确Vimshottari起始点计算 + - Nakshatra兼容性分析 + - Navatara(9星宿组)分类 +""" +from typing import Dict, List, Tuple +import math + +NAK_LIST = [ + ("Ashwini","Ketu",7),("Bharani","Venus",20),("Krittika","Sun",6), + ("Rohini","Moon",10),("Mrigashira","Mars",7),("Ardra","Rahu",18), + ("Punarvasu","Jupiter",16),("Pushya","Saturn",19),("Ashlesha","Mercury",17), + ("Magha","Ketu",7),("Purva Phalguni","Venus",20),("Uttara Phalguni","Sun",6), + ("Hasta","Moon",10),("Chitra","Mars",7),("Swati","Rahu",18), + ("Vishakha","Jupiter",16),("Anuradha","Saturn",19),("Jyeshtha","Mercury",17), + ("Mula","Ketu",7),("Purva Ashadha","Venus",20),("Uttara Ashadha","Sun",6), + ("Shravana","Moon",10),("Dhanishta","Mars",7),("Shatabhisha","Rahu",18), + ("Purva Bhadrapada","Jupiter",16),("Uttara Bhadrapada","Saturn",19),("Revati","Mercury",17), +] +NAK_NAMES = [n[0] for n in NAK_LIST] +NAK_LORDS = [n[1] for n in NAK_LIST] +NAK_YEARS = [n[2] for n in NAK_LIST] + +# Navatara分组(从月亮星宿开始的9个星宿循环) +TARA_NAMES = ['Janma(生命)','Sampat(财富)','Vipat(危险)','Kshema(安全)', + 'Pratyak(障碍)','Sadhana(成就)','Vadha(毁灭)','Mitra(友好)','ParamaMitra(至友)'] +TARA_CN = {0:'生命Tara',1:'财富Tara',2:'危险Tara',3:'安全Tara', + 4:'障碍Tara',5:'成就Tara',6:'毁灭Tara',7:'友好Tara',8:'至友Tara'} + +# Nakshatra元素和属性 +NAK_GANA = { # 气质类型 + 0:'Dev(神圣)',1:'Manushya(人类)',2:'Rakshasa(罗刹)', + 3:'Dev',4:'Dev',5:'Manushya',6:'Dev',7:'Dev',8:'Rakshasa', + 9:'Rakshasa',10:'Manushya',11:'Manushya',12:'Dev',13:'Rakshasa',14:'Dev', + 15:'Rakshasa',16:'Dev',17:'Rakshasa',18:'Rakshasa',19:'Manushya',20:'Manushya', + 21:'Dev',22:'Rakshasa',23:'Rakshasa',24:'Manushya',25:'Manushya',26:'Dev', +} +NAK_ELEMENT = { + 0:'火',1:'土',2:'火',3:'土',4:'土',5:'风',6:'风',7:'水',8:'水', + 9:'火',10:'土',11:'火',12:'土',13:'火',14:'风',15:'火',16:'水',17:'风', + 18:'火',19:'水',20:'土',21:'土',22:'风',23:'风',24:'水',25:'火',26:'水', +} + + +def find_nakshatra(longitude: float) -> Dict: + """从恒星黄道经度精确计算Nakshatra""" + nak_span = 360.0 / 27 # 每个星宿13.333...° + nak_idx = int(longitude / nak_span) % 27 + nak_start = nak_idx * nak_span + deg_in_nak = longitude - nak_start + pada_span = nak_span / 4 # 每个Pada 3.333...° + pada = int(deg_in_nak / pada_span) + 1 + deg_in_pada = deg_in_nak - (pada - 1) * pada_span + + return { + 'nakshatra': NAK_NAMES[nak_idx], + 'nakshatra_idx': nak_idx, + 'nakshatra_lord': NAK_LORDS[nak_idx], + 'dasha_years': NAK_YEARS[nak_idx], + 'pada': pada, + 'degree_in_nakshatra': round(deg_in_nak, 4), + 'degree_in_pada': round(deg_in_pada, 4), + 'gana': NAK_GANA.get(nak_idx, ''), + 'element': NAK_ELEMENT.get(nak_idx, ''), + } + + +def calc_tara_bala(moon_nak_idx: int, target_nak_idx: int) -> Dict: + """ + Tara Bala计算:月亮星宿到目标星宿的9星宿循环关系 + + 用于判断某个行星/事件星宿与月亮星宿的关系 + 0=Janma(中性), 1=Sampat(吉), 2=Vipat(凶), 3=Kshema(吉), + 4=Pratyak(凶), 5=Sadhana(吉), 6=Vadha(凶), 7=Mitra(吉), 8=ParamaMitra(大吉) + """ + distance = (target_nak_idx - moon_nak_idx) % 9 + tara_name = TARA_NAMES[distance] + tara_cn = TARA_CN[distance] + + is_auspicious = distance in (1, 3, 5, 7, 8) + is_dangerous = distance in (2, 4, 6) + + return { + 'tara_index': distance, + 'tara_name': tara_name, + 'tara_cn': tara_cn, + 'is_auspicious': is_auspicious, + 'is_dangerous': is_dangerous, + 'quality': 'auspicious' if is_auspicious else 'dangerous' if is_dangerous else 'neutral', + 'interpretation': _tara_interp(distance), + } + + +def calc_all_tara_balas(moon_nak_idx: int, planet_lons: Dict[str, float]) -> Dict: + """计算所有行星相对于月亮的Tara Bala""" + results = {} + for pname, lon in planet_lons.items(): + p_nak = find_nakshatra(lon) + tara = calc_tara_bala(moon_nak_idx, p_nak['nakshatra_idx']) + results[pname] = { + 'nakshatra': p_nak['nakshatra'], + 'tara': tara, + } + return results + + +def calc_vimshottari_start(moon_longitude: float) -> Dict: + """ + 精确计算Vimshottari Dasha的起始点 + + 返回月亮所在星宿的守护星、已用度数比例、剩余年数 + """ + nak = find_nakshatra(moon_longitude) + nak_idx = nak['nakshatra_idx'] + nak_lord = NAK_LORDS[nak_idx] + total_years = NAK_YEARS[nak_idx] + + # 已用比例 + nak_span = 360.0 / 27 + nak_start = nak_idx * nak_span + deg_in_nak = moon_longitude - nak_start + used_ratio = deg_in_nak / nak_span + remaining_ratio = 1 - used_ratio + + # 第一个Mahadasha的剩余年数 + first_mahadasha_remaining = total_years * remaining_ratio + first_mahadasha_elapsed = total_years * used_ratio + + return { + 'moon_nakshatra': nak['nakshatra'], + 'moon_nakshatra_lord': nak_lord, + 'moon_pada': nak['pada'], + 'total_dasha_years': total_years, + 'used_ratio': round(used_ratio, 6), + 'remaining_ratio': round(remaining_ratio, 6), + 'first_mahadasha_remaining_years': round(first_mahadasha_remaining, 4), + 'first_mahadasha_elapsed_years': round(first_mahadasha_elapsed, 4), + 'first_mahadasha_lord': nak_lord, + } + + +def calc_sub_lord(longitude: float, division: int = 9) -> Dict: + """ + Sub-Lord计算(KP占星体系) + 将星宿进一步细分为Sub-Lord和Sub-Sub-Lord + + 参数: + longitude: 恒星黄道经度 + division: 细分层级(9=Sub-Lord, 81=Sub-Sub-Lord) + """ + # 主星宿 + nak = find_nakshatra(longitude) + nak_span = 360.0 / 27 + + # Sub-Lord: 将星宿按守护星的大运年数比例细分 + total_dasha_years = sum(NAK_YEARS) # 120年 + nak_lord = NAK_NAMES[nak['nakshatra_idx']] + + # 计算Sub-Lord + deg_in_nak = nak['degree_in_nakshatra'] + sub_lord_span = nak_span * (NAK_YEARS[NAK_LORDS.index(NAK_LORDS[nak['nakshatra_idx']])]) / total_dasha_years + + # 简化版:直接按9等分 + sub_span = nak_span / 9 + sub_idx = int(deg_in_nak / sub_span) % 9 + + # Sub-Lord是第sub_idx个行星(从星宿守护星开始的大运顺序) + DASHA_ORDER = ["Ketu","Venus","Sun","Moon","Mars","Rahu","Jupiter","Saturn","Mercury"] + nak_lord_idx = DASHA_ORDER.index(NAK_LORDS[nak['nakshatra_idx']]) + sub_lord = DASHA_ORDER[(nak_lord_idx + sub_idx) % 9] + + return { + 'nakshatra': nak['nakshatra'], + 'nakshatra_lord': nak_lord, + 'sub_lord': sub_lord, + 'sub_index': sub_idx, + 'pada': nak['pada'], + 'degree_in_nakshatra': round(deg_in_nak, 4), + } + + +def nakshatra_compatibility(nak1_idx: int, nak2_idx: int) -> Dict: + """ + Nakshatra兼容性分析(Koota系统中的星宿匹配部分) + 基于Tara Bala和Nakshatra元素匹配 + """ + # Tara匹配 + tara = calc_tara_bala(nak1_idx, nak2_idx) + tara_score = 3 if tara['is_auspicious'] else 0 if tara['is_dangerous'] else 1.5 + + # 元素匹配 + el1 = NAK_ELEMENT.get(nak1_idx, '') + el2 = NAK_ELEMENT.get(nak2_idx, '') + el_compatible = el1 == el2 or (el1 in ('火','风') and el2 in ('火','风')) or (el1 in ('土','水') and el2 in ('土','水')) + + # Gana匹配 + g1 = NAK_GANA.get(nak1_idx, '') + g2 = NAK_GANA.get(nak2_idx, '') + gana_score = 6 if g1 == g2 else 3 if ('Dev' in g1 and 'Manushya' in g2) or ('Manushya' in g1 and 'Dev' in g2) else 0 + + return { + 'nak1': NAK_NAMES[nak1_idx], + 'nak2': NAK_NAMES[nak2_idx], + 'tara_bala': tara, + 'tara_score': tara_score, + 'element_match': {'e1': el1, 'e2': el2, 'compatible': el_compatible}, + 'gana_match': {'g1': g1, 'g2': g2, 'score': gana_score}, + 'overall': 'compatible' if tara_score >= 1.5 and el_compatible else 'challenging' if tara['is_dangerous'] else 'moderate', + } + + +def _tara_interp(idx): + interps = { + 0: '生命Tara—中性,代表自我,不吉不凶', + 1: '财富Tara—吉,带来物质和精神增长', + 2: '危险Tara—凶,需要谨慎,可能带来损失', + 3: '安全Tara—吉,提供保护和稳定', + 4: '障碍Tara—凶,可能面临困难和延误', + 5: '成就Tara—吉,有利于实现目标', + 6: '毁灭Tara—凶,最大凶Tara,需特别注意', + 7: '友好Tara—吉,提供支持和帮助', + 8: '至友Tara—大吉,最强吉Tara', + } + return interps.get(idx, '') diff --git a/scripts/synastry.py b/scripts/synastry.py new file mode 100644 index 00000000..e705a876 --- /dev/null +++ b/scripts/synastry.py @@ -0,0 +1,434 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +synastry.py — 合盘分析模块 +Ashta Koota 36分制 + Mangal Dosha + Papasamya + Dasha兼容性 + +依赖: swisseph (可选,用于从出生数据直接计算) +""" + +import math + +SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo', + 'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] + +# 27 Nakshatras: (name, lord, yoni) +NAK27 = [ + ("Ashwini",0),("Bharani",1),("Krittika",2),("Rohini",3),("Mrigashira",4), + ("Ardra",5),("Punarvasu",6),("Pushya",7),("Ashlesha",8),("Magha",0), + ("Purva Phalguni",1),("Uttara Phalguni",2),("Hasta",3),("Chitra",4), + ("Swati",5),("Vishakha",6),("Anuradha",7),("Jyeshtha",8),("Mula",0), + ("Purva Ashadha",1),("Uttara Ashadha",2),("Shravana",3),("Dhanishta",4), + ("Shatabhisha",5),("Purva Bhadrapada",6),("Uttara Bhadrapada",7),("Revati",8), +] +# Gana: Deva(0)=0,1,6,7,14; Manushya(1)=2,4,8,10,12,15,18,20,22; Rakshasa(2)=3,5,9,11,13,16,17,19,21,23,24,25,26 +GANA_MAP = [0,0,1,2,1,2,0,0,1,2,1,2,1,2,0,1,2,2,2,2,1,2,1,2,2,2,2] +# Yoni: 14种动物配对 +YONI_MAP = [0,1,2,3,4,5,6,7,2,0,1,8,3,4,5,6,7,8,9,1,8,3,10,4,5,6,11] +YONI_NAME = ['Horse','Elephant','Sheep','Serpent','Dog','Cat','Rat','Cow','Buffalo', + 'Tiger','Lion','Fish','?','?'] +# Nadi: 3种 (Vata=0, Pitta=1, Kapha=2) +NADI_MAP = [0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,1,2] +# Varna: 4种种姓 (Brahmin=3, Kshatriya=2, Vaishya=1, Shudra=0) +VARNA_MAP = [0,2,1,3,0,2,1,3,0,2,1,3,0,2,1,3,0,2,1,3,0,2,1,3,0,2,1] +# Vashya: sign-based +VASHYA_SIGN = [0,1,2,3,0,1,2,3,0,1,2,3] # 0=quadruped,1=biped,2=insect,3=water + +# Dasha years for Vimshottari +DASHA_YEARS = {"Ketu":7,"Venus":20,"Sun":6,"Moon":10,"Mars":7,"Rahu":18,"Jupiter":16,"Saturn":19,"Mercury":17} +DASHA_ORDER = ["Ketu","Venus","Sun","Moon","Mars","Rahu","Jupiter","Saturn","Mercury"] + +def _nak_idx(lon): + """黄经→Nakshatra索引(0-26)""" + return int(lon / (360/27)) % 27 + +def _sign_idx(lon): + return int(lon / 30) % 12 + +# ============================================================================ +# Ashta Koota 8维度评分 +# ============================================================================ +def _calc_varna(n1, n2): + """Varna Koota (1分) - 精神兼容""" + v1, v2 = VARNA_MAP[n1], VARNA_MAP[n2] + if v2 >= v1: return {'score':1,'max':1,'v1':v1,'v2':v2,'note':'女方种姓≥男方,吉'} + return {'score':0,'max':1,'v1':v1,'v2':v2,'note':'女方种姓<男方,不吉'} + +def _calc_vashya(s1, s2): + """Vashya Koota (2分) - 控制力""" + v1, v2 = VASHYA_SIGN[s1], VASHYA_SIGN[s2] + if v1 == v2: return {'score':2,'max':2,'note':'同Vashya,满分'} + # 互补对 + pairs = {(0,3),(3,0),(1,2),(2,1)} + if (v1,v2) in pairs: return {'score':1,'max':2,'note':'互补Vashya,半分'} + return {'score':0,'max':2,'note':'Vashya不合'} + +def _calc_tara(n1, n2): + """Tara Koota (3分) - 星宿距离吉凶""" + diff = (n2 - n1) % 9 + auspicious = {0,2,4,6,8} # Janma,Sampat,Kshema,Pratyak,Sadhana → 2,4,6,8,0 mod9 + # 好Tara: Sampat(2),Kshema(4),Sadhana(6),Priya(8) + good = {2,4,6,8} + if diff in good: return {'score':3,'max':3,'diff':diff,'note':'吉Tara'} + if diff == 0: return {'score':2,'max':3,'diff':diff,'note':'中性Tara(Janma)'} + return {'score':0,'max':3,'diff':diff,'note':'凶Tara'} + +def _calc_yoni(n1, n2): + """Yoni Koota (4分) - 生理兼容""" + y1, y2 = YONI_MAP[n1], YONI_MAP[n2] + if y1 == y2: return {'score':4,'max':4,'note':'同Yoni,满分'} + # 敌对Yoni + enemy = {(0,5),(5,0),(2,4),(4,2),(3,2),(2,3)} + if (y1,y2) in enemy: return {'score':0,'max':4,'note':'敌对Yoni'} + # 友好Yoni + friendly = {(0,7),(7,0),(1,8),(8,1),(6,9),(9,6)} + if (y1,y2) in friendly: return {'score':3,'max':4,'note':'友好Yoni'} + return {'score':1,'max':4,'note':'中性Yoni'} + +def _calc_graha_maitri(lord1, lord2): + """Graha Maitri Koota (5分) - 行星友谊""" + # 行星友谊表 + friends = {'Sun':['Moon','Mars','Jupiter'],'Moon':['Sun','Mercury'], + 'Mars':['Sun','Moon','Jupiter'],'Mercury':['Sun','Venus'], + 'Jupiter':['Sun','Moon','Mars'],'Venus':['Mercury','Saturn'], + 'Saturn':['Mercury','Venus'],'Rahu':['Venus','Saturn'], + 'Ketu':['Venus','Saturn']} + enemies = {'Sun':['Saturn','Venus'],'Moon':['Mercury'],'Mars':['Mercury','Saturn'], + 'Mercury':['Mars','Jupiter','Saturn'],'Jupiter':['Mercury','Venus'], + 'Venus':['Sun','Moon','Mars'],'Saturn':['Sun','Moon','Mars'], + 'Rahu':['Sun','Moon','Mars'],'Ketu':['Sun','Moon','Mars']} + if lord1 == lord2: return {'score':5,'max':5,'note':'同主星,满分'} + l2_friends = friends.get(lord2,[]) + l2_enemies = enemies.get(lord2,[]) + if lord1 in l2_friends: return {'score':4,'max':5,'note':f'{lord1}是{lord2}的友星'} + if lord1 in l2_enemies: return {'score':0,'max':5,'note':f'{lord1}是{lord2}的敌星'} + return {'score':2,'max':5,'note':f'{lord1}对{lord2}中性'} + +def _calc_gana(n1, n2): + """Gana Koota (6分) - 气质类型""" + g1, g2 = GANA_MAP[n1], GANA_MAP[n2] + gana_name = {0:'Deva(天神)',1:'Manushya(人类)',2:'Rakshasa(罗刹)'} + if g1 == g2: return {'score':6,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'同Gana,满分'} + # Deva+Manushya + combo = {g1,g2} + if combo == {0,1}: return {'score':5,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'天神+人类,良好'} + if combo == {1,2}: return {'score':2,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'人类+罗刹,较差'} + # Deva+Rakshasa + return {'score':1,'max':6,'g1':gana_name[g1],'g2':gana_name[g2],'note':'天神+罗刹,很差'} + +def _calc_bhakuta(n1, n2): + """Bhakuta Koota (7分) - 情感+经济""" + diff = (n2 - n1) % 9 + # 满分组合 + full = {1,3,5,7} + if diff in full: return {'score':7,'max':7,'diff':diff,'note':'吉Bhakuta,满分'} + if diff == 0: return {'score':4,'max':7,'diff':diff,'note':'中性Bhakuta'} + return {'score':0,'max':7,'diff':diff,'note':'凶Bhakuta,零分'} + +def _calc_nadi(n1, n2): + """Nadi Koota (8分) - 生命力/健康""" + na1, na2 = NADI_MAP[n1], NADI_MAP[n2] + nadi_name = {0:'Vata(风)',1:'Pitta(胆汁)',2:'Kapha(粘液)'} + if na1 == na2: + return {'score':0,'max':8,'n1':nadi_name[na1],'n2':nadi_name[na2], + 'note':'同Nadi=不吉(传统认为影响后代健康)'} + return {'score':8,'max':8,'n1':nadi_name[na1],'n2':nadi_name[na2], + 'note':'不同Nadi=吉,互补生命力'} + +# ============================================================================ +# 合盘主函数 +# ============================================================================ +def calc_synastry(person1: dict, person2: dict) -> dict: + """ + 计算两人的完整合盘分析 + + person1/person2 格式: + { + 'moon_lon': float, # 月亮黄经(恒星黄道) + 'mars_lon': float, # 火星黄经(恒星黄道) (可选) + 'asc_lon': float, # 上升黄经(恒星黄道) (可选) + 'nakshatra_lord': str, # 月亮Nakshatra主星 (可选,自动计算) + 'gender': str, # 'M' or 'F' (影响Varna方向) + } + + 返回: 完整合盘报告 dict + """ + m1 = person1.get('moon_lon', 0) + m2 = person2.get('moon_lon', 0) + n1 = _nak_idx(m1) + n2 = _nak_idx(m2) + s1 = _sign_idx(m1) + s2 = _sign_idx(m2) + + # Nakshatra主星 + lords = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury'] + lord1 = person1.get('nakshatra_lord') or lords[n1 % 9] + lord2 = person2.get('nakshatra_lord') or lords[n2 % 9] + + # Ashta Koota 8维度 + varna = _calc_varna(n1, n2) + vashya = _calc_vashya(s1, s2) + tara = _calc_tara(n1, n2) + yoni = _calc_yoni(n1, n2) + graha = _calc_graha_maitri(lord1, lord2) + gana = _calc_gana(n1, n2) + bhakuta = _calc_bhakuta(n1, n2) + nadi = _calc_nadi(n1, n2) + + kootas = [ + ('Varna(精神)', varna), ('Vashya(控制)', vashya), ('Tara(星宿距离)', tara), + ('Yoni(生理)', yoni), ('Graha Maitri(行星友谊)', graha), + ('Gana(气质)', gana), ('Bhakuta(情感经济)', bhakuta), ('Nadi(生命力)', nadi), + ] + + total = sum(k[1]['score'] for k in kootas) + max_total = sum(k[1]['max'] for k in kootas) + pct = round(total / max_total * 100, 1) if max_total else 0 + + # 综合判定 + verdict = _compatibility_verdict(total, pct) + + # Mangal Dosha 检查 + dosha1 = _check_mangal_dosha(person1) + dosha2 = _check_mangal_dosha(person2) + mangal_note = _dosha_match(dosha1, dosha2) + + # Papasamya (双方凶星抵消度) + papasamya = _calc_papasamya(person1, person2) + + return { + 'method': 'Ashta Koota 36分制 + Mangal Dosha + Papasamya', + 'version': '3.7', + 'person1': {'moon_nakshatra': NAK27[n1][0], 'nak_idx': n1, 'moon_sign': SIGNS[s1]}, + 'person2': {'moon_nakshatra': NAK27[n2][0], 'nak_idx': n2, 'moon_sign': SIGNS[s2]}, + 'kootas': {k:v for k,v in kootas}, + 'total_score': total, + 'max_score': max_total, + 'percentage': pct, + 'verdict': verdict, + 'mangal_dosha': {'person1': dosha1, 'person2': dosha2, 'compatibility': mangal_note}, + 'papasamya': papasamya, + 'recommendations': _generate_recommendations(total, pct, kootas, mangal_note), + } + + +def _compatibility_verdict(score, pct): + """综合判定""" + if pct >= 75: return {'level':'极佳','color':'绿色','cn':'强烈推荐,天然契合'} + if pct >= 60: return {'level':'良好','color':'蓝绿','cn':'推荐,小问题可化解'} + if pct >= 45: return {'level':'一般','color':'黄色','cn':'需要努力调和'} + if pct >= 30: return {'level':'较差','color':'橙色','cn':'不推荐,阻力较大'} + return {'level':'很差','color':'红色','cn':'强烈不推荐,冲突严重'} + + +def _check_mangal_dosha(person): + """Mangal Dosha(火星凶相)检查""" + mars_lon = person.get('mars_lon') + if mars_lon is None: + return {'has_dosha': None, 'note': '缺少火星数据'} + mars_house = person.get('mars_house') + mars_sign = _sign_idx(mars_lon) if mars_house is None else None + + # 需要计算火星在第几宫(相对于上升) + asc_lon = person.get('asc_lon') + if asc_lon is not None and mars_house is None: + asc_si = _sign_idx(asc_lon) + mars_si = _sign_idx(mars_lon) + mars_house = ((mars_si - asc_si) % 12) + 1 + + if mars_house is None: + return {'has_dosha': None, 'note': '缺少上升数据,无法确定宫位'} + + # Mangal Dosha: 火星在1/2/4/7/8/12宫 + dosha_houses = {1, 2, 4, 7, 8, 12} + has = mars_house in dosha_houses + + # 强度分级 + severity = '无' + if has: + if mars_house in {7, 8}: + severity = '强' + elif mars_house in {1, 2}: + severity = '中' + else: + severity = '轻' + + return { + 'has_dosha': has, + 'severity': severity if has else '无', + 'mars_house': mars_house, + 'note': f'火星在第{mars_house}宫' + ('→有Mangal Dosha' if has else '→无Mangal Dosha'), + } + + +def _dosha_match(dosha1, dosha2): + """双方Mangal Dosha匹配""" + d1 = dosha1.get('has_dosha') + d2 = dosha2.get('has_dosha') + if d1 is None or d2 is None: + return {'status':'数据不足','note':'缺少一方火星数据'} + if d1 and d2: + return {'status':'双Dosha匹配','note':'双方都有Mangal Dosha,互相抵消→可接受'} + if d1 and not d2: + return {'status':'仅一方Dosha','note':'仅Person1有Dosha,Person2需有足够力量承受'} + if not d1 and d2: + return {'status':'仅一方Dosha','note':'仅Person2有Dosha,Person1需有足够力量承受'} + return {'status':'双方无Dosha','note':'双方均无Mangal Dosha→理想'} + + +def _calc_papasamya(p1, p2): + """Papasamya - 双方凶星点数抵消""" + # 简化版:检查太阳/火星/土星/北交点在凶宫(6/8/12)的情况 + def _count_papa(person): + count = 0 + for key in ['sun_house','mars_house','saturn_house','rahu_house']: + h = person.get(key) + if h and h in {6, 8, 12}: + count += 1 + return count + + c1 = _count_papa(p1) + c2 = _count_papa(p2) + diff = abs(c1 - c2) + + if diff == 0: + balance = '均衡' + note = '双方凶星负担相当,Papasamya良好' + elif diff == 1: + balance = '轻微失衡' + note = '一方凶星负担略重,可接受' + else: + balance = '明显失衡' + note = '双方凶星负担差异较大,需注意' + + return {'person1_papa': c1, 'person2_papa': c2, 'difference': diff, + 'balance': balance, 'note': note} + + +def _generate_recommendations(score, pct, kootas, mangal_note): + """生成建议""" + recs = [] + + # 低分维度警告 + for name, data in kootas: + if data['score'] < data['max'] * 0.3: + recs.append(f"⚠ {name}得分低({data['score']}/{data['max']}):{data['note']}") + + # Nadi特别警告 + nadi_score = kootas[7][1]['score'] # Nadi是第8个 + if nadi_score == 0: + recs.append("🔴 Nadi Koota零分!传统认为同Nadi婚姻可能影响后代健康,建议咨询专业占星师") + recs.append("化解方案:Nadi Dosha Pooja、佩戴对应宝石、或寻找Nadi不同的伴侣") + + # Mangal Dosha建议 + if mangal_note.get('status') == '仅一方Dosha': + recs.append("⚠ 一方有Mangal Dosha:建议找有Dosha的伴侣匹配,或进行Mangal Dosha化解仪式") + recs.append("化解方案:Kumbh Vivah(与陶罐结婚仪式)、在28岁后结婚、或匹配有Dosha的对象") + + # 总分建议 + if pct >= 60: + recs.append(f"✅ 总分{pct}%,在可接受范围内。建议深入了解彼此价值观和生活目标") + elif pct >= 45: + recs.append(f"⚠ 总分{pct}%,需要双方更多努力和理解。建议进行详细的合盘咨询") + else: + recs.append(f"❌ 总分{pct}%,传统认为阻力较大。强烈建议咨询专业占星师后再做决定") + + return recs + + +# ============================================================================ +# Dasha 兼容性分析 +# ============================================================================ +def calc_dasha_compatibility(dasha1: list, dasha2: list, start_year: int) -> dict: + """ + 分析两人Dasha时间线的兼容性 + + dasha1/dasha2: [{'lord':'Venus','start':'1990-01-01','end':'2010-01-01','years':20}, ...] + start_year: 分析起始年份 + """ + from datetime import datetime + + periods = [] + conflicts = [] + harmonies = [] + + # 行星兼容性 + friends = {'Sun':['Moon','Mars','Jupiter'],'Moon':['Sun','Mercury'], + 'Mars':['Sun','Moon','Jupiter'],'Mercury':['Sun','Venus'], + 'Jupiter':['Sun','Moon','Mars'],'Venus':['Mercury','Saturn'], + 'Saturn':['Mercury','Venus'],'Rahu':['Venus','Saturn'],'Ketu':['Venus','Saturn']} + + for d1 in dasha1: + for d2 in dasha2: + # 检查时间重叠 + try: + s1 = datetime.strptime(d1['start'],'%Y-%m-%d') + e1 = datetime.strptime(d1['end'],'%Y-%m-%d') + s2 = datetime.strptime(d2['start'],'%Y-%m-%d') + e2 = datetime.strptime(d2['end'],'%Y-%m-%d') + except: + continue + + overlap_start = max(s1, s2) + overlap_end = min(e1, e2) + if overlap_start >= overlap_end: + continue + if overlap_start.year < start_year: + continue + + lord1 = d1.get('lord','') + lord2 = d2.get('lord','') + + # 判断关系 + l2_friends = friends.get(lord2, []) + rel = '中性' + if lord1 == lord2: + rel = '同步' + elif lord1 in l2_friends: + rel = '友好' + elif lord1 in ['Rahu','Ketu'] or lord2 in ['Rahu','Ketu']: + rel = '变化' + else: + rel = '紧张' + + period_info = { + 'period': f"{overlap_start.strftime('%Y-%m')} ~ {overlap_end.strftime('%Y-%m')}", + 'person1_dasha': lord1, + 'person2_dasha': lord2, + 'relationship': rel, + } + periods.append(period_info) + + if rel in ('紧张','变化'): + conflicts.append(period_info) + elif rel in ('同步','友好'): + harmonies.append(period_info) + + return { + 'method': 'Dasha时间线兼容性', + 'periods_analyzed': len(periods), + 'conflicts': conflicts[:5], + 'harmonies': harmonies[:5], + 'conflict_count': len(conflicts), + 'harmony_count': len(harmonies), + 'assessment': '和谐' if len(harmonies) > len(conflicts) * 1.5 else + ('一般' if len(harmonies) >= len(conflicts) else '紧张'), + } + + +# ============================================================================ +# CLI 入口 +# ============================================================================ +if __name__ == '__main__': + import json + import sys + + # 示例用法 + if len(sys.argv) > 1 and sys.argv[1] == 'test': + p1 = {'moon_lon': 45.5, 'mars_lon': 120.3, 'asc_lon': 150.0, 'gender': 'M'} + p2 = {'moon_lon': 200.7, 'mars_lon': 30.1, 'asc_lon': 60.0, 'gender': 'F'} + result = calc_synastry(p1, p2) + print(json.dumps(result, ensure_ascii=False, indent=2)) + else: + print("Usage: python synastry.py test") + print(" test - 运行示例测试") diff --git a/scripts/tajika.py b/scripts/tajika.py new file mode 100644 index 00000000..d955100e --- /dev/null +++ b/scripts/tajika.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tajika/Varshaphala年运盘模块 v1.0 +太阳返照盘分析系统 + +支持: + - Muntha(年度上升点移动) + - Varshaphala计算(太阳回到出生位置时的星盘) + - Mudda Dasha(年度大运) + - Tajika Yoga(年度特殊格局) + - Tri-Pataka(三旗系统) + -年度Lord(Year Lord) +""" +from typing import Dict, List, Optional +from datetime import datetime +import math + +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'} + + +def calc_muntha(birth_asc_idx: int, age: int) -> Dict: + """ + Muntha计算:年度上升点 + 从出生上升星座开始,每年前进一个星座 + + 参数: + birth_asc_idx: 出生上升星座索引(0=Aries) + age: 当前年龄(整数) + """ + muntha_idx = (birth_asc_idx + age) % 12 + muntha_sign = SIGNS[muntha_idx] + lord = SIGN_LORDS[muntha_sign] + + # Muntha落在的宫位(从本命盘上升算起) + # 需要本命盘上升来计算 + + return { + 'muntha_sign': muntha_sign, + 'muntha_sign_idx': muntha_idx, + 'muntha_lord': lord, + 'age': age, + 'interpretation': _muntha_interp(muntha_sign, lord), + } + + +def calc_year_lord(birth_asc_idx: int, age: int) -> Dict: + """ + Year Lord(年度守护星) + 基于Muntha位置确定该年的守护行星 + + 规则: + - Muntha落在某星座→该星座守护星为Year Lord + - Muntha的2/5/9/11宫的守护星为辅助 + """ + muntha_idx = (birth_asc_idx + age) % 12 + muntha_sign = SIGNS[muntha_idx] + year_lord = SIGN_LORDS[muntha_sign] + + # 辅助星(2/5/9/11宫主) + aux_houses = [2, 5, 9, 11] + aux_lords = [] + for h in aux_houses: + h_sign_idx = (muntha_idx + h - 1) % 12 + h_sign = SIGNS[h_sign_idx] + aux_lords.append({'house': h, 'sign': h_sign, 'lord': SIGN_LORDS[h_sign]}) + + return { + 'age': age, + 'year_lord': year_lord, + 'muntha_sign': muntha_sign, + 'auxiliary_lords': aux_lords, + 'year_theme': _year_theme(year_lord), + } + + +def calc_varshaphala(birth_datetime: datetime, + target_year: int, + birth_lon: float, birth_lat: float, + birth_tz: float) -> Dict: + """ + Varshaphala(太阳返照盘)计算 + + 原理: 太阳回到出生时精确位置的时刻,重新起盘 + 这个新的上升星座和行星配置代表该年的运势 + + 参数: + birth_datetime: 出生时间 + target_year: 目标年份 + birth_lon/lat: 出生经纬度 + birth_tz: 出生时区 + """ + # 简化版:用Muntha + Year Lord + 基本Tajika规则 + # 完整版需要Swiss Ephemeris计算太阳精确返照时间 + + # 出生上升(需要传入,这里用简化版) + age = target_year - birth_datetime.year + + return { + 'target_year': target_year, + 'age': age, + 'note': 'Varshaphala完整计算需要Swiss Ephemeris精确返照时间', + 'components': { + 'muntha': calc_muntha(0, age), # 需要实际出生上升 + 'year_lord': calc_year_lord(0, age), + } + } + + +def calc_mudda_dasha(asc_sign_idx: int, + varsha_lord: str, + birth_month: int) -> Dict: + """ + Mudda Dasha(年度大运) + 基于Varshaphala上升的12个月大运系统 + + 规则: 从年度守护星开始,按Vimshottari顺序排列 + 每个大运按比例分配12个月 + """ + DASHA_ORDER = ["Ketu","Venus","Sun","Moon","Mars","Rahu","Jupiter","Saturn","Mercury"] + DASHA_YEARS = {"Ketu":7,"Venus":20,"Sun":6,"Moon":10,"Mars":7,"Rahu":18,"Jupiter":16,"Saturn":19,"Mercury":17} + + start_idx = DASHA_ORDER.index(varsha_lord) if varsha_lord in DASHA_ORDER else 0 + + sequence = [] + remaining_months = 12.0 + + for i in range(9): + lord = DASHA_ORDER[(start_idx + i) % 9] + years = DASHA_YEARS[lord] + months = years * 12.0 / 120.0 # 按比例分配 + + if months > remaining_months: + months = remaining_months + remaining_months -= months + + sequence.append({ + 'lord': lord, + 'months': round(months, 2), + 'order': i + 1, + }) + + if remaining_months <= 0: + break + + return { + 'varsha_lord': varsha_lord, + 'dasha_sequence': sequence, + 'total_months': 12, + } + + +def calc_tri_pataka(planet_lons: Dict[str, float], + varsha_lord: str, + muntha_sign_idx: int) -> Dict: + """ + Tri-Pataka(三旗系统) + Tajika占星中判断年度吉凶的重要技法 + + 三旗: + 1. Dasha Lord(大运守护星)的强度 + 2. Muntha Lord(Muntha守护星)的强度 + 3. Year Lord(年度守护星)的强度 + + 三者都强→大吉年;三者都弱→凶年 + """ + muntha_lord = SIGN_LORDS.get(SIGNS[muntha_sign_idx], '') + + # 评估各守护星强度(简化版) + def _strength(planet, lons): + if planet not in lons: + return 'unknown' + lon = lons[planet] + si = int(lon / 30) % 12 + # 简化:在角宫(1/4/7/10)=强,三方(5/9)=中,其他=弱 + house_from_asc = ((si - muntha_sign_idx) % 12) + 1 + if house_from_asc in (1, 4, 7, 10): return 'strong' + if house_from_asc in (5, 9): return 'moderate' + return 'weak' + + dl_strength = _strength(varsha_lord, planet_lons) + ml_strength = _strength(muntha_lord, planet_lons) + yl_strength = _strength(varsha_lord, planet_lons) + + strong_count = sum(1 for s in [dl_strength, ml_strength, yl_strength] if s == 'strong') + weak_count = sum(1 for s in [dl_strength, ml_strength, yl_strength] if s == 'weak') + + if strong_count >= 2: verdict = 'excellent' + elif weak_count >= 2: verdict = 'challenging' + else: verdict = 'mixed' + + return { + 'dasha_lord': {'planet': varsha_lord, 'strength': dl_strength}, + 'muntha_lord': {'planet': muntha_lord, 'strength': ml_strength}, + 'year_lord': {'planet': varsha_lord, 'strength': yl_strength}, + 'verdict': verdict, + 'interpretation': { + 'excellent': '三旗中两旗以上强旺,年度运势极佳', + 'mixed': '三旗力量参差不齐,年度运势起伏', + 'challenging': '三旗中两旗以上衰弱,年度运势挑战较大', + }.get(verdict, ''), + } + + +def _muntha_interp(sign, lord): + """Muntha在12星座的基本解读""" + interps = { + 'Aries': '年度主题:新开始、冒险、独立行动', + 'Taurus': '年度主题:财务稳定、物质积累、感官享受', + 'Gemini': '年度主题:学习、沟通、多元发展', + 'Cancer': '年度主题:家庭、情感、内在安全感', + 'Leo': '年度主题:创造力、领导力、自我表达', + 'Virgo': '年度主题:健康、服务、细节完善', + 'Libra': '年度主题:关系、合作、美学追求', + 'Scorpio': '年度主题:转化、深层变革、隐藏事物', + 'Sagittarius': '年度主题:远方旅行、哲学、高等教育', + 'Capricorn': '年度主题:事业成就、社会地位、长期规划', + 'Aquarius': '年度主题:社交网络、创新、人道主义', + 'Pisces': '年度主题:灵性成长、隐退、创意灵感', + } + return interps.get(sign, '') + + +def _year_theme(lord): + """年度守护星主题""" + themes = { + 'Sun': '权威、政府、父亲、领导力', + 'Moon': '公众、母亲、情感、直觉', + 'Mars': '行动、竞争、房地产、手术', + 'Mercury': '沟通、商业、学习、旅行', + 'Jupiter': '智慧、子女、宗教、财富', + 'Venus': '爱情、艺术、奢侈品、婚姻', + 'Saturn': '纪律、长寿、建筑、责任', + } + return themes.get(lord, '') diff --git a/scripts/varga.py b/scripts/varga.py new file mode 100644 index 00000000..8c2ec5ab --- /dev/null +++ b/scripts/varga.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +吠陀占星分盘计算模块 v1.0 +BPHS Shodasavarga(十六分盘体系) +支持: D2/D3/D4/D7/D9/D10/D12/D16/D20/D24/D27/D30/D40/D45/D60 +每个分盘输出精确度数,支持进一步分析。 +""" +from typing import Dict, List, Optional + +SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo', + 'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] +SIGNS_CN = {s: f"{cn}" for s, cn in zip(SIGNS, + ['白羊座','金牛座','双子座','巨蟹座','狮子座','处女座', + '天秤座','天蝎座','射手座','摩羯座','水瓶座','双鱼座'])} +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'} +EXALT_SIGN = {'Sun':0,'Moon':1,'Mars':9,'Mercury':5,'Jupiter':3,'Venus':11,'Saturn':6} +DEBIL_SIGN = {'Sun':6,'Moon':7,'Mars':3,'Mercury':11,'Jupiter':9,'Venus':5,'Saturn':0} +OWN_SIGNS = {'Sun':[4],'Moon':[3],'Mars':[0,7],'Mercury':[2,5], + 'Jupiter':[8,11],'Venus':[1,6],'Saturn':[9,10]} +VARGA_META = { + 2:{'name':'Hora','cn':'财富','area':'财富资源'},3:{'name':'Drekkana','cn':'兄弟','area':'兄弟姐妹'}, + 4:{'name':'Turyamsa','cn':'财产','area':'财产住所'},7:{'name':'Saptamsa','cn':'子女','area':'子女后代'}, + 9:{'name':'Navamsa','cn':'婚姻','area':'婚姻伴侣灵魂'},10:{'name':'Dasamsa','cn':'事业','area':'事业公众形象'}, + 12:{'name':'Dwadashamsa','cn':'父母','area':'父母祖先'},16:{'name':'Shodasamsa','cn':'享受','area':'车辆物质'}, + 20:{'name':'Vimsamsa','cn':'修行','area':'精神修行'},24:{'name':'Siddhamsa','cn':'学识','area':'教育学识'}, + 27:{'name':'Bhamsa','cn':'力量','area':'力量弱点'},30:{'name':'Trimsamsa','cn':'苦难','area':'灾难苦难'}, + 40:{'name':'Khavedamsa','cn':'运势','area':'吉凶运势'},45:{'name':'Akshavedamsa','cn':'格局','area':'整体格局'}, + 60:{'name':'Shashtyamsa','cn':'业力','area':'前世业力同盘区分'}} + +def _si(lon): return int(lon/30)%12 +def _sn(i): return SIGNS[i%12] +def _odd(si): return si%2==0 # Aries(0)=odd + +def _d30_map(si, pi): + if _odd(si): + if pi<5: return 0 + if pi<10: return 10 + if pi<18: return 8 + if pi<25: return 2 + return 6 + else: + if pi<5: return 1 + if pi<12: return 5 + if pi<20: return 9 + if pi<25: return 7 + return 11 + +def varga_map(si, pi, div): + """BPHS分盘映射:星座索引si的第pi份→目标星座索引""" + o = _odd(si) + if div==2: return (4 if o else 3) if pi==0 else (3 if o else 4) + if div==3: return (si+pi*4)%12 if o else (si+8+pi*4)%12 + if div==4: return (si+pi)%12 if o else (si+8+pi)%12 + if div==7: return (si+pi)%12 if o else (si+6+pi)%12 + if div==9: + el={0:0,1:9,2:6,3:3}; return (el[si%4]+pi)%12 + if div==10: return (si+pi)%12 if o else (si+8+pi)%12 + if div==12: return (si+pi)%12 + if div==16: return ((0 if o else 1)+pi)%12 + if div==20: return ((0 if o else 8)+pi)%12 + if div==24: return ((4 if o else 3)+pi)%12 + if div==27: return ((0 if o else 6)+pi)%12 + if div==30: return _d30_map(si,pi) + if div==40: return ((0 if o else 6)+pi)%12 + if div==45: return ((0 if o else 6)+pi)%12 + if div==60: return (si+pi)%12 if o else (si+1+pi)%12 + raise ValueError(f"不支持的D{div}") + +def calc_varga(lon, div): + """计算行星在指定分盘的位置(星座+精确度数+尊贵状态)""" + si=_si(lon); d=lon-si*30; ps=30.0/div; pi=int(d/ps); dp=d-pi*ps + vsi=varga_map(si,pi,div) + r={'sign':_sn(vsi),'sign_idx':vsi,'degree_in_sign':round(dp,4), + 'part_index':pi,'lord':SIGN_LORDS.get(_sn(vsi),'')} + if div==9: r['pada']=pi+1 + return r + +def dignity(planet, sign_idx): + if planet in EXALT_SIGN and sign_idx==EXALT_SIGN[planet]: return 'Exalted' + if planet in DEBIL_SIGN and sign_idx==DEBIL_SIGN[planet]: return 'Debilitated' + if planet in OWN_SIGNS and sign_idx in OWN_SIGNS[planet]: return 'Own Sign' + return 'Neutral' + +def calc_all_vargas(planet_lons, asc_lon, divisions=None): + """批量计算所有指定分盘""" + if divisions is None: + divisions=[2,3,4,7,9,10,12,16,20,24,27,30,40,45,60] + results={} + for div in divisions: + m=VARGA_META.get(div,{}) + key=f"D{div}_{m.get('name',f'D{div}')}" + vd={'_meta':{'div':div,'name':m.get('name',''),'cn':m.get('cn',''), + 'area':m.get('area',''),'part_size':30.0/div}} + vd['Ascendant']=calc_varga(asc_lon,div) + for pn,lon in planet_lons.items(): + vd[pn]=calc_varga(lon,div) + # 尊贵状态 + vd['_dignity']={pn:dignity(pn,pd['sign_idx']) + for pn,pd in vd.items() if pn not in ('Ascendant','_meta') and isinstance(pd,dict) and 'sign_idx' in pd} + # D9专项 + if div==9: + ai=vd['Ascendant']['sign_idx'] + s7=(ai+6)%12 + p7=[pn for pn,pd in vd.items() if isinstance(pd,dict) and pd.get('sign_idx')==s7] + vd['_d9_analysis']={ + 'navamsa_7th_sign':_sn(s7),'navamsa_7th_lord':SIGN_LORDS.get(_sn(s7),''), + 'planets_in_d9_7th':p7, + 'venus_d9':f"{vd.get('Venus',{}).get('sign','?')} {vd.get('Venus',{}).get('degree_in_sign',0):.2f}°", + 'jupiter_d9':f"{vd.get('Jupiter',{}).get('sign','?')} {vd.get('Jupiter',{}).get('degree_in_sign',0):.2f}°", + 'venus_dignity':dignity('Venus',vd.get('Venus',{}).get('sign_idx',-1)), + 'jupiter_dignity':dignity('Jupiter',vd.get('Jupiter',{}).get('sign_idx',-1)), + } + # D60专项 + if div==60: + ai=vd['Ascendant']['sign_idx'] + ben,mal=[],[] + for pn,pd in vd.items(): + if not isinstance(pd,dict) or 'sign_idx' not in pd: continue + h=((pd['sign_idx']-ai)%12)+1 + if h in (1,4,7,10): + if pn in ('Jupiter','Venus','Moon'): ben.append(f"{pn}({h}宫)") + elif pn in ('Saturn','Mars','Sun','Rahu','Ketu'): mal.append(f"{pn}({h}宫)") + vd['_d60_analysis']={'benefics_in_kendras':ben,'malefics_in_kendras':mal, + 'karma_hint':'前世善业较重' if len(ben)>len(mal) else '前世有业力课题' if len(mal)>len(ben) else '善恶交织'} + results[key]=vd + return results