Add Jyotish capability matrix and gap patches

This commit is contained in:
732642856
2026-06-03 13:55:51 +08:00
parent 0ff7a9125e
commit a837b7f9d1
6 changed files with 273 additions and 15 deletions
+128 -4
View File
@@ -108,6 +108,117 @@ PERMANENT_ENEMIES = {
'Rahu': ['Sun', 'Moon', 'Jupiter'],
'Ketu': ['Sun', 'Moon'],
}
PUSHKARA_NAVAMSA_RANGES = {
'fire': [(6 + 40/60, 10), (23 + 20/60, 26 + 40/60)],
'earth': [(3 + 20/60, 6 + 40/60), (16 + 40/60, 20)],
'air': [(13 + 20/60, 16 + 40/60), (26 + 40/60, 30)],
'water': [(0, 3 + 20/60), (10, 13 + 20/60)],
}
PUSHKARA_BHAGA_DEGREES = {'fire': 21, 'earth': 14, 'air': 24, 'water': 7}
SIGN_ELEMENTS = {
'Aries': 'fire', 'Leo': 'fire', 'Sagittarius': 'fire',
'Taurus': 'earth', 'Virgo': 'earth', 'Capricorn': 'earth',
'Gemini': 'air', 'Libra': 'air', 'Aquarius': 'air',
'Cancer': 'water', 'Scorpio': 'water', 'Pisces': 'water',
}
def _element_key(sign):
return SIGN_ELEMENTS.get(sign)
def _is_pushkara_navamsa(sign, deg_in_sign):
element = _element_key(sign)
if element is None or deg_in_sign is None:
return False, None
for start, end in PUSHKARA_NAVAMSA_RANGES[element]:
if start <= deg_in_sign < end:
return True, {"element": element, "range": [round(start, 4), round(end, 4)]}
return False, None
def _is_pushkara_bhaga(sign, deg_in_sign, orb=1.0):
element = _element_key(sign)
if element is None or deg_in_sign is None:
return False, None
target = PUSHKARA_BHAGA_DEGREES[element]
delta = abs(deg_in_sign - target)
return delta <= orb, {"element": element, "target_degree": target, "orb": orb, "delta": round(delta, 4)}
def _calc_vargottama(planets, varga_full):
d9 = varga_full.get('D9_Navamsa', {}) if isinstance(varga_full, dict) else {}
result = {}
for pn, pd in planets.items():
if not isinstance(pd, dict) or 'sign' not in pd:
continue
d9_pd = d9.get(pn, {}) if isinstance(d9, dict) else {}
d9_sign = d9_pd.get('sign') if isinstance(d9_pd, dict) else None
result[pn] = {
'd1_sign': pd.get('sign'),
'd9_sign': d9_sign,
'is_vargottama': bool(d9_sign and pd.get('sign') == d9_sign),
}
return result
def _calc_pushkara_flags(planets):
result = {}
for pn, pd in planets.items():
if not isinstance(pd, dict) or 'sign' not in pd:
continue
sign = pd.get('sign')
deg = pd.get('degree_in_sign', pd.get('degree', 0) % 30)
in_pna, pna_meta = _is_pushkara_navamsa(sign, deg)
in_pb, pb_meta = _is_pushkara_bhaga(sign, deg)
result[pn] = {
'sign': sign,
'sign_cn': SIGNS_CN.get(sign, ''),
'degree_in_sign': round(deg, 4),
'pushkara_navamsa': in_pna,
'pushkara_navamsa_meta': pna_meta,
'pushkara_bhaga': in_pb,
'pushkara_bhaga_meta': pb_meta,
}
return result
def _calc_dasha_sandhi(dasha_result, reference_date=None, orb_days=90):
ref = datetime.strptime(reference_date, "%Y-%m-%d") if reference_date else datetime.now()
sandhi = []
timeline = dasha_result.get('timeline', []) if isinstance(dasha_result, dict) else []
for md in timeline:
for boundary_key in ['start', 'end']:
if boundary_key not in md:
continue
bdt = datetime.strptime(md[boundary_key], "%Y-%m-%d")
delta = (bdt - ref).days
if abs(delta) <= orb_days:
sandhi.append({
'level': 'mahadasha',
'lord': md.get('lord'),
'boundary': boundary_key,
'date': md.get(boundary_key),
'days_from_reference': delta,
'within_orb': True,
})
for ad in md.get('antardasha_timeline', []):
for boundary_key in ['start', 'end']:
if boundary_key not in ad:
continue
bdt = datetime.strptime(ad[boundary_key], "%Y-%m-%d")
delta = (bdt - ref).days
if abs(delta) <= orb_days:
sandhi.append({
'level': 'antardasha',
'mahadasha_lord': md.get('lord'),
'lord': ad.get('lord'),
'boundary': boundary_key,
'date': ad.get(boundary_key),
'days_from_reference': delta,
'within_orb': True,
})
return {'reference_date': ref.strftime('%Y-%m-%d'), 'orb_days': orb_days, 'sandhi_windows': sandhi}
def _get_dignity_level(planet, sign, deg_in_sign=None):
@@ -2981,19 +3092,28 @@ def cmd_full_reading(args):
birth_time=birth_dt,
sunrise_time=sunrise_dt
)
# 补充 Arudha Lagna 和 Upapada Lagna
# 补充 Arudha Lagna、A10/Karma Pada 和 Upapada Lagna
try:
first_house_sign_idx = asc_idx
first_lord = SIGN_LORDS.get(SIGNS[first_house_sign_idx], '')
first_lord_deg = planet_lons.get(first_lord, 0)
sl_result['Arudha_Lagna'] = sl_calc.calculate_arudha_lagna(asc_deg, first_lord_deg)
except: pass
except Exception as e:
sl_result['Arudha_Lagna'] = {"error": str(e)}
try:
tenth_house_sign_idx = (asc_idx + 9) % 12
tenth_lord = SIGN_LORDS.get(SIGNS[tenth_house_sign_idx], '')
tenth_lord_deg = planet_lons.get(tenth_lord, 0)
sl_result['A10_Karma_Pada'] = sl_calc.calculate_a10(asc_deg, tenth_lord_deg)
except Exception as e:
sl_result['A10_Karma_Pada'] = {"error": str(e)}
try:
twelfth_house_sign_idx = (asc_idx + 11) % 12
twelfth_lord = SIGN_LORDS.get(SIGNS[twelfth_house_sign_idx], '')
twelfth_lord_deg = planet_lons.get(twelfth_lord, 0)
sl_result['Upapada_Lagna'] = sl_calc.calculate_upapada_lagna(asc_deg, twelfth_lord_deg)
except: pass
except Exception as e:
sl_result['Upapada_Lagna'] = {"error": str(e)}
report['modules']['special_lagnas'] = sl_result
except Exception as e:
report['errors'].append(f"special-lagnas: {e}")
@@ -3008,12 +3128,13 @@ def cmd_full_reading(args):
pada = int((moon_lon % (360/27)) / (360/108)) + 1
birthdate = f"{args.year}-{args.month:02d}-{args.day:02d}"
today_str = datetime.now().strftime('%Y-%m-%d')
today_str = getattr(args, 'today', None) or datetime.now().strftime('%Y-%m-%d')
dasha_result = cmd_dasha(type('Args', (), {
'nakshatra': nak_name, 'pada': pada,
'moon_lon': moon_lon, 'birthdate': birthdate, 'today': today_str
})())
report['modules']['dasha'] = dasha_result
report['modules']['dasha_sandhi'] = _calc_dasha_sandhi(dasha_result, today_str)
except Exception as e:
report['errors'].append(f"dasha: {e}")
@@ -3051,6 +3172,8 @@ def cmd_full_reading(args):
if d1_data:
varga_result["D1_Rashi"] = d1_data
report['modules']['varga_full'] = varga_result
report['modules']['vargottama'] = _calc_vargottama(planets, varga_result)
report['modules']['pushkara'] = _calc_pushkara_flags(planets)
except Exception as e:
report['errors'].append(f"varga-full: {e}")
@@ -3598,6 +3721,7 @@ def main():
p = sub.add_parser('full-reading', help='全自动综合解盘(出生信息→全链路→完整报告)')
_add_chart_args(p)
p.add_argument('--age', type=int, default=None, help='当前年龄(不提供则自动计算)')
p.add_argument('--today', default=None, help='Dasha/Sandhi参考日期 YYYY-MM-DD(默认今天)')
# 23. prashna (v3.9新增)
p = sub.add_parser('prashna', help='Prashna问事占星(提问时刻星盘+Arudha+Sphuta+Sahams')
+59
View File
@@ -218,6 +218,60 @@ class SpecialLagnasCalculator:
"meaning": "公众形象和社会地位,他人如何看待你"
}
def calculate_arudha_pada(self, asc_degree: float, source_house: int, source_lord_degree: float,
label: str = "Arudha_Pada") -> Dict:
"""
计算任意宫位的 Arudha PadaA1-A12)。
Args:
asc_degree: 上升点度数(0-360
source_house: 源宫位(1-12);例如 10 = A10 / Karma Pada / Rajya Pada
source_lord_degree: 源宫主星度数(0-360
label: 输出标签
Formula:
1. 找到源宫位星座。
2. 计算源宫主星距离源宫位的星座数。
3. 从源宫主星再数同样距离得到 Pada。
4. 若 Pada 落回源宫或源宫第7宫,按 Jaimini 例外规则改取该位置第10宫。
"""
if source_house < 1 or source_house > 12:
raise ValueError("source_house must be between 1 and 12")
asc_sign = int(asc_degree // 30)
source_sign = (asc_sign + source_house - 1) % 12
lord_sign = int(source_lord_degree // 30)
distance = (lord_sign - source_sign) % 12
pada_sign = (lord_sign + distance) % 12
exception_applied = False
if pada_sign == source_sign or pada_sign == (source_sign + 6) % 12:
pada_sign = (pada_sign + 9) % 12
exception_applied = True
pada_degree = (pada_sign * 30 + (source_lord_degree % 30)) % 360
return {
"label": label,
"source_house": source_house,
"degree": round(pada_degree, 4),
"sign": self.SIGNS[pada_sign],
"sign_degree": round(pada_degree % 30, 4),
"house": ((pada_sign - asc_sign) % 12) + 1,
"source_sign": self.SIGNS[source_sign],
"source_lord_sign": self.SIGNS[lord_sign],
"distance_from_source": distance if distance != 0 else 12,
"exception_applied": exception_applied,
"formula": f"A{source_house}: from house lord count the same distance from source house; apply 1/7 exception",
"meaning": "Arudha Pada:该宫位主题在外界/社会层面的显化"
}
def calculate_a10(self, asc_degree: float, tenth_lord_degree: float) -> Dict:
"""计算 A10 / Karma Pada / Rajya Pada:事业名声、职业可见度、社会身份。"""
result = self.calculate_arudha_pada(asc_degree, 10, tenth_lord_degree, "A10_Karma_Pada")
result["meaning"] = "A10/Karma Pada/Rajya Pada:事业名声、职业可见度、社会身份与公众层面的职业结果"
return result
def calculate_upapada_lagna(self, asc_degree: float, twelfth_lord_degree: float) -> Dict:
"""
计算Upapada Lagna(配偶映像上升/UL
@@ -295,6 +349,7 @@ def main():
parser.add_argument("--birth-time", type=str, help="Birth time (YYYY-MM-DD HH:MM:SS)")
parser.add_argument("--sunrise-time", type=str, help="Sunrise time (YYYY-MM-DD HH:MM:SS)")
parser.add_argument("--first-lord", type=float, help="1st house lord degree (for AL)")
parser.add_argument("--tenth-lord", type=float, help="10th house lord degree (for A10/Karma Pada)")
parser.add_argument("--twelfth-lord", type=float, help="12th house lord degree (for UL)")
args = parser.parse_args()
@@ -311,6 +366,10 @@ def main():
# 如果提供了1宫主星位置,计算AL
if args.first_lord is not None:
lagnas["Arudha_Lagna"] = calc.calculate_arudha_lagna(args.asc, args.first_lord)
# 如果提供了10宫主星位置,计算A10/Karma Pada
if args.tenth_lord is not None:
lagnas["A10_Karma_Pada"] = calc.calculate_a10(args.asc, args.tenth_lord)
# 如果提供了12宫主星位置,计算UL
if args.twelfth_lord is not None: