Files
Jyotisha/scripts/prashna.py
T
732642856 62abe08ece v3.9.0: Prashna问事占星完整系统
- 新增 prashna-complete-guide.md (350行, 10大计算体系+十步断卦+35 Sahams)
- 新增 prashna.py 引擎 (240行, 8种CLI模式)
- 新增 single-event-inquiry-protocol.md 单事推理模板
- jyotish_engine.py 新增第23子命令 prashna
- SKILL.md v3.8.0→v3.9.0, 新增§14 Prashna能力表+触发词
2026-04-25 22:36:52 +08:00

236 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Prashna Shastra(问事占星)计算引擎 v1.0
Jyotish Vedic Astrology Skill - Prashna Module
依赖: pyswisseph
"""
import math, json, argparse
from datetime import datetime, timedelta
SIGN_NAMES = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
SIGN_CN = ['白羊座','金牛座','双子座','巨蟹座','狮子座','处女座',
'天秤座','天蝎座','射手座','摩羯座','水瓶座','双鱼座']
SIGN_LORDS = ['Mars','Venus','Mercury','Moon','Sun','Mercury',
'Venus','Mars','Jupiter','Saturn','Saturn','Jupiter']
PLANET_CN = {'Sun':'太阳','Moon':'月亮','Mars':'火星','Mercury':'水星',
'Jupiter':'木星','Venus':'金星','Saturn':'土星','Rahu':'罗睺','Ketu':'计都'}
GULIKA_DAY = {'Sunday':26,'Monday':22,'Tuesday':18,'Wednesday':14,'Thursday':10,'Friday':6,'Saturday':2}
GULIKA_NIGHT = {'Sunday':10,'Monday':6,'Tuesday':2,'Wednesday':26,'Thursday':22,'Friday':18,'Saturday':14}
SAHAM_DEFS = {
'Punya':('福德','Moon','Sun','Asc'), 'Vidya':('学业','Sun','Moon','Asc'),
'Bhratru':('兄弟','Jupiter','Saturn','Asc'), 'Pitru':('父亲','Saturn','Sun','Asc'),
'Putra':('子女','Jupiter','Moon','Asc'), 'Vivaha':('婚姻','Venus','Saturn','Asc'),
'Karma':('职业','Mars','Mercury','Asc'), 'Roga':('疾病','Asc','Moon','Asc'),
'Raja':('权力','Saturn','Sun','Asc'), 'Asha':('愿望','Saturn','Mars','Asc'),
'Matru':('母亲','Moon','Venus','Asc'), 'Jeeva':('生计','Saturn','Jupiter','Asc'),
'Kali':('冲突','Jupiter','Mars','Asc'), 'Satru':('敌人','Mars','Saturn','Asc'),
'Paradesa':('出国','H9','H9Lord','Asc'), 'Mrityu':('死亡','H8','Moon','Asc'),
'Vidya':('学业','Sun','Moon','Asc'), 'Artha':('财富','H2','H2Lord','Asc'),
'Vyapara':('商业','Mars','Saturn','Asc'), 'Bandhana':('监禁','Punya','Saturn','Asc'),
}
def sign_of(lon): return int((lon % 360) / 30)
def norm(lon): return lon % 360
def lon_cn(lon):
s, d = sign_of(lon), lon % 30
return f"{SIGN_CN[s]} {int(d)}°{int((d%1)*60)}'"
# ── Arudha Lagna ──
def calc_arudha(asc_lon, planet_lons):
asc_s = sign_of(asc_lon)
lord = SIGN_LORDS[asc_s]
lord_lon = planet_lons.get(lord, 0)
lord_s = sign_of(lord_lon)
count = ((lord_s - asc_s) % 12) or 12
al_s = (lord_s + count) % 12
if al_s == asc_s or al_s == (asc_s + 6) % 12:
al_s = (al_s + 10) % 12
al_lon = al_s * 30 + (asc_lon % 30)
return {'longitude': norm(al_lon), 'sign_cn': SIGN_CN[al_s], 'lord': lord}
# ── Gulika ──
def calc_gulika_simple(dt_str):
dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M")
dn = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'][dt.weekday()]
is_day = 6 <= dt.hour < 18
gh = GULIKA_DAY.get(dn, 14) if is_day else GULIKA_NIGHT.get(dn, 14)
return {'ghatika': gh, 'minutes': gh*24, 'day': dn, 'is_daytime': is_day}
# ── Sphuta 组合 ──
def calc_sphutas(planet_lons, gulika_lon=0):
sun, moon = planet_lons.get('Sun',0), planet_lons.get('Moon',0)
rahu = planet_lons.get('Rahu',0)
tri = norm(sun + moon + gulika_lon)
catu = norm(tri + sun)
pancha = norm(catu + rahu)
return {'trisphuta': {'lon': tri, 'cn': lon_cn(tri)},
'catusphuta': {'lon': catu, 'cn': lon_cn(catu)},
'pancasphuta': {'lon': pancha, 'cn': lon_cn(pancha)}}
# ── Prana/Deha/Mrityu ──
def calc_life_sphutas(asc_lon, moon_lon, sun_lon, gulika_lon):
prana = norm(asc_lon * 5 + gulika_lon)
deha = norm(moon_lon * 8 + gulika_lon)
mrityu = norm(gulika_lon * 7 + sun_lon)
danger = norm(prana + deha) < mrityu
return {'prana_cn': lon_cn(prana), 'deha_cn': lon_cn(deha), 'mrityu_cn': lon_cn(mrityu),
'judgment': '⚠️ 生命能量<死亡指标' if danger else '✅ 生命能量>死亡指标'}
# ── Sahams ──
def calc_sahams(planet_lons, asc_lon):
asc_s = sign_of(asc_lon)
vals = dict(planet_lons)
vals['Asc'] = asc_lon
vals['AscLord'] = planet_lons.get(SIGN_LORDS[asc_s], 0)
vals['H2'] = (asc_s+1)%12*30+15; vals['H2Lord'] = planet_lons.get(SIGN_LORDS[(asc_s+1)%12], 0)
vals['H8'] = (asc_s+7)%12*30+15; vals['H9'] = (asc_s+8)%12*30+15
vals['H9Lord'] = planet_lons.get(SIGN_LORDS[(asc_s+8)%12], 0)
results = {}
for name, (cn, m, s, b) in SAHAM_DEFS.items():
mv = vals.get(m, 0); sv = vals.get(s, 0); bv = vals.get(b, asc_lon)
if name == 'Punya':
pass # Punya always computable
elif isinstance(mv, str) or isinstance(sv, str):
continue
try:
v = norm(mv - sv + bv)
results[name] = {'cn': cn, 'longitude': round(v,4), 'sign_cn': SIGN_CN[sign_of(v)]}
except: pass
return results
# ── Kunda 验证 ──
def kunda_verify(asc_lon):
mins = int(asc_lon * 60)
idx = (mins * 81) % 12
naks = ['Ashwini','Bharani','Krittika','Rohini','Mrigashira','Ardra',
'Punarvasu','Pushya','Ashlesha','Magha','P.Phalguni','U.Phalguni']
return {'derived_nakshatra': naks[idx] if idx < len(naks) else '?', 'index': idx}
# ── 失物分析 ──
def analyze_lost_item(planet_lons, asc_lon):
asc_s = sign_of(asc_lon)
h2_lord = SIGN_LORDS[(asc_s+1)%12]
h7_lord = SIGN_LORDS[(asc_s+6)%12]
h11_lord = SIGN_LORDS[(asc_s+10)%12]
h2_lon = planet_lons.get(h2_lord, 0)
h7_lon = planet_lons.get(h7_lord, 0)
h2_elem = (asc_s+1) % 4
dirs = {0:'东方(火象)', 1:'南方(土象)', 2:'西方(风象)', 3:'北方(水象)'}
h7_house = (sign_of(h7_lon) - asc_s) % 12 + 1
return {
'item_lord': h2_lord, 'item_cn': PLANET_CN.get(h2_lord,''),
'direction': dirs.get(h2_elem, '未知'),
'thief_lord': h7_lord, 'thief_cn': PLANET_CN.get(h7_lord,''),
'thief_in_house': h7_house,
'thief_type': '已知/附近' if h7_house in [1,4,7,10] else '隐秘/远方' if h7_house in [6,8,12] else '待定',
'recovery_lord': h11_lord, 'recovery_cn': PLANET_CN.get(h11_lord,'')
}
# ── 完整 Prashna 星盘 ──
def cast_prashna(dt_str, lat, lon):
try:
import swisseph as swe
swe.set_sid_mode(swe.SIDM_LAHIRI)
except ImportError:
return {'error': '需要 pip install pyswisseph'}
dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M")
jd = swe.julday(dt.year, dt.month, dt.day, dt.hour + dt.minute/60.0)
cusps, ascmc = swe.houses_ex(jd, lat, lon, b'W', swe.FLG_SIDEREAL)
asc_lon = ascmc[0]
p_lons = {}
se_map = {0:'Sun',1:'Moon',2:'Mars',3:'Mercury',4:'Jupiter',5:'Venus',6:'Saturn'}
for sid, name in se_map.items():
r = swe.calc_ut(jd, sid, swe.FLG_SIDEREAL)
p_lons[name] = r[0][0]
rahu = swe.calc_ut(jd, 8, swe.FLG_SIDEREAL)[0][0]
p_lons['Rahu'] = rahu; p_lons['Ketu'] = norm(rahu + 180)
arudha = calc_arudha(asc_lon, p_lons)
gulika = calc_gulika_simple(dt_str)
sphutas = calc_sphutas(p_lons, 0) # 简化无精确Gulika经度
life = calc_life_sphutas(asc_lon, p_lons['Moon'], p_lons['Sun'], 0)
sahams = calc_sahams(p_lons, asc_lon)
kunda = kunda_verify(asc_lon)
return {
'time': dt_str, 'lat': lat, 'lon': lon,
'ascendant': {'lon': round(asc_lon,4), 'cn': lon_cn(asc_lon),
'lord': SIGN_LORDS[sign_of(asc_lon)]},
'planets': {n: {'lon': round(l,4), 'cn': lon_cn(l)} for n,l in p_lons.items()},
'arudha_lagna': arudha,
'sphutas': sphutas,
'life_sphutas': life,
'sahams': sahams,
'kunda': kunda,
'gulika_info': gulika
}
# ── CLI ──
def main():
p = argparse.ArgumentParser(description='Prashna Shastra Engine')
sub = p.add_subparsers(dest='cmd')
# chart
ch = sub.add_parser('chart', help='铸造Prashna星盘')
ch.add_argument('--datetime', required=True, help='提问时间 YYYY-MM-DD HH:MM')
ch.add_argument('--lat', type=float, required=True)
ch.add_argument('--lon', type=float, required=True)
# arudha
ar = sub.add_parser('arudha', help='计算Arudha Lagna')
ar.add_argument('--asc-lon', type=float, required=True)
ar.add_argument('--planet-lons', required=True, help='JSON: {"Sun":123.4,...}')
# sphutas
sp = sub.add_parser('sphutas', help='计算Sphuta组合')
sp.add_argument('--planet-lons', required=True, help='JSON')
sp.add_argument('--gulika-lon', type=float, default=0)
# sahams
sa = sub.add_parser('sahams', help='计算Sahams')
sa.add_argument('--planet-lons', required=True, help='JSON')
sa.add_argument('--asc-lon', type=float, required=True)
# lost-item
li = sub.add_parser('lost-item', help='失物查询')
li.add_argument('--planet-lons', required=True, help='JSON')
li.add_argument('--asc-lon', type=float, required=True)
# life-sphutas
ls = sub.add_parser('life', help='生命Sphuta')
ls.add_argument('--asc-lon', type=float, required=True)
ls.add_argument('--moon-lon', type=float, required=True)
ls.add_argument('--sun-lon', type=float, required=True)
ls.add_argument('--gulika-lon', type=float, required=True)
args = p.parse_args()
if args.cmd == 'chart':
print(json.dumps(cast_prashna(args.datetime, args.lat, args.lon), ensure_ascii=False, indent=2))
elif args.cmd == 'arudha':
pl = json.loads(args.planet_lons)
print(json.dumps(calc_arudha(args.asc_lon, pl), ensure_ascii=False, indent=2))
elif args.cmd == 'sphutas':
pl = json.loads(args.planet_lons)
print(json.dumps(calc_sphutas(pl, args.gulika_lon), ensure_ascii=False, indent=2))
elif args.cmd == 'sahams':
pl = json.loads(args.planet_lons)
print(json.dumps(calc_sahams(pl, args.asc_lon), ensure_ascii=False, indent=2))
elif args.cmd == 'lost-item':
pl = json.loads(args.planet_lons)
print(json.dumps(analyze_lost_item(pl, args.asc_lon), ensure_ascii=False, indent=2))
elif args.cmd == 'life':
r = calc_life_sphutas(args.asc_lon, args.moon_lon, args.sun_lon, args.gulika_lon)
print(json.dumps(r, ensure_ascii=False, indent=2))
else:
p.print_help()
if __name__ == '__main__':
main()