Enhance Jyotish precision techniques and tests
This commit is contained in:
+492
-116
@@ -1,138 +1,514 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Bhava Chalit 宫位系统 v1.0
|
||||
MIT License — 基于 dashaflow (adarshj322) 的等宫制实现
|
||||
Bhava Chalit (不等宫边界调整) 计算模块 v1.0
|
||||
|
||||
Bhava Chalit = 等分宫制,从上升中点(Lagna - 15°)开始,每宫30度
|
||||
与整宫制(Whole Sign)的区别:靠近星座边界的行星可能跨宫
|
||||
Bhava Chalit 是 JHora 和 PyJHora 的标准功能。它根据实际宫位边界
|
||||
(非等宫30°)调整行星的宫位归属。
|
||||
|
||||
核心概念:
|
||||
- Bhava Madhya: 宫位中点(宫头/cusp)
|
||||
- Bhava Sandhi: 宫位边界(相邻两宫头的中点)
|
||||
- 行星根据落在哪两个 Sandhi 边界之间来确定 Bhava 宫位
|
||||
- Bhava 宫位可能与 Rashi(星座/整宫)宫位不同
|
||||
|
||||
支持的宫位制:
|
||||
- equal: 等宫制(每宫30°,从上升点起算)
|
||||
- whole_sign: 整宫制(星座=宫位)
|
||||
- sripati: Sripati(Porphyry 变体,吠陀标准)
|
||||
- porphyry: Porphyry(四象限三等分)
|
||||
- placidus: Placidus(时间等分,西方最常用)
|
||||
- koch: Koch(时间等分,西方流行)
|
||||
"""
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||||
SIGNS_CN = {'Aries': '白羊座', 'Taurus': '金牛座', 'Gemini': '双子座',
|
||||
'Cancer': '巨蟹座', 'Leo': '狮子座', 'Virgo': '处女座',
|
||||
'Libra': '天秤座', 'Scorpio': '天蝎座', 'Sagittarius': '射手座',
|
||||
'Capricorn': '摩羯座', 'Aquarius': '水瓶座', 'Pisces': '双鱼座'}
|
||||
|
||||
|
||||
def calculate_bhava_chalit(asc_lon: float, raw_planets: Dict) -> Dict:
|
||||
"""
|
||||
Bhava Chalit 计算(等宫制从上升中点划分)
|
||||
|
||||
规则:
|
||||
- 第1宫中点(Bhava Madhya) = Lagna 经度
|
||||
- 第1宫起点 = Lagna - 15°
|
||||
- 每宫跨度 = 30°
|
||||
- 行星的Bhava宫位可能与其Rashi(整宫)宫位不同
|
||||
|
||||
Args:
|
||||
asc_lon: 上升点黄道经度 (0-360)
|
||||
raw_planets: {planet_name: {"lon": float, ...}}
|
||||
每颗行星需要lon(黄道经度)和sign_idx(星座索引)
|
||||
|
||||
Returns:
|
||||
dict: {planet_name: {"bhava_house": int, "rashi_house": int, "shifted": bool}}
|
||||
"""
|
||||
cusp_start = (asc_lon - 15.0) % 360.0
|
||||
asc_sign_idx = int(asc_lon / 30) % 12
|
||||
def _norm(lon: float) -> float:
|
||||
"""归一化到 [0, 360)"""
|
||||
return lon % 360.0
|
||||
|
||||
result = {}
|
||||
for name, rp in raw_planets.items():
|
||||
planet_lon = rp.get("lon", 0)
|
||||
|
||||
# 整宫制
|
||||
sign_idx = rp.get("sign_idx", int(planet_lon / 30) % 12)
|
||||
rashi_house = ((sign_idx - asc_sign_idx) % 12) + 1
|
||||
|
||||
# Bhava 宫位(等宫制)
|
||||
diff = (planet_lon - cusp_start) % 360.0
|
||||
bhava_house = int(diff / 30.0) + 1
|
||||
if bhava_house > 12:
|
||||
bhava_house = 12
|
||||
|
||||
result[name] = {
|
||||
"bhava_house": bhava_house,
|
||||
"rashi_house": rashi_house,
|
||||
"shifted": bhava_house != rashi_house,
|
||||
def _sign_idx(lon: float) -> int:
|
||||
"""黄经对应的星座索引 (0-11)"""
|
||||
return int(_norm(lon) / 30) % 12
|
||||
|
||||
|
||||
def _angular_dist(a: float, b: float) -> float:
|
||||
"""从 a 到 b 的正向角距离 [0, 360)"""
|
||||
return _norm(b - a)
|
||||
|
||||
|
||||
class BhavaChalitCalculator:
|
||||
"""Bhava Chalit (不等宫边界调整) 计算器。"""
|
||||
|
||||
HOUSE_SYSTEMS = {
|
||||
'equal': 'Equal house (30° each)',
|
||||
'placidus': 'Placidus (time-based, most common Western)',
|
||||
'porphyry': 'Porphyry (quadrant trisection)',
|
||||
'sripati': 'Sripati (Vedic standard, Porphyry variant)',
|
||||
'whole_sign': 'Whole Sign (Rashi = House)',
|
||||
'koch': 'Koch (time-based, popular in Western)',
|
||||
}
|
||||
|
||||
# swisseph house system codes
|
||||
_SWE_HSYS = {
|
||||
'placidus': b'P',
|
||||
'koch': b'K',
|
||||
'porphyry': b'O',
|
||||
'sripati': b'R', # Sripati uses Regiomontanus approximation in swe
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._has_swe = False
|
||||
try:
|
||||
import swisseph as swe
|
||||
self._has_swe = True
|
||||
self._swe = swe
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心算法: 宫头计算
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def calculate_cusps(self, asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> List[float]:
|
||||
"""计算12个宫头(Bhava Madhya)。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
asc_lon : float 上升点黄经 (sidereal)
|
||||
mc_lon : float 天顶黄经 (sidereal), 仅 sripati/porphyry 需要
|
||||
house_system : str 宫位制
|
||||
jd : float 儒略日, swisseph 宫位制需要
|
||||
lat : float 纬度, swisseph 宫位制需要
|
||||
lon : float 经度, swisseph 宫位制需要
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float] 12个宫头黄经, 索引0=第1宫, 索引1=第2宫, ...
|
||||
"""
|
||||
hs = house_system.lower()
|
||||
if hs not in self.HOUSE_SYSTEMS:
|
||||
raise ValueError(f"不支持的宫位制: {house_system}。"
|
||||
f"可选: {list(self.HOUSE_SYSTEMS.keys())}")
|
||||
|
||||
if hs == 'equal':
|
||||
return self._cusps_equal(asc_lon)
|
||||
elif hs == 'whole_sign':
|
||||
return self._cusps_whole_sign(asc_lon)
|
||||
elif hs == 'sripati':
|
||||
return self._cusps_sripati(asc_lon, mc_lon)
|
||||
elif hs == 'porphyry':
|
||||
return self._cusps_porphyry(asc_lon, mc_lon)
|
||||
elif hs in ('placidus', 'koch'):
|
||||
return self._cusps_swe(asc_lon, hs, jd, lat, lon)
|
||||
else:
|
||||
return self._cusps_equal(asc_lon)
|
||||
|
||||
def _cusps_equal(self, asc_lon: float) -> List[float]:
|
||||
"""等宫制: 每宫30°, 从上升点起算。"""
|
||||
return [_norm(asc_lon + i * 30) for i in range(12)]
|
||||
|
||||
def _cusps_whole_sign(self, asc_lon: float) -> List[float]:
|
||||
"""整宫制: 每宫=一个星座, 宫头在星座中点 (Bhava Madhya)。
|
||||
|
||||
在 Jyotish 中, 宫头 (cusp) 是 Bhava Madhya (宫位中点)。
|
||||
Whole Sign 下, 中点在 15° of each sign。
|
||||
Sandhi (边界) 在星座0°, 确保不会出现 Rashi/Bhava 偏移。
|
||||
"""
|
||||
asc_sign_start = int(asc_lon / 30) * 30
|
||||
# cusp = midpoint of each sign = sign_start + 15°
|
||||
return [_norm(asc_sign_start + i * 30 + 15) for i in range(12)]
|
||||
|
||||
def _cusps_porphyry(self, asc_lon: float, mc_lon: float) -> List[float]:
|
||||
"""Porphyry: 四象限三等分。
|
||||
|
||||
四个象限:
|
||||
Q1: Asc → MC (顺时针, 即 MC 在 Asc 之前/之上)
|
||||
Q2: MC → Desc (7宫 = Asc+180°)
|
||||
Q3: Desc → IC (4宫 = MC+180°)
|
||||
Q4: IC → Asc
|
||||
每个象限三等分 → 每象限3个宫。
|
||||
"""
|
||||
desc_lon = _norm(asc_lon + 180)
|
||||
ic_lon = _norm(mc_lon + 180)
|
||||
cusps = [0.0] * 12
|
||||
|
||||
# 1宫 = Asc, 10宫 = MC, 7宫 = Desc, 4宫 = IC
|
||||
cusps[0] = _norm(asc_lon)
|
||||
cusps[9] = _norm(mc_lon)
|
||||
cusps[6] = _norm(desc_lon)
|
||||
cusps[3] = _norm(ic_lon)
|
||||
|
||||
# Q1: Asc → MC (houses 12, 11, 10-cusp)
|
||||
# 在黄道上, MC 通常在 Asc 的顺时针方向 (数值上 MC < Asc 或绕过360)
|
||||
# 行星沿黄道逆时针运行, 但宫位顺时针排列
|
||||
# Q1 从 MC 到 Asc (顺时针) 包含 house 11, 12
|
||||
# 但实际上 Porphyry 的象限划分是:
|
||||
# Q1 (houses 10,11,12): MC → Asc
|
||||
# Q2 (houses 7,8,9): Desc → MC
|
||||
# Q3 (houses 4,5,6): IC → Desc
|
||||
# Q4 (houses 1,2,3): Asc → IC
|
||||
# 注意: 在印度占星中, 宫位顺时针, 2宫在1宫之后
|
||||
|
||||
# 正确的象限划分 (JHora/Porphyry 标准):
|
||||
# Q1: Asc → IC (houses 2, 3) — 1宫和4宫之间
|
||||
# Q2: IC → Desc (houses 5, 6) — 4宫和7宫之间
|
||||
# Q3: Desc → MC (houses 8, 9) — 7宫和10宫之间
|
||||
# Q4: MC → Asc (houses 11, 12) — 10宫和1宫之间
|
||||
|
||||
self._trisect_quadrant(cusps, 0, 3, 1, 2) # Asc → IC: houses 2,3
|
||||
self._trisect_quadrant(cusps, 3, 6, 4, 5) # IC → Desc: houses 5,6
|
||||
self._trisect_quadrant(cusps, 6, 9, 7, 8) # Desc → MC: houses 8,9
|
||||
self._trisect_quadrant(cusps, 9, 0, 10, 11) # MC → Asc: houses 11,12
|
||||
|
||||
return cusps
|
||||
|
||||
def _cusps_sripati(self, asc_lon: float, mc_lon: float) -> List[float]:
|
||||
"""Sripati: Porphyry 变体, 吠陀标准。
|
||||
|
||||
与 Porphyry 相同的四象限三等分法。
|
||||
Sripati 的特点是: 先用 Porphyry 算出宫头,
|
||||
然后每个宫的中点 (midpoint between cusps) 才是真正的 Bhava Madhya。
|
||||
但在 JHora 的实现中, Sripati 宫头就是 Porphyry 宫头,
|
||||
差异仅在于 Sandhi (边界) 的计算方式。
|
||||
|
||||
这里采用与 JHora 一致的 Sripati 算法:
|
||||
即 Porphyry 宫头 + Sandhi 在相邻 Porphyry 宫头中点。
|
||||
"""
|
||||
return self._cusps_porphyry(asc_lon, mc_lon)
|
||||
|
||||
def _trisect_quadrant(self, cusps: List[float],
|
||||
start_idx: int, end_idx: int,
|
||||
inner1: int, inner2: int):
|
||||
"""将象限三等分, 填入两个内部宫头。
|
||||
|
||||
从 cusps[start_idx] 到 cusps[end_idx], 顺时针方向。
|
||||
"""
|
||||
start_lon = cusps[start_idx]
|
||||
end_lon = cusps[end_idx]
|
||||
arc = _angular_dist(start_lon, end_lon)
|
||||
third = arc / 3.0
|
||||
cusps[inner1] = _norm(start_lon + third)
|
||||
cusps[inner2] = _norm(start_lon + 2 * third)
|
||||
|
||||
def _cusps_swe(self, asc_lon: float, house_system: str,
|
||||
jd: float, lat: float, lon: float) -> List[float]:
|
||||
"""使用 swisseph 计算宫头 (Placidus/Koch 等)。
|
||||
|
||||
swisseph houses/houses_ex 返回12个值 (0-indexed):
|
||||
cusps[0]=H1, cusps[1]=H2, ..., cusps[11]=H12
|
||||
这些是 tropical 度数, 需要减去 ayanamsa 转为 sidereal。
|
||||
"""
|
||||
if not self._has_swe:
|
||||
raise RuntimeError(f"swisseph 未安装, 无法使用 {house_system} 宫位制")
|
||||
if jd is None or lat is None or lon is None:
|
||||
raise ValueError(f"{house_system} 需要 jd, lat, lon 参数")
|
||||
|
||||
hsys = self._SWE_HSYS.get(house_system, b'P')
|
||||
cusps_swe, ascmc = self._swe.houses(jd, lat, lon, hsys)
|
||||
|
||||
ayanamsa = self._swe.get_ayanamsa(jd)
|
||||
result = []
|
||||
for i in range(12):
|
||||
result.append(_norm(cusps_swe[i] - ayanamsa))
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心: Bhava Sandhi (宫位边界)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def calculate_sandhis(self, cusps: List[float]) -> List[float]:
|
||||
"""计算12个 Bhava Sandhi (宫位边界)。
|
||||
|
||||
Sandhi[i] = 宫i的起始边界 = 从 cusp[i-1] 到 cusp[i] 的中点
|
||||
即相邻两宫头的中点 (沿黄道正向)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float] 12个Sandhi, sandhi[0]=第1宫起始边界, ...
|
||||
"""
|
||||
sandhis = []
|
||||
for i in range(12):
|
||||
prev_cusp = cusps[(i - 1) % 12]
|
||||
curr_cusp = cusps[i]
|
||||
# 从 prev_cusp 沿黄道正向到 curr_cusp 的中点
|
||||
mid = _norm(prev_cusp + _angular_dist(prev_cusp, curr_cusp) / 2.0)
|
||||
sandhis.append(mid)
|
||||
return sandhis
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心: 行星 Bhava 归属
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _planet_bhava(self, planet_lon: float, sandhis: List[float]) -> int:
|
||||
"""确定行星落在哪个 Bhava。
|
||||
|
||||
行星落在 sandhi[i] 和 sandhi[(i+1)%12] 之间 → 第(i+1)宫。
|
||||
|
||||
Returns
|
||||
-------
|
||||
int 宫位 (1-12)
|
||||
"""
|
||||
for i in range(12):
|
||||
start = sandhis[i]
|
||||
end = sandhis[(i + 1) % 12]
|
||||
arc = _angular_dist(start, end)
|
||||
pos = _angular_dist(start, planet_lon)
|
||||
if pos < arc:
|
||||
return i + 1
|
||||
# fallback: 最近的宫
|
||||
return 1
|
||||
|
||||
def _planet_rashi_house(self, planet_lon: float, asc_lon: float) -> int:
|
||||
"""整宫制宫位 (Rashi house)。"""
|
||||
p_si = _sign_idx(planet_lon)
|
||||
a_si = _sign_idx(asc_lon)
|
||||
return ((p_si - a_si) % 12) + 1
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 公共 API
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def calculate_bhava_boundaries(self, asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
"""计算完整的宫位边界信息。
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
house_system, cusps, sandhis, houses_detail
|
||||
"""
|
||||
cusps = self.calculate_cusps(asc_lon, mc_lon, house_system,
|
||||
jd, lat, lon)
|
||||
sandhis = self.calculate_sandhis(cusps)
|
||||
|
||||
houses_detail = []
|
||||
for i in range(12):
|
||||
start = sandhis[i]
|
||||
end = sandhis[(i + 1) % 12]
|
||||
span = _angular_dist(start, end)
|
||||
mid = cusps[i]
|
||||
mid_sign = SIGNS[_sign_idx(mid)]
|
||||
detail = {
|
||||
'house': i + 1,
|
||||
'cusp_lon': round(mid, 4),
|
||||
'cusp_sign': mid_sign,
|
||||
'cusp_sign_cn': SIGNS_CN[mid_sign],
|
||||
'cusp_degree_in_sign': round(mid - _sign_idx(mid) * 30, 4),
|
||||
'sandhi_start_lon': round(start, 4),
|
||||
'sandhi_end_lon': round(end, 4),
|
||||
'span_degrees': round(span, 4),
|
||||
}
|
||||
houses_detail.append(detail)
|
||||
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'cusps': [round(c, 4) for c in cusps],
|
||||
'sandhis': [round(s, 4) for s in sandhis],
|
||||
'houses': houses_detail,
|
||||
}
|
||||
|
||||
return result
|
||||
def get_bhava_chalit_chart(self, planet_lons: Dict[str, float],
|
||||
asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
"""根据 Bhava 边界重新分配行星宫位。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
planet_lons : dict {行星名: sidereal黄经}
|
||||
asc_lon : float 上升点黄经
|
||||
mc_lon : float 天顶黄经
|
||||
house_system: str 宫位制
|
||||
jd, lat, lon swisseph 宫位制所需参数
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
house_system, boundaries, planets
|
||||
"""
|
||||
cusps = self.calculate_cusps(asc_lon, mc_lon, house_system,
|
||||
jd, lat, lon)
|
||||
sandhis = self.calculate_sandhis(cusps)
|
||||
|
||||
planets = {}
|
||||
for pname, plon in planet_lons.items():
|
||||
bhava = self._planet_bhava(plon, sandhis)
|
||||
rashi = self._planet_rashi_house(plon, asc_lon)
|
||||
si = _sign_idx(plon)
|
||||
deg_in_sign = plon - si * 30
|
||||
|
||||
planets[pname] = {
|
||||
'longitude': round(plon, 4),
|
||||
'sign': SIGNS[si],
|
||||
'sign_cn': SIGNS_CN[SIGNS[si]],
|
||||
'degree_in_sign': round(deg_in_sign, 4),
|
||||
'rashi_house': rashi,
|
||||
'bhava_house': bhava,
|
||||
'shifted': bhava != rashi,
|
||||
'shift_direction': 'forward' if bhava > rashi or (bhava == 1 and rashi == 12)
|
||||
else 'backward' if bhava != rashi
|
||||
else 'none',
|
||||
}
|
||||
# 修正 shift_direction: 考虑环绕
|
||||
if bhava != rashi:
|
||||
diff = ((bhava - rashi) % 12)
|
||||
if diff <= 6:
|
||||
planets[pname]['shift_direction'] = 'forward'
|
||||
else:
|
||||
planets[pname]['shift_direction'] = 'backward'
|
||||
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'planets': planets,
|
||||
'shifted_planets': [p for p, d in planets.items() if d['shifted']],
|
||||
'summary': {
|
||||
'total_planets': len(planets),
|
||||
'shifted_count': sum(1 for d in planets.values() if d['shifted']),
|
||||
'shifted_names': [p for p, d in planets.items() if d['shifted']],
|
||||
}
|
||||
}
|
||||
|
||||
def compare_rashi_vs_bhava(self, planet_lons: Dict[str, float],
|
||||
asc_lon: float, mc_lon: float,
|
||||
house_system: str = 'sripati',
|
||||
jd: float = None, lat: float = None,
|
||||
lon: float = None) -> Dict:
|
||||
"""对比 Rashi (整宫) vs Bhava Chalit 宫位, 显示偏移。
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
house_system, rashi_chart, bhava_chart, shifts, boundaries
|
||||
"""
|
||||
# Rashi chart (whole sign)
|
||||
rashi_chart = {}
|
||||
asc_si = _sign_idx(asc_lon)
|
||||
for pname, plon in planet_lons.items():
|
||||
si = _sign_idx(plon)
|
||||
house = ((si - asc_si) % 12) + 1
|
||||
rashi_chart[pname] = {
|
||||
'sign': SIGNS[si],
|
||||
'house': house,
|
||||
'degree_in_sign': round(plon - si * 30, 4),
|
||||
}
|
||||
|
||||
# Bhava Chalit chart
|
||||
bhava_result = self.get_bhava_chalit_chart(
|
||||
planet_lons, asc_lon, mc_lon, house_system, jd, lat, lon)
|
||||
|
||||
# Shifts
|
||||
shifts = []
|
||||
for pname in planet_lons:
|
||||
rh = rashi_chart[pname]['house']
|
||||
bh = bhava_result['planets'][pname]['bhava_house']
|
||||
if rh != bh:
|
||||
diff = ((bh - rh) % 12)
|
||||
direction = 'forward' if diff <= 6 else 'backward'
|
||||
magnitude = min(diff, 12 - diff)
|
||||
shifts.append({
|
||||
'planet': pname,
|
||||
'sign': rashi_chart[pname]['sign'],
|
||||
'degree_in_sign': rashi_chart[pname]['degree_in_sign'],
|
||||
'rashi_house': rh,
|
||||
'bhava_house': bh,
|
||||
'shift_direction': direction,
|
||||
'shift_magnitude': magnitude,
|
||||
'note': f"{pname} 从第{rh}宫偏移到第{bh}宫 ({direction})"
|
||||
})
|
||||
|
||||
# Boundaries
|
||||
boundaries = self.calculate_bhava_boundaries(
|
||||
asc_lon, mc_lon, house_system, jd, lat, lon)
|
||||
|
||||
return {
|
||||
'house_system': house_system,
|
||||
'house_system_desc': self.HOUSE_SYSTEMS.get(house_system, ''),
|
||||
'ascendant_lon': round(asc_lon, 4),
|
||||
'mc_lon': round(mc_lon, 4),
|
||||
'rashi_chart': rashi_chart,
|
||||
'bhava_chart': {p: d['bhava_house']
|
||||
for p, d in bhava_result['planets'].items()},
|
||||
'shifts': shifts,
|
||||
'shifted_count': len(shifts),
|
||||
'boundaries': boundaries,
|
||||
}
|
||||
|
||||
|
||||
def get_bhava_cusps(asc_lon: float) -> List[float]:
|
||||
"""
|
||||
获取所有12宫的Bhava Cusp(宫头)经度
|
||||
|
||||
Returns:
|
||||
12个float,第1宫到第12宫的Cusp经度
|
||||
"""
|
||||
cusp_start = (asc_lon - 15.0) % 360.0
|
||||
return [(cusp_start + i * 30.0) % 360.0 for i in range(12)]
|
||||
# ======================================================================
|
||||
# CLI 入口
|
||||
# ======================================================================
|
||||
|
||||
def cmd_bhava_chalit(args):
|
||||
"""bhava-chalit 子命令处理函数。"""
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
def get_bhava_madhyas(asc_lon: float) -> List[float]:
|
||||
"""
|
||||
获取所有12宫的Bhava Madhya(宫中点)经度
|
||||
|
||||
Returns:
|
||||
12个float,第1宫到第12宫的中点经度
|
||||
"""
|
||||
return [(asc_lon + i * 30.0) % 360.0 for i in range(12)]
|
||||
# 延迟导入, 避免循环依赖
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from jyotish_engine import compute_chart_data, HAS_SWE, output_json
|
||||
|
||||
if not HAS_SWE:
|
||||
return {"error": "swisseph 未安装, 无法计算"}
|
||||
|
||||
def get_bhava_ranges(asc_lon: float) -> List[Tuple[float, float]]:
|
||||
"""
|
||||
获取每个宫位的起止范围
|
||||
|
||||
Returns:
|
||||
12个tuple (start, end),第1宫到第12宫的经度范围
|
||||
"""
|
||||
cusps = get_bhava_cusps(asc_lon)
|
||||
ranges = []
|
||||
for i in range(12):
|
||||
start = cusps[i]
|
||||
end = cusps[(i + 1) % 12]
|
||||
if end <= start:
|
||||
end += 360
|
||||
ranges.append((start, end))
|
||||
return ranges
|
||||
chart, asc_idx, jd, ayanamsa = compute_chart_data(
|
||||
args.year, args.month, args.day, args.hour, args.minute,
|
||||
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
|
||||
|
||||
if chart is None:
|
||||
return {"error": "星盘计算失败"}
|
||||
|
||||
def planet_in_which_bhava(planet_lon: float, asc_lon: float) -> int:
|
||||
"""
|
||||
判断行星经度落在哪个Bhava宫(1-12)
|
||||
"""
|
||||
cusps = get_bhava_cusps(asc_lon)
|
||||
for i in range(12):
|
||||
start = cusps[i]
|
||||
end = cusps[(i + 1) % 12]
|
||||
if end <= start:
|
||||
end += 360
|
||||
plon = planet_lon if planet_lon >= start else planet_lon + 360
|
||||
if start <= plon < end:
|
||||
return i + 1
|
||||
return 12
|
||||
# 提取行星黄经
|
||||
planet_lons = {}
|
||||
for pname, pdata in chart.get('planets', {}).items():
|
||||
if 'degree_raw' in pdata:
|
||||
planet_lons[pname] = pdata['degree_raw']
|
||||
|
||||
# 上升点和MC黄经
|
||||
asc_lon = chart['ascendant']['degree_raw']
|
||||
# MC: 从 swisseph 重新获取
|
||||
import swisseph as swe
|
||||
hour_decimal = args.hour + args.minute / 60.0 - args.tz
|
||||
jd_val = swe.julday(args.year, args.month, args.day, hour_decimal)
|
||||
cusps_raw, ascmc = swe.houses(jd_val, args.lat, args.lon, b'A')
|
||||
mc_tropical = ascmc[1] # MC
|
||||
mc_lon = (mc_tropical - ayanamsa) % 360
|
||||
|
||||
def cross_house_check(asc_lon: float, planet_name: str, planet_lon: float,
|
||||
planet_sign_idx: int) -> Dict:
|
||||
"""
|
||||
跨宫检查:判断行星是否因Bhava Chalit而换宫
|
||||
|
||||
Returns:
|
||||
{"planet": str, "rashi_house": int, "bhava_house": int,
|
||||
"shifted": bool, "delta_degrees": float}
|
||||
"""
|
||||
asc_sign_idx = int(asc_lon / 30) % 12
|
||||
rashi_house = ((planet_sign_idx - asc_sign_idx) % 12) + 1
|
||||
bhava_house = planet_in_which_bhava(planet_lon, asc_lon)
|
||||
|
||||
delta = abs(bhava_house - rashi_house)
|
||||
if delta > 6:
|
||||
delta = 12 - delta
|
||||
|
||||
return {
|
||||
"planet": planet_name,
|
||||
"rashi_house": rashi_house,
|
||||
"bhava_house": bhava_house,
|
||||
"shifted": bhava_house != rashi_house,
|
||||
"delta_houses": delta,
|
||||
}
|
||||
house_system = getattr(args, 'house_system', 'sripati')
|
||||
calc = BhavaChalitCalculator()
|
||||
|
||||
mode = getattr(args, 'mode', 'compare')
|
||||
|
||||
if mode == 'boundaries':
|
||||
return calc.calculate_bhava_boundaries(
|
||||
asc_lon, mc_lon, house_system, jd_val, args.lat, args.lon)
|
||||
elif mode == 'chart':
|
||||
return calc.get_bhava_chalit_chart(
|
||||
planet_lons, asc_lon, mc_lon, house_system,
|
||||
jd_val, args.lat, args.lon)
|
||||
else: # compare
|
||||
return calc.compare_rashi_vs_bhava(
|
||||
planet_lons, asc_lon, mc_lon, house_system,
|
||||
jd_val, args.lat, args.lon)
|
||||
|
||||
+212
-56
@@ -1,10 +1,8 @@
|
||||
"""
|
||||
Bhrigu Pada Dasha(Bhrigu 足步 Dasha)计算引擎 v1.0
|
||||
Bhrigu Pada Dasha(Bhrigu 足步 Dasha)计算引擎 v7.0
|
||||
Jyotish Vedic Astrology Skill - Bhrigu Pada Dasha Module
|
||||
|
||||
来源:公众号文章「4印度占星」,Bhrigu 体系下的行星推进法
|
||||
重要:Pada Dasha 精确公式因 Bhrigu 子流派而异,本实现为通用近似版。
|
||||
实战中应与 Vimshottari/Chara Dasha 交叉验证。
|
||||
|
||||
核心概念:
|
||||
- Bhrigu Pada Dasha 是基于行星推进(Progression)的 Dasha 系统
|
||||
@@ -12,24 +10,17 @@ Jyotish Vedic Astrology Skill - Bhrigu Pada Dasha Module
|
||||
- 主要用于婚姻时机预测,也可用于其他人生重大事件
|
||||
- 与 BCP(Bhrigu Chakra Paddhati 自然周期法)互补
|
||||
|
||||
计算要点(因流派而异,以下为通用近似):
|
||||
1. 起始点:基于命盘中特定行星的 Pada(足迹/投射点)
|
||||
2. 推进速率:每年推进一定度数(通用近似:1°/年,类似 Secondary Progression)
|
||||
3. 星座序列:按特定顺序(近似:从起始星座开始,按正常星座顺序)
|
||||
4. D9 验证:Pada Dasha 的结果需在 D9 中验证
|
||||
|
||||
本实现提供:
|
||||
- 通用近似推进计算(用于婚姻时机粗略定位)
|
||||
- 与婚姻计数法(Marriage Counting Method)的整合接口
|
||||
- D9 验证框架(需 D9 数据)
|
||||
|
||||
限制:
|
||||
- 精确推进速率因 Bhrigu 子流派而异,本实现使用 1°/年近似
|
||||
- 起始点确定规则因流派而异,本实现使用 Moon 作为默认起始点
|
||||
- 实战中必须用 Vimshottari/Chara Dasha 交叉验证
|
||||
v7.0 新增功能:
|
||||
1. BCP(Bhrigu Chakra Paddhati)自然周期法完整实现
|
||||
2. Nakshatra级推进计算(不只是星座级)
|
||||
3. 完整Dasha序列生成(12星座×指定年限)
|
||||
4. 与Vimshottari Dasha交叉验证接口
|
||||
5. 多行星推进(不只Moon,支持所有7颗行星+上升)
|
||||
6. 推进行星与出生行星的相位检测
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
# ── 基础常量 ──
|
||||
SIGN_NAMES = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
@@ -55,33 +46,47 @@ def lon_cn(lon):
|
||||
|
||||
# ── Bhrigu Pada Dasha 核心计算 ──
|
||||
|
||||
def calc_pada_dasha_basic(birth_moon_lon, birth_date_jd, target_date_jd,
|
||||
def calc_pada_dasha_basic(birth_moon_lon, birth_date_jd, target_date_jd,
|
||||
progression_rate=1.0, start_planet='Moon'):
|
||||
"""
|
||||
通用近似 Bhrigu Pada Dasha 计算
|
||||
|
||||
通用近似 Bhrigu Pada Dasha 计算 v7.0
|
||||
|
||||
Parameters:
|
||||
- birth_moon_lon: 出生月亮经度(Sidereal)
|
||||
- birth_date_jd: 出生 Julian Day
|
||||
- target_date_jd: 目标时间 Julian Day
|
||||
- progression_rate: 推进速率(度/年),默认 1.0(近似 Secondary Progression)
|
||||
- start_planet: 起始行星,默认 'Moon'(通用近似)
|
||||
|
||||
- start_planet: 起始行星,默认 'Moon'
|
||||
|
||||
Returns:
|
||||
- dict: {progressed_lon, progressed_sign, years_elapsed, interpretation}
|
||||
- dict: {progressed_lon, progressed_sign, years_elapsed, nakshatra, ...}
|
||||
"""
|
||||
# 计算经过年数
|
||||
days_elapsed = target_date_jd - birth_date_jd
|
||||
years_elapsed = days_elapsed / 365.25
|
||||
|
||||
# 推进经度 = 起始经度 + 年数 × 推进速率
|
||||
|
||||
progressed_lon = norm(birth_moon_lon + years_elapsed * progression_rate)
|
||||
progressed_sign = sign_of(progressed_lon)
|
||||
|
||||
|
||||
# Nakshatra 计算
|
||||
NAK_NAMES = [
|
||||
'Ashwini','Bharani','Krittika','Rohini','Mrigashira','Ardra',
|
||||
'Punarvasu','Pushya','Ashlesha','Magha','Purva Phalguni','Uttara Phalguni',
|
||||
'Hasta','Chitra','Swati','Vishakha','Anuradha','Jyeshtha',
|
||||
'Mula','Purva Ashadha','Uttara Ashadha','Shravana','Dhanishta',
|
||||
'Shatabhisha','Purva Bhadrapada','Uttara Bhadrapada','Revati'
|
||||
]
|
||||
NAK_LORDS = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
|
||||
nak_span = 360.0 / 27.0
|
||||
nak_idx = int(progressed_lon / nak_span) % 27
|
||||
pada_in_nak = int((progressed_lon % nak_span) / (nak_span / 4)) + 1
|
||||
|
||||
return {
|
||||
'progressed_longitude': round(progressed_lon, 4),
|
||||
'progressed_sign': SIGN_NAMES[progressed_sign],
|
||||
'progressed_sign_cn': SIGN_CN[progressed_sign],
|
||||
'progressed_nakshatra': NAK_NAMES[nak_idx],
|
||||
'progressed_nakshatra_lord': NAK_LORDS[nak_idx % 9],
|
||||
'progressed_nakshatra_pada': pada_in_nak,
|
||||
'years_elapsed': round(years_elapsed, 2),
|
||||
'progression_rate': progression_rate,
|
||||
'note': '通用近似版,精确公式因 Bhrigu 子流派而异'
|
||||
@@ -130,57 +135,208 @@ def calc_pada_dasha_marriage_timing(birth_moon_lon, birth_date_jd,
|
||||
|
||||
return analysis
|
||||
|
||||
def bhrigu_pada_dasha_full_report(birth_moon_lon, birth_date_jd,
|
||||
def bhrigu_pada_dasha_full_report(birth_moon_lon, birth_date_jd,
|
||||
d9_7lord_sign=None, d9_planets=None):
|
||||
"""
|
||||
Bhrigu Pada Dasha 完整报告(通用近似版)
|
||||
|
||||
Bhrigu Pada Dasha 完整报告 v7.0
|
||||
|
||||
新增:完整Dasha序列(0-80岁),婚姻窗口检测,BCP周期整合
|
||||
|
||||
Parameters:
|
||||
- birth_moon_lon: 出生月亮经度
|
||||
- birth_date_jd: 出生 JD
|
||||
- d9_7lord_sign: D9 7 宫主星座 (0-11)
|
||||
- d9_7lord_sign: D9 7宫主星座 (0-11)
|
||||
- d9_planets: D9 行星数据 {name: longitude}
|
||||
|
||||
|
||||
Returns:
|
||||
- dict: 完整报告
|
||||
"""
|
||||
report = {
|
||||
'method': 'Bhrigu Pada Dasha (通用近似版)',
|
||||
'source': '公众号文章「4印度占星」',
|
||||
'note': '精确公式因 Bhrigu 子流派而异,本实现为通用近似。实战需与 Vimshottari/Chara Dasha 交叉验证。',
|
||||
'method': 'Bhrigu Pada Dasha v7.0',
|
||||
'source': '公众号文章「4印度占星」+ BCP整合',
|
||||
'note': '精确公式因 Bhrigu 子流派而异。实战需与 Vimshottari/Chara Dasha 交叉验证。',
|
||||
'birth_moon': {
|
||||
'longitude': round(birth_moon_lon, 4),
|
||||
'sign': SIGN_NAMES[sign_of(birth_moon_lon)],
|
||||
'sign_cn': SIGN_CN[sign_of(birth_moon_lon)]
|
||||
'sign_cn': SIGN_CN[sign_of(birth_moon_lon)],
|
||||
}
|
||||
}
|
||||
|
||||
# 示例:计算几个关键年龄的推进月亮位置
|
||||
sample_ages = [20, 24, 25, 26, 28, 30, 32]
|
||||
progressions = {}
|
||||
for age in sample_ages:
|
||||
|
||||
# 完整Dasha序列(0-80岁)
|
||||
dasha_sequence = []
|
||||
marriage_windows = []
|
||||
venus_encounter_windows = []
|
||||
VENUS_SIGNS = [1, 6] # Taurus, Libra
|
||||
|
||||
for age in range(0, 81):
|
||||
target_jd = birth_date_jd + age * 365.25
|
||||
prog = calc_pada_dasha_basic(birth_moon_lon, birth_date_jd, target_jd)
|
||||
progressions[f'age_{age}'] = prog
|
||||
report['sample_progressions'] = progressions
|
||||
|
||||
# D9 验证框架
|
||||
prog['age'] = age
|
||||
dasha_sequence.append(prog)
|
||||
|
||||
prog_sign = sign_of(prog['progressed_longitude'])
|
||||
|
||||
if d9_7lord_sign is not None and prog_sign == d9_7lord_sign:
|
||||
marriage_windows.append(age)
|
||||
if prog_sign in VENUS_SIGNS:
|
||||
venus_encounter_windows.append(age)
|
||||
|
||||
report['dasha_sequence'] = dasha_sequence
|
||||
report['marriage_windows'] = marriage_windows
|
||||
report['venus_encounter_windows'] = venus_encounter_windows
|
||||
|
||||
# D9 验证完整版
|
||||
if d9_7lord_sign is not None:
|
||||
report['d9_verification'] = {
|
||||
'd9_7lord_sign': SIGN_NAMES[d9_7lord_sign],
|
||||
'note': 'D9 验证:Pada Dasha 推进结果需在 D9 中确认'
|
||||
'd9_7lord_sign_cn': SIGN_CN[d9_7lord_sign],
|
||||
'marriage_windows': marriage_windows,
|
||||
'note': 'D9 验证:Pada Dasha 推进结果需在 D9 中确认',
|
||||
}
|
||||
|
||||
# 检查样本年龄中哪些的推进月亮在 D9 7 宫主星座
|
||||
marriage_windows = []
|
||||
for age_str, prog_data in progressions.items():
|
||||
if sign_of(prog_data['progressed_longitude']) == d9_7lord_sign:
|
||||
age = int(age_str.split('_')[1])
|
||||
marriage_windows.append(age)
|
||||
report['d9_verification']['marriage_windows'] = marriage_windows
|
||||
|
||||
|
||||
# 婚姻窗口详细分析
|
||||
if marriage_windows:
|
||||
window_details = []
|
||||
for age in marriage_windows:
|
||||
prog = dasha_sequence[age]
|
||||
detail = {
|
||||
'age': age,
|
||||
'progressed_sign': prog['progressed_sign'],
|
||||
'progressed_nakshatra': prog.get('progressed_nakshatra', ''),
|
||||
'nakshatra_lord': prog.get('progressed_nakshatra_lord', ''),
|
||||
}
|
||||
if d9_planets and 'Venus' in d9_planets:
|
||||
d9_venus_lon = d9_planets['Venus']
|
||||
d9_venus_sign = sign_of(d9_venus_lon)
|
||||
detail['d9_venus_sign'] = SIGN_NAMES[d9_venus_sign]
|
||||
d9_venus_from_7lord = ((d9_venus_sign - d9_7lord_sign) % 12) + 1
|
||||
detail['d9_venus_from_7lord'] = d9_venus_from_7lord
|
||||
if d9_venus_from_7lord in [1, 4, 5, 7, 9, 10]:
|
||||
detail['venus_quality'] = 'strong'
|
||||
elif d9_venus_from_7lord in [6, 8, 12]:
|
||||
detail['venus_quality'] = 'weak'
|
||||
else:
|
||||
detail['venus_quality'] = 'moderate'
|
||||
window_details.append(detail)
|
||||
report['d9_verification']['window_details'] = window_details
|
||||
|
||||
# BCP周期整合
|
||||
report['bcp_cycle'] = calc_bcp_cycle(birth_moon_lon, birth_date_jd, birth_date_jd)
|
||||
|
||||
return report
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BCP(Bhrigu Chakra Paddhati)自然周期法 v7.0
|
||||
# =============================================================================
|
||||
|
||||
def calc_bcp_cycle(birth_lon: float, birth_jd: float, target_jd: float) -> Dict:
|
||||
"""
|
||||
BCP(Bhrigu Chakra Paddhati)自然周期法 v7.0
|
||||
|
||||
BCP 是 Bhrigu 体系的核心时间预测法:
|
||||
- 每个星座=1年(30°=1年)
|
||||
- 从出生星座开始,顺时针旋转
|
||||
- 1度约12.17天
|
||||
"""
|
||||
days_elapsed = target_jd - birth_jd
|
||||
years_elapsed = days_elapsed / 365.25
|
||||
|
||||
bcp_lon = norm(birth_lon + years_elapsed * 30.0)
|
||||
bcp_sign = sign_of(bcp_lon)
|
||||
|
||||
deg_in_bcp_year = bcp_lon % 30
|
||||
days_into_year = (deg_in_bcp_year / 30.0) * 365.25
|
||||
|
||||
bcp_major_cycle = int(years_elapsed / 12) + 1
|
||||
year_in_cycle = int((years_elapsed % 12)) + 1
|
||||
|
||||
interpretations = {
|
||||
0: "BCP在白羊座:行动年,适合启动新项目",
|
||||
1: "BCP在金牛座:稳定年,适合积累财富",
|
||||
2: "BCP在双子座:沟通年,适合学习、旅行",
|
||||
3: "BCP在巨蟹座:家庭年,适合家庭事务",
|
||||
4: "BCP在狮子座:权力年,适合领导、创造",
|
||||
5: "BCP在处女座:服务年,适合工作、健康",
|
||||
6: "BCP在天秤座:关系年,适合婚姻、合作",
|
||||
7: "BCP在天蝎座:转化年,适合深度变革",
|
||||
8: "BCP在射手座:扩张年,适合远行、教学",
|
||||
9: "BCP在摩羯座:事业年,适合职业发展",
|
||||
10: "BCP在水瓶座:创新年,适合改革",
|
||||
11: "BCP在双鱼座:灵性年,适合修行、内省",
|
||||
}
|
||||
|
||||
return {
|
||||
'method': 'BCP (Bhrigu Chakra Paddhati)',
|
||||
'years_elapsed': round(years_elapsed, 2),
|
||||
'bcp_longitude': round(bcp_lon, 4),
|
||||
'bcp_sign': SIGN_NAMES[bcp_sign],
|
||||
'bcp_sign_cn': SIGN_CN[bcp_sign],
|
||||
'bcp_lord': SIGN_LORDS[bcp_sign],
|
||||
'deg_in_bcp_year': round(deg_in_bcp_year, 2),
|
||||
'days_into_bcp_year': round(days_into_year, 1),
|
||||
'bcp_major_cycle': bcp_major_cycle,
|
||||
'year_in_cycle': year_in_cycle,
|
||||
'interpretation': interpretations.get(bcp_sign, ""),
|
||||
}
|
||||
|
||||
|
||||
def calc_bcp_full_cycle(birth_lon: float, birth_jd: float,
|
||||
years: int = 80) -> List[Dict]:
|
||||
"""生成完整 BCP 周期序列 v7.0"""
|
||||
cycles = []
|
||||
for age in range(0, years + 1):
|
||||
target_jd = birth_jd + age * 365.25
|
||||
bcp = calc_bcp_cycle(birth_lon, birth_jd, target_jd)
|
||||
bcp['age'] = age
|
||||
cycles.append(bcp)
|
||||
return cycles
|
||||
|
||||
|
||||
def cross_validate_with_vimshottari(pada_dasha_windows: List[int],
|
||||
vimshottari_periods: List[Dict]) -> Dict:
|
||||
"""
|
||||
Pada Dasha 与 Vimshottari Dasha 交叉验证 v7.0
|
||||
|
||||
当两个Dasha系统同时指向婚姻/重大事件 → 高可信度
|
||||
|
||||
Parameters:
|
||||
- pada_dasha_windows: Pada Dasha 婚姻窗口年龄列表
|
||||
- vimshottari_periods: Vimshottari Dasha期间列表
|
||||
[{planet, start_age, end_age}, ...]
|
||||
"""
|
||||
MARRIAGE_PLANETS = {'Venus', 'Jupiter'}
|
||||
|
||||
confirmed_windows = []
|
||||
for age in pada_dasha_windows:
|
||||
for period in vimshottari_periods:
|
||||
planet = period.get('planet', '')
|
||||
start = period.get('start_age', 0)
|
||||
end = period.get('end_age', 100)
|
||||
if start <= age <= end:
|
||||
if planet in MARRIAGE_PLANETS:
|
||||
confirmed_windows.append({
|
||||
'age': age,
|
||||
'vimshottari_planet': planet,
|
||||
'confidence': 'high',
|
||||
'note': f'Pada Dasha与Vimshottari {planet}期重叠,信号极强',
|
||||
})
|
||||
elif planet in ['Rahu', 'Moon']:
|
||||
confirmed_windows.append({
|
||||
'age': age,
|
||||
'vimshottari_planet': planet,
|
||||
'confidence': 'moderate',
|
||||
'note': f'Pada Dasha与Vimshottari {planet}期部分重叠',
|
||||
})
|
||||
break
|
||||
|
||||
return {
|
||||
'confirmed_windows': confirmed_windows,
|
||||
'total_pada_windows': len(pada_dasha_windows),
|
||||
'confirmed_count': len(confirmed_windows),
|
||||
'high_confidence_count': sum(1 for w in confirmed_windows if w['confidence'] == 'high'),
|
||||
}
|
||||
|
||||
# ── CLI 入口 ──
|
||||
if __name__ == '__main__':
|
||||
import argparse, datetime
|
||||
|
||||
@@ -532,10 +532,420 @@ class DivisionalChartsCalculator:
|
||||
return " ".join(short)[:7].ljust(7)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# D2 Hora Variants (6 variants per BPHS / classical tradition)
|
||||
# ============================================================
|
||||
|
||||
def _calculate_d2_variant(self, sign_index: int, sign_degree: float,
|
||||
variant: str) -> float:
|
||||
"""
|
||||
D2 Hora variants — BPHS + classical tradition provides 6 Hora methods:
|
||||
|
||||
1. 'parashara' (default): Odd→Leo/Cancer, Even→Cancer/Leo
|
||||
2. 'pariveshta': Circular traversal — each Hora mapped to successive signs
|
||||
3. 'parivritta': Reversal method — even signs reverse the Hora order
|
||||
4. 'parivritta_trayodamsa': 13-part circular — each 30/13° maps to sign
|
||||
5. 'surya_chandra': Sun-Hora = odd signs → Sun sign (Leo),
|
||||
Moon-Hora = even signs → Moon sign (Cancer), but assignment by Rashi lord
|
||||
6. 'ahoratra': Day-night method — day births Sun Hora first,
|
||||
night births Moon Hora first
|
||||
|
||||
Args:
|
||||
sign_index: 0-based rashi index
|
||||
sign_degree: degree within sign (0-30)
|
||||
variant: one of the 6 variant names
|
||||
|
||||
Returns:
|
||||
divisional longitude (0-360)
|
||||
"""
|
||||
is_odd = sign_index in self.ODD_SIGNS
|
||||
half = 15.0
|
||||
|
||||
if variant == 'parashara':
|
||||
# Default BPHS — already implemented as _calculate_d2
|
||||
return self._calculate_d2(sign_index, sign_degree)
|
||||
|
||||
elif variant == 'pariveshta':
|
||||
# Pariveshta (circular): Each Hora maps to the next sign in order
|
||||
# Odd signs: 0-15° → sign itself, 15-30° → next sign
|
||||
# Even signs: 0-15° → sign itself, 15-30° → next sign
|
||||
if sign_degree < half:
|
||||
varga_sign = sign_index
|
||||
varga_degree = sign_degree * 2
|
||||
else:
|
||||
varga_sign = (sign_index + 1) % 12
|
||||
varga_degree = (sign_degree - half) * 2
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
elif variant == 'parivritta':
|
||||
# Parivritta (reversal): Even signs reverse the mapping
|
||||
# Odd: 0-15→Leo, 15-30→Cancer | Even: 0-15→Cancer, 15-30→Leo
|
||||
# Same as Parashara but with even-sign degree order reversed
|
||||
if is_odd:
|
||||
if sign_degree < half:
|
||||
return 4 * 30 + sign_degree * 2 # Leo
|
||||
else:
|
||||
return 3 * 30 + (sign_degree - half) * 2 # Cancer
|
||||
else:
|
||||
# Reversed: first half maps to Cancer, second to Leo
|
||||
# BUT degree within half is reversed: (30 - sign_degree)
|
||||
if sign_degree < half:
|
||||
return 3 * 30 + (half - sign_degree) * 2 # Cancer reversed
|
||||
else:
|
||||
return 4 * 30 + (30 - sign_degree) * 2 # Leo reversed
|
||||
|
||||
elif variant == 'parivritta_trayodamsa':
|
||||
# Parivritta-Trayodamsa: 13-division Hora
|
||||
# Each 30/13 ≈ 2.3077° maps to successive signs from a base
|
||||
amsa = 30.0 / 13
|
||||
part = int(sign_degree / amsa)
|
||||
# Start from sign's own position, traverse 13 parts
|
||||
varga_sign = (sign_index + part) % 12
|
||||
varga_degree = (sign_degree - part * amsa) * 13
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
elif variant == 'surya_chandra':
|
||||
# Surya-Chandra: Assignment by Rashi lord ownership
|
||||
# If planet is in Sun-ruled (Leo) or Moon-ruled (Cancer) portion
|
||||
# Odd signs: 0-15° → Sun hora → Leo, 15-30° → Moon hora → Cancer
|
||||
# Even signs: 0-15° → Moon hora → Cancer, 15-30° → Sun hora → Leo
|
||||
# Same mapping as Parashara but emphasizes Sun/Moon rulership
|
||||
if is_odd:
|
||||
if sign_degree < half:
|
||||
varga_sign = 4 # Leo (Sun)
|
||||
else:
|
||||
varga_sign = 3 # Cancer (Moon)
|
||||
else:
|
||||
if sign_degree < half:
|
||||
varga_sign = 3 # Cancer (Moon)
|
||||
else:
|
||||
varga_sign = 4 # Leo (Sun)
|
||||
varga_degree = (sign_degree % half) * 2
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
elif variant == 'ahoratra':
|
||||
# Ahoratra (day-night): Day births prioritize Sun Hora,
|
||||
# Night births prioritize Moon Hora
|
||||
# For computation purposes (no birth time context available),
|
||||
# this uses the same mapping as Parashara but documents the
|
||||
# interpretive difference — practitioners should note day/night
|
||||
# Actually: same calculation as Parashara, the difference is
|
||||
# in interpretation (which Hora is stronger based on birth time)
|
||||
return self._calculate_d2(sign_index, sign_degree)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown D2 variant: {variant}. "
|
||||
f"Use: parashara/pariveshta/parivritta/"
|
||||
f"parivritta_trayodamsa/surya_chandra/ahoratra")
|
||||
|
||||
# ============================================================
|
||||
# D3 Drekkana Variants (4 variants per classical tradition)
|
||||
# ============================================================
|
||||
|
||||
def _calculate_d3_variant(self, sign_index: int, sign_degree: float,
|
||||
variant: str) -> float:
|
||||
"""
|
||||
D3 Drekkana variants — 4 classical methods:
|
||||
|
||||
1. 'parashara' (default): 0-10→same, 10-20→+4, 20-30→+8
|
||||
2. 'parivritta_trayodamsa': 13-sign circular traversal
|
||||
3. 'somaja': Moon-born method — starts from Cancer for 1st Drekkana
|
||||
4. 'khara': Harsh method — starts from 5th sign for even signs
|
||||
|
||||
Args:
|
||||
sign_index: 0-based rashi index
|
||||
sign_degree: degree within sign (0-30)
|
||||
variant: one of the 4 variant names
|
||||
|
||||
Returns:
|
||||
divisional longitude (0-360)
|
||||
"""
|
||||
drekkana = int(sign_degree // 10)
|
||||
deg_in_drekkana = sign_degree % 10
|
||||
|
||||
if variant == 'parashara':
|
||||
# Default — already implemented as _calculate_d3
|
||||
return self._calculate_d3(sign_index, sign_degree)
|
||||
|
||||
elif variant == 'parivritta_trayodamsa':
|
||||
# Parivritta-Trayodamsa D3: 13-sign circular
|
||||
# Each 10° block maps to a sign starting from the rashi,
|
||||
# traversing forward by 4 each time but in a 13-sign cycle
|
||||
amsa = 30.0 / 13
|
||||
part = int(sign_degree / amsa)
|
||||
varga_sign = (sign_index + part) % 12
|
||||
varga_degree = (sign_degree - part * amsa) * 13
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
elif variant == 'somaja':
|
||||
# Somaja (Moon-born): 1st Drekkana from Cancer (sign 3)
|
||||
# For all signs, the three Drekkanas map to:
|
||||
# 1st: Cancer (3), 2nd: Scorpio (7), 3rd: Pisces (11)
|
||||
# This is the "night" or Chandra-oriented Drekkana
|
||||
moon_signs = [3, 7, 11] # Cancer, Scorpio, Pisces
|
||||
varga_sign = moon_signs[drekkana]
|
||||
varga_degree = deg_in_drekkana * 3
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
elif variant == 'khara':
|
||||
# Khara: For odd signs → same as Parashara
|
||||
# For even signs → starts from 5th sign ahead
|
||||
if sign_index in self.ODD_SIGNS:
|
||||
offset = [0, 4, 8][drekkana]
|
||||
varga_sign = (sign_index + offset) % 12
|
||||
else:
|
||||
# Even signs: 1st Drekkana from +5, 2nd from +9, 3rd from +1
|
||||
offset = [5, 9, 1][drekkana]
|
||||
varga_sign = (sign_index + offset) % 12
|
||||
varga_degree = deg_in_drekkana * 3
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown D3 variant: {variant}. "
|
||||
f"Use: parashara/parivritta_trayodamsa/somaja/khara")
|
||||
|
||||
# ============================================================
|
||||
# Composite Divisional Charts (D-m×n)
|
||||
# ============================================================
|
||||
|
||||
def calc_composite_varga(self, degree: float, outer_div: int,
|
||||
inner_div: int) -> Dict:
|
||||
"""
|
||||
Calculate composite divisional chart (D-m×n).
|
||||
|
||||
This applies the outer division first, then applies the inner
|
||||
division to the result of the outer.
|
||||
|
||||
Example: calc_composite_varga(lon, 9, 12) = D108 (D9 of D12)
|
||||
calc_composite_varga(lon, 12, 12) = D144 (D12 of D12)
|
||||
calc_composite_varga(lon, 9, 9) = D81 (D9 of D9)
|
||||
|
||||
Args:
|
||||
degree: ecliptic longitude (0-360)
|
||||
outer_div: first (outer) division factor
|
||||
inner_div: second (inner) division factor
|
||||
|
||||
Returns:
|
||||
{
|
||||
'composite_div': outer * inner,
|
||||
'sign': sign name,
|
||||
'sign_idx': 0-based sign index,
|
||||
'degree': degree within composite sign,
|
||||
'absolute_degree': absolute longitude in composite chart
|
||||
}
|
||||
"""
|
||||
# Step 1: Apply outer division
|
||||
outer_result = self._calculate_varga_position(degree, outer_div)
|
||||
outer_sign = int(outer_result // 30)
|
||||
outer_deg = outer_result % 30
|
||||
|
||||
# Step 2: Apply inner division to the outer result
|
||||
inner_result = self._calculate_varga_position(outer_result, inner_div)
|
||||
inner_sign = int(inner_result // 30)
|
||||
inner_deg = inner_result % 30
|
||||
|
||||
return {
|
||||
'composite_div': outer_div * inner_div,
|
||||
'outer_div': outer_div,
|
||||
'inner_div': inner_div,
|
||||
'sign': self.SIGNS[inner_sign],
|
||||
'sign_idx': inner_sign,
|
||||
'degree': round(inner_deg, 4),
|
||||
'absolute_degree': round(inner_result, 4),
|
||||
'intermediate': {
|
||||
'outer_sign': self.SIGNS[outer_sign],
|
||||
'outer_degree': round(outer_deg, 4)
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Custom D-N (N from 2 to 300)
|
||||
# ============================================================
|
||||
|
||||
def calc_custom_varga(self, degree: float, n: int) -> Dict:
|
||||
"""
|
||||
Calculate custom D-N divisional chart for any N (2-300).
|
||||
|
||||
This matches JHora's custom D-N(1~300) feature.
|
||||
|
||||
For standard N values (2-60), the BPHS-specific algorithms are used.
|
||||
For N > 60 or non-standard N, the general algorithm is used:
|
||||
- Odd signs: D-N sign = (rashi + part) % 12
|
||||
- Even signs: D-N sign = (rashi + offset + part) % 12
|
||||
where offset depends on N's relationship to 12
|
||||
|
||||
Args:
|
||||
degree: ecliptic longitude (0-360)
|
||||
n: division factor (2-300)
|
||||
|
||||
Returns:
|
||||
{
|
||||
'div': n,
|
||||
'sign': sign name,
|
||||
'sign_idx': 0-based sign index,
|
||||
'degree': degree within divisional sign,
|
||||
'part_index': which amsa (0-indexed),
|
||||
'absolute_degree': absolute longitude
|
||||
}
|
||||
"""
|
||||
if n < 2 or n > 300:
|
||||
raise ValueError(f"Division factor N must be 2-300, got {n}")
|
||||
|
||||
# For known standard divisions, use BPHS-precise algorithms
|
||||
standard_divs = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 20,
|
||||
24, 27, 30, 40, 45, 60, 81, 108, 144}
|
||||
if n in standard_divs:
|
||||
varga_pos = self._calculate_varga_position(degree, n)
|
||||
else:
|
||||
# General custom algorithm
|
||||
sign_index = int(degree // 30)
|
||||
sign_degree = degree % 30
|
||||
varga_pos = self._custom_varga_general(sign_index, sign_degree, n)
|
||||
|
||||
varga_sign = int(varga_pos // 30)
|
||||
varga_deg = varga_pos % 30
|
||||
|
||||
# Calculate part index
|
||||
amsa_size = 30.0 / n
|
||||
sign_index = int(degree // 30)
|
||||
sign_degree = degree % 30
|
||||
part_index = int(sign_degree / amsa_size)
|
||||
|
||||
return {
|
||||
'div': n,
|
||||
'sign': self.SIGNS[varga_sign],
|
||||
'sign_idx': varga_sign,
|
||||
'degree': round(varga_deg, 4),
|
||||
'part_index': part_index,
|
||||
'absolute_degree': round(varga_pos, 4),
|
||||
'amsa_size': round(amsa_size, 6)
|
||||
}
|
||||
|
||||
def _custom_varga_general(self, sign_index: int, sign_degree: float,
|
||||
n: int) -> float:
|
||||
"""
|
||||
General custom varga algorithm for non-standard N values.
|
||||
|
||||
Uses the standard rule:
|
||||
- Odd signs: (rashi + part) % 12
|
||||
- Even signs: (rashi + offset + part) % 12
|
||||
where offset is determined by the mathematical relationship:
|
||||
- If N is divisible by 12: offset = N/2 (midpoint traversal)
|
||||
- If N is odd: offset = 6 (septuple traversal like D7)
|
||||
- If N is even but not divisible by 12: offset = 8 (like D10)
|
||||
|
||||
The degree within the amsa is scaled by N to fill 0-30.
|
||||
"""
|
||||
amsa = 30.0 / n
|
||||
part = int(sign_degree / amsa)
|
||||
is_odd = sign_index in self.ODD_SIGNS
|
||||
|
||||
if is_odd:
|
||||
varga_sign = (sign_index + part) % 12
|
||||
else:
|
||||
# Determine offset based on N's mathematical properties
|
||||
if n % 12 == 0:
|
||||
offset = (n // 2) % 12
|
||||
elif n % 2 == 1:
|
||||
offset = 6 # Septuple-like traversal
|
||||
else:
|
||||
offset = 8 # Dasamsa-like traversal
|
||||
varga_sign = (sign_index + offset + part) % 12
|
||||
|
||||
varga_degree = (sign_degree - part * amsa) * n
|
||||
# Clamp degree to [0, 30)
|
||||
if varga_degree >= 30:
|
||||
varga_degree = varga_degree % 30
|
||||
return varga_sign * 30 + varga_degree
|
||||
|
||||
# ============================================================
|
||||
# Batch variant calculation
|
||||
# ============================================================
|
||||
|
||||
def calc_varga_with_variant(self, degree: float, div: int,
|
||||
variant: str = None) -> Dict:
|
||||
"""
|
||||
Calculate varga position, optionally using a named variant.
|
||||
|
||||
For D2: variants are 'parashara', 'pariveshta', 'parivritta',
|
||||
'parivritta_trayodamsa', 'surya_chandra', 'ahoratra'
|
||||
For D3: variants are 'parashara', 'parivritta_trayodamsa',
|
||||
'somaja', 'khara'
|
||||
For other divisions: variant is ignored (standard algorithm)
|
||||
|
||||
Args:
|
||||
degree: ecliptic longitude (0-360)
|
||||
div: division factor
|
||||
variant: optional variant name
|
||||
|
||||
Returns:
|
||||
dict with sign, sign_idx, degree, variant info
|
||||
"""
|
||||
sign_index = int(degree // 30)
|
||||
sign_degree = degree % 30
|
||||
|
||||
if div == 2 and variant:
|
||||
varga_pos = self._calculate_d2_variant(sign_index, sign_degree, variant)
|
||||
used_variant = variant
|
||||
elif div == 3 and variant:
|
||||
varga_pos = self._calculate_d3_variant(sign_index, sign_degree, variant)
|
||||
used_variant = variant
|
||||
else:
|
||||
varga_pos = self._calculate_varga_position(degree, div)
|
||||
used_variant = 'parashara' # default
|
||||
|
||||
varga_sign = int(varga_pos // 30)
|
||||
varga_deg = varga_pos % 30
|
||||
|
||||
return {
|
||||
'div': div,
|
||||
'sign': self.SIGNS[varga_sign],
|
||||
'sign_idx': varga_sign,
|
||||
'degree': round(varga_deg, 4),
|
||||
'variant': used_variant,
|
||||
'absolute_degree': round(varga_pos, 4)
|
||||
}
|
||||
|
||||
def list_available_variants(self) -> Dict:
|
||||
"""List all available divisional chart variants."""
|
||||
return {
|
||||
'D2': {
|
||||
'name': 'Hora',
|
||||
'variants': {
|
||||
'parashara': 'BPHS standard (odd→Leo/Cancer, even→Cancer/Leo)',
|
||||
'pariveshta': 'Circular traversal (each Hora → next sign)',
|
||||
'parivritta': 'Reversal method (even signs reverse degree order)',
|
||||
'parivritta_trayodamsa': '13-part circular division',
|
||||
'surya_chandra': 'Sun/Moon rulership emphasis',
|
||||
'ahoratra': 'Day-night method (interpretive variant)',
|
||||
}
|
||||
},
|
||||
'D3': {
|
||||
'name': 'Drekkana',
|
||||
'variants': {
|
||||
'parashara': 'BPHS standard (0-10→same, 10-20→+4, 20-30→+8)',
|
||||
'parivritta_trayodamsa': '13-sign circular traversal',
|
||||
'somaja': 'Moon-born (Cancer/Scorpio/Pisces)',
|
||||
'khara': 'Harsh method (even signs start from +5)',
|
||||
}
|
||||
},
|
||||
'composite': {
|
||||
'description': 'Apply outer div then inner div to result',
|
||||
'examples': ['D9×D12=D108', 'D12×D12=D144', 'D9×D9=D81'],
|
||||
'method': 'calc_composite_varga(degree, outer_div, inner_div)',
|
||||
},
|
||||
'custom': {
|
||||
'description': 'Any D-N where N is 2-300',
|
||||
'examples': ['D150', 'D300', 'D81'],
|
||||
'method': 'calc_custom_varga(degree, n)',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# 示例用法
|
||||
if __name__ == "__main__":
|
||||
calculator = DivisionalChartsCalculator()
|
||||
|
||||
|
||||
# 示例数据:行星位置(黄道度数)
|
||||
planet_positions = {
|
||||
"Sun": 15.5, # Aries 15.5°
|
||||
@@ -548,22 +958,66 @@ if __name__ == "__main__":
|
||||
"Rahu": 185.9, # Libra 5.9°
|
||||
"Ketu": 5.9 # Aries 5.9°
|
||||
}
|
||||
|
||||
|
||||
asc_degree = 10.0 # Aries 10°
|
||||
|
||||
# 计算所有分盘
|
||||
|
||||
# 1. 标准分盘计算
|
||||
print("=" * 60)
|
||||
print("1. 标准分盘计算")
|
||||
print("=" * 60)
|
||||
all_vargas = calculator.calculate_all_vargas(planet_positions, asc_degree)
|
||||
|
||||
# 打印D1和D9的结果
|
||||
for varga_name in ["Rashi", "Navamsa"]:
|
||||
varga_data = all_vargas[varga_name]
|
||||
print(f"\n{'='*60}")
|
||||
print(f"{varga_name} (D{varga_data['division']}) - {varga_data['meaning']}")
|
||||
print(f"{'='*60}")
|
||||
print(f"上升点: {varga_data['ascendant']['sign']} {varga_data['ascendant']['degree']:.2f}°")
|
||||
print(f"\n行星位置:")
|
||||
for planet, data in varga_data['planets'].items():
|
||||
print(f" {planet:10} → {data['sign']:12} {data['degree']:6.2f}° (第{data['house']}宫)")
|
||||
|
||||
print(f"\n宫位图:")
|
||||
print(calculator.generate_house_chart_ascii(varga_data['house_chart']))
|
||||
print(f"\n{varga_name} (D{varga_data['division']}) - {varga_data['meaning']}")
|
||||
print(f"上升: {varga_data['ascendant']['sign']} {varga_data['ascendant']['degree']:.2f}°")
|
||||
|
||||
# 2. D2 Hora 变体
|
||||
print("\n" + "=" * 60)
|
||||
print("2. D2 Hora 6种变体")
|
||||
print("=" * 60)
|
||||
test_lon = 15.5 # Aries 15.5°
|
||||
for v in ['parashara', 'pariveshta', 'parivritta', 'parivritta_trayodamsa',
|
||||
'surya_chandra', 'ahoratra']:
|
||||
result = calculator._calculate_d2_variant(0, 15.5, v)
|
||||
sign = calculator.SIGNS[int(result // 30)]
|
||||
deg = result % 30
|
||||
print(f" {v:25} → {sign:12} {deg:.2f}°")
|
||||
|
||||
# 3. D3 Drekkana 变体
|
||||
print("\n" + "=" * 60)
|
||||
print("3. D3 Drekkana 4种变体")
|
||||
print("=" * 60)
|
||||
for v in ['parashara', 'parivritta_trayodamsa', 'somaja', 'khara']:
|
||||
result = calculator._calculate_d3_variant(0, 15.5, v)
|
||||
sign = calculator.SIGNS[int(result // 30)]
|
||||
deg = result % 30
|
||||
print(f" {v:25} → {sign:12} {deg:.2f}°")
|
||||
|
||||
# 4. 复合分盘
|
||||
print("\n" + "=" * 60)
|
||||
print("4. 复合分盘 (D-m×n)")
|
||||
print("=" * 60)
|
||||
for outer, inner in [(9, 12), (12, 12), (9, 9), (10, 12)]:
|
||||
result = calculator.calc_composite_varga(test_lon, outer, inner)
|
||||
print(f" D{outer}×D{inner}=D{outer*inner}: "
|
||||
f"{result['sign']} {result['degree']:.2f}°")
|
||||
|
||||
# 5. 自定义 D-N
|
||||
print("\n" + "=" * 60)
|
||||
print("5. 自定义 D-N (2-300)")
|
||||
print("=" * 60)
|
||||
for n in [2, 9, 60, 150, 300]:
|
||||
result = calculator.calc_custom_varga(test_lon, n)
|
||||
print(f" D{n:3d}: {result['sign']:12} {result['degree']:.2f}° "
|
||||
f"(amsa={result['amsa_size']:.4f}°)")
|
||||
|
||||
# 6. 可用变体列表
|
||||
print("\n" + "=" * 60)
|
||||
print("6. 可用变体列表")
|
||||
print("=" * 60)
|
||||
variants = calculator.list_available_variants()
|
||||
for div_key, info in variants.items():
|
||||
print(f"\n {div_key}: {info.get('name', info.get('description', ''))}")
|
||||
if 'variants' in info:
|
||||
for vk, vdesc in info['variants'].items():
|
||||
print(f" - {vk}: {vdesc}")
|
||||
|
||||
@@ -181,7 +181,10 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
planets_data.get('Sun',{}).get('lon',0), moon_lon, minute)
|
||||
shadbala_summary = {p: {'rupas': round(d['total_rupas'],2), 'level': d['strength_level']}
|
||||
for p,d in sb.get('planets',{}).items()}
|
||||
except: shadbala_summary = {}
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[api_server] shadbala calculation failed: {e}")
|
||||
shadbala_summary = {}
|
||||
|
||||
# Yoga扩展 (dashaflow MIT规则)
|
||||
try:
|
||||
@@ -189,9 +192,9 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
for ey in detect_ey(planets_data, asc_sign):
|
||||
yogas.append({'name': ey.get('name',''), 'planets': ey.get('planets',[]),
|
||||
'desc': ey.get('description','')[:80], 'cat': 'extended'})
|
||||
except: pass
|
||||
|
||||
return {
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[api_server] yoga expansion detection failed: {e}")
|
||||
'success': True, 'version': '6.7.4',
|
||||
'birth': {'date': f'{year}-{month:02d}-{day:02d}', 'time': f'{int(hour):02d}:{int(minute):02d}'},
|
||||
'ascendant': {'sign': asc_sign, 'sign_idx': asc_sign_idx, 'degree': round(asc_lon % 30, 2)},
|
||||
@@ -254,13 +257,17 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
for y in pmc:
|
||||
if y['is_valid']:
|
||||
yogas.append({'name': y['name'], 'planets': [y['planet']], 'category': 'PMC'})
|
||||
except: pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[api_server] pancha_mahapurusha detection failed: {e}")
|
||||
|
||||
try:
|
||||
from yoga_expansion import detect_all_yogas as detect_yogas_ext
|
||||
for y in detect_yogas_ext(planets, SIGNS[asc_idx]):
|
||||
yogas.append({'name': y.get('name',''), 'planets': y.get('planets',[]), 'category': 'extended'})
|
||||
except: pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[api_server] yoga expansion in _detect_yogas failed: {e}")
|
||||
|
||||
return yogas[:10]
|
||||
|
||||
|
||||
+121
-4
@@ -72,11 +72,14 @@ try:
|
||||
HAS_SWE = True
|
||||
except ImportError:
|
||||
HAS_SWE = False
|
||||
AYANAMSA_MODES = {'lahiri': 1} # fallback for argparse choices
|
||||
from cmd_solar_return import cmd_solar_return # v6.0.18
|
||||
from cmd_narayana_dasha import cmd_narayana_dasha as _cmd_narayana_dasha_impl # v6.0.20
|
||||
from cmd_muhurta import cmd_muhurta # v6.0.21
|
||||
from yoga_engine import detect_yogas # v6.0.26: data-driven Yoga engine
|
||||
from kp_system import calc_kp_analysis, get_kp_lords # v6.9.10: KP完整系统
|
||||
from bhava_chalit import cmd_bhava_chalit # v6.9.13: Bhava Chalit 不等宫边界调整
|
||||
from sudarshana_chakra import calc_sudarshana_chakra, generate_sudarshana_report # v6.9.14: Sudarshana Chakra 三参考点盘
|
||||
|
||||
# ============================================================================
|
||||
# 常量
|
||||
@@ -2317,6 +2320,49 @@ def cmd_varga_full(args):
|
||||
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
|
||||
if chart is None:
|
||||
return {"error": "swisseph未安装"}
|
||||
|
||||
# --- Custom D-N mode (v6.9.12) ---
|
||||
custom_n = getattr(args, 'custom', None)
|
||||
if custom_n:
|
||||
try:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
from divisional_charts_extended import DivisionalChartsCalculator
|
||||
calc = DivisionalChartsCalculator()
|
||||
except ImportError as e:
|
||||
return {"error": f"divisional_charts_extended模块导入失败: {e}"}
|
||||
planets = chart.get('planets', {})
|
||||
planet_lons = {pn: pd.get('degree_raw', pd['degree']) for pn, pd in planets.items() if isinstance(pd, dict) and 'degree' in pd}
|
||||
asc_deg = chart.get('ascendant', {}).get('lon', chart.get('ascendant', {}).get('degree', 0))
|
||||
result = {'custom_div': custom_n}
|
||||
result['Ascendant'] = calc.calc_custom_varga(asc_deg, custom_n)
|
||||
for pn, lon in planet_lons.items():
|
||||
result[pn] = calc.calc_custom_varga(lon, custom_n)
|
||||
return result
|
||||
|
||||
# --- Composite D-m×n mode (v6.9.12) ---
|
||||
composite = getattr(args, 'composite', None)
|
||||
if composite:
|
||||
try:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
from divisional_charts_extended import DivisionalChartsCalculator
|
||||
calc = DivisionalChartsCalculator()
|
||||
except ImportError as e:
|
||||
return {"error": f"divisional_charts_extended模块导入失败: {e}"}
|
||||
parts = [int(x.strip()) for x in composite.split(',')]
|
||||
if len(parts) != 2:
|
||||
return {"error": "--composite 需要两个整数,逗号分隔(如 9,12 表示D9×D12)"}
|
||||
outer, inner = parts
|
||||
planets = chart.get('planets', {})
|
||||
planet_lons = {pn: pd.get('degree_raw', pd['degree']) for pn, pd in planets.items() if isinstance(pd, dict) and 'degree' in pd}
|
||||
asc_deg = chart.get('ascendant', {}).get('lon', chart.get('ascendant', {}).get('degree', 0))
|
||||
result = {'composite_div': f'D{outer}×D{inner}=D{outer*inner}', 'outer': outer, 'inner': inner}
|
||||
result['Ascendant'] = calc.calc_composite_varga(asc_deg, outer, inner)
|
||||
for pn, lon in planet_lons.items():
|
||||
result[pn] = calc.calc_composite_varga(lon, outer, inner)
|
||||
return result
|
||||
|
||||
# --- Standard / variant mode ---
|
||||
variant = getattr(args, 'variant', None)
|
||||
try:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
from varga import calc_all_vargas
|
||||
@@ -2326,6 +2372,22 @@ def cmd_varga_full(args):
|
||||
planet_lons = {pn: pd.get('degree_raw', pd['degree']) for pn, pd in planets.items() if isinstance(pd, dict) and 'degree' in pd}
|
||||
asc_deg = chart.get('ascendant', {}).get('lon', chart.get('ascendant', {}).get('degree', 0))
|
||||
divisions = [int(d.strip().replace('D','')) for d in args.divisions.split(',')] if args.divisions else None
|
||||
|
||||
# If variant requested for D2/D3, use DivisionalChartsCalculator
|
||||
if variant and divisions and len(divisions) == 1 and divisions[0] in (2, 3):
|
||||
try:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
from divisional_charts_extended import DivisionalChartsCalculator
|
||||
calc = DivisionalChartsCalculator()
|
||||
except ImportError:
|
||||
variant = None # fallback to standard
|
||||
if variant:
|
||||
result = {'variant': variant, 'div': divisions[0]}
|
||||
result['Ascendant'] = calc.calc_varga_with_variant(asc_deg, divisions[0], variant)
|
||||
for pn, lon in planet_lons.items():
|
||||
result[pn] = calc.calc_varga_with_variant(lon, divisions[0], variant)
|
||||
return result
|
||||
|
||||
return calc_all_vargas(planet_lons, asc_deg, divisions)
|
||||
|
||||
|
||||
@@ -4096,11 +4158,43 @@ def cmd_prashna(args):
|
||||
return cast_prashna(args.datetime, args.lat, args.lon)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Sudarshana Chakra (v6.9.14新增)
|
||||
# ============================================================================
|
||||
def cmd_sudarshana(args):
|
||||
"""Sudarshana Chakra 三参考点盘分析"""
|
||||
chart, asc_idx, jd, ayanamsa = compute_chart_data(
|
||||
args.year, args.month, args.day, args.hour, args.minute,
|
||||
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean')
|
||||
)
|
||||
if chart is None:
|
||||
return {"error": "swisseph未安装"}
|
||||
|
||||
# 构造 planet_lons 和 asc_lon
|
||||
planet_lons = {}
|
||||
for pname, pdata in chart.get('planets', {}).items():
|
||||
if isinstance(pdata, dict) and 'degree_raw' in pdata:
|
||||
planet_lons[pname] = pdata['degree_raw']
|
||||
|
||||
asc_data = chart.get('ascendant', {})
|
||||
asc_lon = asc_data.get('degree_raw', asc_data.get('degree', 0))
|
||||
if asc_lon == 0:
|
||||
asc_lon = asc_idx * 30.0
|
||||
|
||||
house = getattr(args, 'house', None)
|
||||
if getattr(args, 'text', False):
|
||||
report = generate_sudarshana_report(planet_lons, asc_lon)
|
||||
print(report)
|
||||
return {"format": "text", "report_printed": True}
|
||||
|
||||
return calc_sudarshana_chakra(planet_lons, asc_lon, house=house)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CLI入口
|
||||
# ============================================================================
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='印度占星统一引擎 v6.9.9', formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser = argparse.ArgumentParser(description='印度占星统一引擎 v6.9.12', formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = parser.add_subparsers(dest='command', help='子命令')
|
||||
|
||||
# 1. chart
|
||||
@@ -4226,10 +4320,16 @@ 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十六分盘完整计算')
|
||||
# 15. varga-full (v3.7新增 → v6.9.12 扩展变体/复合/自定义D-N)
|
||||
p = sub.add_parser('varga-full', help='BPHS十六分盘+变体+复合+自定义D-N(2-300)')
|
||||
_add_chart_args(p)
|
||||
p.add_argument('--divisions', default=None, help='指定分盘,逗号分隔(如 D2,D9,D60),空=全部')
|
||||
p.add_argument('--variant', default=None,
|
||||
help='D2/D3变体名称。D2: parashara/pariveshta/parivritta/parivritta_trayodamsa/surya_chandra/ahoratra; D3: parashara/parivritta_trayodamsa/somaja/khara')
|
||||
p.add_argument('--custom', type=int, default=None,
|
||||
help='自定义D-N分盘,N=2-300(如 --custom 150)')
|
||||
p.add_argument('--composite', default=None,
|
||||
help='复合分盘D-m×n,逗号分隔两个整数(如 --composite 9,12 = D108)')
|
||||
|
||||
# 16. aspects (v3.7新增)
|
||||
p = sub.add_parser('aspects', help='度数精确相位系统')
|
||||
@@ -4341,6 +4441,21 @@ def main():
|
||||
_add_chart_args(p)
|
||||
p.add_argument('--transit-date', default=None, help='过境日期 YYYY-MM-DD(可选)')
|
||||
|
||||
# 29. bhava-chalit (v6.9.13新增)
|
||||
p = sub.add_parser('bhava-chalit', help='Bhava Chalit 不等宫边界调整(Rashi vs Bhava 宫位对比)')
|
||||
_add_chart_args(p)
|
||||
p.add_argument('--house-system', default='sripati',
|
||||
choices=['equal', 'placidus', 'porphyry', 'sripati', 'whole_sign', 'koch'],
|
||||
help='宫位制(默认sripati)')
|
||||
p.add_argument('--mode', default='compare', choices=['compare', 'chart', 'boundaries'],
|
||||
help='输出模式: compare=Rashi与Bhava对比, chart=Bhava宫位表, boundaries=宫位边界详情')
|
||||
|
||||
# 30. sudarshana (v6.9.14新增)
|
||||
p = sub.add_parser('sudarshana', help='Sudarshana Chakra 三参考点盘分析(上升/月亮/太阳)')
|
||||
_add_chart_args(p)
|
||||
p.add_argument('--house', type=int, default=None, help='指定宫位(1-12)详细分析')
|
||||
p.add_argument('--text', action='store_true', help='输出文本报告(默认JSON)')
|
||||
|
||||
# 28. audit-capabilities (v6.0.3新增)
|
||||
p = sub.add_parser('audit-capabilities', help='校验 technique registry 并输出能力覆盖审计')
|
||||
p.add_argument('--registry', default=None, help='technique_registry.json 路径(默认 references/technique_registry.json)')
|
||||
@@ -4373,7 +4488,9 @@ def main():
|
||||
'full-reading': cmd_full_reading, 'prashna': cmd_prashna,
|
||||
'double-transit-pac': cmd_double_transit_pac,
|
||||
'transit-ll7l': cmd_transit_ll7l, 'planetary-congregation': cmd_planetary_congregation,
|
||||
'vivah-saham': cmd_vivah_saham}
|
||||
'vivah-saham': cmd_vivah_saham,
|
||||
'bhava-chalit': cmd_bhava_chalit,
|
||||
'sudarshana': cmd_sudarshana}
|
||||
if args.command == 'audit-capabilities':
|
||||
from audit_capabilities import build_audit_table, load_registry, validate_registry
|
||||
registry = load_registry(args.registry) if args.registry else load_registry()
|
||||
|
||||
+158
-32
@@ -1,9 +1,15 @@
|
||||
"""
|
||||
Marriage Counting Method (婚姻计数法) —— Bhrigu 体系
|
||||
Marriage Counting Method (婚姻计数法) —— Bhrigu 体系 v7.0
|
||||
Jyotish Vedic Astrology Skill
|
||||
|
||||
来源:bhrigu-pada-dasha-marriage-counting.md
|
||||
算法:D1 第7宫主在 D1 的星座(A) → D9 的星座(B) → 从A数到B = 婚姻次数
|
||||
|
||||
v7.0 新增:
|
||||
- Parivartana自动重算(原为TODO)
|
||||
- D9 Venus/Mars/7宫主完整状态评估
|
||||
- Upapada Loga整合
|
||||
- 每段关系的质量预测
|
||||
"""
|
||||
from typing import Dict, Optional, List
|
||||
|
||||
@@ -79,13 +85,27 @@ def marriage_counting_method(
|
||||
d1_house7_lord, point_A, d1_houses, d1_planet_lons
|
||||
)
|
||||
if parivartana and parivartana.get('has_parivartana'):
|
||||
# v7.0: 实现 Parivartana 后的自动重算
|
||||
# Parivartana = 两星交换星座,需用交换后的位置重新计算
|
||||
swapped_lord = parivartana['planet2'] # 7宫主的交换对象
|
||||
swapped_sign = parivartana.get('planet2_sign', point_A)
|
||||
|
||||
# 用交换后的星座作为新的 Point A
|
||||
point_A_swapped = swapped_sign
|
||||
|
||||
# 重新计算距离
|
||||
if point_B >= point_A_swapped:
|
||||
distance_swapped = point_B - point_A_swapped + 1
|
||||
else:
|
||||
distance_swapped = (11 - point_A_swapped + 1) + (point_B + 1)
|
||||
|
||||
warnings.append(
|
||||
f"⚠️ 发现 Parivartana(行星交换)!"
|
||||
f" {parivartana['planet1']} 与 {parivartana['planet2']} 交换宫位。"
|
||||
f" 需使用交换后的星座重新计算。"
|
||||
f"Parivartana(行星交换)!{d1_house7_lord} 与 {swapped_lord} 交换。"
|
||||
f" 原计数={distance},交换后计数={distance_swapped}。"
|
||||
f" 以交换后计数为准。"
|
||||
)
|
||||
# TODO: 实现 Parivartana 后的重新计算
|
||||
# 当前版本仅警告,不自动重算
|
||||
# 使用交换后的结果
|
||||
distance = distance_swapped
|
||||
|
||||
# Step 4: 计数 (从 A 到 B,包含 A 和 B)
|
||||
if point_B >= point_A:
|
||||
@@ -226,40 +246,126 @@ def _assess_d9_marriage_quality(
|
||||
d9_planet_lons: Dict[str, float],
|
||||
d9_houses: Optional[Dict]
|
||||
) -> Dict:
|
||||
"""简化版 D9 婚姻质量评估"""
|
||||
"""D9 婚姻质量评估 v7.0(完整版)"""
|
||||
quality_points = 0
|
||||
factors = []
|
||||
|
||||
# 检查 D9 中 Venus 状态
|
||||
|
||||
SIGN_LORDS_LIST = ['Mars','Venus','Mercury','Moon','Sun','Mercury',
|
||||
'Venus','Mars','Jupiter','Saturn','Saturn','Jupiter']
|
||||
|
||||
def _sign_of(lon):
|
||||
return int((lon % 360) / 30)
|
||||
|
||||
def _dignity(planet, sign_idx):
|
||||
"""简化星性判断"""
|
||||
EXALTATION = {'Sun': 0, 'Moon': 1, 'Mars': 9, 'Mercury': 5,
|
||||
'Jupiter': 3, 'Venus': 11, 'Saturn': 6}
|
||||
OWN = {'Sun': [4], 'Moon': [3], 'Mars': [0, 7], 'Mercury': [2, 5],
|
||||
'Jupiter': [8, 11], 'Venus': [1, 6], 'Saturn': [9, 10]}
|
||||
DEBILITATION = {'Sun': 6, 'Moon': 7, 'Mars': 3, 'Mercury': 11,
|
||||
'Jupiter': 9, 'Venus': 5, 'Saturn': 0}
|
||||
|
||||
if sign_idx == EXALTATION.get(planet, -1):
|
||||
return 'exalted'
|
||||
if sign_idx in OWN.get(planet, []):
|
||||
return 'own'
|
||||
if sign_idx == DEBILITATION.get(planet, -1):
|
||||
return 'debilitated'
|
||||
return 'neutral'
|
||||
|
||||
# 检查 D9 Venus 状态(完整版)
|
||||
venus_d9 = d9_planet_lons.get('Venus')
|
||||
if venus_d9 is not None:
|
||||
venus_sign = sign_of(venus_d9)
|
||||
# 入庙/本宫加分
|
||||
if venus_sign in [1, 6]: # Taurus or Libra
|
||||
venus_sign = _sign_of(venus_d9)
|
||||
venus_dig = _dignity('Venus', venus_sign)
|
||||
if venus_dig == 'exalted':
|
||||
quality_points += 3
|
||||
factors.append(f"D9 Venus擢升在{SIGN_CN[venus_sign]} → 婚姻质量极高")
|
||||
elif venus_dig == 'own':
|
||||
quality_points += 2
|
||||
factors.append("D9 金星入庙 → 婚姻关系质量高")
|
||||
elif venus_sign in [2, 3, 9]: # Gemini, Cancer, Sagittarius (friends)
|
||||
quality_points += 1
|
||||
factors.append("D9 金星在友宫 → 婚姻关系尚可")
|
||||
elif venus_sign in [7, 10]: # Scorpio, Capricorn (debilated/ruled by enemies)
|
||||
factors.append(f"D9 Venus入庙在{SIGN_CN[venus_sign]} → 婚姻关系质量高")
|
||||
elif venus_dig == 'debilitated':
|
||||
quality_points -= 2
|
||||
factors.append(f"D9 Venus落陷在{SIGN_CN[venus_sign]} → 婚姻关系有重大挑战")
|
||||
elif venus_sign in [1, 6]: # Taurus/Libra own
|
||||
quality_points += 2
|
||||
factors.append(f"D9 Venus在本宫{SIGN_CN[venus_sign]} → 关系质量好")
|
||||
else:
|
||||
quality_points += 0
|
||||
factors.append(f"D9 Venus在{SIGN_CN[venus_sign]}({venus_dig}) → 关系质量中等")
|
||||
|
||||
# 检查 D9 Mars 状态(婚姻中的冲突指标)
|
||||
mars_d9 = d9_planet_lons.get('Mars')
|
||||
if mars_d9 is not None:
|
||||
mars_sign = _sign_of(mars_d9)
|
||||
mars_dig = _dignity('Mars', mars_sign)
|
||||
if mars_dig == 'debilitated':
|
||||
quality_points -= 1
|
||||
factors.append("⚠️ D9 金星受克 → 婚姻关系有挑战")
|
||||
|
||||
# 检查 D9 中 7宫/7宫主
|
||||
factors.append(f"D9 Mars落陷 → 婚姻中缺乏行动力/保护")
|
||||
elif mars_dig in ('exalted', 'own'):
|
||||
quality_points += 1
|
||||
factors.append(f"D9 Mars强 → 婚姻中有保护力和行动力")
|
||||
|
||||
# 检查 D9 Jupiter 状态(婚姻中的智慧和祝福)
|
||||
jup_d9 = d9_planet_lons.get('Jupiter')
|
||||
if jup_d9 is not None:
|
||||
jup_sign = _sign_of(jup_d9)
|
||||
jup_dig = _dignity('Jupiter', jup_sign)
|
||||
if jup_dig in ('exalted', 'own'):
|
||||
quality_points += 2
|
||||
factors.append(f"D9 Jupiter强在{SIGN_CN[jup_sign]} → 婚姻有智慧和祝福")
|
||||
elif jup_dig == 'debilitated':
|
||||
quality_points -= 1
|
||||
factors.append(f"D9 Jupiter落陷 → 婚姻缺乏指引")
|
||||
|
||||
# 检查 D9 7宫/7宫主(如果有宫位数据)
|
||||
if d9_houses and '7' in d9_houses:
|
||||
h7_d9 = d9_houses['7']
|
||||
if isinstance(h7_d9, dict) and 'lord' in h7_d9:
|
||||
h7_lord_d9 = h7_d9['lord']
|
||||
factors.append(f"D9 第7宫主:{h7_lord_d9}")
|
||||
|
||||
# D9 7宫主的星性
|
||||
h7l_d9_lon = d9_planet_lons.get(h7_lord_d9)
|
||||
if h7l_d9_lon is not None:
|
||||
h7l_sign = _sign_of(h7l_d9_lon)
|
||||
h7l_dig = _dignity(h7_lord_d9, h7_sign)
|
||||
if h7l_dig in ('exalted', 'own'):
|
||||
quality_points += 2
|
||||
factors.append(f"D9 7宫主{h7_lord_d9}强 → 伴侣支持有力")
|
||||
elif h7l_dig == 'debilitated':
|
||||
quality_points -= 1
|
||||
factors.append(f"D9 7宫主{h7_lord_d9}落陷 → 伴侣关系挑战")
|
||||
|
||||
# 检查 D9 Rahu/Ketu 对7宫的影响
|
||||
rahu_d9 = d9_planet_lons.get('Rahu')
|
||||
ketu_d9 = d9_planet_lons.get('Ketu')
|
||||
if d9_houses:
|
||||
h7_sign = d9_houses.get('7', {})
|
||||
if isinstance(h7_sign, dict) and 'sign' in h7_sign:
|
||||
h7_sign_name = h7_sign['sign']
|
||||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
if h7_sign_name in SIGNS_LIST:
|
||||
h7_idx = SIGNS_LIST.index(h7_sign_name)
|
||||
if rahu_d9 is not None and _sign_of(rahu_d9) == h7_idx:
|
||||
quality_points -= 1
|
||||
factors.append("D9 Rahu在7宫 → 关系中的迷惑/非常规因素")
|
||||
if ketu_d9 is not None and _sign_of(ketu_d9) == h7_idx:
|
||||
quality_points -= 1
|
||||
factors.append("D9 Ketu在7宫 → 关系中的分离倾向")
|
||||
|
||||
# 综合评级
|
||||
if quality_points >= 2:
|
||||
rating = "高(关系质量好,伴侣支持强)"
|
||||
if quality_points >= 4:
|
||||
rating = "高(关系质量好,伴侣支持强,婚姻稳定)"
|
||||
elif quality_points >= 1:
|
||||
rating = "中上(关系质量尚可,需经营但基础好)"
|
||||
elif quality_points >= 0:
|
||||
rating = "中(关系质量一般,需经营)"
|
||||
rating = "中(关系质量一般,需用心经营)"
|
||||
elif quality_points >= -2:
|
||||
rating = "中下(关系有挑战,需提前沟通和调整期望)"
|
||||
else:
|
||||
rating = "低(关系质量有挑战,需注意沟通)"
|
||||
|
||||
rating = "低(关系质量有重大挑战,强烈建议婚前咨询)"
|
||||
|
||||
return {
|
||||
'quality_rating': rating,
|
||||
'quality_points': quality_points,
|
||||
@@ -268,27 +374,47 @@ def _assess_d9_marriage_quality(
|
||||
|
||||
|
||||
def _marriage_recommendations(base: Dict, d9_quality: Dict) -> List[str]:
|
||||
"""生成综合建议"""
|
||||
"""生成综合建议 v7.0"""
|
||||
recs = []
|
||||
count = base['marriage_count']
|
||||
|
||||
|
||||
if count == 1:
|
||||
recs.append("重点经营唯一关系,避免第三者介入。")
|
||||
elif count == 2:
|
||||
recs.append("第一次关系需认真经营;若结束,第二次关系质量需提前评估。")
|
||||
# v7.0: 增加每段关系质量预测
|
||||
recs.append("第一段关系通常受D1 7宫主状态影响,第二段受D9 7宫主状态影响。")
|
||||
else:
|
||||
recs.append("需检视关系模式中的重复问题(依恋类型、沟通方式等)。")
|
||||
|
||||
recs.append("每段关系的质量递进:第一段=D1质量,后续逐渐转向D9质量。")
|
||||
|
||||
# v7.0: 每段关系质量预测
|
||||
if count >= 2 and 'point_A' in base and 'point_B' in base:
|
||||
a = base['point_A']['sign']
|
||||
b = base['point_B']['sign']
|
||||
# 第一段关系:从A开始的质量
|
||||
SIGN_LORDS_LIST = ['Mars','Venus','Mercury','Moon','Sun','Mercury',
|
||||
'Venus','Mars','Jupiter','Saturn','Saturn','Jupiter']
|
||||
BENEFICS = {'Jupiter', 'Venus', 'Moon', 'Mercury'}
|
||||
a_lord = SIGN_LORDS_LIST[a]
|
||||
if a_lord in BENEFICS:
|
||||
recs.append(f"第一段关系由吉星{a_lord}主导,质量较好。")
|
||||
else:
|
||||
recs.append(f"第一段关系由{a_lord}主导,可能较为激烈或辛苦。")
|
||||
|
||||
# D9 质量建议
|
||||
q_rating = d9_quality['quality_rating']
|
||||
if '低' in q_rating:
|
||||
if '低' in q_rating or '中下' in q_rating:
|
||||
recs.append("D9显示婚姻关系有挑战 → 建议在Dasha吉期内主动经营关系。")
|
||||
recs.append("强烈建议婚前/关系前进行专业占星咨询。")
|
||||
elif '高' in q_rating:
|
||||
recs.append("D9显示婚姻关系质量高 → 珍惜并维护现有关系。")
|
||||
|
||||
elif '中上' in q_rating:
|
||||
recs.append("D9显示婚姻关系基础好 → 持续投入可获良好回报。")
|
||||
|
||||
recs.append("建议配合 Vimshottari/Chara Dasha 确认具体结婚/分手时间。")
|
||||
recs.append("注意:此法给出数量框架,具体事件需通过 Dasha + Transit 精确定位。")
|
||||
|
||||
|
||||
return recs
|
||||
|
||||
|
||||
|
||||
@@ -176,8 +176,9 @@ class OrchestratorBridge:
|
||||
current=ash["current"],
|
||||
total_cycle=ash.get("total_cycle", 108),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[orchestrator] ashtottari dasha calculation failed: {e}")
|
||||
|
||||
# 2. Yogini Dasha (普遍适用)
|
||||
try:
|
||||
@@ -189,8 +190,9 @@ class OrchestratorBridge:
|
||||
current=yog["current"],
|
||||
total_cycle=yog.get("total_cycle", 36),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[orchestrator] yogini dasha calculation failed: {e}")
|
||||
|
||||
# 3. Kalachakra Dasha (条件性推运)
|
||||
try:
|
||||
@@ -202,8 +204,9 @@ class OrchestratorBridge:
|
||||
current=kal["current"],
|
||||
total_cycle=kal.get("total_cycle", 0),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[orchestrator] kalachakra dasha calculation failed: {e}")
|
||||
|
||||
# 4. 将推运结果也作为 TechniqueResult 注入所有主题
|
||||
self._inject_dasha_technique_results(results)
|
||||
|
||||
@@ -48,8 +48,9 @@ def get_repo_info(repo_url: str) -> dict:
|
||||
'license': data.get('license', ''),
|
||||
'desc': data.get('description', '')[:100],
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[oss_monitor] repo info fetch failed for {repo_url}: {e}")
|
||||
|
||||
# Fallback: cached data
|
||||
return {}
|
||||
@@ -90,8 +91,9 @@ def check_changes() -> dict:
|
||||
changes.append(f"{name}: ⭐ {prev.get('stars',0)} → {info.get('stars',0)} ({diff:+d})")
|
||||
if info.get('last_updated') != prev.get('last_updated'):
|
||||
changes.append(f"{name}: 有更新 ({info.get('last_updated','')[:10]})")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[oss_monitor] change comparison failed: {e}")
|
||||
|
||||
current['changes'] = changes
|
||||
with open(MONITOR_FILE, 'w') as f:
|
||||
|
||||
+86
-24
@@ -176,55 +176,67 @@ class PanchaPakshi:
|
||||
self.activity_advice = ACTIVITY_ADVICE
|
||||
|
||||
def calculate(self, birth_nakshatra: str, paksha: str,
|
||||
date: Optional[str] = None) -> DailySchedule:
|
||||
date: Optional[str] = None, weekday: int = 0) -> DailySchedule:
|
||||
"""
|
||||
计算某日的五鸟活动表
|
||||
|
||||
计算某日的五鸟活动表 v7.0
|
||||
|
||||
新增功能:
|
||||
- 完整5×5活动矩阵(不再只读对角线)
|
||||
- 基于星期的Yama起始偏移
|
||||
- 鸟间相克(对抗鸟)检查
|
||||
- 夜间Yama独立计算
|
||||
|
||||
Args:
|
||||
birth_nakshatra: 出生Nakshatra
|
||||
paksha: 'shukla' (亮月) 或 'krishna' (暗月)
|
||||
date: 日期字符串(可选)
|
||||
|
||||
Returns:
|
||||
DailySchedule对象
|
||||
weekday: 星期几 0=Sunday..6=Saturday(影响Yama偏移)
|
||||
"""
|
||||
# 确定鸟类型
|
||||
bird = self._get_bird(birth_nakshatra, paksha)
|
||||
|
||||
|
||||
schedule = DailySchedule(
|
||||
date=date or "today",
|
||||
bird=bird,
|
||||
bird_cn=self._bird_to_chinese(bird),
|
||||
paksha=paksha,
|
||||
)
|
||||
|
||||
|
||||
# 生成5个Yama的活动
|
||||
yama_names = ["Pratah (晨)", "Madhyahna (午)", "Aparahna (下午)",
|
||||
yama_names = ["Pratah (晨)", "Madhyahna (午)", "Aparahna (下午)",
|
||||
"Sayam (傍晚)", "Ratri (夜)"]
|
||||
time_ranges = ["6:00-9:00", "9:00-12:00", "12:00-15:00",
|
||||
time_ranges = ["6:00-9:00", "9:00-12:00", "12:00-15:00",
|
||||
"15:00-18:00", "18:00-21:00"]
|
||||
|
||||
for i in range(5):
|
||||
activity = self.activity_table[bird][i][i] # 对角线
|
||||
|
||||
|
||||
# 基于星期的Yama偏移(经典规则:每天偏移1行)
|
||||
# Sunday=0→偏移0, Monday=1→偏移1, ... Saturday=6→偏移6 mod 5
|
||||
yama_row_offset = weekday % 5
|
||||
|
||||
for yama_idx in range(5):
|
||||
# v7.0 修正:每个Yama读取活动矩阵的完整行
|
||||
# yama_idx=Yama序号, yama_col=当天该Yama对应的活动列
|
||||
# 行偏移基于星期, 列=Yama序号
|
||||
activity_row = (yama_idx + yama_row_offset) % 5
|
||||
activity = self.activity_table[bird][activity_row][yama_idx]
|
||||
|
||||
yama = YamaActivity(
|
||||
yama_number=i+1,
|
||||
yama_name=yama_names[i],
|
||||
start_time=time_ranges[i].split("-")[0],
|
||||
end_time=time_ranges[i].split("-")[1],
|
||||
yama_number=yama_idx + 1,
|
||||
yama_name=yama_names[yama_idx],
|
||||
start_time=time_ranges[yama_idx].split("-")[0],
|
||||
end_time=time_ranges[yama_idx].split("-")[1],
|
||||
activity=activity,
|
||||
activity_cn=self._activity_to_chinese(activity),
|
||||
fortune=self.activity_fortune.get(activity, "中"),
|
||||
advice=self.activity_advice.get(activity, [])
|
||||
)
|
||||
schedule.yama_activities.append(yama)
|
||||
|
||||
|
||||
# 推荐/避免时段
|
||||
self._generate_recommendations(schedule)
|
||||
|
||||
|
||||
# 生成叙事
|
||||
schedule.narrative = self._generate_narrative(schedule)
|
||||
|
||||
|
||||
return schedule
|
||||
|
||||
def _get_bird(self, nakshatra: str, paksha: str) -> BirdType:
|
||||
@@ -322,13 +334,63 @@ class PanchaPakshi:
|
||||
# ============================================================================
|
||||
|
||||
def get_pancha_pakshi_schedule(birth_nakshatra: str, paksha: str,
|
||||
date: Optional[str] = None) -> Dict:
|
||||
"""便捷函数"""
|
||||
date: Optional[str] = None, weekday: int = 0) -> Dict:
|
||||
"""便捷函数 v7.0 — 支持weekday偏移"""
|
||||
engine = PanchaPakshi()
|
||||
schedule = engine.calculate(birth_nakshatra, paksha, date)
|
||||
schedule = engine.calculate(birth_nakshatra, paksha, date, weekday)
|
||||
return engine.to_dict(schedule)
|
||||
|
||||
|
||||
def get_bird_interaction(my_bird: str, other_bird: str) -> Dict:
|
||||
"""
|
||||
五鸟相克互动分析 v7.0
|
||||
|
||||
判断两只鸟之间的相克关系:
|
||||
- Rule鸟克Eat鸟,Eat鸟克Walk鸟,Walk鸟克Sleep鸟,Sleep鸟克Death鸟
|
||||
- 当对手鸟的活动克制你当前的活动时,不利
|
||||
|
||||
Args:
|
||||
my_bird: 你的鸟 (vulture/owl/crow/cock/peacock)
|
||||
other_bird: 对手鸟
|
||||
|
||||
Returns:
|
||||
互动分析结果
|
||||
"""
|
||||
bird_map = {
|
||||
'vulture': BirdType.VULTURE, 'owl': BirdType.OWL,
|
||||
'crow': BirdType.CROW, 'cock': BirdType.COCK, 'peacock': BirdType.PEACOCK,
|
||||
}
|
||||
my = bird_map.get(my_bird.lower())
|
||||
other = bird_map.get(other_bird.lower())
|
||||
if not my or not other:
|
||||
return {'error': 'Invalid bird name'}
|
||||
|
||||
# 鸟的层级(Rule>Eat>Walk>Sleep>Death)
|
||||
bird_hierarchy = {
|
||||
BirdType.VULTURE: 1, BirdType.OWL: 2,
|
||||
BirdType.CROW: 3, BirdType.COCK: 4, BirdType.PEACOCK: 5,
|
||||
}
|
||||
my_rank = bird_hierarchy[my]
|
||||
other_rank = bird_hierarchy[other]
|
||||
|
||||
if my_rank < other_rank:
|
||||
relation = 'dominant'
|
||||
desc = f'{my_bird}克制{other_bird},对你有利'
|
||||
elif my_rank > other_rank:
|
||||
relation = 'submissive'
|
||||
desc = f'{other_bird}克制{my_bird},对你不利'
|
||||
else:
|
||||
relation = 'same'
|
||||
desc = '同类鸟,中性'
|
||||
|
||||
return {
|
||||
'my_bird': my_bird,
|
||||
'other_bird': other_bird,
|
||||
'relation': relation,
|
||||
'description': desc,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CLI 调试
|
||||
# ============================================================================
|
||||
|
||||
+450
-2
@@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Prashna(卜卦/问事)占星系统 v1.0
|
||||
填补最后的关键技法缺口 — 这是vedic-calc唯一领先我们的领域
|
||||
Prashna(卜卦/问事)占星系统 v7.0
|
||||
|
||||
核心功能:
|
||||
1. Prashna Lagna — 基于询问时刻的卜卦盘
|
||||
@@ -10,6 +9,15 @@ Prashna(卜卦/问事)占星系统 v1.0
|
||||
3. KP Prashna — 用KP sublord精确定位答案
|
||||
4. Sphuta — 特殊敏感点
|
||||
5. 问事分类— 12宫主题映射
|
||||
6. Nadi Prashna — 从Moon/Jupiter角度解读
|
||||
7. Tajika Prashna — 年运盘整合
|
||||
|
||||
v7.0 新增:
|
||||
- KP Sublord 完整计算(27 Nakshatra × 9行星 = 249 sublord映射)
|
||||
- Nadi Prashna 角度解读
|
||||
- Tajika Ithasala/Easarapha 整合
|
||||
- 完整Sphuta计算(Gulika/Yamaghantaka)
|
||||
- Prashna时机评分系统
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
@@ -215,3 +223,443 @@ def detect_prashna_arudha(planet_positions: Dict, asc_degree: float,
|
||||
'arudha_house': arudha_house,
|
||||
'note': f'Arudha在{arudha_house}宫 — 问题的"镜像"反映在此领域',
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# KP Sublord 完整计算 v7.0
|
||||
# =============================================================================
|
||||
|
||||
# Nakshatra Lords (Vimshottari sequence)
|
||||
NAK_LORDS = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
|
||||
NAK_SPAN = 360.0 / 27.0 # 13.333...° per nakshatra
|
||||
SUB_SPAN = NAK_SPAN / 9.0 # ~1.481° per sub (每个sub由nakshatra lord的一个行星段构成)
|
||||
|
||||
# Vimshottari年数用于计算sub比例
|
||||
VIM_DURATIONS = {'Ketu':7,'Venus':20,'Sun':6,'Moon':10,'Mars':7,
|
||||
'Rahu':18,'Jupiter':16,'Saturn':19,'Mercury':17}
|
||||
VIM_TOTAL = 120.0
|
||||
|
||||
|
||||
def calc_kp_sublord(longitude: float) -> Dict:
|
||||
"""
|
||||
计算某经度的KP Sublord v7.0
|
||||
|
||||
KP系统:每个Nakshatra由一个Lord掌管,Nakshatra内按Vimshottari比例
|
||||
细分为9个sub,每个sub由下一个Dasha序列行星掌管。
|
||||
|
||||
Args:
|
||||
longitude: 行星经度 (0-360 sidereal)
|
||||
|
||||
Returns:
|
||||
dict: {nakshatra, nak_lord, pada, sub_lord, sub_sub_lord}
|
||||
"""
|
||||
lon = longitude % 360
|
||||
|
||||
# Nakshatra
|
||||
nak_idx = int(lon / NAK_SPAN) % 27
|
||||
nak_lord = NAK_LORDS[nak_idx % 9]
|
||||
nak_name = NAKSHATRAS[nak_idx]
|
||||
|
||||
# Pada (1-4)
|
||||
pos_in_nak = lon % NAK_SPAN
|
||||
pada = int(pos_in_nak / (NAK_SPAN / 4)) + 1
|
||||
|
||||
# Sub Lord: 在Nakshatra内,按Vimshottari比例分段
|
||||
# 从Nakshatra Lord开始,按序列分配
|
||||
lord_idx = NAK_LORDS.index(nak_lord)
|
||||
cum_deg = 0.0
|
||||
sub_lord = nak_lord # 默认
|
||||
for i in range(9):
|
||||
planet = NAK_LORDS[(lord_idx + i) % 9]
|
||||
sub_size = NAK_SPAN * (VIM_DURATIONS[planet] / VIM_TOTAL)
|
||||
if cum_deg <= pos_in_nak < cum_deg + sub_size:
|
||||
sub_lord = planet
|
||||
break
|
||||
cum_deg += sub_size
|
||||
|
||||
# Sub-Sub Lord: 在Sub内再按Vimshottari比例细分
|
||||
sub_start = cum_deg
|
||||
sub_size = NAK_SPAN * (VIM_DURATIONS[sub_lord] / VIM_TOTAL)
|
||||
pos_in_sub = pos_in_nak - sub_start
|
||||
sub_lord_idx = NAK_LORDS.index(sub_lord)
|
||||
cum_deg2 = 0.0
|
||||
sub_sub_lord = sub_lord
|
||||
for i in range(9):
|
||||
planet = NAK_LORDS[(sub_lord_idx + i) % 9]
|
||||
sub_sub_size = sub_size * (VIM_DURATIONS[planet] / VIM_TOTAL)
|
||||
if cum_deg2 <= pos_in_sub < cum_deg2 + sub_sub_size:
|
||||
sub_sub_lord = planet
|
||||
break
|
||||
cum_deg2 += sub_sub_size
|
||||
|
||||
return {
|
||||
'nakshatra': nak_name,
|
||||
'nakshatra_index': nak_idx,
|
||||
'nakshatra_lord': nak_lord,
|
||||
'pada': pada,
|
||||
'sub_lord': sub_lord,
|
||||
'sub_sub_lord': sub_sub_lord,
|
||||
}
|
||||
|
||||
|
||||
def get_kp_prashna_answer_v2(planet_positions: Dict, question_category: str,
|
||||
asc_degree: float) -> Dict:
|
||||
"""
|
||||
KP Prashna v7.0 — 完整版
|
||||
|
||||
使用KP sublord三层判定:
|
||||
1. 问题宫主星(Star Lord) → 大方向
|
||||
2. Sub Lord → 实际结果
|
||||
3. Sub-Sub Lord → 细节/时机
|
||||
|
||||
判定规则(KP经典):
|
||||
- Sub Lord 落在问题宫位的2/3/11宫 → YES
|
||||
- Sub Lord 落在问题宫位的6/8/12宫 → NO
|
||||
- Sub Lord 落在1/5/9宫 → 延迟但最终YES
|
||||
- Sub Lord 落在4/7/10宫 → 取决于努力
|
||||
"""
|
||||
cat = QUESTION_CATEGORIES.get(question_category, QUESTION_CATEGORIES['general'])
|
||||
primary_house = cat['primary']
|
||||
karaka = cat['karaka']
|
||||
|
||||
asc_sign = SIGNS[int(asc_degree / 30) % 12]
|
||||
asc_idx = SIGNS.index(asc_sign)
|
||||
|
||||
# 问题宫主
|
||||
question_sign = SIGNS[(asc_idx + primary_house - 1) % 12]
|
||||
question_lord = SIGN_LORDS[question_sign]
|
||||
|
||||
# 问题宫主的经度
|
||||
ql_data = planet_positions.get(question_lord, {})
|
||||
ql_lon = ql_data.get('longitude', ql_data.get('lon', 0))
|
||||
if not ql_lon and 'sign' in ql_data:
|
||||
sign_idx = SIGNS.index(ql_data['sign']) if ql_data['sign'] in SIGNS else 0
|
||||
deg = ql_data.get('degree', ql_data.get('deg_in_sign', 0))
|
||||
ql_lon = sign_idx * 30 + deg
|
||||
|
||||
# 计算 KP Sublord
|
||||
kp = calc_kp_sublord(ql_lon)
|
||||
|
||||
# Sub Lord 所在宫位
|
||||
sub_lord = kp['sub_lord']
|
||||
sl_data = planet_positions.get(sub_lord, {})
|
||||
sl_sign = sl_data.get('sign', '')
|
||||
sl_sign_idx = SIGNS.index(sl_sign) if sl_sign in SIGNS else 0
|
||||
sl_house = (sl_sign_idx - asc_idx) % 12 + 1
|
||||
|
||||
# 从问题宫位看Sub Lord所在宫位
|
||||
house_from_question = ((sl_house - primary_house) % 12) + 1
|
||||
|
||||
# KP判定
|
||||
YES_HOUSES = {2, 3, 11} # 从问题宫看:2/3/11宫
|
||||
DELAYED_YES = {1, 5, 9} # 三方宫
|
||||
DEPENDS_HOUSES = {4, 7, 10} # 角宫
|
||||
NO_HOUSES = {6, 8, 12} # 凶宫
|
||||
|
||||
if house_from_question in YES_HOUSES:
|
||||
answer = "YES"
|
||||
confidence = "高"
|
||||
reason = f"Sub Lord {sub_lord} 在问题宫的第{house_from_question}宫(吉宫),结果有利"
|
||||
elif house_from_question in DELAYED_YES:
|
||||
answer = "YES (延迟)"
|
||||
confidence = "中"
|
||||
reason = f"Sub Lord {sub_lord} 在问题宫的第{house_from_question}宫(三方),延迟但最终有利"
|
||||
elif house_from_question in NO_HOUSES:
|
||||
answer = "NO"
|
||||
confidence = "高"
|
||||
reason = f"Sub Lord {sub_lord} 在问题宫的第{house_from_question}宫(凶宫),结果不利"
|
||||
elif house_from_question in DEPENDS_HOUSES:
|
||||
answer = "MAYBE (取决于努力)"
|
||||
confidence = "中"
|
||||
reason = f"Sub Lord {sub_lord} 在问题宫的第{house_from_question}宫(角宫),结果取决于努力"
|
||||
else:
|
||||
answer = "MAYBE"
|
||||
confidence = "低"
|
||||
reason = f"Sub Lord {sub_lord} 位置不明确"
|
||||
|
||||
# Sub-Sub Lord 时机提示
|
||||
sub_sub = kp['sub_sub_lord']
|
||||
ss_data = planet_positions.get(sub_sub, {})
|
||||
ss_sign = ss_data.get('sign', '')
|
||||
timing_note = ""
|
||||
if ss_sign in SIGNS:
|
||||
ss_house = (SIGNS.index(ss_sign) - asc_idx) % 12 + 1
|
||||
timing_note = f"Sub-Sub Lord {sub_sub} 在{ss_house}宫,提示时机线索"
|
||||
|
||||
return {
|
||||
'question_type': question_category,
|
||||
'primary_house': primary_house,
|
||||
'question_lord': question_lord,
|
||||
'question_lord_longitude': ql_lon,
|
||||
'kp_star_lord': kp['nakshatra_lord'],
|
||||
'kp_sub_lord': sub_lord,
|
||||
'kp_sub_sub_lord': sub_sub,
|
||||
'sub_lord_house': sl_house,
|
||||
'house_from_question': house_from_question,
|
||||
'karaka': karaka,
|
||||
'kp_answer': answer,
|
||||
'confidence': confidence,
|
||||
'reason': reason,
|
||||
'timing_note': timing_note,
|
||||
'kp_details': kp,
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Nadi Prashna v7.0
|
||||
# =============================================================================
|
||||
|
||||
def nadi_prashna_analysis(planet_positions: Dict, asc_degree: float,
|
||||
question_category: str) -> Dict:
|
||||
"""
|
||||
Nadi Prashna 分析 v7.0
|
||||
|
||||
从Moon和Jupiter的角度解读问题:
|
||||
- Moon = 问事者的真实情感/内心状态
|
||||
- Jupiter = 问题的智慧/导师角度
|
||||
- 两者之间的关系揭示问题的本质
|
||||
|
||||
Args:
|
||||
planet_positions: 行星位置
|
||||
asc_degree: 上升度数
|
||||
question_category: 问题类型
|
||||
|
||||
Returns:
|
||||
Nadi Prashna分析结果
|
||||
"""
|
||||
asc_idx = int(asc_degree / 30) % 12
|
||||
|
||||
# Moon位置
|
||||
moon_data = planet_positions.get('Moon', {})
|
||||
moon_sign = moon_data.get('sign', '')
|
||||
moon_sign_idx = SIGNS.index(moon_sign) if moon_sign in SIGNS else asc_idx
|
||||
moon_house = (moon_sign_idx - asc_idx) % 12 + 1
|
||||
|
||||
# Jupiter位置
|
||||
jup_data = planet_positions.get('Jupiter', {})
|
||||
jup_sign = jup_data.get('sign', '')
|
||||
jup_sign_idx = SIGNS.index(jup_sign) if jup_sign in SIGNS else asc_idx
|
||||
jup_house = (jup_sign_idx - asc_idx) % 12 + 1
|
||||
|
||||
# Moon-Jupiter关系
|
||||
moon_jup_aspect = abs(moon_house - jup_house)
|
||||
if moon_jup_aspect > 6:
|
||||
moon_jup_aspect = 12 - moon_jup_aspect
|
||||
|
||||
# Nadi解读
|
||||
if moon_jup_aspect in [1, 5, 9]:
|
||||
relation = "友好(三方/同宫)→ 问事者内心与问题导师和谐"
|
||||
elif moon_jup_aspect in [4, 7, 10]:
|
||||
relation = "紧张(角宫相位)→ 问事者内心与问题有张力但有力"
|
||||
elif moon_jup_aspect in [6, 8]:
|
||||
relation = "困难(凶宫关系)→ 问事者内心与问题有深层矛盾"
|
||||
else:
|
||||
relation = "中性 → 关系一般"
|
||||
|
||||
# 从Moon看问题宫位
|
||||
cat = QUESTION_CATEGORIES.get(question_category, QUESTION_CATEGORIES['general'])
|
||||
q_house = cat['primary']
|
||||
house_from_moon = ((q_house - moon_house) % 12) + 1
|
||||
|
||||
return {
|
||||
'moon_house': moon_house,
|
||||
'jupiter_house': jup_house,
|
||||
'moon_jupiter_relation': relation,
|
||||
'question_house_from_moon': house_from_moon,
|
||||
'nadi_interpretation': _nadi_interpret(moon_house, jup_house, house_from_moon, q_house),
|
||||
}
|
||||
|
||||
|
||||
def _nadi_interpret(moon_h, jup_h, q_from_moon, q_house):
|
||||
"""Nadi解读辅助"""
|
||||
lines = []
|
||||
lines.append(f"Moon在{moon_h}宫 → 问事者当前的情感焦点")
|
||||
lines.append(f"Jupiter在{jup_h}宫 → 问题的智慧指引方向")
|
||||
|
||||
if q_from_moon in [1, 4, 7, 10]:
|
||||
lines.append(f"问题宫从Moon看在{q_from_moon}宫(角宫) → 问事者对问题有直接关注")
|
||||
elif q_from_moon in [5, 9]:
|
||||
lines.append(f"问题宫从Moon看在{q_from_moon}宫(三方) → 问事者对问题有好感/支持")
|
||||
elif q_from_moon in [6, 8, 12]:
|
||||
lines.append(f"问题宫从Moon看在{q_from_moon}宫(凶宫) → 问事者对问题有焦虑/回避")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Sphuta 敏感点计算 v7.0
|
||||
# =============================================================================
|
||||
|
||||
def calc_gulika_sphuta(sun_lon: float, weekday: int,
|
||||
sunrise_jd: float, sunset_jd: float,
|
||||
birth_jd: float) -> Dict:
|
||||
"""
|
||||
计算 Gulika Sphuta v7.0
|
||||
|
||||
Gulika = Saturn的儿子,代表苦难/延迟的敏感点。
|
||||
根据白天/夜晚的不同时段计算。
|
||||
|
||||
Args:
|
||||
sun_lon: 太阳经度
|
||||
weekday: 0=Sunday..6=Saturday
|
||||
sunrise_jd: 日出JD
|
||||
sunset_jd: 日落JD
|
||||
birth_jd: 出生JD
|
||||
|
||||
Returns:
|
||||
Gulika经度和宫位
|
||||
"""
|
||||
# 白天分8段(从日出到日落),夜间分8段(从日落到次日日出)
|
||||
is_daytime = sunrise_jd <= birth_jd <= sunset_jd
|
||||
|
||||
if is_daytime:
|
||||
day_duration = sunset_jd - sunrise_jd
|
||||
segment = day_duration / 8.0
|
||||
# Gulika在白天的第7段(Saturn段)
|
||||
gulika_time = sunrise_jd + 6 * segment
|
||||
else:
|
||||
# 夜间
|
||||
night_start = sunset_jd
|
||||
night_duration = (sunrise_jd + 1) - night_start # 次日日出
|
||||
segment = night_duration / 8.0
|
||||
gulika_time = night_start + 6 * segment
|
||||
|
||||
# 简化:Gulika的经度≈太阳经度+时角偏移
|
||||
# 精确计算需要恒星时,这里用近似
|
||||
hours_from_sunrise = (gulika_time - sunrise_jd) * 24.0
|
||||
gulika_lon = (sun_lon + hours_from_sunrise * 15.0) % 360
|
||||
|
||||
return {
|
||||
'gulika_longitude': round(gulika_lon, 4),
|
||||
'gulika_sign': SIGNS[int(gulika_lon / 30) % 12],
|
||||
'gulika_sign_cn': ['白羊座','金牛座','双子座','巨蟹座','狮子座','处女座',
|
||||
'天秤座','天蝎座','射手座','摩羯座','水瓶座','双鱼座'][int(gulika_lon / 30) % 12],
|
||||
'is_daytime': is_daytime,
|
||||
'note': 'Gulika代表苦难/延迟的敏感点,需检查其与凶星的联系',
|
||||
}
|
||||
|
||||
|
||||
def calc_yamaghantaka_sphuta(sun_lon: float, weekday: int,
|
||||
sunrise_jd: float, birth_jd: float) -> Dict:
|
||||
"""
|
||||
计算 Yamaghantaka Sphuta v7.0
|
||||
|
||||
Yamaghantaka = Jupiter的儿子,代表幸运/保护的敏感点。
|
||||
在白天的特定时段出现。
|
||||
|
||||
Args:
|
||||
sun_lon: 太阳经度
|
||||
weekday: 0=Sunday..6=Saturday
|
||||
sunrise_jd: 日出JD
|
||||
birth_jd: 出生JD
|
||||
|
||||
Returns:
|
||||
Yamaghantaka经度和宫位
|
||||
"""
|
||||
# Yamaghantaka在白天的Jupiter段
|
||||
# 白天分8段,Jupiter段 = 第5段
|
||||
day_duration_approx = 0.5 # 约12小时
|
||||
segment = day_duration_approx / 8.0
|
||||
yama_time = sunrise_jd + 4 * segment # 第5段
|
||||
|
||||
hours_from_sunrise = (yama_time - sunrise_jd) * 24.0
|
||||
yama_lon = (sun_lon + hours_from_sunrise * 15.0) % 360
|
||||
|
||||
return {
|
||||
'yamaghantaka_longitude': round(yama_lon, 4),
|
||||
'yamaghantaka_sign': SIGNS[int(yama_lon / 30) % 12],
|
||||
'note': 'Yamaghantaka代表保护/幸运的敏感点',
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Prashna 时机评分系统 v7.0
|
||||
# =============================================================================
|
||||
|
||||
def prashna_timing_score(planet_positions: Dict, asc_degree: float,
|
||||
question_category: str) -> Dict:
|
||||
"""
|
||||
Prashna 时机评分 v7.0
|
||||
|
||||
综合评估当前时刻是否适合回答该类问题。
|
||||
评分因素:
|
||||
1. 上升主星状态
|
||||
2. Moon状态
|
||||
3. 问题宫主星状态
|
||||
4. KP Sublord判定
|
||||
5. 凶星干扰
|
||||
|
||||
Returns:
|
||||
评分和解读
|
||||
"""
|
||||
score = 50 # 基础分
|
||||
factors = []
|
||||
|
||||
asc_idx = int(asc_degree / 30) % 12
|
||||
asc_lord = SIGN_LORDS[SIGNS[asc_idx]]
|
||||
|
||||
# 1. 上升主星状态
|
||||
al_data = planet_positions.get(asc_lord, {})
|
||||
al_house = al_data.get('house', 0)
|
||||
if al_house in [1, 4, 7, 10, 5, 9]:
|
||||
score += 15
|
||||
factors.append(f"上升主星{asc_lord}在{al_house}宫(强宫) +15")
|
||||
elif al_house in [6, 8, 12]:
|
||||
score -= 10
|
||||
factors.append(f"上升主星{asc_lord}在{al_house}宫(弱宫) -10")
|
||||
|
||||
# 2. Moon状态
|
||||
moon_data = planet_positions.get('Moon', {})
|
||||
moon_sign = moon_data.get('sign', '')
|
||||
if moon_sign in ['Taurus', 'Cancer']: # Moon入庙/本宫
|
||||
score += 10
|
||||
factors.append("Moon入庙/本宫 +10")
|
||||
elif moon_sign in ['Scorpio']: # Moon落陷
|
||||
score -= 10
|
||||
factors.append("Moon落陷 -10")
|
||||
|
||||
# 3. 问题宫主星状态
|
||||
cat = QUESTION_CATEGORIES.get(question_category, QUESTION_CATEGORIES['general'])
|
||||
q_house = cat['primary']
|
||||
q_sign = SIGNS[(asc_idx + q_house - 1) % 12]
|
||||
q_lord = SIGN_LORDS[q_sign]
|
||||
ql_data = planet_positions.get(q_lord, {})
|
||||
ql_house = ql_data.get('house', 0)
|
||||
if ql_house in [1, 4, 7, 10, 5, 9]:
|
||||
score += 10
|
||||
factors.append(f"问题宫主{q_lord}在{ql_house}宫(强宫) +10")
|
||||
elif ql_house in [6, 8, 12]:
|
||||
score -= 10
|
||||
factors.append(f"问题宫主{q_lord}在{ql_house}宫(弱宫) -10")
|
||||
|
||||
# 4. 凶星干扰检查
|
||||
for malefic in ['Saturn', 'Mars', 'Rahu']:
|
||||
m_data = planet_positions.get(malefic, {})
|
||||
m_house = m_data.get('house', 0)
|
||||
if m_house == q_house:
|
||||
score -= 10
|
||||
factors.append(f"凶星{malefic}在问题宫({q_house}宫) -10")
|
||||
|
||||
# 5. 逆行检查
|
||||
for pname, pdata in planet_positions.items():
|
||||
if isinstance(pdata, dict) and pdata.get('retrograde'):
|
||||
if pname in ['Mercury', 'Venus']:
|
||||
score -= 5
|
||||
factors.append(f"{pname}逆行 -5")
|
||||
|
||||
# 综合评级
|
||||
if score >= 75:
|
||||
rating = "极佳(高度适合进行Prashna)"
|
||||
elif score >= 60:
|
||||
rating = "良好(适合进行Prashna)"
|
||||
elif score >= 45:
|
||||
rating = "一般(可以进行,但结果需更多验证)"
|
||||
else:
|
||||
rating = "不佳(不建议此时进行重要Prashna)"
|
||||
|
||||
return {
|
||||
'score': score,
|
||||
'rating': rating,
|
||||
'factors': factors,
|
||||
'recommendation': "建议在更佳时机重新询问" if score < 45 else "可以进行Prashna分析",
|
||||
}
|
||||
|
||||
@@ -198,21 +198,23 @@ def calc_varsha_maasa_dina_hora_bala(pname: str, year: int, month: int, day: int
|
||||
maasa_lord = get_maasa_lord(solar_lon, year)
|
||||
if pname == maasa_lord:
|
||||
bala += 30.0
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[shadbala] maasa lord calculation failed: {e}")
|
||||
|
||||
# Dina Lord
|
||||
vaara_lord = get_vaara_lord(year, month, day, hour)
|
||||
if pname == vaara_lord:
|
||||
bala += 45.0
|
||||
|
||||
|
||||
# Hora Lord
|
||||
try:
|
||||
hora_lord = get_hora_lord(year, month, day, hour, lat, lon, tz)
|
||||
if pname == hora_lord:
|
||||
bala += 60.0
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.warning(f"[shadbala] hora lord calculation failed: {e}")
|
||||
|
||||
return bala
|
||||
|
||||
|
||||
+541
-157
@@ -1,203 +1,587 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Sudarshana Chakra(苏达沙那轮)模块
|
||||
基于BPHS传统三参考点盘系统
|
||||
Sudarshana Chakra(苏达沙那轮)— 三参考点复合分析
|
||||
====================================================
|
||||
基于BPHS传统技法,将星盘分别以上升、月亮、太阳为第一宫,
|
||||
生成三张参考盘并叠加分析。
|
||||
|
||||
三个参考点:
|
||||
1. Lagna (上升) → 自我、身体
|
||||
2. Chandra (月亮) → 情感、心理
|
||||
3. Surya (太阳) → 灵魂、生命力
|
||||
1. Ascendant Lagna (AL) → 自我、身体
|
||||
2. Moon Lagna (ML) → 情感、心理
|
||||
3. Sun Lagna (SL) → 灵魂、生命力
|
||||
|
||||
当三个参考点中同一宫位/行星配置一致时,事件确认度高。
|
||||
|
||||
版本: v2.0 | 2026-06-13 重构为完整分析器
|
||||
"""
|
||||
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||||
|
||||
SIGNS_CN = {
|
||||
'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'
|
||||
}
|
||||
|
||||
PLANETS_ALL = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu']
|
||||
|
||||
def _build_reference_chart(planet_positions: Dict, reference_sign_idx: int) -> Dict:
|
||||
"""
|
||||
基于指定参考点构建重新排列的星盘。
|
||||
# 吉宫: 1,2,3,4,5,7,9,10,11 凶宫: 6,8,12
|
||||
FAVORABLE_HOUSES = {1, 2, 3, 4, 5, 7, 9, 10, 11}
|
||||
UNFAVORABLE_HOUSES = {6, 8, 12}
|
||||
# 中性宫: 无(传统分法中6,8,12为dusthana,其余吉)
|
||||
|
||||
以reference_sign_idx为第1宫,重新计算所有行星的宫位。
|
||||
HOUSE_MEANINGS = {
|
||||
1: ('自我/健康', 'Self/Health'),
|
||||
2: ('财富/家庭', 'Wealth/Family'),
|
||||
3: ('勇气/兄弟姐妹', 'Courage/Siblings'),
|
||||
4: ('幸福/母亲/住所', 'Happiness/Mother/Home'),
|
||||
5: ('子女/智力/过去善业', 'Children/Intelligence/Poorvapunya'),
|
||||
6: ('疾病/敌人/债务', 'Disease/Enemies/Debt'),
|
||||
7: ('婚姻/伴侣/合作', 'Marriage/Partnership'),
|
||||
8: ('寿命/变革/隐秘', 'Longevity/Transformation'),
|
||||
9: ('幸运/导师/宗教', 'Fortune/Guru/Religion'),
|
||||
10: ('事业/地位/名声', 'Career/Status/Fame'),
|
||||
11: ('收益/愿望/朋友圈', 'Gains/Wishes/Circles'),
|
||||
12: ('损失/解脱/海外', 'Loss/Liberation/Foreign'),
|
||||
}
|
||||
|
||||
Args:
|
||||
planet_positions: {planet: {'sign_idx': int, 'degree': float}}
|
||||
reference_sign_idx: 参考星座索引(作为第1宫)
|
||||
# 宫主星飞入各宫的吉凶权重
|
||||
LORD_PLACEMENT_WEIGHTS = {
|
||||
# 自身宫位 → 强
|
||||
'own_house': 1.0,
|
||||
# 吉宫
|
||||
'favorable': 0.8,
|
||||
# 凶宫
|
||||
'unfavorable': 0.2,
|
||||
# 角宫(kendra) 1,4,7,10
|
||||
'kendra': 0.7,
|
||||
# 三方宫(trikona) 1,5,9
|
||||
'trikona': 0.9,
|
||||
}
|
||||
|
||||
Returns:
|
||||
重新排列的星盘 {planet: {'house': int, 'sign': str, ...}}
|
||||
"""
|
||||
chart = {
|
||||
'reference_sign': SIGNS[reference_sign_idx],
|
||||
'houses': {},
|
||||
'planets': {},
|
||||
}
|
||||
|
||||
# 计算每个宫位对应的星座
|
||||
for house_num in range(1, 13):
|
||||
sign_idx = (reference_sign_idx + house_num - 1) % 12
|
||||
chart['houses'][house_num] = {
|
||||
'sign': SIGNS[sign_idx],
|
||||
'rasi_lord': SIGN_LORDS[SIGNS[sign_idx]],
|
||||
class SudarshanaChakraAnalyzer:
|
||||
"""Sudarshana Chakra — 三 Lagna 叠加分析器"""
|
||||
|
||||
SIGNS = SIGNS
|
||||
SIGN_LORDS = SIGN_LORDS
|
||||
|
||||
def __init__(self):
|
||||
self.seven_planets = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 内部工具方法
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _sign_idx(sign_or_idx) -> int:
|
||||
"""将星座名或索引统一转为 0-11 索引"""
|
||||
if isinstance(sign_or_idx, int):
|
||||
return sign_or_idx % 12
|
||||
if isinstance(sign_or_idx, str) and sign_or_idx in SIGNS:
|
||||
return SIGNS.index(sign_or_idx)
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def _house_from_refs(planet_sign_idx: int, reference_sign_idx: int) -> int:
|
||||
"""计算行星在以 reference_sign_idx 为第一宫时的宫位 (1-12)"""
|
||||
return (planet_sign_idx - reference_sign_idx) % 12 + 1
|
||||
|
||||
@staticmethod
|
||||
def _sign_idx_from_lon(lon: float) -> int:
|
||||
"""从黄经获取星座索引 (0-11)"""
|
||||
return int(lon / 30) % 12
|
||||
|
||||
def _planet_sign_idx(self, planet_lons: Dict, planet: str) -> int:
|
||||
"""从 planet_lons 字典获取行星星座索引"""
|
||||
lon = planet_lons.get(planet)
|
||||
if lon is None:
|
||||
return 0
|
||||
return self._sign_idx_from_lon(lon)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心方法
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def generate_three_charts(self, planet_lons: Dict, asc_lon: float) -> Dict:
|
||||
"""
|
||||
生成三参考点盘。
|
||||
|
||||
Args:
|
||||
planet_lons: {planet_name: longitude_in_sidereal_0_360}
|
||||
asc_lon: 上升点黄经 (sidereal, 0-360)
|
||||
|
||||
Returns:
|
||||
{
|
||||
'ascendant_lagna': {planet: {'sign': str, 'sign_idx': int, 'house': int, 'degree': float}},
|
||||
'moon_lagna': {planet: {'sign': str, 'sign_idx': int, 'house': int, 'degree': float}},
|
||||
'sun_lagna': {planet: {'sign': str, 'sign_idx': int, 'house': int, 'degree': float}},
|
||||
}
|
||||
"""
|
||||
asc_idx = self._sign_idx_from_lon(asc_lon)
|
||||
moon_idx = self._planet_sign_idx(planet_lons, 'Moon')
|
||||
sun_idx = self._planet_sign_idx(planet_lons, 'Sun')
|
||||
|
||||
refs = {
|
||||
'ascendant_lagna': asc_idx,
|
||||
'moon_lagna': moon_idx,
|
||||
'sun_lagna': sun_idx,
|
||||
}
|
||||
|
||||
# 重新计算行星宫位
|
||||
for planet, data in planet_positions.items():
|
||||
sign_idx = data.get('sign_idx', data.get('sign', 0))
|
||||
if isinstance(sign_idx, str):
|
||||
sign_idx = SIGNS.index(sign_idx) if sign_idx in SIGNS else 0
|
||||
result = {}
|
||||
for chart_name, ref_idx in refs.items():
|
||||
chart = {}
|
||||
for planet, lon in planet_lons.items():
|
||||
p_sign_idx = self._sign_idx_from_lon(lon)
|
||||
house = self._house_from_refs(p_sign_idx, ref_idx)
|
||||
chart[planet] = {
|
||||
'sign': SIGNS[p_sign_idx],
|
||||
'sign_cn': SIGNS_CN[SIGNS[p_sign_idx]],
|
||||
'sign_idx': p_sign_idx,
|
||||
'house': house,
|
||||
'degree': round(lon, 4),
|
||||
'degree_in_sign': round(lon - p_sign_idx * 30, 4),
|
||||
}
|
||||
result[chart_name] = chart
|
||||
|
||||
house = (sign_idx - reference_sign_idx) % 12 + 1
|
||||
chart['planets'][planet] = {
|
||||
'sign': SIGNS[sign_idx],
|
||||
'house': house,
|
||||
'degree': data.get('degree', 0),
|
||||
return result
|
||||
|
||||
def composite_analysis(self, planet_lons: Dict, asc_lon: float) -> Dict:
|
||||
"""
|
||||
分析每个行星在三张盘中的吉凶强度。
|
||||
|
||||
对于每颗行星:
|
||||
- 统计三张盘中落入吉宫(1,2,3,4,5,7,9,10,11)的次数
|
||||
- 统计落入凶宫(6,8,12)的次数
|
||||
- 复合评分: favorable_count / 3 (0.0 ~ 1.0)
|
||||
|
||||
Returns:
|
||||
{planet: {
|
||||
'asc_house': int, 'moon_house': int, 'sun_house': int,
|
||||
'favorable_count': int (0-3),
|
||||
'unfavorable_count': int (0-3),
|
||||
'composite_score': float (0.0-1.0),
|
||||
'interpretation': str
|
||||
}}
|
||||
"""
|
||||
charts = self.generate_three_charts(planet_lons, asc_lon)
|
||||
|
||||
result = {}
|
||||
for planet in planet_lons:
|
||||
if planet not in charts['ascendant_lagna']:
|
||||
continue
|
||||
|
||||
asc_h = charts['ascendant_lagna'][planet]['house']
|
||||
moon_h = charts['moon_lagna'][planet]['house']
|
||||
sun_h = charts['sun_lagna'][planet]['house']
|
||||
|
||||
houses = [asc_h, moon_h, sun_h]
|
||||
fav = sum(1 for h in houses if h in FAVORABLE_HOUSES)
|
||||
unfav = sum(1 for h in houses if h in UNFAVORABLE_HOUSES)
|
||||
score = fav / 3.0
|
||||
|
||||
if fav == 3:
|
||||
interp = "三盘皆吉 — 该领域高度确认,力量极强"
|
||||
elif fav == 2 and unfav == 0:
|
||||
interp = "两盘吉、一中性 — 总体有利"
|
||||
elif fav == 2 and unfav == 1:
|
||||
interp = "两盘吉、一凶 — 有利但有隐患"
|
||||
elif fav == 1 and unfav == 2:
|
||||
interp = "一盘吉、两盘凶 — 矛盾信号,需结合大运判断"
|
||||
elif unfav == 3:
|
||||
interp = "三盘皆凶 — 该领域挑战极大"
|
||||
elif fav == 1 and unfav == 1:
|
||||
interp = "一吉一凶一中性 — 混合信号"
|
||||
elif unfav == 2:
|
||||
interp = "两盘凶 — 力量偏弱"
|
||||
else:
|
||||
interp = "中性 — 无明显吉凶偏向"
|
||||
|
||||
result[planet] = {
|
||||
'asc_house': asc_h,
|
||||
'moon_house': moon_h,
|
||||
'sun_house': sun_h,
|
||||
'favorable_count': fav,
|
||||
'unfavorable_count': unfav,
|
||||
'composite_score': round(score, 3),
|
||||
'interpretation': interp,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def house_analysis(self, planet_lons: Dict, asc_lon: float, house_number: int) -> Dict:
|
||||
"""
|
||||
分析指定宫位在三张盘中的情况。
|
||||
|
||||
Returns:
|
||||
{
|
||||
'house': int,
|
||||
'meaning_cn': str,
|
||||
'meaning_en': str,
|
||||
'asc_lagna': {'planets': [...], 'lord': str, 'lord_house': int},
|
||||
'moon_lagna': {'planets': [...], 'lord': str, 'lord_house': int},
|
||||
'sun_lagna': {'planets': [...], 'lord': str, 'lord_house': int},
|
||||
'composite_strength': float (0.0-1.0),
|
||||
'interpretation': str
|
||||
}
|
||||
"""
|
||||
if not 1 <= house_number <= 12:
|
||||
return {'error': f'宫位号 {house_number} 超出范围(1-12)'}
|
||||
|
||||
charts = self.generate_three_charts(planet_lons, asc_lon)
|
||||
|
||||
def _analyze_house(chart: Dict, ref_sign_idx: int) -> Dict:
|
||||
"""分析单个参考盘中某宫位"""
|
||||
# 该宫位对应的星座
|
||||
house_sign_idx = (ref_sign_idx + house_number - 1) % 12
|
||||
house_sign = SIGNS[house_sign_idx]
|
||||
lord = SIGN_LORDS[house_sign]
|
||||
|
||||
# 该宫位中的行星
|
||||
planets_in_house = []
|
||||
for pname, pdata in chart.items():
|
||||
if pdata['house'] == house_number:
|
||||
planets_in_house.append(pname)
|
||||
|
||||
# 宫主星所在宫位
|
||||
lord_sign_idx = self._planet_sign_idx(planet_lons, lord) if lord in planet_lons else None
|
||||
lord_house = self._house_from_refs(lord_sign_idx, ref_sign_idx) if lord_sign_idx is not None else None
|
||||
|
||||
return {
|
||||
'sign': house_sign,
|
||||
'sign_cn': SIGNS_CN[house_sign],
|
||||
'lord': lord,
|
||||
'lord_house': lord_house,
|
||||
'planets': planets_in_house,
|
||||
}
|
||||
|
||||
asc_idx = self._sign_idx_from_lon(asc_lon)
|
||||
moon_idx = self._planet_sign_idx(planet_lons, 'Moon')
|
||||
sun_idx = self._planet_sign_idx(planet_lons, 'Sun')
|
||||
|
||||
al = _analyze_house(charts['ascendant_lagna'], asc_idx)
|
||||
ml = _analyze_house(charts['moon_lagna'], moon_idx)
|
||||
sl = _analyze_house(charts['sun_lagna'], sun_idx)
|
||||
|
||||
# 复合强度评分
|
||||
strength_components = []
|
||||
|
||||
for ref_data in [al, ml, sl]:
|
||||
s = 0.0
|
||||
# 宫内有行星加分(尤其吉星)
|
||||
for p in ref_data['planets']:
|
||||
if p in ('Jupiter', 'Venus', 'Moon', 'Mercury'):
|
||||
s += 0.2
|
||||
elif p in ('Saturn', 'Mars', 'Rahu', 'Ketu'):
|
||||
s += 0.05
|
||||
else:
|
||||
s += 0.1
|
||||
# 宫主星落入吉宫加分
|
||||
lh = ref_data.get('lord_house')
|
||||
if lh:
|
||||
if lh in FAVORABLE_HOUSES:
|
||||
s += 0.3
|
||||
elif lh in UNFAVORABLE_HOUSES:
|
||||
s += 0.05
|
||||
else:
|
||||
s += 0.15
|
||||
strength_components.append(min(s, 1.0))
|
||||
|
||||
composite_strength = round(sum(strength_components) / 3.0, 3)
|
||||
|
||||
# 解读
|
||||
if composite_strength >= 0.7:
|
||||
interp = f"第{house_number}宫整体强势,三盘均支持该领域发展"
|
||||
elif composite_strength >= 0.4:
|
||||
interp = f"第{house_number}宫中等强度,部分参考盘有利,部分偏弱"
|
||||
else:
|
||||
interp = f"第{house_number}宫整体偏弱,该领域需更多努力与补救"
|
||||
|
||||
meaning_cn, meaning_en = HOUSE_MEANINGS.get(house_number, ('未知', 'Unknown'))
|
||||
|
||||
return {
|
||||
'house': house_number,
|
||||
'meaning_cn': meaning_cn,
|
||||
'meaning_en': meaning_en,
|
||||
'asc_lagna': al,
|
||||
'moon_lagna': ml,
|
||||
'sun_lagna': sl,
|
||||
'composite_strength': composite_strength,
|
||||
'interpretation': interp,
|
||||
}
|
||||
|
||||
return chart
|
||||
def life_area_analysis(self, planet_lons: Dict, asc_lon: float) -> Dict:
|
||||
"""
|
||||
12个生活领域的综合分析。
|
||||
|
||||
Returns:
|
||||
{area_name: {house: int, meaning_cn: str, composite_strength: float, details: dict, verdict: str}}
|
||||
"""
|
||||
areas = {}
|
||||
for h in range(1, 13):
|
||||
ha = self.house_analysis(planet_lons, asc_lon, h)
|
||||
meaning_cn = ha.get('meaning_cn', f'第{h}宫')
|
||||
|
||||
strength = ha['composite_strength']
|
||||
if strength >= 0.7:
|
||||
verdict = '强'
|
||||
elif strength >= 0.5:
|
||||
verdict = '中上'
|
||||
elif strength >= 0.35:
|
||||
verdict = '中'
|
||||
elif strength >= 0.2:
|
||||
verdict = '偏弱'
|
||||
else:
|
||||
verdict = '弱'
|
||||
|
||||
areas[meaning_cn] = {
|
||||
'house': h,
|
||||
'meaning_cn': meaning_cn,
|
||||
'meaning_en': ha.get('meaning_en', ''),
|
||||
'composite_strength': strength,
|
||||
'asc_lagna': ha['asc_lagna'],
|
||||
'moon_lagna': ha['moon_lagna'],
|
||||
'sun_lagna': ha['sun_lagna'],
|
||||
'verdict': verdict,
|
||||
}
|
||||
|
||||
return areas
|
||||
|
||||
def generate_report(self, planet_lons: Dict, asc_lon: float, format: str = 'text') -> str:
|
||||
"""
|
||||
生成人类可读的 Sudarshana Chakra 报告。
|
||||
|
||||
Args:
|
||||
planet_lons: {planet: sidereal_longitude}
|
||||
asc_lon: sidereal ascendant longitude
|
||||
format: 'text' 或 'json'
|
||||
"""
|
||||
charts = self.generate_three_charts(planet_lons, asc_lon)
|
||||
composite = self.composite_analysis(planet_lons, asc_lon)
|
||||
areas = self.life_area_analysis(planet_lons, asc_lon)
|
||||
|
||||
if format == 'json':
|
||||
import json
|
||||
return json.dumps({
|
||||
'charts': charts,
|
||||
'composite_analysis': composite,
|
||||
'life_areas': areas,
|
||||
}, ensure_ascii=False, indent=2, default=str)
|
||||
|
||||
# 文本报告
|
||||
asc_idx = self._sign_idx_from_lon(asc_lon)
|
||||
moon_idx = self._planet_sign_idx(planet_lons, 'Moon')
|
||||
sun_idx = self._planet_sign_idx(planet_lons, 'Sun')
|
||||
|
||||
lines = []
|
||||
lines.append("=" * 60)
|
||||
lines.append("Sudarshana Chakra 三参考点盘分析")
|
||||
lines.append("=" * 60)
|
||||
lines.append("")
|
||||
lines.append(f"参考点:")
|
||||
lines.append(f" 上升 Lagna: {SIGNS[asc_idx]}({SIGNS_CN[SIGNS[asc_idx]]}) — 自我/身体")
|
||||
lines.append(f" 月亮 Lagna: {SIGNS[moon_idx]}({SIGNS_CN[SIGNS[moon_idx]]}) — 情感/心理")
|
||||
lines.append(f" 太阳 Lagna: {SIGNS[sun_idx]}({SIGNS_CN[SIGNS[sun_idx]]}) — 灵魂/生命力")
|
||||
lines.append("")
|
||||
|
||||
# 三盘宫位一览
|
||||
lines.append("-" * 60)
|
||||
lines.append("行星在三盘中的宫位分布:")
|
||||
lines.append(f"{'行星':10s} {'上升盘':>6s} {'月亮盘':>6s} {'太阳盘':>6s} {'吉宫数':>6s} {'评分':>6s} {'解读'}")
|
||||
lines.append("-" * 60)
|
||||
for planet in planet_lons:
|
||||
if planet not in composite:
|
||||
continue
|
||||
c = composite[planet]
|
||||
lines.append(
|
||||
f"{planet:10s} {c['asc_house']:>6d} {c['moon_house']:>6d} {c['sun_house']:>6d} "
|
||||
f"{c['favorable_count']:>6d} {c['composite_score']:>6.2f} {c['interpretation']}"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
# 12宫综合分析
|
||||
lines.append("-" * 60)
|
||||
lines.append("12宫生活领域综合评估:")
|
||||
lines.append(f"{'宫位':>4s} {'领域':18s} {'强度':>6s} {'判定':>6s} {'上升盘主星':>10s} {'月亮盘主星':>10s} {'太阳盘主星':>10s}")
|
||||
lines.append("-" * 60)
|
||||
for area_name, data in areas.items():
|
||||
h = data['house']
|
||||
al_lord = data['asc_lagna']['lord'] + f"(H{data['asc_lagna']['lord_house'] or '?'})"
|
||||
ml_lord = data['moon_lagna']['lord'] + f"(H{data['moon_lagna']['lord_house'] or '?'})"
|
||||
sl_lord = data['sun_lagna']['lord'] + f"(H{data['sun_lagna']['lord_house'] or '?'})"
|
||||
lines.append(
|
||||
f"{h:>4d} {area_name:18s} {data['composite_strength']:>6.2f} {data['verdict']:>6s} "
|
||||
f"{al_lord:>10s} {ml_lord:>10s} {sl_lord:>10s}"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
# 收敛性分析
|
||||
lines.append("-" * 60)
|
||||
lines.append("三盘收敛性分析 (行星在至少两盘中落入同一宫位):")
|
||||
lines.append("-" * 60)
|
||||
convergences = self._find_convergences_text(charts)
|
||||
if convergences:
|
||||
for c in convergences:
|
||||
lines.append(f" {c}")
|
||||
else:
|
||||
lines.append(" 无显著收敛")
|
||||
lines.append("")
|
||||
|
||||
# 总体评估
|
||||
scores = [c['composite_score'] for c in composite.values() if isinstance(c.get('composite_score'), (int, float))]
|
||||
avg_score = sum(scores) / len(scores) if scores else 0
|
||||
strong = sum(1 for s in scores if s >= 0.67)
|
||||
weak = sum(1 for s in scores if s <= 0.33)
|
||||
|
||||
lines.append("=" * 60)
|
||||
lines.append("总体评估:")
|
||||
lines.append(f" 平均复合评分: {avg_score:.2f}")
|
||||
lines.append(f" 强势行星数(≥0.67): {strong}")
|
||||
lines.append(f" 弱势行星数(≤0.33): {weak}")
|
||||
if avg_score >= 0.6:
|
||||
lines.append(" 总体判断: 盘面偏强,多数领域有支撑")
|
||||
elif avg_score >= 0.4:
|
||||
lines.append(" 总体判断: 盘面中等,需结合大运看时机")
|
||||
else:
|
||||
lines.append(" 总体判断: 盘面偏弱,需补救措施加强")
|
||||
lines.append("=" * 60)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _find_convergences_text(self, charts: Dict) -> List[str]:
|
||||
"""查找三盘收敛性,返回文本列表"""
|
||||
results = []
|
||||
for planet in self.seven_planets:
|
||||
if planet not in charts.get('ascendant_lagna', {}):
|
||||
continue
|
||||
al_h = charts['ascendant_lagna'][planet]['house']
|
||||
ml_h = charts['moon_lagna'][planet]['house']
|
||||
sl_h = charts['sun_lagna'][planet]['house']
|
||||
|
||||
if al_h == ml_h == sl_h:
|
||||
results.append(f"{planet}: 三盘同宫(H{al_h}) ★★★ 强收敛")
|
||||
elif al_h == ml_h:
|
||||
results.append(f"{planet}: 上升盘=月亮盘(H{al_h}) ★★ 中收敛")
|
||||
elif al_h == sl_h:
|
||||
results.append(f"{planet}: 上升盘=太阳盘(H{al_h}) ★★ 中收敛")
|
||||
elif ml_h == sl_h:
|
||||
results.append(f"{planet}: 月亮盘=太阳盘(H{ml_h}) ★★ 中收敛")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _find_convergences(lagna_chart: Dict, chandra_chart: Dict, surya_chart: Dict) -> Dict:
|
||||
# ============================================================================
|
||||
# 便捷函数 — 供 jyotish_engine.py 调用
|
||||
# ============================================================================
|
||||
|
||||
def calc_sudarshana_chakra(planet_lons: Dict, asc_lon: float,
|
||||
house: int = None) -> Dict:
|
||||
"""
|
||||
寻找三个参考点盘中的一致性(Convergence)。
|
||||
|
||||
当同一宫位在至少两个参考点中有重要配置时,标记为收敛点。
|
||||
|
||||
Returns:
|
||||
收敛分析结果
|
||||
"""
|
||||
convergences = []
|
||||
SEVEN_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
|
||||
|
||||
for house_num in range(1, 13):
|
||||
lagna_planets = [p for p in SEVEN_PLANETS
|
||||
if p in lagna_chart.get('planets', {})
|
||||
and lagna_chart['planets'][p].get('house') == house_num]
|
||||
chandra_planets = [p for p in SEVEN_PLANETS
|
||||
if p in chandra_chart.get('planets', {})
|
||||
and chandra_chart['planets'][p].get('house') == house_num]
|
||||
surya_planets = [p for p in SEVEN_PLANETS
|
||||
if p in surya_chart.get('planets', {})
|
||||
and surya_chart['planets'][p].get('house') == house_num]
|
||||
|
||||
# 寻找至少两个参考点中共有的行星
|
||||
all_in_house = set(lagna_planets + chandra_planets + surya_planets)
|
||||
for planet in all_in_house:
|
||||
count = (1 if planet in lagna_planets else 0) + \
|
||||
(1 if planet in chandra_planets else 0) + \
|
||||
(1 if planet in surya_planets else 0)
|
||||
if count >= 2:
|
||||
convergences.append({
|
||||
'house': house_num,
|
||||
'planet': planet,
|
||||
'references': count,
|
||||
'significance': 'high' if count == 3 else 'medium',
|
||||
})
|
||||
|
||||
# 去重:同宫位多行星收敛
|
||||
house_convergences = {}
|
||||
for c in convergences:
|
||||
h = c['house']
|
||||
if h not in house_convergences:
|
||||
house_convergences[h] = []
|
||||
house_convergences[h].append(c)
|
||||
|
||||
return {
|
||||
'total_convergences': len(convergences),
|
||||
'high_confidence': [c for c in convergences if c['significance'] == 'high'],
|
||||
'house_analysis': house_convergences,
|
||||
}
|
||||
|
||||
|
||||
def calc_sudarshana_chakra(planet_positions: Dict,
|
||||
asc_sign: str = None,
|
||||
asc_sign_idx: int = None,
|
||||
moon_degree: float = None,
|
||||
sun_degree: float = None) -> Dict:
|
||||
"""
|
||||
计算Sudarshana Chakra(三参考点盘)。
|
||||
计算完整的 Sudarshana Chakra 分析。
|
||||
|
||||
Args:
|
||||
planet_positions: {planet: {'sign': str 或 'sign_idx': int, 'degree': float}}
|
||||
asc_sign: 上升星座名称(优先)
|
||||
asc_sign_idx: 上升星座索引
|
||||
moon_degree: 月亮黄道经度(0-360,用于确定月亮星座)
|
||||
sun_degree: 太阳黄道经度(0-360,用于确定太阳星座)
|
||||
planet_lons: {planet_name: sidereal_longitude_0_360}
|
||||
asc_lon: 上升点黄经 (sidereal, 0-360)
|
||||
house: 可选,指定分析某宫位 (1-12)
|
||||
|
||||
Returns:
|
||||
完整的Sudarshana Chakra分析
|
||||
完整分析结果字典
|
||||
"""
|
||||
# 确定三个参考点星座索引
|
||||
if asc_sign and asc_sign in SIGNS:
|
||||
lagna_ref = SIGNS.index(asc_sign)
|
||||
elif asc_sign_idx is not None:
|
||||
lagna_ref = asc_sign_idx % 12
|
||||
else:
|
||||
lagna_ref = 0
|
||||
analyzer = SudarshanaChakraAnalyzer()
|
||||
|
||||
if moon_degree is not None:
|
||||
chandra_ref = int(moon_degree / 30) % 12
|
||||
else:
|
||||
# 尝试从行星位置中获取
|
||||
moon_data = planet_positions.get('Moon', {})
|
||||
chandra_ref = moon_data.get('sign_idx', 0)
|
||||
if isinstance(chandra_ref, str):
|
||||
chandra_ref = SIGNS.index(chandra_ref) if chandra_ref in SIGNS else 3
|
||||
charts = analyzer.generate_three_charts(planet_lons, asc_lon)
|
||||
composite = analyzer.composite_analysis(planet_lons, asc_lon)
|
||||
areas = analyzer.life_area_analysis(planet_lons, asc_lon)
|
||||
|
||||
if sun_degree is not None:
|
||||
surya_ref = int(sun_degree / 30) % 12
|
||||
else:
|
||||
sun_data = planet_positions.get('Sun', {})
|
||||
surya_ref = sun_data.get('sign_idx', 0)
|
||||
if isinstance(surya_ref, str):
|
||||
surya_ref = SIGNS.index(surya_ref) if surya_ref in SIGNS else 4
|
||||
|
||||
# 构建三个参考点盘
|
||||
lagna_chart = _build_reference_chart(planet_positions, lagna_ref)
|
||||
chandra_chart = _build_reference_chart(planet_positions, chandra_ref)
|
||||
surya_chart = _build_reference_chart(planet_positions, surya_ref)
|
||||
|
||||
# 寻找收敛
|
||||
convergence = _find_convergences(lagna_chart, chandra_chart, surya_chart)
|
||||
|
||||
return {
|
||||
result = {
|
||||
'method': 'Sudarshana Chakra 三参考点盘 (BPHS标准)',
|
||||
'version': '1.0',
|
||||
'references': {
|
||||
'lagna': {'sign': SIGNS[lagna_ref], 'role': '自我/身体'},
|
||||
'chandra': {'sign': SIGNS[chandra_ref], 'role': '情感/心理'},
|
||||
'surya': {'sign': SIGNS[surya_ref], 'role': '灵魂/生命力'},
|
||||
'version': '2.0',
|
||||
'reference_points': {
|
||||
'ascendant_lagna': {
|
||||
'sign': SIGNS[analyzer._sign_idx_from_lon(asc_lon)],
|
||||
'sign_cn': SIGNS_CN[SIGNS[analyzer._sign_idx_from_lon(asc_lon)]],
|
||||
'role': '自我/身体',
|
||||
},
|
||||
'moon_lagna': {
|
||||
'sign': SIGNS[analyzer._planet_sign_idx(planet_lons, 'Moon')],
|
||||
'sign_cn': SIGNS_CN[SIGNS[analyzer._planet_sign_idx(planet_lons, 'Moon')]],
|
||||
'role': '情感/心理',
|
||||
},
|
||||
'sun_lagna': {
|
||||
'sign': SIGNS[analyzer._planet_sign_idx(planet_lons, 'Sun')],
|
||||
'sign_cn': SIGNS_CN[SIGNS[analyzer._planet_sign_idx(planet_lons, 'Sun')]],
|
||||
'role': '灵魂/生命力',
|
||||
},
|
||||
},
|
||||
'charts': {
|
||||
'lagna_based': lagna_chart,
|
||||
'chandra_based': chandra_chart,
|
||||
'surya_based': surya_chart,
|
||||
},
|
||||
'convergence': convergence,
|
||||
'assessment': _assess_chakra(convergence),
|
||||
'three_charts': charts,
|
||||
'composite_analysis': composite,
|
||||
'life_area_analysis': areas,
|
||||
}
|
||||
|
||||
if house is not None:
|
||||
result['specific_house'] = analyzer.house_analysis(planet_lons, asc_lon, house)
|
||||
|
||||
def _assess_chakra(convergence: Dict) -> str:
|
||||
"""评估Sudarshana Chakra的总体结构"""
|
||||
high = len(convergence.get('high_confidence', []))
|
||||
total = convergence.get('total_convergences', 0)
|
||||
# 收敛性
|
||||
convergences = []
|
||||
for planet in analyzer.seven_planets:
|
||||
if planet not in charts.get('ascendant_lagna', {}):
|
||||
continue
|
||||
al_h = charts['ascendant_lagna'][planet]['house']
|
||||
ml_h = charts['moon_lagna'][planet]['house']
|
||||
sl_h = charts['sun_lagna'][planet]['house']
|
||||
if al_h == ml_h == sl_h:
|
||||
convergences.append({
|
||||
'planet': planet, 'house': al_h,
|
||||
'level': 'triple', 'significance': 'high',
|
||||
})
|
||||
elif al_h == ml_h or al_h == sl_h or ml_h == sl_h:
|
||||
match_h = al_h if al_h == ml_h or al_h == sl_h else ml_h
|
||||
convergences.append({
|
||||
'planet': planet, 'house': match_h,
|
||||
'level': 'double', 'significance': 'medium',
|
||||
})
|
||||
|
||||
if high >= 3:
|
||||
return '强烈收敛 — 三个参考点高度一致,事件确认度极高'
|
||||
elif high >= 1 or total >= 5:
|
||||
return '中等收敛 — 部分领域一致性较强'
|
||||
elif total >= 1:
|
||||
return '弱收敛 — 少数领域有一致性'
|
||||
result['convergence'] = {
|
||||
'items': convergences,
|
||||
'high_confidence': [c for c in convergences if c['significance'] == 'high'],
|
||||
'medium_confidence': [c for c in convergences if c['significance'] == 'medium'],
|
||||
}
|
||||
|
||||
# 总体评估
|
||||
scores = [c['composite_score'] for c in composite.values()]
|
||||
avg = sum(scores) / len(scores) if scores else 0
|
||||
strong = sum(1 for s in scores if s >= 0.67)
|
||||
weak = sum(1 for s in scores if s <= 0.33)
|
||||
|
||||
if avg >= 0.6:
|
||||
overall = '盘面偏强,多数领域有支撑'
|
||||
elif avg >= 0.4:
|
||||
overall = '盘面中等,需结合大运看时机'
|
||||
else:
|
||||
return '无收敛 — 三个参考点分散,需从多角度分别分析'
|
||||
overall = '盘面偏弱,需补救措施加强'
|
||||
|
||||
result['overall_assessment'] = {
|
||||
'average_score': round(avg, 3),
|
||||
'strong_planets': strong,
|
||||
'weak_planets': weak,
|
||||
'judgment': overall,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_sudarshana_report(planet_lons: Dict, asc_lon: float) -> str:
|
||||
"""生成文本报告"""
|
||||
analyzer = SudarshanaChakraAnalyzer()
|
||||
return analyzer.generate_report(planet_lons, asc_lon, format='text')
|
||||
|
||||
+343
-89
@@ -765,29 +765,51 @@ SIGNS_CN = {
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tajika Yogas 完整检测(P1.4)
|
||||
# 基于PyJHora tajika/yogas.py 算法翻译
|
||||
# 10种年度Yoga + Vedha阻碍逻辑
|
||||
# Tajika Yogas 完整检测(v7.0 complete)
|
||||
# 基于 BPHS Tajika + PyJHora tajika/yogas.py 算法翻译
|
||||
# 10种年度Yoga + 完整Vedha阻碍逻辑 + Tajika相位规则
|
||||
# =============================================================================
|
||||
|
||||
# Tajika相位表(不同于Parashara!Tajika使用西方式7种相位)
|
||||
# 行=相位类型,列=度数范围
|
||||
TAJIKA_ASPECT_DEGREES = {0, 30, 60, 90, 120, 150, 180}
|
||||
|
||||
# Tajika 容许度(orb)—— 各行星的标准容许度
|
||||
TAJIKA_ORBS = {
|
||||
'Sun': 15, 'Moon': 12, 'Mars': 8, 'Mercury': 7,
|
||||
'Jupiter': 9, 'Venus': 7, 'Saturn': 9,
|
||||
}
|
||||
|
||||
# Vedha 阻碍点表(Tajika经典)
|
||||
# 每对行星间有固定的Vedha敏感度数位置
|
||||
# 格式: (planet1_deg, planet2_deg) → 如果第三方行星在这个度数,则形成Vedha
|
||||
VEDHA_TABLE = {
|
||||
# 从Ithasala点出发的度数偏移
|
||||
1: 7, 2: 5, 3: 9, 4: 3, 5: 8, 6: 2, 7: 10, 8: 4, 9: 6, 10: 1,
|
||||
11: 9, 12: 3, 13: 7, 14: 5, 15: 2, 16: 8, 17: 4, 18: 6, 19: 1, 20: 10,
|
||||
21: 3, 22: 9, 23: 5, 24: 7, 25: 2, 26: 8, 27: 4, 28: 6, 29: 1, 30: 10,
|
||||
}
|
||||
|
||||
|
||||
def detect_tajika_yogas(varsha_planets: Dict, year_lord: str = None) -> List[Dict]:
|
||||
"""
|
||||
检测Tajika Yogas(年度Yoga)。
|
||||
检测Tajika Yogas(年度Yoga)—— 完整版 v7.0。
|
||||
|
||||
10种Yoga分类:
|
||||
1. Itasala — 友好相位瑜伽
|
||||
2. Ishkavala — 单向相位瑜伽
|
||||
3. Vasala — 无效相位瑜伽
|
||||
4. Tambira — 阻碍瑜伽
|
||||
5. Kambira — 双重阻碍瑜伽
|
||||
6. Dakshina — 右向瑜伽
|
||||
7. Vama — 左向瑜伽
|
||||
8. Ubhaya — 双向瑜伽
|
||||
9. Vedha — 穿刺阻碍
|
||||
10. Kuta — 组合瑜伽
|
||||
1. Itasala (Ithasala) — 连接瑜伽(快追慢,orb≤行星容许度)
|
||||
2. Ishkavala — 单向相位瑜伽(一星与多星形成Ithasala)
|
||||
3. Vasala — 无效相位瑜伽(落陷星形成Ithasala)
|
||||
4. Tambira — 阻碍瑜伽(凶星在Ithasala之间)
|
||||
5. Kambira — 双重阻碍瑜伽(两凶星同时阻碍)
|
||||
6. Dakshina — 右向瑜伽(快星在慢星右侧)
|
||||
7. Vama — 左向瑜伽(快星在慢星左侧)
|
||||
8. Ubhaya — 双向瑜伽(两对Ithasala互相支持)
|
||||
9. Vedha — 穿刺阻碍(第三方在Vedha敏感点)
|
||||
10. Kuta — 组合瑜伽(三行星聚集同星座)
|
||||
|
||||
Args:
|
||||
varsha_planets: 年运盘行星位置 {planet: {'sign':str, 'degree':float, ...}}
|
||||
或 {planet: longitude_float}
|
||||
year_lord: 年度主星
|
||||
|
||||
Returns:
|
||||
@@ -798,19 +820,54 @@ def detect_tajika_yogas(varsha_planets: Dict, year_lord: str = None) -> List[Dic
|
||||
MALEFICS = {'Saturn', 'Mars', 'Sun', 'Rahu', 'Ketu'}
|
||||
BENEFICS = {'Jupiter', 'Venus', 'Mercury', 'Moon'}
|
||||
|
||||
# 落陷星座表
|
||||
DEBILITATION = {
|
||||
'Sun': 'Libra', 'Moon': 'Scorpio', 'Mars': 'Cancer',
|
||||
'Mercury': 'Pisces', 'Jupiter': 'Capricorn',
|
||||
'Venus': 'Virgo', 'Saturn': 'Aries',
|
||||
}
|
||||
|
||||
def _get_longitude(pname):
|
||||
pd = varsha_planets.get(pname, {})
|
||||
if isinstance(pd, (int, float)):
|
||||
return float(pd)
|
||||
sign = pd.get('sign', '')
|
||||
deg = pd.get('degree', 0) % 30
|
||||
if sign in SIGNS:
|
||||
return SIGNS.index(sign) * 30 + deg
|
||||
return 0
|
||||
return pd.get('longitude', pd.get('lon', 0))
|
||||
|
||||
def _get_sign(pname):
|
||||
lon = _get_longitude(pname)
|
||||
return int(lon / 30) % 12
|
||||
|
||||
def _degree_in_sign(pname):
|
||||
lon = _get_longitude(pname)
|
||||
return lon % 30
|
||||
|
||||
def _is_debilitated(pname):
|
||||
sign_idx = _get_sign(pname)
|
||||
sign_name = SIGNS[sign_idx]
|
||||
return DEBILITATION.get(pname) == sign_name
|
||||
|
||||
def _is_faster(p1, p2):
|
||||
speeds = {
|
||||
'Moon': 13.176, 'Mercury': 4.092, 'Venus': 1.602,
|
||||
'Sun': 0.986, 'Mars': 0.524, 'Jupiter': 0.083, 'Saturn': 0.034,
|
||||
}
|
||||
return speeds.get(p1, 0) > speeds.get(p2, 0)
|
||||
|
||||
def _orb_between(p1, p2):
|
||||
d = abs(_get_longitude(p1) - _get_longitude(p2))
|
||||
return min(d, 360 - d)
|
||||
|
||||
# 遍历所有行星对
|
||||
def _effective_orb(p1, p2):
|
||||
"""计算两星间的有效容许度(取较小者)"""
|
||||
return min(TAJIKA_ORBS.get(p1, 7), TAJIKA_ORBS.get(p2, 7))
|
||||
|
||||
# ── 1. Ithasala Yoga(连接瑜伽)完整版 ──
|
||||
# 条件:快星追赶慢星(applying),orb ≤ 有效容许度
|
||||
ithasala_pairs = []
|
||||
checked = set()
|
||||
for p1 in SEVEN:
|
||||
for p2 in SEVEN:
|
||||
@@ -820,96 +877,293 @@ def detect_tajika_yogas(varsha_planets: Dict, year_lord: str = None) -> List[Dic
|
||||
continue
|
||||
checked.add((p1, p2))
|
||||
|
||||
l1 = _get_longitude(p1)
|
||||
l2 = _get_longitude(p2)
|
||||
orb = _orb_between(p1, p2)
|
||||
p1_long = _get_longitude(p1)
|
||||
p2_long = _get_longitude(p2)
|
||||
eff_orb = _effective_orb(p1, p2)
|
||||
|
||||
# 检查Vedha(穿刺阻碍)— 第三方行星在两星之间
|
||||
vedha_planet = None
|
||||
for p3 in SEVEN:
|
||||
if p3 in (p1, p2):
|
||||
continue
|
||||
p3l = _get_longitude(p3)
|
||||
if min(p1_long, p2_long) < p3l < max(p1_long, p2_long):
|
||||
if p3 in MALEFICS:
|
||||
vedha_planet = p3
|
||||
break
|
||||
if orb > eff_orb:
|
||||
continue
|
||||
|
||||
# 分类判定
|
||||
if orb <= 1.0:
|
||||
# 紧密合相 — Kuta(组合)
|
||||
yogas.append({
|
||||
'type': 'Kuta',
|
||||
'planets': [p1, p2],
|
||||
'description': f'{p1}和{p2}紧密合相(orb={orb:.1f}°),形成Kuta组合Yoga',
|
||||
fast = p1 if _is_faster(p1, p2) else p2
|
||||
slow = p2 if fast == p1 else p1
|
||||
fast_lon = _get_longitude(fast)
|
||||
slow_lon = _get_longitude(slow)
|
||||
|
||||
# 判断是否applying(快追慢)
|
||||
# 快星度数 < 慢星度数(同一方向)= applying
|
||||
applying = (fast_lon % 30) < (slow_lon % 30)
|
||||
|
||||
if applying or orb <= 3.0: # 3°内视为紧密连接
|
||||
ithasala_pairs.append({
|
||||
'fast': fast, 'slow': slow, 'orb': orb,
|
||||
'fast_lon': fast_lon, 'slow_lon': slow_lon,
|
||||
})
|
||||
elif orb <= 5.0:
|
||||
if vedha_planet:
|
||||
yogas.append({
|
||||
'type': 'Vedha',
|
||||
'planets': [p1, p2, vedha_planet],
|
||||
'description': f'{p1}-{p2}之间有{vedha_planet}穿刺阻碍,形成Vedha Yoga',
|
||||
})
|
||||
elif p1 in BENEFICS or p2 in BENEFICS:
|
||||
# 双吉星 — Itasala或Ishkavala
|
||||
if p1 in BENEFICS and p2 in BENEFICS:
|
||||
yogas.append({
|
||||
'type': 'Itasala',
|
||||
'planets': [p1, p2],
|
||||
'description': f'{p1}和{p2}互相友好相位,形成Itasala Yoga',
|
||||
})
|
||||
else:
|
||||
yogas.append({
|
||||
'type': 'Ishkavala',
|
||||
'planets': [p1, p2],
|
||||
'description': f'{p1}和{p2}单向相位,形成Ishkavala Yoga',
|
||||
})
|
||||
elif p1 in MALEFICS and p2 in MALEFICS:
|
||||
|
||||
for pair in ithasala_pairs:
|
||||
yogas.append({
|
||||
'type': 'Itasala',
|
||||
'planets': [pair['fast'], pair['slow']],
|
||||
'orb': round(pair['orb'], 2),
|
||||
'direction': 'applying',
|
||||
'description': f"{pair['fast']}(快)追{pair['slow']}(慢),orb={pair['orb']:.1f}°,形成Itasala连接瑜伽",
|
||||
})
|
||||
|
||||
# ── 2. Ishkavala Yoga(单向相位瑜伽)──
|
||||
# 条件:一星与多星形成Ithasala,且该星不在其他Ithasala中作为慢星
|
||||
planet_ithasala_count = {}
|
||||
for pair in ithasala_pairs:
|
||||
for p in [pair['fast'], pair['slow']]:
|
||||
planet_ithasala_count[p] = planet_ithasala_count.get(p, 0) + 1
|
||||
|
||||
for p, count in planet_ithasala_count.items():
|
||||
if count >= 2:
|
||||
partners = []
|
||||
for pair in ithasala_pairs:
|
||||
if p in (pair['fast'], pair['slow']):
|
||||
partner = pair['slow'] if p == pair['fast'] else pair['fast']
|
||||
partners.append(partner)
|
||||
yogas.append({
|
||||
'type': 'Ishkavala',
|
||||
'planets': [p] + partners,
|
||||
'description': f'{p}与{", ".join(partners)}形成多个Ithasala,Ishkavala单向相位瑜伽',
|
||||
})
|
||||
|
||||
# ── 3. Vasala Yoga(无效相位瑜伽)──
|
||||
# 条件:落陷星形成Ithasala
|
||||
for pair in ithasala_pairs:
|
||||
for p in [pair['fast'], pair['slow']]:
|
||||
if _is_debilitated(p):
|
||||
yogas.append({
|
||||
'type': 'Vasala',
|
||||
'planets': [pair['fast'], pair['slow']],
|
||||
'description': f'{p}落陷状态下与{pair["slow"] if p == pair["fast"] else pair["fast"]}形成Ithasala,Vasala无效相位瑜伽',
|
||||
})
|
||||
|
||||
# ── 4. Tambira Yoga(阻碍瑜伽)──
|
||||
# 条件:凶星在Ithasala两星之间(度数上)
|
||||
for pair in ithasala_pairs:
|
||||
l1, l2 = pair['fast_lon'], pair['slow_lon']
|
||||
for p3 in SEVEN:
|
||||
if p3 in (pair['fast'], pair['slow']):
|
||||
continue
|
||||
if p3 not in MALEFICS:
|
||||
continue
|
||||
l3 = _get_longitude(p3)
|
||||
# 检查p3是否在l1和l2之间
|
||||
lo, hi = min(l1, l2), max(l1, l2)
|
||||
if hi - lo > 180:
|
||||
# 跨越0°的情况
|
||||
if l3 > hi or l3 < lo:
|
||||
yogas.append({
|
||||
'type': 'Tambira',
|
||||
'planets': [p1, p2],
|
||||
'description': f'{p1}和{p2}双凶星阻碍,形成Tambira Yoga',
|
||||
'planets': [pair['fast'], pair['slow'], p3],
|
||||
'description': f'凶星{p3}在{pair["fast"]}-{pair["slow"]}之间阻碍,Tambira阻碍瑜伽',
|
||||
})
|
||||
else:
|
||||
yogas.append({
|
||||
'type': 'Vasala',
|
||||
'planets': [p1, p2],
|
||||
'description': f'{p1}和{p2}无效相位,形成Vasala Yoga',
|
||||
})
|
||||
|
||||
# 左/右向判定
|
||||
for y in yogas:
|
||||
if len(y['planets']) >= 2:
|
||||
p1, p2 = y['planets'][0], y['planets'][1]
|
||||
l1, l2 = _get_longitude(p1), _get_longitude(p2)
|
||||
if l2 > l1:
|
||||
y['direction'] = 'Dakshina(右向)'
|
||||
break
|
||||
else:
|
||||
y['direction'] = 'Vama(左向)'
|
||||
if lo < l3 < hi:
|
||||
yogas.append({
|
||||
'type': 'Tambira',
|
||||
'planets': [pair['fast'], pair['slow'], p3],
|
||||
'description': f'凶星{p3}在{pair["fast"]}-{pair["slow"]}之间阻碍,Tambira阻碍瑜伽',
|
||||
})
|
||||
break
|
||||
|
||||
# ── 5. Kambira Yoga(双重阻碍瑜伽)──
|
||||
# 条件:两个凶星同时阻碍同一对Ithasala
|
||||
for pair in ithasala_pairs:
|
||||
l1, l2 = pair['fast_lon'], pair['slow_lon']
|
||||
blockers = []
|
||||
for p3 in SEVEN:
|
||||
if p3 in (pair['fast'], pair['slow']) or p3 not in MALEFICS:
|
||||
continue
|
||||
l3 = _get_longitude(p3)
|
||||
lo, hi = min(l1, l2), max(l1, l2)
|
||||
between = False
|
||||
if hi - lo > 180:
|
||||
between = (l3 > hi or l3 < lo)
|
||||
else:
|
||||
between = (lo < l3 < hi)
|
||||
if between:
|
||||
blockers.append(p3)
|
||||
if len(blockers) >= 2:
|
||||
yogas.append({
|
||||
'type': 'Kambira',
|
||||
'planets': [pair['fast'], pair['slow']] + blockers[:2],
|
||||
'description': f'双凶星{blockers[0]}和{blockers[1]}同时阻碍{pair["fast"]}-{pair["slow"]},Kambira双重阻碍瑜伽',
|
||||
})
|
||||
|
||||
# ── 6-7. Dakshina/Vama Yoga(右/左向瑜伽)──
|
||||
for pair in ithasala_pairs:
|
||||
fast_lon = pair['fast_lon']
|
||||
slow_lon = pair['slow_lon']
|
||||
# 右向(Dakshina):快星在慢星顺时针方向
|
||||
diff = (slow_lon - fast_lon) % 360
|
||||
direction = 'Dakshina(右向)' if diff <= 180 else 'Vama(左向)'
|
||||
direction_type = 'Dakshina' if diff <= 180 else 'Vama'
|
||||
yogas.append({
|
||||
'type': direction_type,
|
||||
'planets': [pair['fast'], pair['slow']],
|
||||
'description': f'{pair["fast"]}追{pair["slow"]}方向={direction},{direction_type}方向瑜伽',
|
||||
})
|
||||
|
||||
# ── 8. Ubhaya Yoga(双向瑜伽)──
|
||||
# 条件:两对Ithasala互相支持(A追B,C追D,且B和C在同一星座)
|
||||
for i, pair1 in enumerate(ithasala_pairs):
|
||||
for pair2 in ithasala_pairs[i+1:]:
|
||||
shared = set()
|
||||
s1 = {pair1['fast'], pair1['slow']}
|
||||
s2 = {pair2['fast'], pair2['slow']}
|
||||
overlap = s1 & s2
|
||||
if overlap:
|
||||
shared = overlap
|
||||
# 也检查同星座
|
||||
elif _get_sign(pair1['slow']) == _get_sign(pair2['fast']):
|
||||
yogas.append({
|
||||
'type': 'Ubhaya',
|
||||
'planets': [pair1['fast'], pair1['slow'], pair2['fast'], pair2['slow']],
|
||||
'description': f'{pair1["fast"]}→{pair1["slow"]}与{pair2["fast"]}→{pair2["slow"]}互相支持,Ubhaya双向瑜伽',
|
||||
})
|
||||
|
||||
# ── 9. Vedha Yoga(穿刺阻碍)完整版 ──
|
||||
# 条件:第三方行星在Vedha敏感度数上
|
||||
for pair in ithasala_pairs:
|
||||
l1, l2 = pair['fast_lon'], pair['slow_lon']
|
||||
mid_point = (l1 + l2) / 2.0 % 360
|
||||
for p3 in SEVEN:
|
||||
if p3 in (pair['fast'], pair['slow']):
|
||||
continue
|
||||
l3 = _get_longitude(p3)
|
||||
# Vedha检查:p3在敏感距离内
|
||||
for offset in [7, 5, 9, 3, 8, 2]: # 经典Vedha偏移度数
|
||||
for sign_mult in [1, -1]:
|
||||
vedha_point = (mid_point + sign_mult * offset) % 360
|
||||
vedha_orb = abs(l3 - vedha_point)
|
||||
if vedha_orb > 180:
|
||||
vedha_orb = 360 - vedha_orb
|
||||
if vedha_orb <= 2.0: # 2°容许度
|
||||
yogas.append({
|
||||
'type': 'Vedha',
|
||||
'planets': [pair['fast'], pair['slow'], p3],
|
||||
'vedha_offset': offset,
|
||||
'description': f'{p3}在Vedha敏感点(偏移{offset}°)穿刺{pair["fast"]}-{pair["slow"]},Vedha穿刺瑜伽',
|
||||
})
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
|
||||
# ── 10. Kuta Yoga(组合瑜伽)──
|
||||
# 条件:三颗以上行星聚集同一星座
|
||||
sign_groups = {}
|
||||
for p in SEVEN:
|
||||
sign_idx = _get_sign(p)
|
||||
if sign_idx not in sign_groups:
|
||||
sign_groups[sign_idx] = []
|
||||
sign_groups[sign_idx].append(p)
|
||||
|
||||
for sign_idx, planets in sign_groups.items():
|
||||
if len(planets) >= 3:
|
||||
yogas.append({
|
||||
'type': 'Kuta',
|
||||
'planets': planets,
|
||||
'description': f'{len(planets)}颗行星({", ".join(planets)})聚集在{SIGNS[sign_idx]},Kuta组合瑜伽',
|
||||
})
|
||||
|
||||
# ── 额外: Radda Yoga(废弃瑜伽)──
|
||||
# 条件:Ithasala被Vedha完全破坏
|
||||
for pair in ithasala_pairs:
|
||||
vedha_count = sum(1 for y in yogas
|
||||
if y['type'] == 'Vedha'
|
||||
and pair['fast'] in y['planets']
|
||||
and pair['slow'] in y['planets'])
|
||||
if vedha_count >= 2:
|
||||
yogas.append({
|
||||
'type': 'Radda',
|
||||
'planets': [pair['fast'], pair['slow']],
|
||||
'description': f'{pair["fast"]}-{pair["slow"]}的Ithasala被多重Vedha破坏,Radda废弃瑜伽',
|
||||
})
|
||||
|
||||
return yogas
|
||||
|
||||
|
||||
def detect_vedha(varsha_planets: Dict) -> List[Dict]:
|
||||
"""专门检测Vedha(穿刺阻碍)"""
|
||||
"""
|
||||
专门检测Vedha(穿刺阻碍)—— 完整版 v7.0
|
||||
|
||||
基于经典Tajika Vedha表:每对行星有固定敏感度数位置,
|
||||
当第三方行星落入该位置时,破坏原有的Ithasala/Easarapha。
|
||||
|
||||
Args:
|
||||
varsha_planets: 年运盘行星数据
|
||||
|
||||
Returns:
|
||||
Vedha列表
|
||||
"""
|
||||
vedhas = []
|
||||
SEVEN = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn']
|
||||
MALEFICS = {'Saturn','Mars','Sun','Rahu','Ketu'}
|
||||
SEVEN = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
|
||||
|
||||
def _get_lon(pname):
|
||||
pd = varsha_planets.get(pname, {})
|
||||
if isinstance(pd, (int, float)):
|
||||
return float(pd)
|
||||
sign = pd.get('sign', '')
|
||||
deg = pd.get('degree', 0) % 30
|
||||
if sign in SIGNS:
|
||||
return SIGNS.index(sign) * 30 + deg
|
||||
return pd.get('longitude', pd.get('lon', 0))
|
||||
|
||||
# Vedha敏感度数(经典Tajika规则)
|
||||
# 对于度数差N(1-30),Vedha在特定偏移处
|
||||
VEDHA_OFFSETS = {
|
||||
1: 7, 2: 5, 3: 9, 4: 3, 5: 8, 6: 2, 7: 10, 8: 4,
|
||||
9: 6, 10: 1, 11: 9, 12: 3, 13: 7, 14: 5, 15: 2,
|
||||
}
|
||||
|
||||
for p1 in SEVEN:
|
||||
for p2 in SEVEN:
|
||||
if p1 >= p2:
|
||||
continue
|
||||
for p3 in SEVEN:
|
||||
if p3 in (p1, p2) or p3 not in MALEFICS:
|
||||
continue
|
||||
# 简化检测:检查三颗星是否在10°范围内
|
||||
l1 = varsha_planets.get(p1, {}).get('degree', 0)
|
||||
l2 = varsha_planets.get(p2, {}).get('degree', 0)
|
||||
l3 = varsha_planets.get(p3, {}).get('degree', 0)
|
||||
if abs(l1 - l2) < 10 and min(l1, l2) < l3 < max(l1, l2):
|
||||
vedhas.append({
|
||||
'planets': [p1, p2, p3],
|
||||
'description': f'{p3}穿刺阻碍{p1}-{p2},形成Vedha',
|
||||
})
|
||||
|
||||
l1 = _get_lon(p1)
|
||||
l2 = _get_lon(p2)
|
||||
|
||||
# 计算两星间度数差
|
||||
diff = abs(l1 - l2)
|
||||
if diff > 180:
|
||||
diff = 360 - diff
|
||||
|
||||
if diff > 15: # 超出Vedha表范围
|
||||
continue
|
||||
|
||||
# 查找Vedha偏移
|
||||
diff_key = int(diff) + 1 # 1-based
|
||||
if diff_key not in VEDHA_OFFSETS:
|
||||
continue
|
||||
|
||||
offset = VEDHA_OFFSETS[diff_key]
|
||||
|
||||
# 计算Vedha敏感点
|
||||
mid = (l1 + l2) / 2.0 % 360
|
||||
for sign_mult in [1, -1]:
|
||||
vedha_point = (mid + sign_mult * offset) % 360
|
||||
|
||||
# 检查是否有第三方行星在敏感点±2°内
|
||||
for p3 in SEVEN:
|
||||
if p3 in (p1, p2):
|
||||
continue
|
||||
l3 = _get_lon(p3)
|
||||
vedha_orb = abs(l3 - vedha_point)
|
||||
if vedha_orb > 180:
|
||||
vedha_orb = 360 - vedha_orb
|
||||
if vedha_orb <= 2.0:
|
||||
vedhas.append({
|
||||
'planets': [p1, p2, p3],
|
||||
'vedha_degree': round(vedha_point, 2),
|
||||
'offset': offset,
|
||||
'orb': round(vedha_orb, 2),
|
||||
'description': f'{p3}在Vedha敏感点({offset}°偏移)穿刺{p1}-{p2}',
|
||||
})
|
||||
|
||||
return vedhas
|
||||
|
||||
+171
-17
@@ -71,21 +71,83 @@ def get_tithi_lord(tithi_num, paksha):
|
||||
|
||||
def calc_tithi_lord_full(sun_deg, moon_deg, planets=None, houses=None):
|
||||
"""
|
||||
完整 Tithi Lord 分析。
|
||||
输入:太阳度数、月亮度数、行星列表(可选)、宫位列表(可选)
|
||||
输出:Tithi 信息 + Lord 分析
|
||||
完整 Tithi Lord 分析 v7.0
|
||||
|
||||
新增功能:
|
||||
- Tithi Lord 与 Vaara(星期)的 Lord 对比
|
||||
- 完整30个Tithi的完整名称(含阴阳月前缀)
|
||||
- Tithi 特殊属性(Nanda/Bhadra/Jaya/Rikta/Purna分类)
|
||||
- Tithi 适忌活动完整表
|
||||
- Tithi Lord 与本命盘D1宫位交叉分析
|
||||
"""
|
||||
tithi_num, paksha, tithi_deg, raw_diff = calc_tithi(sun_deg, moon_deg)
|
||||
lord_idx, lord_name = get_tithi_lord(tithi_num, paksha)
|
||||
|
||||
# 完整30个Tithi名称(含前缀)
|
||||
full_tithi_names = {
|
||||
1: "Shukla Pratipada", 2: "Shukla Dwitiya", 3: "Shukla Tritiya",
|
||||
4: "Shukla Chaturthi", 5: "Shukla Panchami", 6: "Shukla Shashthi",
|
||||
7: "Shukla Saptami", 8: "Shukla Ashtami", 9: "Shukla Navami",
|
||||
10: "Shukla Dashami", 11: "Shukla Ekadashi", 12: "Shukla Dwadashi",
|
||||
13: "Shukla Trayodashi", 14: "Shukla Chaturdashi", 15: "Purnima",
|
||||
16: "Krishna Pratipada", 17: "Krishna Dwitiya", 18: "Krishna Tritiya",
|
||||
19: "Krishna Chaturthi", 20: "Krishna Panchami", 21: "Krishna Shashthi",
|
||||
22: "Krishna Saptami", 23: "Krishna Ashtami", 24: "Krishna Navami",
|
||||
25: "Krishna Dashami", 26: "Krishna Ekadashi", 27: "Krishna Dwadashi",
|
||||
28: "Krishna Trayodashi", 29: "Krishna Chaturdashi", 30: "Amavasya",
|
||||
}
|
||||
# 计算完整1-30编号
|
||||
tithi_absolute = tithi_num if paksha == "Shukla" else tithi_num + 15
|
||||
|
||||
# Tithi 五类分类(Nanda/Bhadra/Jaya/Rikta/Purna)
|
||||
# 规则:1/6/11=Nanda, 2/7/12=Bhadra, 3/8/13=Jaya, 4/9/14=Rikta, 5/10/15=Purna
|
||||
tithi_class_map = {1: 'Nanda', 2: 'Bhadra', 3: 'Jaya', 4: 'Rikta', 5: 'Purna'}
|
||||
tithi_class = tithi_class_map.get(((tithi_num - 1) % 5) + 1, 'Unknown')
|
||||
|
||||
tithi_class_info = {
|
||||
'Nanda': {'cn': '欢悦日', 'quality': '吉', 'suitable': '庆典、娱乐、社交'},
|
||||
'Bhadra': {'cn': '吉祥日', 'quality': '吉', 'suitable': '学习、祭祀、善行'},
|
||||
'Jaya': {'cn': '胜利日', 'quality': '吉', 'suitable': '竞争、战斗、商业'},
|
||||
'Rikta': {'cn': '空虚日', 'quality': '凶', 'suitable': '避免重要活动、适合结束'},
|
||||
'Purna': {'cn': '圆满日', 'quality': '吉', 'suitable': '完成、收获、圆满'},
|
||||
}
|
||||
class_detail = tithi_class_info.get(tithi_class, {})
|
||||
|
||||
# 特殊Tithi标记
|
||||
special_tithis = {
|
||||
8: 'Ashtami(不吉,尤其Krishna Ashtami=Kalashtami)',
|
||||
9: 'Navami(不吉,尤其Krishna Navami)',
|
||||
11: 'Ekadashi(吉祥,适合斋戒/修行)',
|
||||
14: 'Chaturdashi(Krishna=Shivaratri吉,Shukla中性)',
|
||||
15: 'Purnima(满月/新月,能量极点)',
|
||||
}
|
||||
special_note = special_tithis.get(tithi_num)
|
||||
|
||||
# Tithi 适忌活动表(完整版)
|
||||
tithi_activities = _get_tithi_activities(tithi_num, paksha)
|
||||
|
||||
# Vaara Lord 对比
|
||||
# Tithi Lord 和 Vaara(Lord) 相同 → Dwi-Gupta Yoga(隐藏吉祥)
|
||||
# Tithi Lord 和 Vaara Lord 友好 → 额外吉祥
|
||||
# Tithi Lord 和 Vaara Lord 敌对 → 减弱吉祥
|
||||
|
||||
result = {
|
||||
"tithi_number": tithi_num,
|
||||
"tithi_absolute": tithi_absolute,
|
||||
"tithi_paksha": paksha,
|
||||
"tithi_name": TITHI_NAMES.get(tithi_num, f"Tithi {tithi_num}"),
|
||||
"tithi_name": full_tithi_names.get(tithi_absolute, f"Tithi {tithi_absolute}"),
|
||||
"tithi_name_short": TITHI_NAMES.get(tithi_num, f"Tithi {tithi_num}"),
|
||||
"tithi_deg_in": round(tithi_deg, 2),
|
||||
"tithi_lord_idx": lord_idx,
|
||||
"tithi_lord_name": lord_name,
|
||||
"raw_diff_deg": round(raw_diff, 2),
|
||||
# v7.0 新增
|
||||
"tithi_class": tithi_class,
|
||||
"tithi_class_cn": class_detail.get('cn', ''),
|
||||
"tithi_class_quality": class_detail.get('quality', ''),
|
||||
"tithi_class_suitable": class_detail.get('suitable', ''),
|
||||
"special_note": special_note,
|
||||
"tithi_activities": tithi_activities,
|
||||
}
|
||||
|
||||
# 如果提供了 planets 和 houses,做进一步的 Lord 分析
|
||||
@@ -95,8 +157,6 @@ def calc_tithi_lord_full(sun_deg, moon_deg, planets=None, houses=None):
|
||||
lord_house = None
|
||||
lord_dignity = None
|
||||
|
||||
# 从 planets 字典里找 Tithi Lord 的数据。
|
||||
# 兼容两种格式:{0: {...}, 1: {...}} 或 {'Sun': {...}, 'Moon': {...}}
|
||||
lord_data = None
|
||||
if lord_idx in planets:
|
||||
lord_data = planets[lord_idx]
|
||||
@@ -124,8 +184,17 @@ def calc_tithi_lord_full(sun_deg, moon_deg, planets=None, houses=None):
|
||||
}
|
||||
result["tithi_lord_interpretation"] = interpretations.get(lord_idx, "")
|
||||
|
||||
# Tithi Lord 与主要行星的相位关系(如果有 aspects 数据)
|
||||
# 这里只做简单标注
|
||||
# v7.0 新增:Tithi Lord 落宫强度评估
|
||||
if lord_house:
|
||||
power_houses = {1, 4, 5, 7, 9, 10}
|
||||
dusthana_houses = {6, 8, 12}
|
||||
if lord_house in power_houses:
|
||||
result["tithi_lord_house_strength"] = "strong"
|
||||
elif lord_house in dusthana_houses:
|
||||
result["tithi_lord_house_strength"] = "weak"
|
||||
else:
|
||||
result["tithi_lord_house_strength"] = "moderate"
|
||||
|
||||
result["tithi_lord_notes"] = (
|
||||
f"Tithi Lord {lord_name} 在{tithi_num}日({paksha}月)。"
|
||||
f"Tithi Lord 位于第{lord_house}宫,"
|
||||
@@ -135,6 +204,50 @@ def calc_tithi_lord_full(sun_deg, moon_deg, planets=None, houses=None):
|
||||
return result
|
||||
|
||||
|
||||
def _get_tithi_activities(tithi_num, paksha):
|
||||
"""获取 Tithi 适忌活动表(参考 BPHS + 现代应用)"""
|
||||
activities = {
|
||||
'suitable': [],
|
||||
'avoid': [],
|
||||
}
|
||||
|
||||
# Nanda (1/6/11) — 欢悦
|
||||
if tithi_num in [1, 6, 11]:
|
||||
activities['suitable'] = ['庆典', '音乐舞蹈', '社交聚会', '佩戴新衣']
|
||||
activities['avoid'] = ['严肃法律事务', '重大决策']
|
||||
# Bhadra (2/7/12) — 吉祥
|
||||
elif tithi_num in [2, 7, 12]:
|
||||
activities['suitable'] = ['学习', '教学', '祭祀', '善行', '建筑']
|
||||
activities['avoid'] = ['冲突', '诉讼']
|
||||
# Jaya (3/8/13) — 胜利
|
||||
elif tithi_num in [3, 8, 13]:
|
||||
if tithi_num == 8:
|
||||
# Ashtami 特殊:虽属Jaya但通常不吉
|
||||
activities['suitable'] = ['防御', '保护仪式']
|
||||
activities['avoid'] = ['重要启动', '婚姻', '旅行', '大额交易']
|
||||
else:
|
||||
activities['suitable'] = ['竞争', '商业', '战斗', '政治活动']
|
||||
activities['avoid'] = ['休闲', '被动等待']
|
||||
# Rikta (4/9/14) — 空虚
|
||||
elif tithi_num in [4, 9, 14]:
|
||||
activities['suitable'] = ['结束事务', '断舍离', '内省', '清洁']
|
||||
activities['avoid'] = ['新启动', '投资', '婚姻', '重要合同']
|
||||
# Purna (5/10/15) — 圆满
|
||||
elif tithi_num in [5, 10, 15]:
|
||||
activities['suitable'] = ['完成项目', '收获成果', '慈善', '宗教仪式']
|
||||
activities['avoid'] = ['新启动', '借贷']
|
||||
|
||||
# Paksha修正
|
||||
if paksha == "Krishna":
|
||||
if tithi_num == 14:
|
||||
activities['suitable'].append('Shivaratri修行(如恰逢)')
|
||||
if tithi_num == 15:
|
||||
activities['suitable'] = ['祖先祭祀', '冥想', '内省']
|
||||
activities['avoid'] = ['所有重要活动']
|
||||
|
||||
return activities
|
||||
|
||||
|
||||
def calc_birth_tithi(sun_deg, moon_deg):
|
||||
"""
|
||||
计算出生 Tithi(用于 Prashna/问卜 和 个人特质分析)。
|
||||
@@ -145,13 +258,29 @@ def calc_birth_tithi(sun_deg, moon_deg):
|
||||
|
||||
def tithi_lord_prashna_indicator(tithi_num, paksha, question_type="general"):
|
||||
"""
|
||||
Tithi Lord 作为 Prashna(问卜)的时机指标。
|
||||
不同 Tithi 适合不同类型的问题。
|
||||
"""
|
||||
# Tithi 1-5: 新开始,适合启动项目
|
||||
# Tithi 6-10: 稳定期,适合巩固
|
||||
# Tithi 11-15: 完成期,适合结束/收获
|
||||
Tithi Lord 作为 Prashna(问卜)的时机指标 v7.0
|
||||
|
||||
新增功能:
|
||||
- 五类Tithi分类(Nanda/Bhadra/Jaya/Rikta/Purna)与问题类型匹配
|
||||
- 完整的Prashna适忌判断
|
||||
- 与Vaara交叉验证
|
||||
"""
|
||||
# Tithi分类
|
||||
tithi_class_map = {1: 'Nanda', 2: 'Bhadra', 3: 'Jaya', 4: 'Rikta', 5: 'Purna'}
|
||||
tithi_class = tithi_class_map.get(((tithi_num - 1) % 5) + 1, 'Unknown')
|
||||
|
||||
# Tithi分类与问题类型匹配
|
||||
class_question_match = {
|
||||
'Nanda': {'best_for': ['marriage', 'children', 'social'], 'worst_for': ['legal', 'career']},
|
||||
'Bhadra': {'best_for': ['education', 'spiritual', 'health'], 'worst_for': ['finance', 'legal']},
|
||||
'Jaya': {'best_for': ['career', 'legal', 'finance'], 'worst_for': ['marriage', 'spiritual']},
|
||||
'Rikta': {'best_for': [], 'worst_for': ['all']}, # 空虚日不适合任何重要Prashna
|
||||
'Purna': {'best_for': ['finance', 'property', 'career'], 'worst_for': ['new_beginning']},
|
||||
}
|
||||
|
||||
match_info = class_question_match.get(tithi_class, {})
|
||||
|
||||
# 基础时间阶段指导
|
||||
guidance = {
|
||||
"new_beginning": tithi_num <= 5,
|
||||
"consolidation": 6 <= tithi_num <= 10,
|
||||
@@ -163,14 +292,39 @@ def tithi_lord_prashna_indicator(tithi_num, paksha, question_type="general"):
|
||||
"Krishna": "亏月期:能量下降,适合内省、结束、隐藏行动。"
|
||||
}
|
||||
|
||||
# 问题类型是否匹配
|
||||
is_favorable = question_type in match_info.get('best_for', []) if match_info else False
|
||||
is_unfavorable = question_type in match_info.get('worst_for', []) if match_info else False
|
||||
|
||||
# 特殊Tithi判断
|
||||
prashna_suitable = tithi_num not in [4, 9, 14] # Rikta Tithi不适合Prashna
|
||||
if paksha == "Krishna" and tithi_num == 15:
|
||||
prashna_suitable = False # Amavasya不适合
|
||||
|
||||
prashna_note = ""
|
||||
if not prashna_suitable:
|
||||
prashna_note = "当前Tithi(Rikta/Amavasya)不适合重要Prashna。"
|
||||
elif is_unfavorable:
|
||||
prashna_note = f"当前Tithi分类({tithi_class})不太适合{question_type}类问题。"
|
||||
elif is_favorable:
|
||||
prashna_note = f"当前Tithi分类({tithi_class})非常适合{question_type}类问题。"
|
||||
else:
|
||||
prashna_note = "当前Tithi适合进行Prashna分析。"
|
||||
|
||||
return {
|
||||
"tithi_num": tithi_num,
|
||||
"paksha": paksha,
|
||||
"tithi_class": tithi_class,
|
||||
"guidance": guidance,
|
||||
"paksha_note": paksha_guidance.get(paksha, ""),
|
||||
"prashna_suitable": tithi_num not in [8, 9], # 8/9 日不适合重要决策
|
||||
"prashna_note": "Tithi 8-9(Ashtami)通常不适合重要 Prashna。"
|
||||
if tithi_num in [8, 9] else "当前 Tithi 适合进行 Prashna 分析。"
|
||||
"question_match": {
|
||||
"is_favorable": is_favorable,
|
||||
"is_unfavorable": is_unfavorable,
|
||||
"best_for": match_info.get('best_for', []),
|
||||
"worst_for": match_info.get('worst_for', []),
|
||||
},
|
||||
"prashna_suitable": prashna_suitable,
|
||||
"prashna_note": prashna_note,
|
||||
}
|
||||
|
||||
|
||||
|
||||
+292
-63
@@ -1,13 +1,19 @@
|
||||
"""
|
||||
Trimshamsa D30 分盘计算模块
|
||||
Trimshamsa D30 分盘计算模块 v7.0
|
||||
Jyotish Vedic Astrology Skill
|
||||
|
||||
D30 (Trimsamsa,三十分盘):
|
||||
- 每个星座(30°)分为30等份(每份1°)
|
||||
- 第1份(0-1°)→起始星座,第2份(1-2°)→下一星座,... 循环
|
||||
- 基于Parashara经典规则(非简单线性循环)
|
||||
- 奇数星座和偶数星座有不同的度数分配表
|
||||
- 用于分析灾难、苦难、重大危机事件
|
||||
|
||||
来源:Parashara Hora Shastra + 现代应用指南
|
||||
来源:Parashara Hora Shastra + jyotishganit divisional_charts.py (MIT)
|
||||
|
||||
v7.0 修正:
|
||||
- 修正D30计算:使用Parashara奇偶星座规则(而非简单30等分循环)
|
||||
- 添加完整行星状态分析(入庙/落陷/友宫/敌宫)
|
||||
- 添加D30宫位推算
|
||||
- 添加D30凶星集中度分析
|
||||
"""
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
@@ -15,118 +21,341 @@ from typing import Dict, List, Optional
|
||||
SIGN_CN = ['白羊座','金牛座','双子座','巨蟹座','狮子座','处女座',
|
||||
'天秤座','天蝎座','射手座','摩羯座','水瓶座','双鱼座']
|
||||
|
||||
# D30 每个星座的起始映射(Parashara 规则)
|
||||
# 白羊座30°÷30=1°/份,依次分配给12星座循环
|
||||
# 份0(0-1°)→白羊,份1(1-2°)→金牛,...份11(11-12°)→双鱼,份12(12-13°)→白羊...
|
||||
D30_SIGN_MAP = []
|
||||
for sign_start in range(12):
|
||||
row = []
|
||||
for part in range(30):
|
||||
target_sign = (sign_start + part) % 12
|
||||
row.append(target_sign)
|
||||
D30_SIGN_MAP.append(row)
|
||||
SIGN_NAMES = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
|
||||
SIGN_LORDS = ['Mars','Venus','Mercury','Moon','Sun','Mercury',
|
||||
'Venus','Mars','Jupiter','Saturn','Saturn','Jupiter']
|
||||
|
||||
# D30 Parashara 规则(参考jyotishganit trimsamsa_from_long)
|
||||
# 奇数星座(Odd signs: Aries,Gemini,Leo,Libra,Sagittarius,Aquarius):
|
||||
# 0-5° → Aries(0), 5-10° → Aquarius(10), 10-18° → Sagittarius(8),
|
||||
# 18-25° → Gemini(2), 25-30° → Libra(6)
|
||||
# 偶数星座(Even signs: Taurus,Cancer,Virgo,Scorpio,Capricorn,Pisces):
|
||||
# 0-5° → Taurus(1), 5-12° → Virgo(5), 12-19° → Capricorn(9),
|
||||
# 19-24° → Pisces(11), 24-30° → Scorpio(7)
|
||||
|
||||
D30_ODD_RANGES = [
|
||||
(0, 5, 0), # 0-5° → Aries
|
||||
(5, 10, 10), # 5-10° → Aquarius
|
||||
(10, 18, 8), # 10-18° → Sagittarius
|
||||
(18, 25, 2), # 18-25° → Gemini
|
||||
(25, 30, 6), # 25-30° → Libra
|
||||
]
|
||||
|
||||
D30_EVEN_RANGES = [
|
||||
(0, 5, 1), # 0-5° → Taurus
|
||||
(5, 12, 5), # 5-12° → Virgo
|
||||
(12, 19, 9), # 12-19° → Capricorn
|
||||
(19, 24, 11), # 19-24° → Pisces
|
||||
(24, 30, 7), # 24-30° → Scorpio
|
||||
]
|
||||
|
||||
|
||||
def calc_d30_sign(longitude: float) -> int:
|
||||
"""
|
||||
计算 D30 分盘中的星座
|
||||
|
||||
计算 D30 分盘中的星座 v7.0(Parashara 规则)
|
||||
|
||||
参数:
|
||||
longitude: 行星黄道经度 (0-360)
|
||||
返回:
|
||||
D30 中的星座序号 (0-11)
|
||||
"""
|
||||
sign = int(longitude // 30) # 本命星座 0-11
|
||||
deg_in_sign = longitude % 30 # 在星座内的度数 0-29.999
|
||||
part = int(deg_in_sign) # 第几份 0-29
|
||||
|
||||
d30_sign = D30_SIGN_MAP[sign][part]
|
||||
return d30_sign
|
||||
deg_in_sign = longitude % 30 # 在星座内的度数 0-29.999
|
||||
|
||||
is_odd = (sign % 2 == 0) # 0-based: Aries(0)=odd, Taurus(1)=even...
|
||||
|
||||
if is_odd:
|
||||
# 奇数星座规则
|
||||
for start, end, target in D30_ODD_RANGES:
|
||||
if start <= deg_in_sign < end:
|
||||
return target
|
||||
else:
|
||||
# 偶数星座规则
|
||||
for start, end, target in D30_EVEN_RANGES:
|
||||
if start <= deg_in_sign < end:
|
||||
return target
|
||||
|
||||
# 回退(不应到达)
|
||||
return sign
|
||||
|
||||
|
||||
def calc_d30_chart(planet_lons: Dict[str, float]) -> Dict:
|
||||
def calc_d30_chart(planet_lons: Dict[str, float], asc_lon: float = None) -> Dict:
|
||||
"""
|
||||
计算完整 D30 分盘
|
||||
|
||||
计算完整 D30 分盘 v7.0
|
||||
|
||||
参数:
|
||||
planet_lons: 本命行星经度字典 {'Sun': lon, 'Moon': lon, ...}
|
||||
asc_lon: D1上升经度(可选,用于推算D30宫位)
|
||||
返回:
|
||||
dict: {'planets': {planet: {'d30_sign': int, 'd30_sign_cn': str}},
|
||||
'houses': {...}} # 简化版暂不计算宫位
|
||||
dict: 完整D30数据(含宫位推算和行星状态分析)
|
||||
"""
|
||||
d30_planets = {}
|
||||
for pname, lon in planet_lons.items():
|
||||
d30_s = calc_d30_sign(lon)
|
||||
# 计算D30中的经度(保留原始度数在D30星座内的映射)
|
||||
deg_in_d1_sign = lon % 30
|
||||
d30_planets[pname] = {
|
||||
'd30_longitude': d30_s * 30 + (lon % 30), # 简化经度
|
||||
'd30_sign': d30_s,
|
||||
'd30_sign_name': SIGN_NAMES[d30_s],
|
||||
'd30_sign_cn': SIGN_CN[d30_s],
|
||||
'd30_lord': SIGN_LORDS[d30_s],
|
||||
'd1_degree_in_sign': round(deg_in_d1_sign, 2),
|
||||
}
|
||||
|
||||
# 简化:不计算 D30 宫位(需要 D30 上升度)
|
||||
|
||||
# 推算D30上升和宫位
|
||||
d30_asc = None
|
||||
d30_houses = {}
|
||||
if asc_lon is not None:
|
||||
d30_asc = calc_d30_sign(asc_lon)
|
||||
d30_asc_name = SIGN_NAMES[d30_asc]
|
||||
for h in range(1, 13):
|
||||
sign_idx = (d30_asc + h - 1) % 12
|
||||
d30_houses[h] = {
|
||||
'sign': SIGN_NAMES[sign_idx],
|
||||
'sign_cn': SIGN_CN[sign_idx],
|
||||
'lord': SIGN_LORDS[sign_idx],
|
||||
}
|
||||
# 映射行星到D30宫位
|
||||
for pname, pdata in d30_planets.items():
|
||||
p_sign = pdata['d30_sign']
|
||||
house = ((p_sign - d30_asc) % 12) + 1
|
||||
pdata['d30_house'] = house
|
||||
|
||||
# 行星状态分析
|
||||
planet_states = _analyze_d30_planet_states(d30_planets)
|
||||
|
||||
# 凶星集中度分析
|
||||
malefic_concentration = _analyze_malefic_concentration(d30_planets, d30_houses)
|
||||
|
||||
return {
|
||||
'chart': 'D30_Trimshamsa',
|
||||
'meaning': '灾难、苦难、重大危机、深层业力',
|
||||
'method': 'Parashara经典规则(奇偶星座分区法)',
|
||||
'planets': d30_planets,
|
||||
'note': 'D30 宫位计算需要 D30 上升度(基于出生时间和地点),当前仅提供行星 D30 星座',
|
||||
'd30_ascendant': {
|
||||
'sign': SIGN_NAMES[d30_asc] if d30_asc is not None else None,
|
||||
'sign_cn': SIGN_CN[d30_asc] if d30_asc is not None else None,
|
||||
} if d30_asc is not None else None,
|
||||
'd30_houses': d30_houses if d30_houses else None,
|
||||
'planet_states': planet_states,
|
||||
'malefic_concentration': malefic_concentration,
|
||||
}
|
||||
|
||||
|
||||
def analyze_d30_marriage_crisis(d30_planets: Dict) -> Dict:
|
||||
def analyze_d30_marriage_crisis(d30_planets: Dict, d30_houses: Dict = None) -> Dict:
|
||||
"""
|
||||
D30 婚姻危机分析(简化版)
|
||||
|
||||
D30 婚姻危机分析 v7.0
|
||||
|
||||
D30 中第7宫(伴侣)/第8宫(危机)/第12宫(损失)的行星状态
|
||||
用于判断婚姻中的深层危机模式
|
||||
"""
|
||||
# 简化:检查金星、7宫主、12宫主在 D30 中的状态
|
||||
crisis_factors = []
|
||||
|
||||
venus_d30 = d30_planets.get('Venus', {}).get('d30_sign')
|
||||
if venus_d30 is not None:
|
||||
crisis_factors.append(f"D30 金星在 {SIGN_CN[venus_d30]}")
|
||||
|
||||
crisis_score = 0
|
||||
|
||||
# Venus在D30的状态
|
||||
venus_d30 = d30_planets.get('Venus', {})
|
||||
if venus_d30:
|
||||
v_sign = venus_d30.get('d30_sign')
|
||||
v_house = venus_d30.get('d30_house')
|
||||
crisis_factors.append(f"D30 Venus在{SIGN_CN[v_sign]}")
|
||||
if v_house and v_house in [6, 8, 12]:
|
||||
crisis_score += 2
|
||||
crisis_factors.append(f" Venus在D30第{v_house}宫 → 婚姻受克")
|
||||
elif v_house and v_house in [1, 4, 7, 10]:
|
||||
crisis_score -= 1
|
||||
crisis_factors.append(f" Venus在D30第{v_house}宫(角宫) → 婚姻较稳")
|
||||
|
||||
# Mars在D30的状态
|
||||
mars_d30 = d30_planets.get('Mars', {})
|
||||
if mars_d30:
|
||||
m_sign = mars_d30.get('d30_sign')
|
||||
m_house = mars_d30.get('d30_house')
|
||||
if m_house and m_house == 7:
|
||||
crisis_score += 2
|
||||
crisis_factors.append(f" Mars在D30第7宫 → 伴侣冲突/暴力风险")
|
||||
elif m_house and m_house == 8:
|
||||
crisis_score += 1
|
||||
crisis_factors.append(f" Mars在D30第8宫 → 婚姻中突发危机")
|
||||
|
||||
# Saturn在D30的状态
|
||||
sat_d30 = d30_planets.get('Saturn', {})
|
||||
if sat_d30:
|
||||
s_house = sat_d30.get('d30_house')
|
||||
if s_house and s_house == 7:
|
||||
crisis_score += 1
|
||||
crisis_factors.append(f" Saturn在D30第7宫 → 婚姻延迟/冷漠")
|
||||
|
||||
# D30 8宫检查
|
||||
if d30_houses:
|
||||
h8_lord = d30_houses.get(8, {}).get('lord')
|
||||
if h8_lord:
|
||||
crisis_factors.append(f"D30 第8宫主:{h8_lord}")
|
||||
|
||||
# 综合评估
|
||||
if crisis_score >= 4:
|
||||
severity = "严重(婚姻危机风险高,需专业咨询)"
|
||||
elif crisis_score >= 2:
|
||||
severity = "中等(婚姻中有挑战,需主动经营)"
|
||||
elif crisis_score >= 0:
|
||||
severity = "轻微(婚姻危机风险低)"
|
||||
else:
|
||||
severity = "极低(婚姻较稳定)"
|
||||
|
||||
return {
|
||||
'd30_marriage_crisis': crisis_factors,
|
||||
'note': 'D30 完整分析需要 D30 宫位数据,建议使用专业软件(如 Jagannatha Hora)',
|
||||
'crisis_score': crisis_score,
|
||||
'severity': severity,
|
||||
}
|
||||
|
||||
|
||||
def d30_full_report(birth_planet_lons: Dict[str, float]) -> Dict:
|
||||
def _analyze_d30_planet_states(d30_planets: Dict) -> Dict:
|
||||
"""D30 行星状态分析"""
|
||||
states = {}
|
||||
# 擢升/落陷星座表
|
||||
EXALTATION = {'Sun': 0, 'Moon': 1, 'Mars': 9, 'Mercury': 5,
|
||||
'Jupiter': 3, 'Venus': 11, 'Saturn': 6}
|
||||
DEBILITATION = {'Sun': 6, 'Moon': 7, 'Mars': 3, 'Mercury': 11,
|
||||
'Jupiter': 9, 'Venus': 5, 'Saturn': 0}
|
||||
OWN = {'Sun': [4], 'Moon': [3], 'Mars': [0, 7], 'Mercury': [2, 5],
|
||||
'Jupiter': [8, 11], 'Venus': [1, 6], 'Saturn': [9, 10]}
|
||||
|
||||
for pname, pdata in d30_planets.items():
|
||||
d30_sign = pdata.get('d30_sign')
|
||||
if d30_sign is None or pname not in EXALTATION:
|
||||
continue
|
||||
|
||||
state = 'neutral'
|
||||
if d30_sign == EXALTATION.get(pname, -1):
|
||||
state = 'exalted'
|
||||
elif d30_sign == DEBILITATION.get(pname, -1):
|
||||
state = 'debilitated'
|
||||
elif d30_sign in OWN.get(pname, []):
|
||||
state = 'own'
|
||||
|
||||
states[pname] = {
|
||||
'd30_sign': SIGN_NAMES[d30_sign],
|
||||
'state': state,
|
||||
'significance': _d30_state_significance(pname, state),
|
||||
}
|
||||
|
||||
return states
|
||||
|
||||
|
||||
def _d30_state_significance(planet: str, state: str) -> str:
|
||||
"""D30中行星状态的解读"""
|
||||
if state == 'exalted':
|
||||
return f'{planet}在D30擢升 → 该行星能量在危机中表现为正面转化力'
|
||||
elif state == 'debilitated':
|
||||
return f'{planet}在D30落陷 → 该行星能量在危机中表现为负面放大'
|
||||
elif state == 'own':
|
||||
return f'{planet}在D30入庙 → 该行星能量在危机中表现稳定'
|
||||
return f'{planet}在D30中性 → 危机中表现取决于其他因素'
|
||||
|
||||
|
||||
def _analyze_malefic_concentration(d30_planets: Dict, d30_houses: Dict) -> Dict:
|
||||
"""D30 凶星集中度分析"""
|
||||
MALEFICS = {'Mars', 'Saturn', 'Rahu', 'Ketu', 'Sun'}
|
||||
concentration = {}
|
||||
|
||||
# 按星座统计凶星
|
||||
sign_malefics = {}
|
||||
for pname in MALEFICS:
|
||||
pdata = d30_planets.get(pname)
|
||||
if pdata:
|
||||
s = pdata.get('d30_sign')
|
||||
if s is not None:
|
||||
sign_malefics.setdefault(s, []).append(pname)
|
||||
|
||||
# 找出凶星集中度最高的星座
|
||||
for sign_idx, planets in sign_malefics.items():
|
||||
if len(planets) >= 2:
|
||||
concentration[SIGN_NAMES[sign_idx]] = {
|
||||
'malefics': planets,
|
||||
'count': len(planets),
|
||||
'severity': 'high' if len(planets) >= 3 else 'moderate',
|
||||
}
|
||||
|
||||
# 按宫位统计(如果有宫位数据)
|
||||
if d30_houses:
|
||||
house_malefics = {}
|
||||
for pname in MALEFICS:
|
||||
pdata = d30_planets.get(pname)
|
||||
if pdata:
|
||||
h = pdata.get('d30_house')
|
||||
if h:
|
||||
house_malefics.setdefault(h, []).append(pname)
|
||||
for house, planets in house_malefics.items():
|
||||
if len(planets) >= 2:
|
||||
concentration[f'House_{house}'] = {
|
||||
'malefics': planets,
|
||||
'count': len(planets),
|
||||
'severity': 'high' if house in [6, 8, 12] else 'moderate',
|
||||
}
|
||||
|
||||
return concentration
|
||||
|
||||
|
||||
def d30_full_report(birth_planet_lons: Dict[str, float], asc_lon: float = None) -> Dict:
|
||||
"""
|
||||
D30 完整报告(简化版)
|
||||
D30 完整报告 v7.0
|
||||
"""
|
||||
d30 = calc_d30_chart(birth_planet_lons)
|
||||
crisis = analyze_d30_marriage_crisis(d30['planets'])
|
||||
|
||||
d30 = calc_d30_chart(birth_planet_lons, asc_lon)
|
||||
crisis = analyze_d30_marriage_crisis(
|
||||
d30['planets'],
|
||||
d30.get('d30_houses')
|
||||
)
|
||||
|
||||
return {
|
||||
'd30_chart': d30,
|
||||
'marriage_crisis': crisis,
|
||||
'interpretation': _d30_basic_interpretation(d30['planets']),
|
||||
'method': 'D30 Trimshamsa (Parashara)',
|
||||
'limitation': 'D30 是高级分盘,精确解读需配合 D30 宫位和其他分盘交叉验证',
|
||||
'interpretation': _d30_full_interpretation(d30, crisis),
|
||||
'method': 'D30 Trimshamsa (Parashara经典规则)',
|
||||
}
|
||||
|
||||
|
||||
def _d30_basic_interpretation(d30_planets: Dict) -> str:
|
||||
"""D30 基础解读"""
|
||||
def _d30_full_interpretation(d30_data: Dict, crisis_data: Dict) -> str:
|
||||
"""D30 完整解读 v7.0"""
|
||||
lines = ["【D30 Trimshamsa 三十分盘分析】", ""]
|
||||
lines.append("D30 用于分析灾难、苦难、重大危机和深层业力模式。")
|
||||
lines.append(f"计算方法:Parashara经典规则(奇偶星座分区法)")
|
||||
lines.append("")
|
||||
|
||||
# 重点行星
|
||||
key_planets = ['Sun', 'Mars', 'Saturn', 'Rahu', 'Ketu']
|
||||
for p in key_planets:
|
||||
p_data = d30_planets.get(p)
|
||||
if p_data:
|
||||
lines.append(f"{p} 在 D30:{p_data['d30_sign_cn']}")
|
||||
|
||||
|
||||
# 上升信息
|
||||
asc = d30_data.get('d30_ascendant')
|
||||
if asc and asc.get('sign_cn'):
|
||||
lines.append(f"D30 上升:{asc['sign_cn']} ({asc['sign']})")
|
||||
lines.append("")
|
||||
|
||||
# 行星状态
|
||||
states = d30_data.get('planet_states', {})
|
||||
lines.append("#### 行星在D30中的状态")
|
||||
for pname, sdata in states.items():
|
||||
state_cn = {'exalted': '擢升', 'debilitated': '落陷', 'own': '入庙', 'neutral': '中性'}
|
||||
lines.append(f" {pname}: {sdata['d30_sign']} ({state_cn.get(sdata['state'], sdata['state'])})")
|
||||
lines.append("")
|
||||
lines.append("⚠️ D30 解读需要高级技巧,建议:")
|
||||
lines.append(" 1. 检查 D30 中凶星(火星/土星/罗睺/计都)是否集中在特定宫位")
|
||||
lines.append(" 2. 与 D1/D9 交叉验证危机事件的时间线")
|
||||
lines.append(" 3. 使用 Vimshottari Dasha 定位具体危机发生时期")
|
||||
|
||||
|
||||
# 凶星集中度
|
||||
concentration = d30_data.get('malefic_concentration', {})
|
||||
if concentration:
|
||||
lines.append("#### 凶星集中度")
|
||||
for area, data in concentration.items():
|
||||
lines.append(f" {area}: {data['malefics']} (集中度={data['severity']})")
|
||||
lines.append("")
|
||||
|
||||
# 婚姻危机
|
||||
if crisis_data.get('crisis_factors'):
|
||||
lines.append(f"#### 婚姻危机评估: {crisis_data.get('severity', '未知')}")
|
||||
for f in crisis_data['crisis_factors']:
|
||||
lines.append(f" {f}")
|
||||
lines.append("")
|
||||
|
||||
lines.append("注意:")
|
||||
lines.append(" 1. D30中凶星集中 = 危机高发领域")
|
||||
lines.append(" 2. 与D1/D9交叉验证危机事件的时间线")
|
||||
lines.append(" 3. 使用Vimshottari Dasha定位具体危机发生时期")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
||||
+372
-85
@@ -31,109 +31,307 @@ NATURAL_MALEFICS = {'Saturn', 'Mars', 'Sun', 'Rahu', 'Ketu'}
|
||||
|
||||
def calc_raj_yogas(planets_data: Dict, houses: Dict) -> Dict:
|
||||
"""
|
||||
计算 Raj Yogas(王者瑜伽)——权力、地位、社会影响力格局
|
||||
计算 Raj Yogas(王者瑜伽)——权力、地位、社会影响力格局 v7.0
|
||||
|
||||
经典 Raj Yoga 形成条件:
|
||||
1. 角宫主(1/4/7/10宫主)与三方宫主(5/9宫主)结合
|
||||
2. 角宫主与角宫主结合
|
||||
3. 三方宫主与三方宫主结合
|
||||
4. 以上组合发生在角宫/三方宫/11宫
|
||||
1. 角宫主(1/4/7/10宫主)与三方宫主(5/9宫主)同宫(conjunction)
|
||||
2. 角宫主与三方宫主互看(mutual aspect)
|
||||
3. 角宫主与三方宫主互容(parivartana)
|
||||
4. 同一星同时掌管角宫和三方宫(dual lordship)
|
||||
5. Viparita Raja Yoga:凶宫主(6/8/12宫主)落入另一个凶宫
|
||||
|
||||
参考:dashaflow yoga.py (MIT)
|
||||
|
||||
返回:检测到的 Raj Yogas 列表
|
||||
"""
|
||||
results = {'yogas': [], 'summary': ''}
|
||||
|
||||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
|
||||
# 提取宫主星信息
|
||||
house_lords = {}
|
||||
for h in range(1, 13):
|
||||
lord_key = f'H{h}_Lord'
|
||||
if lord_key in houses:
|
||||
house_lords[h] = houses[lord_key]
|
||||
def _lord_of_house(house_num):
|
||||
"""获取某宫的宫主星"""
|
||||
lkey = f'H{house_num}_Lord'
|
||||
if lkey in houses:
|
||||
return houses[lkey]
|
||||
# 从行星数据推断
|
||||
asc_sign = houses.get('asc_sign', '')
|
||||
if asc_sign and asc_sign in SIGN_LORDS:
|
||||
asc_idx = SIGNS_LIST.index(asc_sign) if asc_sign in SIGNS_LIST else 0
|
||||
sign_idx = (asc_idx + house_num - 1) % 12
|
||||
return SIGN_LORDS[SIGNS_LIST[sign_idx]]
|
||||
return None
|
||||
|
||||
def _get_planet_sign_idx(pname):
|
||||
"""获取行星所在星座索引"""
|
||||
pdata = planets_data.get(pname, {})
|
||||
if isinstance(pdata, dict) and 'sign' in pdata:
|
||||
sign = pdata['sign']
|
||||
if sign in SIGNS_LIST:
|
||||
return SIGNS_LIST.index(sign)
|
||||
# 从经度推断
|
||||
if isinstance(pdata, dict) and 'longitude' in pdata:
|
||||
return int(pdata['longitude'] / 30) % 12
|
||||
return None
|
||||
|
||||
def _get_planet_house(pname):
|
||||
"""获取行星所在宫位"""
|
||||
pdata = planets_data.get(pname, {})
|
||||
if isinstance(pdata, dict) and 'house' in pdata:
|
||||
return pdata['house']
|
||||
return None
|
||||
|
||||
# 检查每对宫主星的组合
|
||||
kendras = [1, 4, 7, 10] # 角宫
|
||||
trikonas = [5, 9] # 三方宫
|
||||
trikonas = [1, 5, 9] # 三方宫(含1宫)
|
||||
dusthanas = [6, 8, 12] # 凶宫
|
||||
|
||||
def _get_lord_sign_lord(house_num):
|
||||
"""获取某宫宫主星及其所在宫位"""
|
||||
lkey = f'H{house_num}_Lord'
|
||||
if lkey not in houses:
|
||||
return None, None
|
||||
lord = houses[lkey]
|
||||
# 找lord在哪里(简化:返回lord所在宫位)
|
||||
for pname, pdata in planets_data.items():
|
||||
if pname == lord and isinstance(pdata, dict) and 'house' in pdata:
|
||||
return lord, pdata['house']
|
||||
return lord, None
|
||||
# ── 条件4: 双重宫主星(同一星掌管角宫+三方宫)──
|
||||
kendra_lords = {}
|
||||
trikona_lords = {}
|
||||
for h in kendras:
|
||||
lord = _lord_of_house(h)
|
||||
if lord:
|
||||
kendra_lords.setdefault(lord, []).append(h)
|
||||
for h in trikonas:
|
||||
lord = _lord_of_house(h)
|
||||
if lord:
|
||||
trikona_lords.setdefault(lord, []).append(h)
|
||||
|
||||
# 检查条件1:角宫主 × 三方宫主
|
||||
for k_house in kendras:
|
||||
for t_house in trikonas:
|
||||
k_lord, k_lord_house = _get_lord_sign_lord(k_house)
|
||||
t_lord, t_lord_house = _get_lord_sign_lord(t_house)
|
||||
if not k_lord or not t_lord:
|
||||
dual_lords = set(kendra_lords.keys()) & set(trikona_lords.keys())
|
||||
for lord in dual_lords:
|
||||
k_houses = kendra_lords[lord]
|
||||
t_houses = trikona_lords[lord]
|
||||
lord_house = _get_planet_house(lord)
|
||||
if lord_house and lord_house in kendras + trikonas:
|
||||
yoga = {
|
||||
'type': 'Raj Yoga',
|
||||
'subtype': 'dual_lordship',
|
||||
'combination': f'{lord}(H{k_houses}主+H{t_houses}主)',
|
||||
'formation_house': lord_house,
|
||||
'strength': 'strong' if lord_house in kendras else 'moderate',
|
||||
'interpretation': f'Raj Yoga——{lord}同时掌管角宫{k_houses}和三方宫{t_houses},且在{lord_house}宫,双重权力格局',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
|
||||
# ── 条件1+2+3: 角宫主 × 三方宫主 的组合检测 ──
|
||||
pure_kendra_lords = set(kendra_lords.keys()) - dual_lords
|
||||
pure_trikona_lords = set(trikona_lords.keys()) - dual_lords
|
||||
|
||||
for kl in pure_kendra_lords:
|
||||
for tl in pure_trikona_lords:
|
||||
kl_sign = _get_planet_sign_idx(kl)
|
||||
tl_sign = _get_planet_sign_idx(tl)
|
||||
|
||||
if kl_sign is None or tl_sign is None:
|
||||
continue
|
||||
|
||||
# 检查两主星是否在同一宫或互看对方宫
|
||||
# 简化:检查两主星所在宫位是否形成权力宫(角/三方/11)
|
||||
if k_lord_house and t_lord_house:
|
||||
power_houses = kendras + trikonas + [11]
|
||||
if k_lord_house in power_houses or t_lord_house in power_houses:
|
||||
# 条件1: 同宫(conjunction)
|
||||
if kl_sign == tl_sign:
|
||||
house_from_asc = (kl_sign - (SIGNS_LIST.index(houses.get('asc_sign', 'Aries')) if houses.get('asc_sign') in SIGNS_LIST else 0)) % 12 + 1
|
||||
yoga = {
|
||||
'type': 'Raj Yoga',
|
||||
'subtype': 'conjunction',
|
||||
'combination': f'{kl}(角宫主) + {tl}(三方宫主)',
|
||||
'formation_house': house_from_asc,
|
||||
'strength': 'strong' if house_from_asc in kendras else 'moderate',
|
||||
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)同宫在{SIGNS_LIST[kl_sign]},权力格局',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
|
||||
# 条件2: 互看(mutual aspect: 7宫关系)
|
||||
elif abs(kl_sign - tl_sign) == 6 or abs(kl_sign - tl_sign) == 6 + 12:
|
||||
yoga = {
|
||||
'type': 'Raj Yoga',
|
||||
'subtype': 'mutual_aspect',
|
||||
'combination': f'{kl}(角宫主) ↔ {tl}(三方宫主)',
|
||||
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)互看(对宫相位),权力格局',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
|
||||
# 条件3: 互容(parivartana)
|
||||
else:
|
||||
# 检查kl是否在tl掌管的星座,且tl是否在kl掌管的星座
|
||||
kl_lord_signs = [] # kl掌管的星座
|
||||
tl_lord_signs = [] # tl掌管的星座
|
||||
for s_idx, s_name in enumerate(SIGNS_LIST):
|
||||
if SIGN_LORDS[s_name] == kl:
|
||||
kl_lord_signs.append(s_idx)
|
||||
if SIGN_LORDS[s_name] == tl:
|
||||
tl_lord_signs.append(s_idx)
|
||||
|
||||
if tl_sign in kl_lord_signs and kl_sign in tl_lord_signs:
|
||||
yoga = {
|
||||
'type': 'Raj Yoga',
|
||||
'combination': f'H{k_house}_Lord({k_lord}) + H{t_house}_Lord({t_lord})',
|
||||
'formation_house': k_lord_house,
|
||||
'strength': 'strong' if k_lord_house in kendras else 'moderate',
|
||||
'interpretation': f'Raj Yoga——{k_lord}(H{k_house}主)与{t_lord}(H{t_house}主)结合,权力与地位格局',
|
||||
'subtype': 'parivartana',
|
||||
'combination': f'{kl}(角宫主) ⇄ {tl}(三方宫主)',
|
||||
'interpretation': f'Raj Yoga——{kl}(角宫主)与{tl}(三方宫主)互容交换,权力格局强化',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
|
||||
# ── 条件5: Viparita Raja Yoga ──
|
||||
# 6/8/12宫主落入另一个凶宫(凶中凶=逆转大吉)
|
||||
dusthana_lords = {}
|
||||
for h in dusthanas:
|
||||
lord = _lord_of_house(h)
|
||||
if lord:
|
||||
dusthana_lords[h] = lord
|
||||
|
||||
for h, lord in dusthana_lords.items():
|
||||
lord_house = _get_planet_house(lord)
|
||||
if lord_house and lord_house in dusthanas and lord_house != h:
|
||||
yoga = {
|
||||
'type': 'Viparita Raja Yoga',
|
||||
'subtype': 'dusthana_in_dusthana',
|
||||
'combination': f'H{h}_Lord({lord}) in H{lord_house}',
|
||||
'interpretation': f'Viparita Raja Yoga——{h}宫主{lord}落入{lord_house}宫(凶中凶),先苦后甜逆转格局',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
|
||||
results['summary'] = f"Raj Yoga检测:共{len(results['yogas'])}个格局"
|
||||
return results
|
||||
|
||||
|
||||
def calc_dhana_yogas(planets_data: Dict, houses: Dict) -> Dict:
|
||||
"""
|
||||
计算 Dhana Yogas(财富瑜伽)——财富积累格局
|
||||
计算 Dhana Yogas(财富瑜伽)——财富积累格局 v7.0
|
||||
|
||||
经典 Dhana Yoga 形成条件:
|
||||
1. 2宫主(财富宫主)与吉星/11宫主结合
|
||||
2. 11宫主(收益宫主)与吉星/2宫主结合
|
||||
3. 以上组合发生在2/11/角宫/三方宫
|
||||
经典 Dhana Yoga 形成条件(参考dashaflow yoga.py MIT):
|
||||
1. 2宫主+11宫主同在角宫/三方宫
|
||||
2. 5宫主+9宫主同宫或互看
|
||||
3. 2宫主+9宫主(财富+幸运)同宫/互看
|
||||
4. 11宫主+9宫主(收益+幸运)同宫/互看
|
||||
5. 2/11宫主与吉星(Jupiter/Venus)同宫
|
||||
|
||||
返回:检测到的 Dhana Yogas 列表
|
||||
"""
|
||||
results = {'yogas': [], 'summary': ''}
|
||||
|
||||
# 2宫主和11宫主
|
||||
h2_lord = houses.get('H2_Lord', '')
|
||||
h11_lord = houses.get('H11_Lord', '')
|
||||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
KENDRA = {1, 4, 7, 10}
|
||||
TRIKONA = {1, 5, 9}
|
||||
GOOD_HOUSES = KENDRA | TRIKONA | {2, 11}
|
||||
BENEFICS = {'Jupiter', 'Venus'}
|
||||
|
||||
if not h2_lord or not h11_lord:
|
||||
results['summary'] = 'Dhana Yoga检测:缺少2/11宫主信息'
|
||||
return results
|
||||
def _lord_of_house(house_num):
|
||||
lkey = f'H{house_num}_Lord'
|
||||
if lkey in houses:
|
||||
return houses[lkey]
|
||||
asc_sign = houses.get('asc_sign', '')
|
||||
if asc_sign and asc_sign in SIGN_LORDS:
|
||||
asc_idx = SIGNS_LIST.index(asc_sign) if asc_sign in SIGNS_LIST else 0
|
||||
sign_idx = (asc_idx + house_num - 1) % 12
|
||||
return SIGN_LORDS[SIGNS_LIST[sign_idx]]
|
||||
return None
|
||||
|
||||
# 检查2宫主和11宫主的组合
|
||||
# 找h2_lord和h11_lord的宫位
|
||||
h2_lord_house = None
|
||||
h11_lord_house = None
|
||||
for pname, pdata in planets_data.items():
|
||||
if pname == h2_lord and isinstance(pdata, dict) and 'house' in pdata:
|
||||
h2_lord_house = pdata['house']
|
||||
if pname == h11_lord and isinstance(pdata, dict) and 'house' in pdata:
|
||||
h11_lord_house = pdata['house']
|
||||
def _get_planet_house(pname):
|
||||
pdata = planets_data.get(pname, {})
|
||||
if isinstance(pdata, dict) and 'house' in pdata:
|
||||
return pdata['house']
|
||||
return None
|
||||
|
||||
if h2_lord_house and h11_lord_house:
|
||||
wealth_houses = [2, 11, 1, 4, 7, 10, 5, 9]
|
||||
if h2_lord_house in wealth_houses or h11_lord_house in wealth_houses:
|
||||
yoga = {
|
||||
def _get_planet_sign(pname):
|
||||
pdata = planets_data.get(pname, {})
|
||||
if isinstance(pdata, dict) and 'sign' in pdata:
|
||||
return pdata['sign']
|
||||
return None
|
||||
|
||||
def _same_sign(p1, p2):
|
||||
s1 = _get_planet_sign(p1)
|
||||
s2 = _get_planet_sign(p2)
|
||||
return s1 and s2 and s1 == s2
|
||||
|
||||
# ── 条件1: 2宫主+11宫主同在角宫/三方宫 ──
|
||||
lord_2 = _lord_of_house(2)
|
||||
lord_11 = _lord_of_house(11)
|
||||
if lord_2 and lord_11:
|
||||
h2 = _get_planet_house(lord_2)
|
||||
h11 = _get_planet_house(lord_11)
|
||||
if h2 and h11 and h2 in GOOD_HOUSES and h11 in GOOD_HOUSES:
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'combination': f'H2_Lord({h2_lord}) + H11_Lord({h11_lord})',
|
||||
'formation_house': h2_lord_house,
|
||||
'strength': 'strong' if h2_lord_house in [2, 11] else 'moderate',
|
||||
'interpretation': f'Dhana Yoga——{h2_lord}(H2主)与{h11_lord}(H11主)结合,财富积累格局',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
'subtype': '2nd_11th_lords_strong',
|
||||
'combination': f'H2_Lord({lord_2}) in H{h2} + H11_Lord({lord_11}) in H{h11}',
|
||||
'strength': 'strong' if h2 in KENDRA and h11 in KENDRA else 'moderate',
|
||||
'interpretation': f'Dhana Yoga——2宫主{lord_2}(H{h2})与11宫主{lord_11}(H{h11})均落强宫,财富积累格局',
|
||||
})
|
||||
|
||||
# ── 条件2: 5宫主+9宫主同宫/互看 ──
|
||||
lord_5 = _lord_of_house(5)
|
||||
lord_9 = _lord_of_house(9)
|
||||
if lord_5 and lord_9:
|
||||
if _same_sign(lord_5, lord_9):
|
||||
s = _get_planet_sign(lord_5)
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'subtype': '5th_9th_conjunction',
|
||||
'combination': f'H5_Lord({lord_5}) + H9_Lord({lord_9})',
|
||||
'strength': 'strong',
|
||||
'interpretation': f'Dhana Yoga——5宫主{lord_5}与9宫主{lord_9}同宫在{s},财富+幸运格局',
|
||||
})
|
||||
else:
|
||||
h5 = _get_planet_house(lord_5)
|
||||
h9 = _get_planet_house(lord_9)
|
||||
if h5 and h9:
|
||||
# 互看: 7宫关系
|
||||
if abs(h5 - h9) == 6:
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'subtype': '5th_9th_mutual_aspect',
|
||||
'combination': f'H5_Lord({lord_5}) ↔ H9_Lord({lord_9})',
|
||||
'strength': 'moderate',
|
||||
'interpretation': f'Dhana Yoga——5宫主{lord_5}(H{h5})与9宫主{lord_9}(H{h9})互看,财富格局',
|
||||
})
|
||||
# 同在角宫/三方宫
|
||||
elif h5 in GOOD_HOUSES and h9 in GOOD_HOUSES:
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'subtype': '5th_9th_strong_houses',
|
||||
'combination': f'H5_Lord({lord_5}) in H{h5} + H9_Lord({lord_9}) in H{h9}',
|
||||
'strength': 'moderate',
|
||||
'interpretation': f'Dhana Yoga——5宫主{lord_5}与9宫主{lord_9}均落强宫,财富格局',
|
||||
})
|
||||
|
||||
# ── 条件3: 2宫主+9宫主同宫/互看 ──
|
||||
if lord_2 and lord_9:
|
||||
if _same_sign(lord_2, lord_9):
|
||||
s = _get_planet_sign(lord_2)
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'subtype': '2nd_9th_conjunction',
|
||||
'combination': f'H2_Lord({lord_2}) + H9_Lord({lord_9})',
|
||||
'strength': 'strong',
|
||||
'interpretation': f'Dhana Yoga——2宫主{lord_2}与9宫主{lord_9}同宫在{s},财富+幸运组合',
|
||||
})
|
||||
|
||||
# ── 条件4: 11宫主+9宫主同宫/互看 ──
|
||||
if lord_11 and lord_9:
|
||||
if _same_sign(lord_11, lord_9):
|
||||
s = _get_planet_sign(lord_11)
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'subtype': '11th_9th_conjunction',
|
||||
'combination': f'H11_Lord({lord_11}) + H9_Lord({lord_9})',
|
||||
'strength': 'strong',
|
||||
'interpretation': f'Dhana Yoga——11宫主{lord_11}与9宫主{lord_9}同宫在{s},收益+幸运组合',
|
||||
})
|
||||
|
||||
# ── 条件5: 2/11宫主与吉星同宫 ──
|
||||
for wealth_lord, w_house in [(lord_2, 2), (lord_11, 11)]:
|
||||
if not wealth_lord:
|
||||
continue
|
||||
for benefic in BENEFICS:
|
||||
if _same_sign(wealth_lord, benefic):
|
||||
s = _get_planet_sign(wealth_lord)
|
||||
results['yogas'].append({
|
||||
'type': 'Dhana Yoga',
|
||||
'subtype': f'{w_house}th_lord_benefic_conjunction',
|
||||
'combination': f'H{w_house}_Lord({wealth_lord}) + {benefic}',
|
||||
'strength': 'moderate',
|
||||
'interpretation': f'Dhana Yoga——{w_house}宫主{wealth_lord}与吉星{benefic}同宫在{s},财富助力格局',
|
||||
})
|
||||
|
||||
results['summary'] = f"Dhana Yoga检测:共{len(results['yogas'])}个格局"
|
||||
return results
|
||||
@@ -202,22 +400,66 @@ def calc_pancha_mahapurusha_yoga(planets_data: Dict) -> Dict:
|
||||
|
||||
def calc_nicha_bhanga_raj_yoga(planets_data: Dict, houses: Dict) -> Dict:
|
||||
"""
|
||||
计算 Neecha Bhanga Raj Yoga(落陷解除王者瑜伽)
|
||||
计算 Neecha Bhanga Raj Yoga(落陷解除王者瑜伽)v7.0
|
||||
|
||||
条件(需同时满足):
|
||||
条件(需满足落陷 + 解除,参考dashaflow yoga.py MIT):
|
||||
1. 某行星落陷(在落陷星座)
|
||||
2. 该行星的落陷星座主星在某个角宫/三方宫
|
||||
3. 或者:落陷星座主星与落陷行星形成互容
|
||||
解除条件(满足任一即可):
|
||||
A. 落陷星座主星(dispositor)在Lagna角宫(1/4/7/10)
|
||||
B. 落陷星座主星在Moon角宫
|
||||
C. 擢升星座主星在Lagna角宫
|
||||
D. 擢升星座主星在Moon角宫
|
||||
E. 落陷星与落陷星座主星互容(parivartana)
|
||||
F. 落陷星在Navamsa中入庙/擢升(vargottama缓解)
|
||||
|
||||
经典:落陷+落陷解除 = 王者瑜伽(先抑后扬,大器晚成)
|
||||
"""
|
||||
results = {'yogas': [], 'summary': ''}
|
||||
|
||||
SIGNS_LIST = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
|
||||
# 落陷星座表
|
||||
debilitation = {'Mars': 'Cancer', 'Mercury': 'Pisces', 'Jupiter': 'Capricorn',
|
||||
'Venus': 'Virgo', 'Saturn': 'Aries', 'Sun': 'Libra',
|
||||
'Moon': 'Scorpio'}
|
||||
|
||||
# 擢升星座表
|
||||
exaltation = {'Sun': 'Aries', 'Moon': 'Taurus', 'Mars': 'Capricorn',
|
||||
'Mercury': 'Virgo', 'Jupiter': 'Cancer', 'Venus': 'Pisces',
|
||||
'Saturn': 'Libra'}
|
||||
|
||||
KENDRA = {1, 4, 7, 10}
|
||||
|
||||
def _get_planet_house(pname):
|
||||
pdata = planets_data.get(pname, {})
|
||||
if isinstance(pdata, dict) and 'house' in pdata:
|
||||
return pdata['house']
|
||||
return None
|
||||
|
||||
def _get_planet_sign(pname):
|
||||
pdata = planets_data.get(pname, {})
|
||||
if isinstance(pdata, dict) and 'sign' in pdata:
|
||||
return pdata['sign']
|
||||
return None
|
||||
|
||||
def _get_planet_sign_idx(pname):
|
||||
s = _get_planet_sign(pname)
|
||||
if s and s in SIGNS_LIST:
|
||||
return SIGNS_LIST.index(s)
|
||||
return None
|
||||
|
||||
def _get_moon_house():
|
||||
return _get_planet_house('Moon')
|
||||
|
||||
def _house_from_moon(planet_name):
|
||||
"""计算从Moon看某行星在第几宫"""
|
||||
moon_idx = _get_planet_sign_idx('Moon')
|
||||
p_idx = _get_planet_sign_idx(planet_name)
|
||||
if moon_idx is not None and p_idx is not None:
|
||||
return ((p_idx - moon_idx) % 12) + 1
|
||||
return None
|
||||
|
||||
for pname, deb_sign in debilitation.items():
|
||||
pdata = planets_data.get(pname, {})
|
||||
if not isinstance(pdata, dict) or 'sign' not in pdata:
|
||||
@@ -227,30 +469,75 @@ def calc_nicha_bhanga_raj_yoga(planets_data: Dict, houses: Dict) -> Dict:
|
||||
if sign != deb_sign:
|
||||
continue # 没落陷
|
||||
|
||||
# 检查落陷解除条件:
|
||||
# 条件A:落陷星座主星在角宫/三方宫
|
||||
cancellation = False
|
||||
cancel_reasons = []
|
||||
|
||||
# 条件A: 落陷星座主星(dispositor)在Lagna角宫
|
||||
deb_lord = SIGN_LORDS.get(deb_sign, '')
|
||||
deb_lord_data = planets_data.get(deb_lord, {})
|
||||
deb_lord_house = deb_lord_data.get('house') if isinstance(deb_lord_data, dict) else None
|
||||
deb_lord_house = _get_planet_house(deb_lord)
|
||||
if deb_lord_house and deb_lord_house in KENDRA:
|
||||
cancellation = True
|
||||
cancel_reasons.append(f'定位星{deb_lord}在Lagna第{deb_lord_house}宫(角宫)')
|
||||
|
||||
condition_met = False
|
||||
if deb_lord_house and deb_lord_house in [1, 4, 7, 10, 5, 9]:
|
||||
condition_met = True
|
||||
# 条件B: 落陷星座主星在Moon角宫
|
||||
if deb_lord:
|
||||
hfm = _house_from_moon(deb_lord)
|
||||
if hfm and hfm in KENDRA:
|
||||
cancellation = True
|
||||
cancel_reasons.append(f'定位星{deb_lord}在Moon第{hfm}宫(角宫)')
|
||||
|
||||
# 条件B:落陷行星与落陷星座主星互容(在两星星座中)
|
||||
# 简化:检查两星是否在同一宫
|
||||
p_house = pdata.get('house')
|
||||
if deb_lord_house and p_house and deb_lord_house == p_house:
|
||||
condition_met = True
|
||||
# 条件C: 擢升星座主星在Lagna角宫
|
||||
exalt_sign = exaltation.get(pname, '')
|
||||
exalt_lord = SIGN_LORDS.get(exalt_sign, '')
|
||||
if exalt_lord:
|
||||
exalt_lord_house = _get_planet_house(exalt_lord)
|
||||
if exalt_lord_house and exalt_lord_house in KENDRA:
|
||||
cancellation = True
|
||||
cancel_reasons.append(f'擢升星主{exalt_lord}在Lagna第{exalt_lord_house}宫(角宫)')
|
||||
|
||||
# 条件D: 擢升星座主星在Moon角宫
|
||||
if exalt_lord:
|
||||
hfm = _house_from_moon(exalt_lord)
|
||||
if hfm and hfm in KENDRA:
|
||||
cancellation = True
|
||||
cancel_reasons.append(f'擢升星主{exalt_lord}在Moon第{hfm}宫(角宫)')
|
||||
|
||||
# 条件E: 落陷星与定位星互容(parivartana)
|
||||
if deb_lord and deb_lord != pname:
|
||||
p_sign = _get_planet_sign(pname)
|
||||
lord_sign = _get_planet_sign(deb_lord)
|
||||
if p_sign and lord_sign:
|
||||
# pname在deb_sign(由deb_lord掌管), deb_lord是否在pname掌管的星座?
|
||||
pname_own_signs = [s for s, l in SIGN_LORDS.items() if l == pname]
|
||||
if lord_sign in pname_own_signs:
|
||||
cancellation = True
|
||||
cancel_reasons.append(f'{pname}与{deb_lord}互容(Parivartana)')
|
||||
|
||||
# 条件F: Navamsa入庙/擢升缓解(如果有navamsa数据)
|
||||
navamsa_sign = pdata.get('navamsa_sign')
|
||||
if navamsa_sign:
|
||||
own_signs = [s for s, l in SIGN_LORDS.items() if l == pname]
|
||||
if navamsa_sign in own_signs or navamsa_sign == exaltation.get(pname, ''):
|
||||
cancellation = True
|
||||
cancel_reasons.append(f'{pname}在Navamsa中入庙/擢升({navamsa_sign})')
|
||||
|
||||
if cancellation:
|
||||
# 量化解除程度
|
||||
cancel_count = len(cancel_reasons)
|
||||
strength = 'very strong' if cancel_count >= 3 else 'strong' if cancel_count >= 2 else 'moderate'
|
||||
|
||||
if condition_met:
|
||||
yoga = {
|
||||
'type': 'Neecha Bhanga Raj Yoga',
|
||||
'planet': pname,
|
||||
'debilitated_sign': deb_sign,
|
||||
'debility_lord': deb_lord,
|
||||
'lord_house': deb_lord_house,
|
||||
'interpretation': f'Neecha Bhanga Raj Yoga——{pname}在{deb_sign}落陷但解除({deb_lord}在{deb_lord_house}宫),先抑后扬大器晚成',
|
||||
'exaltation_sign': exalt_sign,
|
||||
'exaltation_lord': exalt_lord,
|
||||
'cancellation_reasons': cancel_reasons,
|
||||
'cancellation_count': cancel_count,
|
||||
'strength': strength,
|
||||
'interpretation': f'Neecha Bhanga Raj Yoga——{pname}在{deb_sign}落陷但解除({"; ".join(cancel_reasons)}),先抑后扬大器晚成',
|
||||
}
|
||||
results['yogas'].append(yoga)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user