Files
732642856 f83db2fac1 Enhance Jyotish validation and Jaimini modules
- 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
2026-06-10 20:50:52 +08:00

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},未见对应阻挡'