Add Jyotish capability matrix and gap patches
This commit is contained in:
+128
-4
@@ -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)')
|
||||
|
||||
@@ -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 Pada(A1-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:
|
||||
|
||||
Reference in New Issue
Block a user