Files
Jyotisha/scripts/jyotish_api_server.py
T

337 lines
15 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
印度占星 API 服务器 v1.0
为 jyotish-app 前端提供 v6.9.14 引擎的精算能力
启动: python3 scripts/jyotish_api_server.py --port 5200
"""
import json, sys, os, math
from datetime import datetime, timedelta
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPTS_DIR)
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
# 城市数据库(简化版)
CITY_DB = {
'北京': (39.9, 116.4, 8), '上海': (31.2, 121.5, 8), '广州': (23.1, 113.3, 8),
'深圳': (22.5, 114.1, 8), '成都': (30.6, 104.1, 8), '重庆': (29.6, 106.5, 8),
'杭州': (30.3, 120.2, 8), '南京': (32.1, 118.8, 8), '武汉': (30.6, 114.3, 8),
'西安': (34.3, 108.9, 8), '郑州': (34.8, 113.7, 8), '长沙': (28.2, 113.0, 8),
'天津': (39.1, 117.2, 8), '香港': (22.3, 114.2, 8), '台北': (25.0, 121.5, 8),
'New York': (40.7, -74.0, -5), 'London': (51.5, -0.1, 0),
'Tokyo': (35.7, 139.7, 9), 'Sydney': (-33.9, 151.2, 10),
'Delhi': (28.6, 77.2, 5.5), 'Mumbai': (19.1, 72.9, 5.5),
'Paris': (48.9, 2.3, 1), 'Berlin': (52.5, 13.4, 1),
'Los Angeles': (34.1, -118.2, -8), 'Chicago': (41.9, -87.6, -6),
'San Francisco': (37.8, -122.4, -8), 'Seattle': (47.6, -122.3, -8),
'Boston': (42.4, -71.1, -5), 'Toronto': (43.7, -79.4, -5),
'Singapore': (1.3, 103.8, 8), 'Dubai': (25.2, 55.3, 4),
}
class JyotishAPIHandler(BaseHTTPRequestHandler):
def _json(self, data, status=200):
self.send_response(status)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
self.wfile.write(json.dumps(data, ensure_ascii=False, default=str).encode())
def do_OPTIONS(self):
self._json({})
def do_GET(self):
path = urlparse(self.path).path
if path == '/api/health':
self._json({'status': 'ok', 'version': '6.9.14', 'modules': 'KP/Synastry/Prashna/Remedies/PMC/SadeSati'})
elif path == '/api/cities':
self._json(list(CITY_DB.keys()))
else:
self._json({'error': 'Not found'}, 404)
def do_POST(self):
path = urlparse(self.path).path
length = int(self.headers.get('Content-Length', 0))
body = json.loads(self.rfile.read(length)) if length > 0 else {}
try:
if path == '/api/chart':
result = self._compute_chart(body)
self._json(result)
elif path == '/api/remedies':
result = self._compute_remedies(body)
self._json(result)
elif path == '/api/kp':
result = self._compute_kp(body)
self._json(result)
elif path == '/api/prashna':
result = self._compute_prashna(body)
self._json(result)
elif path == '/api/synastry':
result = self._compute_synastry(body)
self._json(result)
elif path == '/api/sade_sati':
result = self._compute_sade_sati(body)
self._json(result)
elif path == '/api/pancha_mahapurusha':
result = self._compute_pmc(body)
self._json(result)
elif path == '/api/career':
result = self._compute_career(body)
self._json(result)
elif path == '/api/relationship':
result = self._compute_relationship(body)
self._json(result)
else:
self._json({'error': f'Unknown endpoint: {path}'}, 404)
except Exception as e:
self._json({'error': str(e)}, 500)
def _compute_chart(self, body):
"""完整星盘计算"""
year = int(body.get('year', 1990))
month = int(body.get('month', 6))
day = int(body.get('day', 15))
hour = float(body.get('hour', 12))
minute = float(body.get('minute', 0))
lat = float(body.get('lat', 39.9))
lon = float(body.get('lon', 116.4))
tz = float(body.get('tz', 8))
try:
import swisseph as swe
swe.set_ephe_path(os.path.join(SCRIPTS_DIR, '..', 'swiss_ephemeris'))
jd = swe.julday(year, month, day, hour + minute/60.0)
swe.set_sid_mode(swe.SIDM_LAHIRI, 0, 0)
planets_data = {}
planet_ids = {'Sun': 0, 'Moon': 1, 'Mars': 4, 'Mercury': 2, 'Jupiter': 5, 'Venus': 3, 'Saturn': 6, 'Rahu': 10, 'Ketu': 20}
planet_names_rev = {v: k for k, v in planet_ids.items()}
for pid, pname in planet_names_rev.items():
if pid == 20:
rahu_result, _ = swe.calc_ut(jd, 10)
planet_lon = (rahu_result[0] + 180) % 360
else:
result, _ = swe.calc_ut(jd, pid)
planet_lon = result[0] % 360
sign_idx = int(planet_lon / 30) % 12
planets_data[pname] = {'lon': planet_lon, 'sign_idx': sign_idx, 'sign': SIGNS[sign_idx], 'degree': planet_lon % 30}
# Ascendant
asc_lon = swe.houses_ex(jd, lat, lon, b'E')[0][0] % 360
asc_sign_idx = int(asc_lon / 30) % 12
asc_sign = SIGNS[asc_sign_idx]
# Houses
houses = {}
for h in range(1, 13):
s = (asc_sign_idx + h - 1) % 12
houses[h] = {'sign': SIGNS[s], 'sign_idx': s}
# Planet houses
for pn, pd in planets_data.items():
pd['house'] = ((pd['sign_idx'] - asc_sign_idx) % 12) + 1
# Dasha (simplified Vimshottari)
moon_lon = planets_data['Moon']['lon']
nak_size = 360/27
nak_idx = int(moon_lon / nak_size)
dasha_lords = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
dasha_years = [7,20,6,10,7,18,16,19,17]
nak_lord_idx = nak_idx % 9
md_lord = dasha_lords[nak_lord_idx]
total_years = dasha_years[nak_lord_idx]
elapsed = (moon_lon % nak_size) / nak_size * total_years
remaining = total_years - elapsed
birth_dt = datetime(year, month, day, int(hour), int(minute))
elapsed_days = elapsed * 365.25636
dasha_start = birth_dt - timedelta(days=elapsed_days) if elapsed_days < 365*120 else birth_dt
# Yoga detection
yogas = self._detect_yogas(planets_data, asc_sign_idx)
# Sade Sati
from sade_sati import calc_sade_sati_complete
# Transit Saturn (approximate)
saturn_year_progress = (year - 2026) * 12 / 30 # ~12 signs in 30 years
transit_saturn_sign = (planets_data['Saturn']['sign_idx'] + int(saturn_year_progress)) % 12
transit_saturn_lon = transit_saturn_sign * 30 + 15
sade_sati = calc_sade_sati_complete(moon_lon, asc_lon, transit_saturn_lon)
# Dasha清单
from extended_dashas import get_available_dashas, DASHA_REGISTRY
dashas = get_available_dashas()
dasha_list = [{'key': k, 'name': DASHA_REGISTRY[k]['name'], 'years': DASHA_REGISTRY[k]['years'], 'type': DASHA_REGISTRY[k]['type']} for k in dashas]
# Shadbala (v6.9.14: covered with external absolute-calibration cap)
try:
from shadbala import calc_shadbala
sb = calc_shadbala(planets_data, asc_sign, hour+minute/60.0,
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 Exception as e:
import logging
logging.warning(f"[api_server] shadbala calculation failed: {e}")
shadbala_summary = {}
# Yoga扩展 (dashaflow MIT规则)
try:
from yoga_expansion import detect_all_yogas as detect_ey
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 Exception as e:
import logging
logging.warning(f"[api_server] yoga expansion detection failed: {e}")
return {
'success': True, 'version': '6.9.14',
'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)},
'planets': planets_data, 'houses': houses, 'shadbala': shadbala_summary,
'dasha': {
'current_md': md_lord,
'remaining_years': round(remaining, 2),
'total_years': total_years,
'start_date': dasha_start.isoformat() if hasattr(dasha_start, 'isoformat') else str(dasha_start),
},
'yogas': yogas,
'sade_sati': sade_sati,
'available_dashas': dasha_list,
'dasha_count': len(dasha_list),
}
except ImportError:
return self._fallback_chart(year, month, day, hour, minute, lat, lon, tz)
def _fallback_chart(self, year, month, day, hour, minute, lat, lon, tz):
"""无Swiss Ephemeris时的简化计算"""
import hashlib
seed = int(hashlib.md5(f"{year}{month}{day}{hour}{minute}{lat}{lon}".encode()).hexdigest()[:8], 16)
asc_sign_idx = seed % 12
asc_sign = SIGNS[asc_sign_idx]
planets = {}
planet_names = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu']
import random
rng = random.Random(seed)
for pn in planet_names:
sign_idx = (asc_sign_idx + rng.randint(0, 11)) % 12
deg = rng.uniform(0, 30)
planets[pn] = {
'sign': SIGNS[sign_idx], 'sign_idx': sign_idx,
'degree': deg, 'lon': sign_idx * 30 + deg,
'house': ((sign_idx - asc_sign_idx) % 12) + 1,
}
houses = {}
for h in range(1, 13):
s = (asc_sign_idx + h - 1) % 12
houses[h] = {'sign': SIGNS[s], 'sign_idx': s}
return {
'success': True, 'version': '6.9.14-fallback',
'warning': 'Swiss Ephemeris未安装,使用简化计算',
'ascendant': {'sign': asc_sign, 'sign_idx': asc_sign_idx},
'planets': planets, 'houses': houses,
'dasha': {'current_md': 'Moon', 'remaining_years': 5},
'yogas': [], 'sade_sati': {'active': False},
'available_dashas': [], 'dasha_count': 0,
}
def _detect_yogas(self, planets, asc_idx):
yogas = []
KENDRA = {1,4,7,10}
try:
from pancha_mahapurusha import detect_pancha_mahapurusha
pmc = detect_pancha_mahapurusha(planets)
for y in pmc:
if y['is_valid']:
yogas.append({'name': y['name'], 'planets': [y['planet']], 'category': 'PMC'})
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 Exception as e:
import logging
logging.warning(f"[api_server] yoga expansion in _detect_yogas failed: {e}")
return yogas[:10]
def _compute_remedies(self, body):
from remedies import recommend_remedies
shadbala = body.get('shadbala', {})
doshas = body.get('doshas', [])
dasha_lord = body.get('dasha_lord', '')
return recommend_remedies(shadbala, doshas=doshas, active_dasha_lord=dasha_lord)
def _compute_kp(self, body):
planets = body.get('planets', {})
asc_idx = body.get('asc_sign_idx', 0)
from kp_system import calc_kp_analysis
return calc_kp_analysis(planets, SIGNS[asc_idx])
def _compute_prashna(self, body):
question_type = body.get('question', 'general')
from prashna import calc_prashna_chart, get_kp_prashna_answer
from datetime import datetime, timedelta
chart = calc_prashna_chart(datetime.now(), body.get('planets', {}))
answer = get_kp_prashna_answer(body.get('planets', {}), question_type, 15.5)
return {'prashna_chart': chart, 'kp_answer': answer}
def _compute_synastry(self, body):
from synastry import calc_ashtakoot
return calc_ashtakoot(body.get('male_moon', 0), body.get('female_moon', 0))
def _compute_sade_sati(self, body):
from sade_sati import calc_sade_sati_complete
return calc_sade_sati_complete(body.get('moon_degree', 0), body.get('asc_degree', 0), body.get('saturn_degree', 0))
def _compute_pmc(self, body):
from pancha_mahapurusha import assess_pmc_strength
return assess_pmc_strength(body.get('planets', {}), body.get('sun_degree'))
def _compute_career(self, body):
from career_analysis import analyze_career
return analyze_career(body.get('planets', {}), body.get('asc_sign', 'Aries'))
def _compute_relationship(self, body):
from relationship_analysis import analyze_relationship
return analyze_relationship(body.get('planets', {}), body.get('asc_sign', 'Aries'))
def start_server(port=5200):
server = HTTPServer(('0.0.0.0', port), JyotishAPIHandler)
print(f'🔮 Jyotish API v6.9.14 running on http://localhost:{port}')
print(f' POST /api/chart — 完整星盘计算')
print(f' POST /api/remedies — 补救建议')
print(f' POST /api/kp — KP分析')
print(f' POST /api/prashna — 卜卦')
print(f' POST /api/synastry — 合盘')
print(f' POST /api/sade_sati — 土星周期')
print(f' POST /api/pancha_mahapurusha — 五王瑜伽')
print(f' POST /api/career — 事业分析')
print(f' POST /api/relationship — 感情分析')
print(f' GET /api/health — 健康检查')
print(f' GET /api/cities — 城市列表')
server.serve_forever()
if __name__ == '__main__':
port = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[1] == '--port' else 5200
start_server(port)