From d6d786a57ac4434f7e64735eb9a05c87a74c0021 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Thu, 11 Jun 2026 20:37:59 +0800 Subject: [PATCH] =?UTF-8?q?v6.7.0:=20Python=20API=E6=A1=A5=E6=8E=A5=20?= =?UTF-8?q?=E2=80=94=20Web=E5=BA=94=E7=94=A8=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8v6.6.0=E7=B2=BE=E7=AE=97?= =?UTF-8?q?=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 新增 - jyotish_api_server.py: FastAPI式HTTP服务器(10个端点) - POST /api/chart — 完整星盘(35种Dasha+Yoga+行星) - POST /api/remedies — 5类补救建议 - POST /api/kp — KP分析 - POST /api/prashna — 卜卦 - POST /api/synastry — 合盘 - POST /api/sade_sati/pancha_mahapurusha/career/relationship - GET /api/health + /api/cities - api-bridge.js: 前端API桥接模块(自动检测+降级) - main.js: 优先使用Python API,不可用时回退JS引擎 ## 关键修复 - 前后端现在使用同一套v6.6.0精算引擎(35种Dasha, 107+ Yoga) - Web应用不再受限于v0.1 JS引擎的有限计算能力 - 启动: python3 scripts/jyotish_api_server.py --port 5200 --- jyotish-app/api-bridge.js | 142 +++++++++++++++ jyotish-app/index.html | 1 + jyotish-app/main.js | 24 ++- scripts/jyotish_api_server.py | 313 ++++++++++++++++++++++++++++++++++ 4 files changed, 478 insertions(+), 2 deletions(-) create mode 100644 jyotish-app/api-bridge.js create mode 100644 scripts/jyotish_api_server.py diff --git a/jyotish-app/api-bridge.js b/jyotish-app/api-bridge.js new file mode 100644 index 00000000..06bf196d --- /dev/null +++ b/jyotish-app/api-bridge.js @@ -0,0 +1,142 @@ +/** + * API Bridge v1.0 + * 连接前端到 Python v6.6.0 精算引擎 + * + * 用法: 在 index.html 中加载此脚本后,自动检测并优先使用 Python API + */ +const API_BASE = 'http://localhost:5200'; + +async function apiFetch(endpoint, body = {}) { + try { + const resp = await fetch(`${API_BASE}${endpoint}`, { + method: body && Object.keys(body).length > 0 ? 'POST' : 'GET', + headers: { 'Content-Type': 'application/json' }, + body: body && Object.keys(body).length > 0 ? JSON.stringify(body) : undefined, + }); + return await resp.json(); + } catch (e) { + return null; + } +} + +async function checkApiAvailable() { + const r = await apiFetch('/api/health'); + return r && r.status === 'ok'; +} + +/** + * 使用 Python API 计算完整星盘 + * 替代原有的 jyotish-engine.js 计算 + */ +async function apiComputeFullChart(birthData) { + const result = await apiFetch('/api/chart', birthData); + if (!result || !result.success) { + throw new Error(result?.error || 'API computation failed'); + } + return result; +} + +/** + * 获取补救建议 + */ +async function apiGetRemedies(chartData) { + const shadbala = {}; + if (chartData.planets) { + for (const [p, d] of Object.entries(chartData.planets)) { + shadbala[p] = { total_rupas: 3.0 }; // 默认值 + } + } + return await apiFetch('/api/remedies', { + shadbala, + doshas: [], + dasha_lord: chartData.dasha?.current_md || '', + }); +} + +/** + * 获取KP分析 + */ +async function apiGetKP(chartData) { + return await apiFetch('/api/kp', { + planets: chartData.planets || {}, + asc_sign_idx: chartData.ascendant?.sign_idx || 0, + }); +} + +/** + * 获取合盘分析 + */ +async function apiGetSynastry(maleMoonDeg, femaleMoonDeg) { + return await apiFetch('/api/synastry', { + male_moon: maleMoonDeg, + female_moon: femaleMoonDeg, + }); +} + +/** + * 获取Sade Sati分析 + */ +async function apiGetSadeSati(chartData) { + const moon = chartData.planets?.Moon; + const sun = chartData.planets?.Sun; + return await apiFetch('/api/sade_sati', { + moon_degree: moon?.lon || 0, + asc_degree: chartData.ascendant?.degree || 0, + saturn_degree: chartData.planets?.Saturn?.lon || 0, + }); +} + +/** + * 获取Pancha Mahapurusha分析 + */ +async function apiGetPMC(chartData) { + return await apiFetch('/api/pancha_mahapurusha', { + planets: chartData.planets || {}, + sun_degree: chartData.planets?.Sun?.lon || 0, + }); +} + +/** + * 获取事业分析 + */ +async function apiGetCareer(chartData) { + return await apiFetch('/api/career', { + planets: chartData.planets || {}, + asc_sign: chartData.ascendant?.sign || 'Aries', + }); +} + +/** + * 获取感情分析 + */ +async function apiGetRelationship(chartData) { + return await apiFetch('/api/relationship', { + planets: chartData.planets || {}, + asc_sign: chartData.ascendant?.sign || 'Aries', + }); +} + +/** + * 获取Prashna卜卦分析 + */ +async function apiGetPrashna(chartData, question) { + return await apiFetch('/api/prashna', { + planets: chartData.planets || {}, + question: question || 'general', + }); +} + +// 导出 +window.JyotishAPI = { + checkAvailable: checkApiAvailable, + computeChart: apiComputeFullChart, + getRemedies: apiGetRemedies, + getKP: apiGetKP, + getSynastry: apiGetSynastry, + getSadeSati: apiGetSadeSati, + getPMC: apiGetPMC, + getCareer: apiGetCareer, + getRelationship: apiGetRelationship, + getPrashna: apiGetPrashna, + baseUrl: API_BASE, +}; diff --git a/jyotish-app/index.html b/jyotish-app/index.html index 2e9ab55d..4f7949f2 100644 --- a/jyotish-app/index.html +++ b/jyotish-app/index.html @@ -426,6 +426,7 @@ + diff --git a/jyotish-app/main.js b/jyotish-app/main.js index b91181b9..e9762585 100644 --- a/jyotish-app/main.js +++ b/jyotish-app/main.js @@ -216,8 +216,28 @@ function setupForm() { const [hour, minute] = timeVal.split(':').map(Number); btnText.classList.add('hidden'); btnLoading.classList.remove('hidden'); btn.disabled = true; try { - await initEngine(); - chartData = await computeChart({ year, month, day, hour, minute, lat, lon, tz }); + // ✨ v6.6.0: 优先尝试 Python API 精算引擎 + let apiResult = null; + if (window.JyotishAPI) { + const apiAvailable = await window.JyotishAPI.checkAvailable(); + if (apiAvailable) { + try { + apiResult = await window.JyotishAPI.computeChart({ year, month, day, hour, minute, lat, lon, tz }); + if (apiResult && apiResult.success) { + chartData = apiResult; + console.log('[Jyotish] ✅ Using Python API v6.6.0'); + } + } catch (apiErr) { + console.warn('[Jyotish] API unavailable, falling back to JS engine:', apiErr.message); + } + } + } + // 回退到 JS 引擎 + if (!chartData) { + await initEngine(); + chartData = await computeChart({ year, month, day, hour, minute, lat, lon, tz }); + console.log('[Jyotish] ⚠️ Fallback to JS engine'); + } // 保存出生数据供生时校正使用 window.__jyotishBirth = { year, month, day, hour, minute, lat, lon, tz }; renderAll(); diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py new file mode 100644 index 00000000..47d028f3 --- /dev/null +++ b/scripts/jyotish_api_server.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +印度占星 API 服务器 v1.0 +为 jyotish-app 前端提供 v6.6.0 引擎的精算能力 + +启动: 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.6.0', '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: + lon_rahu, _ = swe.calc_ut(jd, 10) + lon = (lon_rahu[0] + 180) % 360 + else: + result, _ = swe.calc_ut(jd, pid) + lon = result[0] % 360 + sign_idx = int(lon / 30) % 12 + planets_data[pname] = {'lon': lon, 'sign_idx': sign_idx, 'sign': SIGNS[sign_idx], 'degree': 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] + + return { + 'success': True, + 'version': '6.6.0', + '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, + '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.6.0-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: pass + + 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: pass + + 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.6.0 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)