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能力表+触发词
This commit is contained in:
@@ -1474,6 +1474,56 @@ def cmd_full_reading(args):
|
||||
return report
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 23. Prashna 问事占星 (v3.9新增)
|
||||
# ============================================================================
|
||||
def cmd_prashna(args):
|
||||
"""Prashna 问事占星:基于提问时刻的即时星盘分析"""
|
||||
try:
|
||||
from prashna import cast_prashna, calc_arudha, calc_sphutas, calc_life_sphutas, calc_sahams, analyze_lost_item, kunda_verify, calc_gulika_simple
|
||||
except ImportError:
|
||||
# 尝试从同目录导入
|
||||
import importlib.util, os
|
||||
spec = importlib.util.spec_from_file_location("prashna", os.path.join(os.path.dirname(__file__), "prashna.py"))
|
||||
prashna_mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(prashna_mod)
|
||||
cast_prashna = prashna_mod.cast_prashna
|
||||
calc_arudha = prashna_mod.calc_arudha
|
||||
calc_sphutas = prashna_mod.calc_sphutas
|
||||
calc_life_sphutas = prashna_mod.calc_life_sphutas
|
||||
calc_sahams = prashna_mod.calc_sahams
|
||||
analyze_lost_item = prashna_mod.analyze_lost_item
|
||||
kunda_verify = prashna_mod.kunda_verify
|
||||
calc_gulika_simple = prashna_mod.calc_gulika_simple
|
||||
|
||||
if args.mode == 'chart':
|
||||
return cast_prashna(args.datetime, args.lat, args.lon)
|
||||
|
||||
# 其他模式需要先铸盘获取行星位置
|
||||
chart = cast_prashna(args.datetime, args.lat, args.lon)
|
||||
if 'error' in chart:
|
||||
return chart
|
||||
|
||||
asc_lon = chart['ascendant']['lon']
|
||||
p_lons = {n: d['lon'] for n, d in chart['planets'].items()}
|
||||
|
||||
if args.mode == 'arudha':
|
||||
return {'arudha_lagna': calc_arudha(asc_lon, p_lons),
|
||||
'ascendant': chart['ascendant']}
|
||||
elif args.mode == 'sphutas':
|
||||
return calc_sphutas(p_lons, 0)
|
||||
elif args.mode == 'sahams':
|
||||
return calc_sahams(p_lons, asc_lon)
|
||||
elif args.mode == 'lost-item':
|
||||
return analyze_lost_item(p_lons, asc_lon)
|
||||
elif args.mode == 'life':
|
||||
return calc_life_sphutas(asc_lon, p_lons.get('Moon',0), p_lons.get('Sun',0), 0)
|
||||
elif args.mode == 'kunda':
|
||||
return kunda_verify(asc_lon)
|
||||
else:
|
||||
return cast_prashna(args.datetime, args.lat, args.lon)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CLI入口
|
||||
# ============================================================================
|
||||
@@ -1608,6 +1658,13 @@ def main():
|
||||
_add_chart_args(p)
|
||||
p.add_argument('--age', type=int, default=None, help='当前年龄(不提供则自动计算)')
|
||||
|
||||
# 23. prashna (v3.9新增)
|
||||
p = sub.add_parser('prashna', help='Prashna问事占星(提问时刻星盘+Arudha+Sphuta+Sahams)')
|
||||
p.add_argument('--datetime', required=True, help='提问时间 YYYY-MM-DD HH:MM')
|
||||
p.add_argument('--lat', type=float, required=True, help='纬度')
|
||||
p.add_argument('--lon', type=float, required=True, help='经度')
|
||||
p.add_argument('--mode', default='chart', choices=['chart','arudha','sphutas','sahams','lost-item','life','kunda'], help='分析模式')
|
||||
|
||||
args = parser.parse_args()
|
||||
if not args.command:
|
||||
parser.print_help(); sys.exit(1)
|
||||
@@ -1618,7 +1675,7 @@ def main():
|
||||
'validate': cmd_validate, 'audit': cmd_audit, 'report': cmd_report,
|
||||
'varga-full': cmd_varga_full, 'aspects': cmd_aspects, 'jaimini': cmd_jaimini,
|
||||
'nakshatra-adv': cmd_nakshatra_adv, 'argala': cmd_argala, 'tajika': cmd_tajika,
|
||||
'synastry': cmd_synastry, 'full-reading': cmd_full_reading}
|
||||
'synastry': cmd_synastry, 'full-reading': cmd_full_reading, 'prashna': cmd_prashna}
|
||||
result = cmds[args.command](args)
|
||||
output_json(result)
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user