v3.7.0: 新增7大模块 - BPHS十六分盘/精确相位/Jaimini/高级Nakshatra/Argala门闩/Tajika年运/合盘分析

- varga.py: D2-D60 BPHS十六分盘完整计算
- aspects.py: 度数精确相位系统(tight/moderate/loose)
- jaimini.py: Chara Karaka 7/8 + Chara Dasha + Karakamsha
- nakshatra_advanced.py: Tara Bala + Sub-Lord KP系统
- argala.py: Argala门闩 + Virodha反干预
- tajika.py: Muntha/YearLord/Mudda Dasha/Tri-Pataka
- synastry.py: Ashta Koota 36分制 + Mangal Dosha + Papasamya

引擎升级至v3.7.0, 21个子命令全链路验证通过
This commit is contained in:
732642856
2026-04-25 00:34:21 +08:00
parent fffc940082
commit 0178dfb3e8
10 changed files with 2009 additions and 16 deletions
+67
View File
@@ -1,5 +1,72 @@
# 印度占星 Skill 更新日志
## v3.7.02026-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 KarakaAK→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门闩系统
- 主Argala2/4/11宫 → 财富/幸福/收益
- 副Argala5/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.02026-04-24)— CNWU16二次借鉴:report_builder + R2b列校验 + P3/P8/冲突仲裁
### 新增文件
+26 -13
View File
@@ -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 高级NakshatraTara 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-24v3.3.0 整合Kimi训练手册精华:排盘软件与Ayanamsa指南+Shodasavarga十六分盘体系+替代推运系统+条件Dasha系统+古典文献翻译指南+专业发展路径
**最后更新**2026-04-25v3.7.0 新增7大计算模块:BPHS十六分盘精确计算+度数精确相位+Jaimini完整系统+高级Nakshatra分析+Argala门闩+Tajika年运盘+合盘分析,引擎子命令14→21
+149
View File
@@ -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, '一般性影响')
+204
View File
@@ -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
+267
View File
@@ -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, ''),
}
+262 -3
View File
@@ -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)
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
高级星宿分析模块 v1.0
Nakshatra深度分析系统
支持:
- Tara Bala(星宿力量匹配)
- Sub-Lord体系(KP占星)
- 精确Vimshottari起始点计算
- Nakshatra兼容性分析
- Navatara9星宿组)分类
"""
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, '')
+434
View File
@@ -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有DoshaPerson2需有足够力量承受'}
if not d1 and d2:
return {'status':'仅一方Dosha','note':'仅Person2有DoshaPerson1需有足够力量承受'}
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 - 运行示例测试")
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tajika/Varshaphala年运盘模块 v1.0
太阳返照盘分析系统
支持:
- Muntha(年度上升点移动)
- Varshaphala计算(太阳回到出生位置时的星盘)
- Mudda Dasha(年度大运)
- Tajika Yoga(年度特殊格局)
- Tri-Pataka(三旗系统)
-年度LordYear 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 LordMuntha守护星)的强度
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, '')
+129
View File
@@ -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