f83db2fac1
- add external validation reports and open-source comparison references - integrate Jaimini arudha/graha pada, enhanced argala, and additional synastry kutas - update skill docs and capability matrices - add smoke tests for open-source integrations
200 lines
7.6 KiB
Python
200 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Argala(门闩)行星干预模块 v1.1
|
|
|
|
从旧版“逐行星Argala”升级为“逐参考点/逐宫完整Argala”:
|
|
- 主Argala: 2/4/11宫
|
|
- Virodhargala: 12/10/3宫分别阻挡2/4/11宫
|
|
- 特殊Argala: 第3宫有2颗以上凶星
|
|
- 次级Argala: 5/9宫
|
|
- Argala Rajayoga分类: Poornargala / Tripadargala / Ardhargala / Padargala
|
|
|
|
MIT复用来源:
|
|
- jaimini-tropical/jaimini/core/argala.py (tunanfang-pixel, MIT)
|
|
适配:使用本项目 chart 输出的英文行星名与恒星黄道星座索引。
|
|
"""
|
|
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'}
|
|
|
|
NATURAL_MALEFICS = {'Sun', 'Mars', 'Saturn'}
|
|
NATURAL_BENEFICS = {'Moon', 'Mercury', 'Jupiter', 'Venus'}
|
|
NODES = {'Rahu', 'Ketu'}
|
|
|
|
PRIMARY_ARGALA = {2, 4, 11}
|
|
VIRODHARGALA_MAP = {2: 12, 4: 10, 11: 3}
|
|
SECONDARY_ARGALA = {5, 9}
|
|
|
|
|
|
def _house_from(ref_sign_idx: int, offset: int) -> int:
|
|
"""从参考星座起算第offset宫对应的星座索引。"""
|
|
return (ref_sign_idx + offset - 1) % 12
|
|
|
|
|
|
def _planets_in_sign(sign_idx: int, planet_sign_indices: Dict[str, int], include_nodes: bool = False) -> List[str]:
|
|
planets = []
|
|
for pname, psi in planet_sign_indices.items():
|
|
if not include_nodes and pname in NODES:
|
|
continue
|
|
if psi % 12 == sign_idx % 12:
|
|
planets.append(pname)
|
|
return planets
|
|
|
|
|
|
def classify_argala_rajayoga(argala_result: Dict) -> Dict:
|
|
"""按主Argala占据数量分类Rajayoga强度。"""
|
|
primary = argala_result.get('primary', {})
|
|
occupied = sum(1 for h in ['H2', 'H4', 'H11'] if primary.get(h, {}).get('planets'))
|
|
blocked = sum(1 for h in ['H2', 'H4', 'H11'] if primary.get(h, {}).get('blockers'))
|
|
if occupied == 3:
|
|
return {'type': 'Poornargala', 'level': 4, 'cn': '完整Argala,最强支持'}
|
|
if occupied == 2:
|
|
return {'type': 'Tripadargala', 'level': 3, 'cn': '三足Argala,强支持'}
|
|
if occupied == 1:
|
|
return {'type': 'Ardhargala', 'level': 2, 'cn': '半Argala,中等支持'}
|
|
if blocked:
|
|
return {'type': 'Padargala', 'level': 1, 'cn': '弱Argala且受阻'}
|
|
return {'type': 'None', 'level': 0, 'cn': '无明显Argala'}
|
|
|
|
|
|
def calc_argala_for_reference(ref_sign_idx: int, planet_sign_indices: Dict[str, int], include_nodes: bool = False) -> Dict:
|
|
"""计算单一参考点的完整Argala。"""
|
|
result = {
|
|
'ref_sign': SIGNS[ref_sign_idx],
|
|
'ref_sign_idx': ref_sign_idx,
|
|
'primary': {},
|
|
'specific': None,
|
|
'secondary': {},
|
|
'argala_count': 0,
|
|
'virodhargala_count': 0,
|
|
'net_result': 'neutral',
|
|
}
|
|
|
|
for h in sorted(PRIMARY_ARGALA):
|
|
argala_sign = _house_from(ref_sign_idx, h)
|
|
planets = _planets_in_sign(argala_sign, planet_sign_indices, include_nodes)
|
|
block_h = VIRODHARGALA_MAP[h]
|
|
block_sign = _house_from(ref_sign_idx, block_h)
|
|
blockers = _planets_in_sign(block_sign, planet_sign_indices, include_nodes)
|
|
effective = len(planets) > len(blockers)
|
|
result['primary'][f'H{h}'] = {
|
|
'house_from_reference': h,
|
|
'sign': SIGNS[argala_sign],
|
|
'sign_idx': argala_sign,
|
|
'planets': planets,
|
|
'blocked_by_house': block_h,
|
|
'blocked_by_sign': SIGNS[block_sign],
|
|
'blockers': blockers,
|
|
'effective': effective,
|
|
'effect': _argala_effect(h, planets, blockers),
|
|
}
|
|
if planets:
|
|
result['argala_count'] += 1
|
|
if blockers:
|
|
result['virodhargala_count'] += 1
|
|
|
|
h3_sign = _house_from(ref_sign_idx, 3)
|
|
h3_planets = _planets_in_sign(h3_sign, planet_sign_indices, include_nodes)
|
|
h3_malefics = [p for p in h3_planets if p in NATURAL_MALEFICS]
|
|
result['specific'] = {
|
|
'house': 3,
|
|
'sign': SIGNS[h3_sign],
|
|
'sign_idx': h3_sign,
|
|
'planets': h3_planets,
|
|
'malefics': h3_malefics,
|
|
'effective': len(h3_malefics) >= 2,
|
|
'rule': '第3宫有2颗以上天然凶星形成特殊Argala',
|
|
}
|
|
if result['specific']['effective']:
|
|
result['argala_count'] += 1
|
|
|
|
for h in sorted(SECONDARY_ARGALA):
|
|
sign_idx = _house_from(ref_sign_idx, h)
|
|
planets = _planets_in_sign(sign_idx, planet_sign_indices, include_nodes)
|
|
result['secondary'][f'H{h}'] = {
|
|
'house_from_reference': h,
|
|
'sign': SIGNS[sign_idx],
|
|
'sign_idx': sign_idx,
|
|
'planets': planets,
|
|
}
|
|
|
|
if result['argala_count'] > result['virodhargala_count']:
|
|
result['net_result'] = 'supported'
|
|
elif result['argala_count'] < result['virodhargala_count']:
|
|
result['net_result'] = 'obstructed'
|
|
result['rajayoga_classification'] = classify_argala_rajayoga(result)
|
|
return result
|
|
|
|
|
|
def calc_argala(planet_sign_indices: Dict[str, int], asc_sign_idx: int, include_nodes: bool = False) -> Dict:
|
|
"""
|
|
计算全盘Argala。
|
|
|
|
Args:
|
|
planet_sign_indices: {'Sun': 0, 'Moon': 3, ...}
|
|
asc_sign_idx: 上升星座索引
|
|
include_nodes: 是否把Rahu/Ketu纳入占位阻挡统计(默认False,遵循Jaimini七行星口径)
|
|
"""
|
|
houses = {}
|
|
for house_num in range(1, 13):
|
|
ref_sign_idx = (asc_sign_idx + house_num - 1) % 12
|
|
houses[f'house_{house_num}'] = calc_argala_for_reference(ref_sign_idx, planet_sign_indices, include_nodes)
|
|
|
|
planet_refs = {}
|
|
for pname, sign_idx in planet_sign_indices.items():
|
|
if pname in NODES and not include_nodes:
|
|
continue
|
|
planet_refs[pname] = calc_argala_for_reference(sign_idx, planet_sign_indices, include_nodes)
|
|
|
|
summary = _summarize_argala(houses)
|
|
return {
|
|
'method': 'Jaimini Argala + Virodhargala (jaimini-tropical MIT adapted)',
|
|
'version': '1.1',
|
|
'include_nodes': include_nodes,
|
|
'ascendant': SIGNS[asc_sign_idx],
|
|
'houses': houses,
|
|
'planets': planet_refs,
|
|
'summary': summary,
|
|
}
|
|
|
|
|
|
def _summarize_argala(houses: Dict) -> Dict:
|
|
supported = []
|
|
obstructed = []
|
|
strongest = []
|
|
for house_key, data in houses.items():
|
|
if data['net_result'] == 'supported':
|
|
supported.append(house_key)
|
|
elif data['net_result'] == 'obstructed':
|
|
obstructed.append(house_key)
|
|
level = data.get('rajayoga_classification', {}).get('level', 0)
|
|
strongest.append((house_key, level, data.get('rajayoga_classification', {}).get('type')))
|
|
strongest.sort(key=lambda x: x[1], reverse=True)
|
|
return {
|
|
'supported_houses': supported,
|
|
'obstructed_houses': obstructed,
|
|
'top_argala_houses': [{'house': h, 'level': lvl, 'type': typ} for h, lvl, typ in strongest[:5] if lvl > 0],
|
|
'supported_count': len(supported),
|
|
'obstructed_count': len(obstructed),
|
|
}
|
|
|
|
|
|
def _argala_effect(house: int, planets: List[str], blockers: List[str]) -> str:
|
|
if not planets:
|
|
return '无行星形成此类Argala'
|
|
base = {
|
|
2: '资源、语言、家族与财务支持',
|
|
4: '内在稳定、住所、母亲、教育与幸福感支持',
|
|
11: '收益、人脉、愿望实现与社会网络支持',
|
|
}.get(house, '一般支持')
|
|
if blockers and len(blockers) >= len(planets):
|
|
return f'{base},但被{len(blockers)}个Virodhargala阻挡'
|
|
if blockers:
|
|
return f'{base},有轻度阻挡但仍可能有效'
|
|
return f'{base},未见对应阻挡'
|