th
Fix BaZi timezone and historical daylight-saving calculation
40d3e55
Raw History Blame Contribute Delete
163 kB
""" 🔮 AstrologyBazi API - Complete Eastern Mysticism Platform BaZi (八字) | Nine Flying Stars (九宫飞星) | 28 Lunar Mansions (二十八星宿) | Chinese Calendar (农历) Powering https://astrologybazi.com 🔒 Protected with API Key Authentication """ from birth_time_context import BirthTimeRequest from fastapi import FastAPI, HTTPException, Header, Depends from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field, field_validator from typing import Optional, Dict, List, Any import logging import os from datetime import date, datetime, timedelta from dotenv import load_dotenv # 🔧 Load environment variables from .env file load_dotenv() from bazi_calculator import BaZiCalculator from lunar_python import Solar, Lunar from nine_stars import NineStarsCalculator from constellation_28 import Constellation28Calculator from shensha_analyzer import ShenshaAnalyzer from genju_analyzer import GenjuAnalyzer from dayun_analyzer import DayunAnalyzer from evidence import build_evidence, build_timing, build_meta, select_dayun_score_preview from dayun_dynamic_assessment import build_dayun_dynamic_assessment from ai_interpreter import AIInterpreter from time_fortune_analyzer import TimeFortuneAnalyzer from fortune_score_calculator import FortuneScoreCalculator # 🎯 Phase 3 新增模块 (2025-12-24) from marriage_analyzer import MarriageAnalyzer from career_analyzer import CareerAnalyzer from wealth_analyzer import WealthAnalyzer from health_analyzer import HealthAnalyzer from official_analyzer import OfficialAnalyzer # 官职分析 from legal_risk_analyzer import LegalRiskAnalyzer # 法律风险分析 from marriage_compatibility_analyzer import MarriageCompatibilityAnalyzer # 八字合婚 # 🌟 Rituals Service (2026-01-25) from rituals_service import rituals_service # 🖐️ Body Reading Analyzer (2026-02-16) from body_reading_analyzer import analyze_body_reading # 💕 Love Clarity Analyzer (2026-02-28) from love_clarity_analyzer import LoveClarityAnalyzer # 🌀 Qimen Dunjia Calculator (2026-04-14) from qimen_calculator import QimenCalculator # 🔮 Liu Yao Calculator (2026-04-17) from liuyao_calculator import calculate_liuyao logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 🔐 API Key Configuration # Support both naming conventions: # - API_KEY: For HuggingFace Space settings (simple, since only one API key) # - SISI_HF_API_KEY: For Cloudflare (clear distinction among many API keys) # Fail closed: no hardcoded fallback — if neither env var is set, all # protected endpoints refuse service instead of accepting a default key. API_KEY = os.environ.get("API_KEY") or os.environ.get("SISI_HF_API_KEY") if not API_KEY: logger.error( "🔒 API key is NOT configured (set API_KEY or SISI_HF_API_KEY). " "All protected endpoints will return 503 until a key is set." ) def verify_api_key(x_api_key: Optional[str] = Header(None)): """Verify API key from request header (fail closed when unconfigured)""" if not API_KEY: raise HTTPException( status_code=503, detail="🔒 API key is not configured on the server. Service unavailable." ) if x_api_key != API_KEY: raise HTTPException( status_code=403, detail="🔒 Invalid or missing API key. Please contact SisiTheFox for access." ) return x_api_key # ==================== Chinese Calendar Service ==================== class ChineseCalendarService: """Service for Chinese lunar calendar operations""" # 地支到生肖的映射 ZHI_TO_ZODIAC = { '子': '鼠', '丑': '牛', '寅': '虎', '卯': '兔', '辰': '龙', '巳': '蛇', '午': '马', '未': '羊', '申': '猴', '酉': '鸡', '戌': '狗', '亥': '猪' } # 生肖emoji映射 ZODIAC_EMOJI = { '鼠': '🐀', '牛': '🐂', '虎': '🐅', '兔': '🐇', '龙': '🐉', '蛇': '🐍', '马': '🐎', '羊': '🐑', '猴': '🐒', '鸡': '🐓', '狗': '🐕', '猪': '🐖' } # 天神吉凶分类(传统六黄道六黑道) # 六黄道(吉):青龙、明堂、金匮、天德、玉堂、司命 # 六黑道(凶):白虎、天刑、朱雀、天牢、玄武、勾陈 JI_SHEN_LIST = ["青龙", "明堂", "金匮", "天德", "玉堂", "司命"] XIONG_SHEN_LIST = ["天刑", "白虎", "朱雀", "天牢", "玄武", "勾陈"] @staticmethod def get_today_calendar() -> Dict: """Get today's Chinese lunar calendar information""" today = datetime.now().date() return ChineseCalendarService.get_calendar_by_date(today) @staticmethod def get_calendar_by_date(target_date: date) -> Dict: """Get Chinese lunar calendar information for a specific date""" try: solar = Solar.fromYmd(target_date.year, target_date.month, target_date.day) lunar = solar.getLunar() lunar_year = lunar.getYear() lunar_month = lunar.getMonth() lunar_day = lunar.getDay() lunar_year_cn = lunar.getYearInChinese() lunar_month_cn = lunar.getMonthInChinese() lunar_day_cn = lunar.getDayInChinese() year_ganzhi = lunar.getYearInGanZhi() month_ganzhi = lunar.getMonthInGanZhi() day_ganzhi = lunar.getDayInGanZhi() zodiac = lunar.getYearShengXiao() weekday_num = target_date.weekday() weekdays_cn = ['一', '二', '三', '四', '五', '六', '日'] weekday_cn = f"星期{weekdays_cn[weekday_num]}" almanac = ChineseCalendarService._get_almanac_for_date(lunar) auspicious_hours = ChineseCalendarService._get_auspicious_hours(lunar) solar_term = ChineseCalendarService._get_solar_term(lunar) festival = ChineseCalendarService._get_festival(target_date, lunar) return { "success": True, "date": str(target_date), "weekday": weekday_cn, "solar": { "year": target_date.year, "month": target_date.month, "day": target_date.day, "date_string": f"{target_date.year}年{target_date.month}月{target_date.day}日" }, "lunar": { "year": lunar_year, "month": lunar_month, "day": lunar_day, "year_cn": lunar_year_cn, "month_cn": lunar_month_cn, "day_cn": lunar_day_cn, "date_string": f"{lunar_year_cn}{lunar_month_cn}{lunar_day_cn}", "is_leap": lunar.getMonth() < 0 }, "ganzhi": { "year": year_ganzhi, "month": month_ganzhi, "day": day_ganzhi, "full": f"{year_ganzhi}年 {month_ganzhi}月 {day_ganzhi}日" }, "zodiac": { "animal": zodiac, "animal_cn": zodiac }, "almanac": almanac, "auspicious_hours": auspicious_hours, "solar_term": solar_term, "festival": festival, "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"Error getting calendar for {target_date}: {str(e)}") return { "success": False, "error": str(e), "date": str(target_date) } @staticmethod def get_calendar_range(start_date: date, end_date: date) -> List[Dict]: """Get calendar information for a date range""" if (end_date - start_date).days > 365: raise ValueError("Date range cannot exceed 365 days") calendars = [] current_date = start_date while current_date <= end_date: calendar = ChineseCalendarService.get_calendar_by_date(current_date) calendars.append(calendar) current_date += timedelta(days=1) return calendars @staticmethod def _get_almanac_for_date(lunar: Lunar) -> Dict: """Get authentic almanac data (宜忌) from lunar-python""" try: # 获取真实的宜忌 yi = lunar.getDayYi() ji = lunar.getDayJi() # 获取冲煞信息 chong_gan = lunar.getChongGan() chong_zhi = lunar.getChong() sha = lunar.getSha() # 转换冲的地支为生肖 chong_zodiac = ChineseCalendarService.ZHI_TO_ZODIAC.get(chong_zhi, chong_zhi) chong_emoji = ChineseCalendarService.ZODIAC_EMOJI.get(chong_zodiac, '') # 组合完整的冲煞格式:"冲🐑羊(丁未)煞东" chong_ganzhi = chong_gan + chong_zhi chong_sha_full = f"冲{chong_emoji}{chong_zodiac}({chong_ganzhi})煞{sha}" # 获取吉神宜趋和凶煞宜忌 ji_shen = lunar.getDayJiShen() xiong_sha = lunar.getDayXiongSha() # 获取彭祖百忌 pengzu_gan = lunar.getPengZuGan() pengzu_zhi = lunar.getPengZuZhi() return { "appropriate": yi, "avoid": ji, "chong": chong_zhi, # 保留原始地支供兼容 "sha": sha, # 保留原始煞向供兼容 "chong_sha": chong_sha_full, # 新增:完整格式"冲🐑羊(丁未)煞东" "ji_shen": ji_shen, # 吉神宜趋 "xiong_sha": xiong_sha, # 凶煞宜忌 "pengzu": { "gan": pengzu_gan, "zhi": pengzu_zhi } } except Exception as e: logger.error(f"Error getting almanac data: {e}") # 返回空数据而不是报错 return { "appropriate": [], "avoid": [], "chong": "", "sha": "", "chong_sha": "", "ji_shen": [], "xiong_sha": [], "pengzu": {"gan": "", "zhi": ""} } @staticmethod def _get_auspicious_hours(lunar: Lunar) -> List[Dict]: """Get 12-hour fortune analysis (十二时辰吉凶)""" try: times = lunar.getTimes() hours = [] # 只取前12个时辰(lunar-python可能返回13个,包含下一天的子时) for i, t in enumerate(times[:12]): time_ganzhi = t.getGanZhi() tian_shen = t.getTianShen() chong = t.getChong() sha = t.getSha() # 判断吉凶(六黄道为吉,六黑道为凶) if tian_shen in ChineseCalendarService.JI_SHEN_LIST: fortune = "吉" elif tian_shen in ChineseCalendarService.XIONG_SHEN_LIST: fortune = "凶" else: fortune = "凶" # 未知天神默认为凶 # 时间段 start_hour = i * 2 end_hour = i * 2 + 1 period = f"{start_hour:02d}:00-{end_hour:02d}:59" hours.append({ "time": f"{time_ganzhi}时", "period": period, "ganzhi": time_ganzhi, "tian_shen": tian_shen, "fortune": fortune, "chong": chong, "sha": sha }) return hours except Exception as e: logger.error(f"Error getting auspicious hours: {e}") return [] @staticmethod def _get_solar_term(lunar: Lunar) -> Optional[str]: """Get solar term (节气)""" try: jieqi = lunar.getJieQi() return jieqi if jieqi else None except: return None @staticmethod def _get_festival(target_date: date, lunar: Lunar) -> Optional[str]: """Get festival name""" try: solar_festivals = lunar.getFestivals() if solar_festivals: return ", ".join(solar_festivals) lunar_festivals = lunar.getOtherFestivals() if lunar_festivals: return ", ".join(lunar_festivals) return None except: return None app = FastAPI( title="AstrologyBazi API", version="2.1.1", description="🔮 Complete Eastern Mysticism API - BaZi Fortune Analysis (Phases 1-5), Nine Flying Stars, 28 Lunar Mansions, Chinese Calendar" ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class BaZiRequest(BirthTimeRequest): """Request model for BaZi calculation""" birth_date: str = Field( ..., description="Birth date in YYYY-MM-DD format (e.g., '1990-01-15')", example="1990-01-15" ) birth_time: str = Field( ..., description="Birth time in HH:MM format (e.g., '08:30')", example="08:30" ) gender: str = Field( default="male", description="Gender: 'male' or 'female'", example="male" ) is_lunar: bool = Field( default=False, description="True if birth_date is lunar calendar, False for solar/Gregorian", example=False ) class BaZiCompleteRequest(BirthTimeRequest): """Request model for complete BaZi calculation with 大运/流年""" birth_date: str = Field( ..., description="Birth date in YYYY-MM-DD format", example="1990-01-15" ) birth_time: str = Field( ..., description="Birth time in HH:MM format", example="08:30" ) gender: str = Field( default="male", description="Gender: 'male' or 'female'", example="male" ) is_lunar: bool = Field( default=False, description="True if lunar calendar", example=False ) num_dayun: int = Field( default=12, description="Number of 大运 periods (each 10 years, default 12 = 120 years)", example=12 ) class BaZiInterpretationRequest(BirthTimeRequest): """Request model for AI interpretation of BaZi""" birth_date: str = Field( ..., description="Birth date in YYYY-MM-DD format", example="1990-01-15" ) birth_time: str = Field( ..., description="Birth time in HH:MM format", example="08:30" ) gender: str = Field( default="male", description="Gender: 'male' or 'female'", example="male" ) is_lunar: bool = Field( default=False, description="True if lunar calendar", example=False ) interpretation_level: str = Field( default="basic", description="Interpretation depth: 'basic' (3000 words, $29), 'professional' (8000 words, $99), 'master' (15000 words, $199)", example="basic" ) language: str = Field( default="zh-CN", description="Language: 'zh-CN', 'en', 'zh-TW'", example="zh-CN" ) num_dayun: int = Field( default=12, description="Number of 大运 periods for complete analysis", example=12 ) class MonthlyFortuneRequest(BaseModel): """Request model for monthly fortune analysis""" birth_date: str = Field(..., description="Birth date in YYYY-MM-DD format", example="1990-01-15") birth_time: str = Field(..., description="Birth time in HH:MM format", example="08:30") gender: str = Field(default="male", description="Gender: 'male' or 'female'", example="male") is_lunar: bool = Field(default=False, description="True if lunar calendar", example=False) target_year: int = Field(..., description="Target year", example=2026) target_month: int = Field(..., description="Target month (1-12)", example=1, ge=1, le=12) language: str = Field( default="zh", description="Output language: 'zh' for Chinese, 'en' for cold-reading English", example="zh" ) class YearlyFortuneRequest(BaseModel): """Request model for yearly fortune analysis (all 12 months)""" birth_date: str = Field(..., description="Birth date in YYYY-MM-DD format", example="1990-01-15") birth_time: str = Field(..., description="Birth time in HH:MM format", example="08:30") gender: str = Field(default="male", description="Gender: 'male' or 'female'", example="male") is_lunar: bool = Field(default=False, description="True if lunar calendar", example=False) target_year: int = Field(default=None, description="Target year (defaults to current year)", example=2026) language: str = Field(default="zh", description="Output language: 'zh' or 'en'", example="zh") class DailyFortuneRequest(BaseModel): """Request model for daily fortune analysis""" birth_date: str = Field(..., description="Birth date in YYYY-MM-DD format", example="1990-01-15") birth_time: str = Field(..., description="Birth time in HH:MM format", example="08:30") gender: str = Field(default="male", description="Gender: 'male' or 'female'", example="male") is_lunar: bool = Field(default=False, description="True if lunar calendar", example=False) target_date: str = Field(..., description="Target date in YYYY-MM-DD format", example="2026-01-01") activities: List[str] = Field(default=[], description="Activities to analyze (e.g., ['嫁娶', '开业'])", example=["嫁娶"]) class CareerMonthlyFortuneRequest(BaseModel): """Request model for career-specific monthly fortune analysis""" birth_date: str = Field(..., description="Birth date in YYYY-MM-DD format", example="1990-01-15") birth_time: str = Field(..., description="Birth time in HH:MM format", example="08:30") gender: str = Field(default="male", description="Gender: 'male' or 'female'", example="male") is_lunar: bool = Field(default=False, description="True if lunar calendar", example=False) target_year: int = Field(..., description="Target year to analyze (e.g., 2026)", example=2026) class FindAuspiciousDatesRequest(BaseModel): """Request model for finding auspicious dates""" birth_date: str = Field(..., description="Birth date in YYYY-MM-DD format", example="1990-01-15") birth_time: str = Field(..., description="Birth time in HH:MM format", example="08:30") gender: str = Field(default="male", description="Gender: 'male' or 'female'", example="male") is_lunar: bool = Field(default=False, description="True if lunar calendar", example=False) start_date: str = Field(..., description="Start date in YYYY-MM-DD format", example="2026-01-01") end_date: str = Field(..., description="End date in YYYY-MM-DD format", example="2026-01-31") activity: str = Field(..., description="Activity type", example="嫁娶") min_score: int = Field(default=70, description="Minimum score requirement (0-100)", example=70, ge=0, le=100) def _get_ordinal(n: int) -> str: """Convert number to ordinal string (1 -> 1st, 2 -> 2nd, etc.)""" if 10 <= n % 100 <= 20: suffix = 'th' else: suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') return f"{n}{suffix}" def apply_cold_reading_english(monthly_fortune: Dict[str, Any], year: int, month: int) -> Dict[str, Any]: """ Transform Chinese fortune data into Barnum Effect English NOT simple translation - apply psychological triggers Core Principles: - Specific numbers: "3 days", "the 15th", "2 weeks" - Sensory details: "that sinking feeling", "you've noticed" - Emotional triggers: "frustrated", "hopeful", "relieved" - Universal specifics: "the conversation you've been avoiding" - Time anchors: "Next Tuesday", "before the month ends" Args: monthly_fortune: Chinese fortune analysis data year: Target year month: Target month Returns: Cold-reading English version with Barnum Effect triggers """ import random score = monthly_fortune['score'] quality_cn = monthly_fortune['quality_cn'] # Map Chinese quality to cold-reading English with Barnum triggers quality_map = { "大吉": "Peak Performance Window", "顺遂": "Favorable Momentum", "平稳": "Strategic Recalibration", "波折": "Protective Defense Phase", "困难": "Strategic Pause Period" } quality_en = quality_map.get(quality_cn, "Transition Phase") # Generate specific peak days based on score patterns if score >= 80: peak_days = [3, 8, 15, 22] caution_days = [18, 27] elif score >= 65: peak_days = [5, 12, 19, 24] caution_days = [9, 21] elif score >= 50: peak_days = [7, 14, 21] caution_days = [4, 11, 25] else: peak_days = [6, 16] caution_days = [3, 10, 20, 28] # Cold-reading matters based on score range matters_templates = [] if score >= 80: # 大吉 matters_templates = [ f"That work project you've been overthinking? Your instincts were right. Start it before the {peak_days[0]}th.", f"You'll see an unexpected opportunity around the {peak_days[1]}th. The one that feels 'too good to be true'? Trust it this time.", f"Someone's been watching your work quietly. Around the {peak_days[2]}th, they speak up. It's the validation you needed.", f"That financial decision you've been postponing? The window opens between the {peak_days[0]}th and {peak_days[2]}th. After that, conditions shift." ] elif score >= 65: # 顺遂 matters_templates = [ f"The progress you've been making feels slow, but others notice. Around the {peak_days[0]}th, you'll see proof.", f"That side project or idea you keep dismissing? Between the {peak_days[1]}th and {peak_days[2]}th, test it quietly.", f"A relationship (work or personal) that's felt stuck will shift around the {peak_days[2]}th. Stay open.", f"Financial stability improves, but not dramatically. The {peak_days[0]}th brings a small win that matters more than you think." ] elif score >= 50: # 平稳 matters_templates = [ f"This month rewards patience over action. The urge to force things around the {caution_days[0]}th? Resist it.", f"Something you started weeks ago finally stabilizes around the {peak_days[1]}th. Not exciting, but necessary.", f"That person who's been distant lately? Around the {peak_days[0]}th, you'll understand why. Don't take it personally.", f"Financially, hold steady. The temptation to spend around the {caution_days[1]}th looks harmless but isn't." ] elif score >= 35: # 波折 matters_templates = [ f"That situation that keeps you up at 3am? Around the {caution_days[0]}th, it gets worse before it gets better.", f"Someone will test your boundaries between the {caution_days[1]}th and {caution_days[2]}th. This is when you draw the line.", f"A plan you've been counting on might delay around the {caution_days[0]}th. Have a backup ready before the {peak_days[0]}th.", f"Financially, protect what you have. The 'opportunity' around the {caution_days[1]}th is actually a trap." ] else: # 困难 matters_templates = [ f"This month is about defense, not offense. Between the {caution_days[0]}th and {caution_days[2]}th, just hold ground.", f"That relationship that's been rocky? Around the {caution_days[1]}th, you'll need to decide: fix it or walk away.", f"The financial pressure you're feeling peaks around the {caution_days[0]}th. Don't make desperate moves - it passes by the {peak_days[0]}th.", f"Someone will offer a solution that sounds perfect around the {caution_days[2]}th. It's not. Wait until next month." ] # Select appropriate number of matters based on score num_matters = 3 if score >= 65 else 2 matters_en = random.sample(matters_templates, num_matters) # Cold-reading advice with emotional triggers if score >= 80: advice_en = f"You know that idea you dismissed last week as 'not the right time'? THIS month IS the time. But don't broadcast it - skeptics drain energy you'll need. Start quietly between the {peak_days[0]}th and {peak_days[1]}th, refine as you go. The urge to perfect before starting? That's fear talking. Ignore it." warning_en = f"Around the {caution_days[0]}th-{caution_days[1]}th: someone offers advice that sounds wise but feels off in your gut. Trust the feeling, not their credentials." elif score >= 65: advice_en = f"This month rewards steady progress over big moves. That thing you've been doing consistently? It compounds between the {peak_days[0]}th and {peak_days[2]}th. The results won't be dramatic, but they're building to something significant next month. Stay the course." warning_en = f"The {peak_days[3]}th feels urgent but isn't. What feels like a deadline is actually just anxiety. Take your time." elif score >= 50: advice_en = f"Hold your position this month. The itch to make changes around the {caution_days[0]}th is restlessness, not intuition. Use this time to observe, not act. Around the {peak_days[1]}th, you'll see why waiting was right. Trust the pause." warning_en = f"Someone pushes you to decide quickly around the {caution_days[1]}th. Anyone rushing you doesn't have your best interests. Delay until you're sure." elif score >= 35: advice_en = f"This is a testing month. Between the {caution_days[0]}th and {caution_days[2]}th, protect your energy like it's money. Say no more than yes. The thing that looks like a failure around the {caution_days[1]}th? It's actually filtering out what wasn't meant for you. Trust that." warning_en = f"Around the {caution_days[1]}th-{caution_days[2]}th: someone will gaslight you about a decision you made. You weren't wrong. They're projecting their regret." else: advice_en = f"This month is about survival, not success. Between the {caution_days[0]}th and {caution_days[2]}th, just keep breathing. The pressure you're feeling peaks around the {caution_days[1]}th - and then it breaks. Don't make permanent decisions in temporary darkness. The {peak_days[0]}th brings the first sign of relief." warning_en = f"Someone offers a quick fix around the {caution_days[2]}th. It will create more problems than it solves. Decline politely, weather the storm alone." # Format peak days string peak_days_str = ", ".join([_get_ordinal(d) for d in peak_days[:3]]) if len(peak_days) > 3: peak_days_str += f" (high energy)" caution_days_str = ", ".join([_get_ordinal(d) for d in caution_days[:2]]) return { 'year': year, 'month': month, 'score': score, 'quality': quality_en, 'matters': matters_en, 'advice': advice_en, 'peak_days': peak_days_str, 'caution_days': caution_days_str, 'warning': warning_en, 'overall_energy': f"This month's energy is {quality_en}. Trust what you feel in your body, not what looks logical on paper." } @app.get("/") def root(): """Root endpoint""" return { "status": "online", "service": "AstrologyBazi API", "version": "2.1.1", "documentation": "/docs", "website": "https://astrologybazi.com" } @app.get("/health") @app.head("/health") def health_check(): """Health check endpoint for monitoring services""" return { "status": "healthy", "service": "AstrologyBazi API", "version": "2.1.1", "uptime": "ok" } @app.post("/calculate-bazi") @app.post("/api/calculate-bazi") # Alternative endpoint def calculate_bazi(req: BaZiRequest, api_key: str = Depends(verify_api_key)): """ Calculate BaZi (八字) chart from birth information 🔒 Requires API Key authentication via x-api-key header **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format (e.g., '1990-01-15') - `birth_time`: Birth time in HH:MM format (e.g., '08:30') - `gender`: 'male' or 'female' (default: 'male') - `is_lunar`: True for lunar calendar, False for solar/Gregorian (default: False) **Response:** - `bazi_chart`: Four Pillars (Year, Month, Day, Hour) with Gan/Zhi - `day_master`: Day stem (日主) - the most important element - `five_elements`: Distribution of Wood, Fire, Earth, Metal, Water - `ten_gods`: Ten Gods (十神) relationships - `hidden_stems`: Hidden stems (藏干) in Earthly Branches **Example:** ```json { "birth_date": "1990-01-15", "birth_time": "08:30", "gender": "male", "is_lunar": false } ``` """ try: logger.info(f"🔮 Calculating BaZi for: {req.birth_date} {req.birth_time}") # Validate date format if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) # Validate time format if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) # Validate gender if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) # Calculate BaZi result = BaZiCalculator.calculate_bazi( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, **req.time_options() ) logger.info(f"✅ BaZi calculation successful") return { "success": True, "data": result } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Calculation Error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"BaZi calculation failed: {str(e)}") @app.post("/calculate-bazi-simple") def calculate_bazi_simple(req: BaZiRequest, api_key: str = Depends(verify_api_key)): """ Simplified BaZi calculation - returns only essential data 🔒 Requires API Key authentication via x-api-key header Perfect for quick lookups and mobile apps. Returns only: Four Pillars, Day Master, Five Elements """ try: logger.info(f"🔮 Simple BaZi for: {req.birth_date} {req.birth_time}") result = BaZiCalculator.calculate_bazi( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, **req.time_options() ) # Return simplified response simplified = { "bazi_chart": result['bazi_chart'], "day_master": result['day_master'], "five_elements": result['five_elements'], "time_context": result['time_context'] } logger.info(f"✅ Simple BaZi calculation successful") return { "success": True, "data": simplified } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Calculation Error: {e}") raise HTTPException(status_code=500, detail=f"BaZi calculation failed: {str(e)}") @app.post("/calculate-bazi-complete") @app.post("/api/calculate-bazi-complete") # Alternative endpoint def calculate_bazi_complete(req: BaZiCompleteRequest, api_key: str = Depends(verify_api_key)): """ Calculate complete BaZi with 大运 (Major Cycles) and 流年 (Yearly Cycles) 🔒 Requires API Key authentication via x-api-key header **Premium Feature** - Returns comprehensive 120-year fortune analysis: **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format (e.g., '1990-01-15') - `birth_time`: Birth time in HH:MM format (e.g., '08:30') - `gender`: 'male' or 'female' (default: 'male') - `is_lunar`: True for lunar calendar, False for solar/Gregorian (default: False) - `num_dayun`: Number of 大运 periods (default: 12, each 10 years = 120 years total) **Response:** - All basic BaZi data (四柱八字, 日主, 五行, 十神, 藏干) - `dayun`: List of major cycles, each containing: - `start_age`, `end_age`: Age range for this cycle - `ganzhi`: Heavenly Stem + Earthly Branch (干支) - `elements`: Five Elements distribution - `ten_gods`: Ten Gods relationships - `nayin`: 60 Jia Zi element (纳音) - `shens`: Spiritual forces (神煞) active in this period - `liunian`: 10 yearly cycles within this major cycle - `yun_direction`: 'forward' (顺) or 'backward' (逆) - `yun_start_age`: Age when cycles begin **Perfect for:** - 10-year fortune predictions - Career timing analysis - Wealth opportunity windows - Investment decision support - Life path planning **Example:** ```json { "birth_date": "1990-01-15", "birth_time": "08:30", "gender": "male", "is_lunar": false, "num_dayun": 12 } ``` """ try: logger.info(f"🔮 Complete BaZi calculation for: {req.birth_date} {req.birth_time}") # Validate date format if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) # Validate time format if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) # Validate gender if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) # Calculate complete BaZi with dayun/liunian result = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=req.num_dayun, **req.time_options() ) # 2026-07-12 Phase 3: 追加 meta/strength/timing/evidence 四层字段,旧字段一个不改。 # 格局分析(genju)在这里只用来丰富evidence,失败不影响这个endpoint本身能不能用—— # 这是新加的能力,不能让它的问题拖垮已经在跑的老功能。 current_age = None try: birth_year = int(req.birth_date.split('-')[0]) current_age = datetime.now().year - birth_year except (ValueError, IndexError): pass if current_age is not None: for d in result.get('dayun', []): d['is_current'] = d.get('start_age', 0) <= current_age <= d.get('end_age', -1) genju_analysis = None try: genju_analysis = GenjuAnalyzer(result).analyze_genju() except Exception as e: logger.warning(f"⚠️ Genju analysis skipped for evidence block (non-fatal): {e}") result['meta'] = build_meta() result['strength'] = result.get('wuxing_strength') result['timing'] = build_timing( result, result['bazi_chart']['day']['zhi'], result['bazi_chart']['month']['zhi'] ) result['evidence'] = build_evidence(result, genju_analysis, current_age) if genju_analysis: result['genju_analysis'] = genju_analysis # Deterministic, redacted teaser for the free trend chart. The # selector prefers the broad support-aware trend_score; the full # structure score and reasons/events/advice remain exclusive to the # paid endpoint. No LLM call is involved here. try: preview_analysis = DayunAnalyzer( result, genju_analysis, current_age=current_age, ).analyze_all_dayun() result['dayun_score_preview'] = select_dayun_score_preview( result.get('dayun', []), preview_analysis.get('dayun_analysis', []), ) except Exception as e: logger.warning(f"⚠️ Dayun score preview skipped (non-fatal): {e}") # New free, evidence-first assessment. It deliberately has no total # score and remains available even when optional Genju enrichment fails. try: result['dayun_dynamics'] = build_dayun_dynamic_assessment( result, genju_analysis, current_age=current_age, ) except Exception as e: logger.warning(f"⚠️ Dayun dynamic assessment skipped (non-fatal): {e}") logger.info(f"✅ Complete BaZi calculation successful with {req.num_dayun} dayun periods") return { "success": True, "data": result } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Calculation Error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Complete BaZi calculation failed: {str(e)}") # ==================== Shensha Analysis API (神煞分析) ==================== @app.post("/analyze-shensha") @app.post("/api/analyze-shensha") # Alternative endpoint def analyze_shensha(req: BaZiRequest, api_key: str = Depends(verify_api_key)): """ 🔮 Analyze Spiritual Forces (神煞分析) - Advanced Feature 🔒 Requires API Key authentication via x-api-key header **Premium Feature** - Comprehensive analysis of 47 spiritual forces: **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format (e.g., '1990-01-15') - `birth_time`: Birth time in HH:MM format (e.g., '08:30') - `gender`: 'male' or 'female' (default: 'male') - `is_lunar`: True for lunar calendar, False for solar/Gregorian (default: False) **Response:** - `bazi_chart`: Basic BaZi chart - `shensha_analysis`: Complete spiritual forces analysis: - `auspicious_gods`: List of beneficial spiritual forces (吉神) - Tianyi Nobleman (天乙贵人) - Most auspicious - Red Phoenix (红鸾) - Marriage fortune - Heavenly Joy (天喜) - Joyful events - Wealth God (禄神) - Financial fortune - Fortune Star (福星) - General luck - Golden Chariot (金舆) - Wealth & status - And more... (10 total) - `inauspicious_gods`: List of challenging forces (凶煞) - Sheep Blade (羊刃) - Strong character - Emptiness (空亡) - Void & loss - Disaster God (灾煞) - Accidents - White Tiger (白虎) - Bloodshed - Loneliness (孤辰) - Solitude - Widowhood (寡宿) - Isolation - And more... (15+ total) - `peach_blossom_system`: Detailed romance analysis - Inner peach blossom (墙内桃花) - Harmonious marriage - Outer peach blossom (墙外桃花) - Affairs risk - `traveling_horse_system`: Movement & career analysis - Wealth horse (禄马同乡) - Fortune through travel - Broken foot horse (折足马) - Travel obstacles - And more... (12 types total) - `overall_judgment`: Comprehensive fortune rating - Auspicious/Inauspicious scores - Fortune level (大吉/中吉/平/不利) - Remedy suggestions **Example:** ```json { "birth_date": "1990-01-15", "birth_time": "08:30", "gender": "male", "is_lunar": false } ``` **Response Example:** ```json { "success": true, "data": { "bazi_chart": {...}, "shensha_analysis": { "auspicious_gods": [ { "name": "天乙贵人", "name_en": "Tianyi Nobleman", "position": "year", "strength": 10, "level": 10, "effect": "逢凶化吉、遇难呈祥、贵人相助", "description": "命中最吉之神,主一生多得贵人扶持..." } ], "inauspicious_gods": [...], "peach_blossom_system": { "has_peach_blossom": true, "count": 1, "details": [...] }, "traveling_horse_system": {...}, "overall_judgment": { "overall_judgment": "moderately_auspicious", "overall_judgment_cn": "中吉", "auspicious_score": 85.2, "inauspicious_score": 32.5, "message": "吉多凶少,总体平顺,稳中向好" } } } } ``` **Perfect for:** - Fortune telling services ($49-$99) - Detailed life analysis - Marriage compatibility - Career guidance - Timing decisions (when to start business, get married, etc.) """ try: logger.info(f"🔮 Analyzing Shenshas for: {req.birth_date} {req.birth_time}") # Validate inputs if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) # Step 1: Calculate basic BaZi chart bazi_result = BaZiCalculator.calculate_bazi( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, **req.time_options() ) # Step 2: Analyze spiritual forces analyzer = ShenshaAnalyzer(bazi_result) shensha_analysis = analyzer.analyze_all_shenshas() logger.info(f"✅ Shensha analysis successful: {shensha_analysis['overall_judgment']['overall_judgment_cn']}") return { "success": True, "data": { "bazi_chart": bazi_result, "shensha_analysis": shensha_analysis } } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Shensha Analysis Error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Shensha analysis failed: {str(e)}") # ==================== Genju Pattern Analysis API (格局分析) ==================== @app.post("/analyze-genju") @app.post("/api/analyze-genju") # Alternative endpoint def analyze_genju(req: BaZiRequest, api_key: str = Depends(verify_api_key)): """ 🎯 Analyze Life Pattern (格局分析) - Core Premium Feature 🔒 Requires API Key authentication via x-api-key header **Premium Feature** - Comprehensive analysis of life patterns and destiny structure: **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format (e.g., '1990-01-15') - `birth_time`: Birth time in HH:MM format (e.g., '08:30') - `gender`: 'male' or 'female' (default: 'male') - `is_lunar`: True for lunar calendar, False for solar/Gregorian (default: False) **Response:** - `bazi_chart`: Basic BaZi chart - `genju_analysis`: Complete pattern analysis: - `primary_pattern`: Main life pattern (格局) - **Six Proper Patterns (六大正格)**: - 正官格 (Proper Officer) - Government, authority - 七杀格 (Seven Killings) - Military, leadership - 正财格 (Proper Wealth) - Business, stable income - 偏财格 (Indirect Wealth) - Investment, windfall - 正印格 (Proper Seal) - Education, wisdom - 偏印格 (Indirect Seal) - Arts, metaphysics - **Special Patterns (特殊格局)**: - 从财格/从杀格/从儿格 (Following patterns) - Resource-following structure; validate carrying capacity by phase - 化气格 (Transformation) - Rare structural configuration; validate with current timing - 专旺格 (Specialized prosperity) - Single element dominance - `yongshen_analysis`: **Yongshen (用神) - MOST CRITICAL CONCEPT** - 用神: The key element needed for balance - 喜神: Supporting elements - 忌神: Harmful elements - 仇神: Enemy elements - 闲神: Neutral elements - `pattern_level`: Pattern quality and current-stage capacity reading - level/score: structural strength indicators, not a lifetime wealth grade - description: stage-aware explanation tied to the chart structure - nobility: traditional pattern signal kept separate from financial claims - `career_suggestions`: Career recommendations by pattern - Primary careers (most suitable) - Secondary careers (acceptable) - Careers to avoid - `wealth_analysis`: Current wealth-channel and carrying-capacity analysis - Wealth star and pattern signals - Current-cycle wealth opportunities and risks - Actionable cash-flow and support checks - `marriage_analysis`: Marriage quality analysis - Marriage quality (excellent/good/moderate/challenging) - Spouse characteristics - Marriage timing (early/normal/late) **Example:** ```json { "birth_date": "1990-01-15", "birth_time": "08:30", "gender": "male", "is_lunar": false } ``` **Response Example:** ```json { "success": true, "data": { "bazi_chart": {...}, "genju_analysis": { "primary_pattern": { "pattern_name": "正官格", "pattern_type": "proper", "is_established": true, "strength": 85, "description": "月令正官透干,为官贵之命...", "favorable_elements": ["正印", "食神", "财星"], "unfavorable_elements": ["七杀", "伤官"] }, "yongshen_analysis": { "用神": "正印", "喜神": ["比劫", "正印"], "忌神": ["伤官", "七杀"], "仇神": ["偏财"], "闲神": ["食神"], "method": "扶抑法", "explanation": "日主偏弱,需印比帮扶..." }, "pattern_level": { "level": "上等", "score": 85, "nobility": "high", "explanation": "格局清纯,用神有力,当前阶段承载力较稳" }, "career_suggestions": { "primary": ["公务员", "政府官员", "企业管理层"], "secondary": ["教师", "军官", "警官"], "avoid": ["自由职业", "销售", "投资"], "best_industry": "政府机构、国企、教育系统" }, "wealth_analysis": { "wealth_potential": "high", "wealth_source": "salary", "best_period": "35-55岁", "investment_advice": "保守稳健型投资为主" }, "marriage_analysis": { "quality": "stable", "spouse_characteristics": "贤惠、稳重、有责任心", "marriage_timing": "normal", "compatibility_advice": "适合门当户对,重视家庭" } } } } ``` **Perfect for:** - Fortune telling services ($99-$299) - Career consulting ($199-$499) - Life planning guidance ($299-$999) - Investment timing analysis - Marriage compatibility consulting """ try: logger.info(f"🎯 Analyzing Genju Pattern for: {req.birth_date} {req.birth_time}") # Validate inputs if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) # Step 1: Calculate basic BaZi chart bazi_result = BaZiCalculator.calculate_bazi( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, **req.time_options() ) # Step 2: Analyze life pattern (格局) analyzer = GenjuAnalyzer(bazi_result) genju_analysis = analyzer.analyze_genju() logger.info(f"✅ Genju analysis successful: {genju_analysis['primary_pattern']['pattern_name']} - {genju_analysis['pattern_level']['level']}") return { "success": True, "data": { "bazi_chart": bazi_result, "genju_analysis": genju_analysis } } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Genju Analysis Error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Genju pattern analysis failed: {str(e)}") # ==================== Evidence-first Dayun Dynamics API ==================== @app.post("/analyze-dayun-dynamics") @app.post("/api/analyze-dayun-dynamics") def analyze_dayun_dynamics(req: BaZiCompleteRequest, api_key: str = Depends(verify_api_key)): """ Return deterministic, role-aware Da Yun dynamics. This endpoint separates the broad Five-Element climate from the way a Da Yun interacts with the natal chart. It reports evidence, counter- evidence, transformation gates, and uncertainty instead of a single fortune score. It does not call an LLM. """ try: if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) if req.num_dayun < 1 or req.num_dayun > 12: raise HTTPException( status_code=400, detail="num_dayun must be between 1 and 12" ) bazi_complete = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=req.num_dayun, **req.time_options() ) genju_analysis = GenjuAnalyzer(bazi_complete).analyze_genju() current_age = None try: current_age = datetime.now().year - int(req.birth_date.split('-')[0]) except (ValueError, IndexError): pass dynamics = build_dayun_dynamic_assessment( bazi_complete, genju_analysis, current_age=current_age, ) return { "success": True, "data": { "bazi_chart": bazi_complete.get("bazi_chart"), "day_master": bazi_complete.get("day_master"), "yun_direction": bazi_complete.get("yun_direction"), "yun_start_age": bazi_complete.get("yun_start_age"), "dayun_dynamics": dynamics, }, } except HTTPException: raise except ValueError as e: logger.error(f"❌ Dayun dynamics validation error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Dayun dynamics calculation error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Dayun dynamics calculation failed: {str(e)}") # ==================== Dayun (Major Cycles) Analysis API (大运分析) ==================== # ⚠️ DEPRECATED (2026-07): 2026-07-12全仓库排查确认astrologybazi前端零调用此endpoint # (唯一出现处是tianji-oracle的proxy allowlist字符串,从未被实际发起请求)。 # 保留仅为兼容,不删除代码——见 API_UNIFICATION_PLAN.md §1.1/§6。 # 大运数据现在应从 /calculate-bazi-complete 的 timing/evidence 字段获取。 @app.post("/analyze-dayun") @app.post("/api/analyze-dayun") # Alternative endpoint def analyze_dayun(req: BaZiCompleteRequest, api_key: str = Depends(verify_api_key)): """ 📅 Analyze Major Cycles (大运分析) - Premium Fortune Prediction ⚠️ DEPRECATED: 零前端调用,详见函数上方模块级注释。 🔒 Requires API Key authentication via x-api-key header **Premium Feature** - Comprehensive 120-year fortune prediction and life planning: **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format (e.g., '1990-01-15') - `birth_time`: Birth time in HH:MM format (e.g., '08:30') - `gender`: 'male' or 'female' (default: 'male') - `is_lunar`: True for lunar calendar, False for solar/Gregorian (default: False) - `num_dayun`: Number of major cycles (default: 12, each 10 years = 120 years) **Response:** - `bazi_chart`: Complete BaZi chart with Dayun data - `genju_analysis`: Pattern analysis with Yongshen (用神) - `dayun_analysis`: Comprehensive Dayun fortune analysis: - `dayun_analysis`: List of all major cycles with: - Fortune quality (大吉/顺遂/平稳/波折/困难) - Detailed score and reasons - Event predictions (career/wealth/marriage/health/study) - Life advice for each period - Critical years identification - `turning_points`: Major life turning points - Age and Ganzhi of turning point - Direction (转吉/转凶/重大变动) - Score changes - `life_stages`: Life stage analysis - Early years (0-30): Average score and judgment - Middle years (30-60): Average score and judgment - Late years (60+): Average score and judgment - Best life stage identification - `overall_summary`: Complete life fortune summary - Total average score - Quality distribution - Best and worst Dayun periods - Overall judgment **Fortune Prediction Features:** 1. **Yongshen-based Analysis** (用神判断法) - The returned 0–100 value is an explainable heuristic signal. - It is not a probability, percentile, or historically calibrated forecast. - `judgment.score_method` and `judgment.score_confidence` identify its semantics. 2. **Special Combinations** (特殊组合) - Relationship adjustments are calculated once by the central XingChongHeHeCalculator. - Sui Yun Bing Lin (岁运并临) is a year-level event only when the full annual GanZhi equals the Dayun GanZhi; it is not a fixed decade penalty. 3. **Auspicious Spirits** (吉神) - Tianyi Nobleman (天乙贵人), Yi Ma (驿马), and Peach Blossom (桃花) remain bounded heuristic signals, not guaranteed outcomes. 4. **Event Predictions** (事件预测) - Career: Promotion, job change, peak period - Wealth: Investment timing, income increase, financial risks - Marriage: Wedding timing, relationship quality, crisis periods - Health: Peak health years, illness risks, prevention advice - Study: Academic success, exam luck, learning opportunities **Example:** ```json { "birth_date": "1990-01-15", "birth_time": "08:30", "gender": "male", "is_lunar": false, "num_dayun": 12 } ``` **Response Example:** ```json { "success": true, "data": { "bazi_chart": {...}, "genju_analysis": {...}, "dayun_analysis": { "dayun_analysis": [ { "ganzhi": "戊寅", "age_range": "7-16岁", "judgment": { "quality": "good", "quality_cn": "顺遂平安", "label": "✅ 良好", "score": 65, "reasons": [ "✅ 大运为用神五行,有力助身", "✅ 大运逢驿马,利于走动变动" ] }, "events": { "career": ["7-10岁:学业进步快"], "wealth": ["..."], "marriage": ["..."], "health": ["..."], "study": ["7-16岁:学业进步快,考试运佳"] }, "advice": [ "✅ 7-16岁运势向好,适合稳健发展", "抓住学习机会,提升知识能力" ], "critical_years": [ { "year": 2011, "age": 22, "ganzhi": "辛卯", "importance": 3, "reasons": ["流年与日柱天克地冲"] } ] } ], "turning_points": [ { "age": 37, "from_dayun": "己卯", "to_dayun": "庚辰", "direction": "转吉", "score_change": 35, "description": "37岁进入庚辰大运,运势转吉" } ], "life_stages": { "early_years": { "age_range": "0-30岁(早年运)", "avg_score": 58.5, "judgment": "运势良好,平稳向上" }, "middle_years": { "age_range": "30-60岁(中年运)", "avg_score": 72.3, "judgment": "运势极佳,大吉大利" }, "late_years": { "age_range": "60岁以上阶段", "avg_score": 45.2, "judgment": "运势平稳,中规中矩" }, "best_stage": "middle" }, "overall_summary": { "total_dayun_count": 12, "avg_score": 58.7, "overall_judgment": "已计算周期整体偏稳,仍需按当前窗口调整节奏", "quality_distribution": { "excellent": 2, "good": 5, "average": 3, "challenging": 2, "difficult": 0 }, "best_dayun": { "ganzhi": "庚辰", "age_range": "37-46岁", "score": 85 }, "worst_dayun": { "ganzhi": "丙子", "age_range": "67-76岁", "score": 32 } } } } } ``` **Perfect for:** - 10-year fortune predictions ($199-$399) - Life planning consultation ($299-$799) - Career timing analysis ($399-$999) - Investment decision support ($499-$1,499) - Marriage timing consultation ($199-$499) - Comprehensive destiny report ($499-$1,999) **Business Value:** - Subscription model: $29-49/month for ongoing Dayun updates - One-time reports: $199-$1,999 depending on depth - Monthly revenue potential: $2,500-$5,000 """ try: logger.info(f"📅 Analyzing Dayun for: {req.birth_date} {req.birth_time}") # Validate inputs if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) # Step 1: Calculate complete BaZi with Dayun bazi_complete = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=req.num_dayun, **req.time_options() ) # Step 2: Analyze life pattern (格局) to get Yongshen genju_analyzer = GenjuAnalyzer(bazi_complete) genju_analysis = genju_analyzer.analyze_genju() # 🎯 计算当前年龄(用于标记当前大运) from datetime import datetime birth_year = int(req.birth_date.split('-')[0]) current_year = datetime.now().year current_age = current_year - birth_year # Step 3: Analyze Dayun (major cycles) dayun_analyzer = DayunAnalyzer(bazi_complete, genju_analysis, current_age=current_age) dayun_analysis = dayun_analyzer.analyze_all_dayun() logger.info(f"✅ Dayun analysis successful: {len(dayun_analysis['dayun_analysis'])} major cycles analyzed") logger.info(f" Overall judgment: {dayun_analysis['overall_summary']['overall_judgment']}") return { "success": True, "data": { "bazi_chart": bazi_complete, "genju_analysis": genju_analysis, "dayun_analysis": dayun_analysis } } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Dayun Analysis Error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Dayun analysis failed: {str(e)}") # ==================== AI Interpretation API (AI解读引擎) ==================== @app.post("/ai-interpretation") @app.post("/api/ai-interpretation") # Alternative endpoint def ai_interpretation(req: BaZiInterpretationRequest, api_key: str = Depends(verify_api_key)): """ 🤖 AI BaZi Interpretation - Transform Technical Analysis into Natural Language 🔒 Requires API Key authentication via x-api-key header **Premium Feature** - Professional natural language interpretation of BaZi destiny analysis: **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format (e.g., '1990-01-15') - `birth_time`: Birth time in HH:MM format (e.g., '08:30') - `gender`: 'male' or 'female' (default: 'male') - `is_lunar`: True for lunar calendar, False for solar/Gregorian (default: False) - `interpretation_level`: Depth of interpretation - **'basic'**: 3,000 words - Essential interpretation ($29) - Personality, Career, Wealth, Marriage, Health, Overall - **'professional'**: 8,000 words - Detailed analysis ($99) - Basic sections + Dayun overview + Life stages analysis - **'master'**: 15,000 words - Complete life planning ($199) - Professional sections + Turning points + All Dayun details + Spiritual forces + Life planning - `language`: Output language 'zh-CN' (Chinese), 'en' (English), 'zh-TW' (Traditional Chinese) - `num_dayun`: Number of major cycles to analyze (default: 12 = 120 years) **Response:** - `bazi_chart`: Complete BaZi chart with all technical data - `shensha_analysis`: Spiritual forces analysis (47 forces) - `genju_analysis`: Pattern analysis with Yongshen (用神) - `dayun_analysis`: Major cycles fortune prediction - `ai_interpretation`: **Natural language interpretation** 🌟 - `level`: Interpretation depth ('basic'/'professional'/'master') - `word_count`: Total word count - `sections`: Natural language sections: - **personality**: Who you are (based on Day Master + Pattern) - Example: "你是甲木日主,就像大树一样,天性正直向上..." - **career**: Career recommendations with specific industries - Example: "你的八字显示你适合在有组织、有制度的环境中工作,如公务员..." - **wealth**: Current wealth-channel and carrying-capacity signals - **marriage**: Marriage quality and spouse characteristics - **health**: Health advice based on Five Elements - **overall**: Structural summary and current-phase suggestions - **(Professional/Master only)** **dayun_overview**: Current and next cycle signals - **(Professional/Master only)** **life_stages**: Current-window calibration - **(Master only)** **turning_points**: Turning points near the current window - **(Master only)** **ten_year_fortunes**: Current and next cycle details - **(Master only)** **auspicious_spirits**: Beneficial forces interpretation - **(Master only)** **inauspicious_spirits**: Challenging forces interpretation - **(Master only)** **life_planning**: Comprehensive life roadmap **Key Transformation Examples:** | Technical Term | Natural Language | |----------------|------------------| | 甲木日主 + 正官格 | "你是一个正直向上、有责任心的人,适合在政府或大企业工作" | | 用神为正印 | "学习是你的贵人,通过教育和知识提升可以改变命运" | | 大运走喜神运 | "未来10年运势极佳,是创业或升职的黄金时期" | | 羊刃 + 七杀 | "性格刚强果断,适合军警或竞争性行业" | | 桃花 + 红鸾 | "异性缘好,容易遇到心仪对象" | **Example:** ```json { "birth_date": "1990-01-15", "birth_time": "08:30", "gender": "male", "is_lunar": false, "interpretation_level": "professional", "language": "zh-CN", "num_dayun": 12 } ``` **Response Example:** ```json { "success": true, "data": { "bazi_chart": {...}, "shensha_analysis": {...}, "genju_analysis": {...}, "dayun_analysis": {...}, "ai_interpretation": { "level": "professional", "word_count": 8247, "sections": { "personality": "你是甲木日主,就像大树一样,天性正直向上、积极进取。你有很强的责任心...", "career": "你的八字显示你适合在有组织、有制度的环境中工作,如公务员、政府官员...", "wealth": "当前财路与承载力信号:先核现金流、支持条件和可重复交付...", "marriage": "你的婚姻质量良好,配偶贤惠、稳重、有责任心...", "health": "作为木性日主,你需要特别注意肝胆的健康...", "overall": "当前结构提示资源与职责如何协同;请结合当前窗口的现实反馈...", "dayun_overview": "当前与下一大运窗口的结构信号与可执行建议...", "life_stages": "当前阶段校准:把机会当作工作假设,用现金流和结果复核..." } } } } ``` **Perfect for:** - BaZi consulting services ($29-$199 per reading) - Automated fortune-telling platforms - Mobile apps with human-readable reports - Life coaching and career consulting - Subscription-based destiny analysis services **Business Value:** - **Basic tier**: $29 × 100 users/month = $2,900/month - **Professional tier**: $99 × 50 users/month = $4,950/month - **Master tier**: $199 × 25 users/month = $4,975/month - **Total potential**: $12,825/month ($153,900/year) **Monthly Revenue Projection:** - Conservative (50 users): $2,500/month - Moderate (200 users): $10,000/month - Optimistic (500 users): $20,000+/month """ try: logger.info(f"🤖 AI Interpretation for: {req.birth_date} {req.birth_time}, level: {req.interpretation_level}") # Validate inputs if len(req.birth_date.split('-')) != 3: raise HTTPException( status_code=400, detail="Invalid birth_date format. Use YYYY-MM-DD (e.g., '1990-01-15')" ) if len(req.birth_time.split(':')) != 2: raise HTTPException( status_code=400, detail="Invalid birth_time format. Use HH:MM (e.g., '08:30')" ) if req.gender not in ['male', 'female']: raise HTTPException( status_code=400, detail="Invalid gender. Use 'male' or 'female'" ) if req.interpretation_level not in ['basic', 'professional', 'master']: raise HTTPException( status_code=400, detail="Invalid interpretation_level. Use 'basic', 'professional', or 'master'" ) if req.language not in ['zh-CN', 'en', 'zh-TW']: raise HTTPException( status_code=400, detail="Invalid language. Use 'zh-CN', 'en', or 'zh-TW'" ) # Step 1: Calculate complete BaZi with Dayun logger.info(" Step 1/5: Calculating complete BaZi with Dayun...") bazi_complete = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=req.num_dayun, **req.time_options() ) logger.info(f" ✅ BaZi calculated: {bazi_complete['day_master']['gan']}日主") # Step 2: Analyze spiritual forces (Shensha) logger.info(" Step 2/5: Analyzing spiritual forces (Shensha)...") shensha_analyzer = ShenshaAnalyzer(bazi_complete) shensha_analysis = shensha_analyzer.analyze_all_shenshas() logger.info(f" ✅ Shensha analyzed: {len(shensha_analysis['auspicious_gods'])} auspicious, {len(shensha_analysis['inauspicious_gods'])} inauspicious") # Step 3: Analyze life pattern (Genju) to get Yongshen logger.info(" Step 3/5: Analyzing life pattern (Genju) for Yongshen...") genju_analyzer = GenjuAnalyzer(bazi_complete) genju_analysis = genju_analyzer.analyze_genju() logger.info(f" ✅ Genju analyzed: {genju_analysis['primary_pattern']['pattern_name']}, 用神: {genju_analysis['yongshen_analysis']['用神']}") # Step 4: Analyze Dayun (major cycles) logger.info(" Step 4/5: Analyzing Dayun (major cycles)...") # 🎯 计算当前年龄(用于标记当前大运) from datetime import datetime birth_year = int(req.birth_date.split('-')[0]) current_year = datetime.now().year current_age = current_year - birth_year logger.info(f" 📍 Current age: {current_age} years (born {birth_year})") dayun_analyzer = DayunAnalyzer(bazi_complete, genju_analysis, current_age=current_age) dayun_analysis = dayun_analyzer.analyze_all_dayun() logger.info(f" ✅ Dayun analyzed: {len(dayun_analysis['dayun_analysis'])} periods, avg score: {dayun_analysis['overall_summary']['avg_score']}") # 🎯 Phase 3: Advanced Analysis Modules (2025-12-24) logger.info(" Step 4.5/6: Running Phase 3 advanced analysis modules...") # 🌐 Map API language to analyzer language ('zh-CN'/'zh-TW' → 'zh', 'en' → 'en') analyzer_lang = 'en' if req.language == 'en' else 'zh' # 婚姻分析 marriage_analyzer = MarriageAnalyzer(bazi_complete, dayun_analysis, language=analyzer_lang) marriage_analysis = marriage_analyzer.analyze_marriage() logger.info(f" ✅ Marriage analyzed: Quality {marriage_analysis['marriage_quality']['score']}/100") # 事业分析 career_analyzer = CareerAnalyzer(bazi_complete, genju_analysis, dayun_analysis) # 🔧 Bug fix: 添加dayun_analysis career_analysis = career_analyzer.analyze_career(language=analyzer_lang) logger.info(f" ✅ Career analyzed: {len(career_analysis['recommended_careers'])} careers recommended") # 🆕 财运分析 wealth_analyzer = WealthAnalyzer(bazi_complete, dayun_analysis) wealth_analysis = wealth_analyzer.analyze_wealth(language=analyzer_lang) logger.info(f" ✅ Wealth analyzed: Score {wealth_analysis['wealth_quality']['score']}/100") # 健康分析 health_analyzer = HealthAnalyzer(bazi_complete) health_analysis = health_analyzer.analyze_health(language=analyzer_lang) logger.info(f" ✅ Health analyzed: {len(health_analysis['health_risks'])} risks identified") # 🆕 官职分析 (2025-12-25) official_analyzer = OfficialAnalyzer(bazi_complete, dayun_analysis) official_analysis = official_analyzer.analyze_official() logger.info(f" ✅ Official analyzed: Level {official_analysis.get('official_level', 'N/A')}") # 🆕 法律风险分析 (2025-12-25) legal_risk_analyzer = LegalRiskAnalyzer(bazi_complete, dayun_analysis) legal_risk_analysis = legal_risk_analyzer.analyze_legal_risk() logger.info(f" ✅ Legal risk analyzed: Score {legal_risk_analysis.get('risk_score', 0)}/100") # Step 5: Generate AI interpretation logger.info(f" Step 5/6: Generating {req.interpretation_level} level AI interpretation...") interpreter = AIInterpreter( bazi_chart=bazi_complete, shensha_analysis=shensha_analysis, genju_analysis=genju_analysis, dayun_analysis=dayun_analysis ) ai_interpretation = interpreter.generate_interpretation( level=req.interpretation_level, language=req.language ) logger.info(f" ✅ Interpretation generated: {ai_interpretation['word_count']} words, {len(ai_interpretation['sections'])} sections") logger.info(f"✅ Complete AI interpretation successful for {req.interpretation_level} level") # 🎯 Extract life curve data (1-100 age fortune table with scores) life_curve_data = None if hasattr(interpreter, 'life_curve_data') and interpreter.life_curve_data: life_curve_data = interpreter.life_curve_data return { "success": True, "data": { "bazi_chart": bazi_complete, "shensha_analysis": shensha_analysis, "genju_analysis": genju_analysis, "dayun_analysis": dayun_analysis, "ai_interpretation": ai_interpretation, "life_curve_data": life_curve_data, # Phase 2: 1-100岁完整运势数据 # 🎯 Phase 3: Advanced Analysis (2025-12-24) "marriage_analysis": marriage_analysis, # 婚姻分析 "career_analysis": career_analysis, # 事业分析 "health_analysis": health_analysis, # 健康分析 "wealth_analysis": wealth_analysis, # 财运分析 # 🆕 Phase 3.5: Official & Legal Analysis (2025-12-25) "official_analysis": official_analysis, # 官职分析 "legal_risk_analysis": legal_risk_analysis # 法律风险分析 } } except ValueError as e: logger.error(f"❌ Validation Error: {e}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ AI Interpretation Error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"AI interpretation failed: {str(e)}") # ==================== Time Fortune Prediction API (时间预测API) ==================== @app.post("/monthly-fortune") @app.post("/api/monthly-fortune") # Alternative endpoint def analyze_monthly_fortune_api(req: MonthlyFortuneRequest, api_key: str = Depends(verify_api_key)): """ 📅 Monthly Fortune Analysis (流月运势分析) 🔒 Requires API Key authentication **Premium Feature** - Analyze fortune for a specific month ($9.9/month subscription) **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format - `birth_time`: Birth time in HH:MM format - `gender`: 'male' or 'female' - `is_lunar`: True if lunar calendar - `target_year`: Year to analyze (e.g., 2026) - `target_month`: Month to analyze (1-12) - `language`: 'zh' (Chinese, default) or 'en' (cold-reading English with Barnum Effect) **Response:** - Monthly fortune score (0-100) - Quality assessment (Chinese: 大吉/顺遂/平稳/波折/困难, English: Peak Performance Window/Favorable Momentum/etc.) - **English mode**: Cold-reading format with specific dates, emotional triggers, Barnum Effect - **Chinese mode**: Traditional fortune analysis with reasons and advice - Suitable/avoid activities (Chinese mode only) **Business Value**: $9.9/month subscription × 200 users = $1,980/month """ try: logger.info(f"📅 Analyzing monthly fortune for {req.target_year}-{req.target_month}") # Calculate BaZi bazi_chart = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=12 ) # Analyze Genju for Yongshen genju_analyzer = GenjuAnalyzer(bazi_chart) genju_analysis = genju_analyzer.analyze_genju() # Analyze Monthly Fortune fortune_analyzer = TimeFortuneAnalyzer(bazi_chart, genju_analysis, None) monthly_fortune = fortune_analyzer.analyze_monthly_fortune(req.target_year, req.target_month) logger.info(f"✅ Monthly fortune analyzed: {monthly_fortune['quality_cn']} ({monthly_fortune['score']}/100)") # Apply cold-reading transformation for English if req.language == "en": monthly_fortune_output = apply_cold_reading_english( monthly_fortune, req.target_year, req.target_month ) logger.info(f"🔮 Applied cold-reading English transformation") else: monthly_fortune_output = monthly_fortune return { "success": True, "data": { "bazi_chart": bazi_chart, "monthly_fortune": monthly_fortune_output } } except Exception as e: logger.error(f"❌ Monthly fortune analysis error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Monthly fortune analysis failed: {str(e)}") @app.post("/career-monthly-fortune") @app.post("/api/career-monthly-fortune") # Alternative endpoint def analyze_career_monthly_fortune_api(req: CareerMonthlyFortuneRequest, api_key: str = Depends(verify_api_key)): """ 💼 Career-Specific Monthly Fortune Analysis (事业月运分析) 🔒 Requires API Key authentication **Career Forecast Feature** - Analyze 12 months of career fortune for a specific year ⚠️ **Product Differentiation**: This endpoint provides CAREER-ONLY monthly analysis. It is different from Tianji Oracle's comprehensive monthly fortune (which includes health, relationships, wealth). This prevents content duplication if users purchase both products. **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format - `birth_time`: Birth time in HH:MM format - `gender`: 'male' or 'female' - `is_lunar`: True if lunar calendar - `target_year`: Year to analyze (e.g., 2026) **Response:** - Array of 12 months with: - `month`: "YYYY-MM" format - `month_ganzhi`: Monthly celestial stem and branch (干支) - `career_score`: Career fortune score (0-100) - `quality_cn`: Quality assessment (大吉/顺遂/平稳/波折/困难) - `opportunities`: List of career opportunities this month - `risks`: List of career risks to watch out for - `advice`: Actionable career advice for the month **Business Value**: Part of Career Forecast product ($19.99) """ try: logger.info(f"💼 Analyzing career monthly fortune for year {req.target_year}") # Calculate BaZi bazi_chart = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=False, # Don't need Dayun for this num_dayun=0 ) # Initialize CareerAnalyzer career_analyzer = CareerAnalyzer(bazi_chart) # Analyze career monthly fortune monthly_fortune = career_analyzer._analyze_career_monthly_fortune(req.target_year) logger.info(f"✅ Career monthly fortune analyzed: {len(monthly_fortune)} months") return { "success": True, "data": { "target_year": req.target_year, "monthly_fortune": monthly_fortune, "note": "Career-only analysis. For comprehensive fortune including health/relationships/wealth, see Tianji Oracle product." } } except Exception as e: logger.error(f"❌ Career monthly fortune analysis error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Career monthly fortune analysis failed: {str(e)}") @app.post("/yearly-fortune") @app.post("/api/yearly-fortune") # Alternative endpoint def analyze_yearly_fortune_api(req: YearlyFortuneRequest, api_key: str = Depends(verify_api_key)): """ 🎯 Yearly Fortune Analysis - 12 Month Complete Forecast (流年12个月运势) 🔒 Requires API Key authentication **Premium Feature** - Get complete yearly fortune prediction for all 12 months **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format - `birth_time`: Birth time in HH:MM format - `gender`: 'male' or 'female' - `is_lunar`: True if lunar calendar - `target_year`: Year to analyze (defaults to current year) - `language`: 'zh' or 'en' for output language **Response:** - Complete BaZi chart with Five Elements analysis - 12-month fortune array with scores, quality, reasons, advice - Yearly overview with best/worst months - Feng Shui recommendations per month **Business Value**: Powers Tianji Oracle Premium ($19.99/reading) """ try: # Default to current year if not specified target_year = req.target_year or datetime.now().year logger.info(f"🎯 Analyzing yearly fortune for {target_year}, birth: {req.birth_date}") # 1. Calculate BaZi chart (only once!) bazi_chart = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=12 ) # 2. Analyze Genju for Yongshen/Jishen genju_analyzer = GenjuAnalyzer(bazi_chart) genju_analysis = genju_analyzer.analyze_genju() # 3. Initialize fortune analyzer once fortune_analyzer = TimeFortuneAnalyzer(bazi_chart, genju_analysis, None) # 4. Analyze all 12 months monthly_fortunes = [] total_score = 0 best_month = {"month": 1, "score": 0} worst_month = {"month": 1, "score": 100} for month in range(1, 13): monthly_fortune = fortune_analyzer.analyze_monthly_fortune(target_year, month) # Track best/worst score = monthly_fortune.get('score', 50) total_score += score if score > best_month["score"]: best_month = {"month": month, "score": score, "quality": monthly_fortune.get('quality_cn', '平稳')} if score < worst_month["score"]: worst_month = {"month": month, "score": score, "quality": monthly_fortune.get('quality_cn', '平稳')} monthly_fortunes.append({ "month": month, "month_name_zh": f"{month}月", "month_name_en": datetime(target_year, month, 1).strftime("%B"), **monthly_fortune }) # 5. Calculate yearly overview avg_score = round(total_score / 12) yearly_quality = ( "大吉" if avg_score >= 80 else "顺遂" if avg_score >= 65 else "平稳" if avg_score >= 50 else "波折" if avg_score >= 35 else "困难" ) # 6. Extract key info for frontend day_master = bazi_chart.get('day_master', {}) five_elements = bazi_chart.get('five_elements', {}) logger.info(f"✅ Yearly fortune analyzed: avg={avg_score}, best=M{best_month['month']}, worst=M{worst_month['month']}") return { "success": True, "data": { # BaZi summary (not full chart to reduce payload) "bazi_summary": { "day_master": day_master, "five_elements": five_elements, "year_pillar": bazi_chart.get('bazi_chart', {}).get('year', {}), "month_pillar": bazi_chart.get('bazi_chart', {}).get('month', {}), "day_pillar": bazi_chart.get('bazi_chart', {}).get('day', {}), "hour_pillar": bazi_chart.get('bazi_chart', {}).get('hour', {}), }, # Yongshen/Jishen "genju_summary": { "yongshen": genju_analysis.get('yongshen', ''), "jishen": genju_analysis.get('jishen', []), "pattern": genju_analysis.get('pattern', ''), }, # Yearly overview "yearly_overview": { "year": target_year, "average_score": avg_score, "quality": yearly_quality, "best_month": best_month, "worst_month": worst_month, }, # All 12 months "monthly_fortunes": monthly_fortunes, } } except Exception as e: logger.error(f"❌ Yearly fortune analysis error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Yearly fortune analysis failed: {str(e)}") @app.post("/daily-fortune") @app.post("/api/daily-fortune") # Alternative endpoint def analyze_daily_fortune_api(req: DailyFortuneRequest, api_key: str = Depends(verify_api_key)): """ 🗓️ Daily Fortune & Date Selection (流日择日分析) 🔒 Requires API Key authentication **Premium Feature** - Analyze if a specific date is suitable for certain activities ($19/reading) **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format - `birth_time`: Birth time in HH:MM format - `gender`: 'male' or 'female' - `is_lunar`: True if lunar calendar - `target_date`: Date to analyze in YYYY-MM-DD format - `activities`: Activities to check (e.g., ['嫁娶', '开业', '搬家']) **Response:** - Daily fortune score (0-100) - Overall verdict (大吉/吉/平/凶/大凶) - Activity-specific analysis with scores - Almanac (宜忌) from traditional calendar **Common Activities:** - 嫁娶 (Marriage), 开业 (Business Opening), 搬家 (Moving) - 动土 (Construction), 出行 (Travel), 签约 (Contract Signing) - 求财 (Wealth Seeking), 祭祀 (Worship), 安葬 (Burial) **Business Value**: $19/reading × 100 users = $1,900/month """ try: logger.info(f"🗓️ Analyzing daily fortune for {req.target_date}") # Parse target date try: year, month, day = map(int, req.target_date.split('-')) target_date_obj = date(year, month, day) except ValueError: raise HTTPException(status_code=400, detail="Invalid target_date format. Use YYYY-MM-DD") # Calculate BaZi bazi_chart = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=12 ) # Analyze Genju for Yongshen genju_analyzer = GenjuAnalyzer(bazi_chart) genju_analysis = genju_analyzer.analyze_genju() # Analyze Daily Fortune fortune_analyzer = TimeFortuneAnalyzer(bazi_chart, genju_analysis, None) daily_fortune = fortune_analyzer.analyze_daily_fortune(target_date_obj, req.activities) logger.info(f"✅ Daily fortune analyzed: {daily_fortune['quality_cn']} ({daily_fortune['score']}/100)") return { "success": True, "data": { "bazi_chart": bazi_chart, "daily_fortune": daily_fortune } } except HTTPException: raise except Exception as e: logger.error(f"❌ Daily fortune analysis error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Daily fortune analysis failed: {str(e)}") @app.post("/hourly-fortune") @app.post("/api/hourly-fortune") # Alternative endpoint def analyze_hourly_fortune_api(req: DailyFortuneRequest, api_key: str = Depends(verify_api_key)): """ ⏰ Hourly Fortune Analysis (时辰吉凶分析) 🔒 Requires API Key authentication **Premium Feature** - Analyze fortune for all 12 hours of a day **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format - `birth_time`: Birth time in HH:MM format - `gender`: 'male' or 'female' - `is_lunar`: True if lunar calendar - `target_date`: Date to analyze in YYYY-MM-DD format **Response:** - 12 hourly fortunes with scores (0-100 each) - Tian Shen (天神) for each hour - Suitable activities for each hour - Recommendations **Use Cases:** - Find best time for important meetings - Schedule medical appointments - Plan business negotiations - Choose wedding ceremony time """ try: logger.info(f"⏰ Analyzing hourly fortune for {req.target_date}") # Parse target date try: year, month, day = map(int, req.target_date.split('-')) target_date_obj = date(year, month, day) except ValueError: raise HTTPException(status_code=400, detail="Invalid target_date format. Use YYYY-MM-DD") # Calculate BaZi bazi_chart = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=12 ) # Analyze Genju for Yongshen genju_analyzer = GenjuAnalyzer(bazi_chart) genju_analysis = genju_analyzer.analyze_genju() # Analyze Hourly Fortune fortune_analyzer = TimeFortuneAnalyzer(bazi_chart, genju_analysis, None) hourly_fortunes = fortune_analyzer.analyze_hourly_fortune(target_date_obj) logger.info(f"✅ Hourly fortune analyzed: 12 hours") return { "success": True, "data": { "date": str(target_date_obj), "hourly_fortunes": hourly_fortunes } } except HTTPException: raise except Exception as e: logger.error(f"❌ Hourly fortune analysis error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Hourly fortune analysis failed: {str(e)}") @app.post("/find-auspicious-dates") @app.post("/api/find-auspicious-dates") # Alternative endpoint def find_auspicious_dates_api(req: FindAuspiciousDatesRequest, api_key: str = Depends(verify_api_key)): """ 🔍 Find Auspicious Dates (寻找吉日) 🔒 Requires API Key authentication **Premium Feature** - Find best dates for specific activities within a date range **Request Body:** - `birth_date`: Birth date in YYYY-MM-DD format - `birth_time`: Birth time in HH:MM format - `gender`: 'male' or 'female' - `is_lunar`: True if lunar calendar - `start_date`: Range start date in YYYY-MM-DD format - `end_date`: Range end date in YYYY-MM-DD format (max 90 days) - `activity`: Activity type (e.g., '嫁娶', '开业', '搬家') - `min_score`: Minimum score requirement (default: 70) **Response:** - List of auspicious dates sorted by score - Each date includes: - Overall score and activity-specific score - Verdict (大吉/吉/平) - Detailed reasons - Weekday information **Common Use Cases:** - 🎊 Wedding date selection (嫁娶) - 🏢 Business opening dates (开业) - 🏠 Moving dates (搬家) - ✍️ Contract signing dates (签约) - ✈️ Travel start dates (出行) **Business Value**: Part of $19 date selection service """ try: logger.info(f"🔍 Finding auspicious dates for {req.activity} from {req.start_date} to {req.end_date}") # Parse dates try: start_year, start_month, start_day = map(int, req.start_date.split('-')) end_year, end_month, end_day = map(int, req.end_date.split('-')) start_date_obj = date(start_year, start_month, start_day) end_date_obj = date(end_year, end_month, end_day) except ValueError: raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD") # Validate date range if end_date_obj <= start_date_obj: raise HTTPException(status_code=400, detail="end_date must be after start_date") if (end_date_obj - start_date_obj).days > 90: raise HTTPException(status_code=400, detail="Date range cannot exceed 90 days") # Calculate BaZi bazi_chart = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, include_dayun=True, num_dayun=12 ) # Analyze Genju for Yongshen genju_analyzer = GenjuAnalyzer(bazi_chart) genju_analysis = genju_analyzer.analyze_genju() # Find Auspicious Dates fortune_analyzer = TimeFortuneAnalyzer(bazi_chart, genju_analysis, None) auspicious_dates = fortune_analyzer.find_auspicious_dates( start_date_obj, end_date_obj, req.activity, req.min_score ) logger.info(f"✅ Found {len(auspicious_dates)} auspicious dates for {req.activity}") return { "success": True, "data": { "activity": req.activity, "date_range": { "start": req.start_date, "end": req.end_date }, "min_score": req.min_score, "found_count": len(auspicious_dates), "auspicious_dates": auspicious_dates[:30] # Limit to 30 results } } except HTTPException: raise except Exception as e: logger.error(f"❌ Find auspicious dates error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Find auspicious dates failed: {str(e)}") # ==================== Chinese Calendar API ==================== @app.get("/api/calendar/today") def get_today_calendar(): """ Get today's Chinese lunar calendar information 📅 Public endpoint - No API key required **Response:** - Solar calendar date - Lunar calendar date (农历) - Gan Zhi (干支) for year, month, day - Zodiac animal (生肖) - Almanac data (宜忌) - Solar term (节气) if applicable - Festivals if applicable **Example Response:** ```json { "success": true, "date": "2025-12-10", "weekday": "星期三", "solar": { "year": 2025, "month": 12, "day": 10, "date_string": "2025年12月10日" }, "lunar": { "year": 2025, "month": 11, "day": 10, "year_cn": "乙巳年", "month_cn": "冬月", "day_cn": "初十", "date_string": "乙巳年冬月初十" }, "ganzhi": { "year": "乙巳", "month": "戊子", "day": "癸未" }, "zodiac": { "animal": "蛇" }, "almanac": { "appropriate": ["祭祀", "祈福", "求嗣"], "avoid": ["嫁娶", "动土", "破土"] } } ``` """ try: logger.info("📅 Fetching today's Chinese calendar") result = ChineseCalendarService.get_today_calendar() logger.info(f"✅ Today's calendar retrieved: {result['date']}") return result except Exception as e: logger.error(f"❌ Error getting today's calendar: {e}") raise HTTPException(status_code=500, detail=f"Calendar service error: {str(e)}") @app.get("/api/calendar/range") def get_calendar_range(start: str, end: str): """ Get Chinese lunar calendar information for a date range 📅 Public endpoint - No API key required **Query Parameters:** - `start`: Start date in YYYY-MM-DD format - `end`: End date in YYYY-MM-DD format **Note:** Maximum range is 365 days **Response:** List of calendar objects for each date in the range **Example:** ``` GET /api/calendar/range?start=2025-12-01&end=2025-12-31 ``` """ try: logger.info(f"📅 Fetching calendar range: {start} to {end}") # Parse dates try: start_year, start_month, start_day = map(int, start.split('-')) end_year, end_month, end_day = map(int, end.split('-')) start_date = date(start_year, start_month, start_day) end_date = date(end_year, end_month, end_day) except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD" ) # Validate date range if end_date < start_date: raise HTTPException( status_code=400, detail="End date must be after start date" ) result = ChineseCalendarService.get_calendar_range(start_date, end_date) logger.info(f"✅ Calendar range retrieved: {len(result)} days") return { "success": True, "start_date": start, "end_date": end, "total_days": len(result), "calendars": result } except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Error getting calendar range: {e}") raise HTTPException(status_code=500, detail=f"Calendar service error: {str(e)}") @app.get("/api/calendar/{date_str}") def get_calendar_by_date(date_str: str): """ Get Chinese lunar calendar information for a specific date 📅 Public endpoint - No API key required **Path Parameter:** - `date_str`: Date in YYYY-MM-DD format (e.g., '2025-12-10') **Response:** Same as `/api/calendar/today` but for the specified date **Example:** ``` GET /api/calendar/2025-12-10 ``` """ try: logger.info(f"📅 Fetching calendar for: {date_str}") # Parse date string try: year, month, day = map(int, date_str.split('-')) target_date = date(year, month, day) except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD (e.g., '2025-12-10')" ) result = ChineseCalendarService.get_calendar_by_date(target_date) logger.info(f"✅ Calendar retrieved for: {date_str}") return result except HTTPException: raise except Exception as e: logger.error(f"❌ Error getting calendar for {date_str}: {e}") raise HTTPException(status_code=500, detail=f"Calendar service error: {str(e)}") class LunarToSolarRequest(BaseModel): """Request model for Lunar to Solar conversion""" lunar_year: int = Field(..., description="Lunar year (e.g., 2025)", ge=1900, le=2100, example=2025) lunar_month: int = Field(..., description="Lunar month (1-12)", ge=1, le=12, example=1) lunar_day: int = Field(..., description="Lunar day (1-30)", ge=1, le=30, example=1) is_leap_month: bool = Field(False, description="Whether it's a leap month (闰月)", example=False) @app.post("/api/calendar/convert-lunar-to-solar") def convert_lunar_to_solar(request: LunarToSolarRequest): """ Convert Chinese Lunar calendar date to Gregorian (Solar) calendar date 🌙 农历转公历 | Lunar to Solar Conversion 📅 Public endpoint - No API key required **Request Body:** ```json { "lunar_year": 2025, "lunar_month": 1, "lunar_day": 1, "is_leap_month": false } ``` **Response:** ```json { "success": true, "solar_date": "2025-01-29", "lunar_date": "2025-01-01", "solar_date_string": "2025年1月29日", "lunar_date_string": "2025年正月初一", "weekday": "星期三", "ganzhi": "己巳年 丁丑月 甲子日", "zodiac": "蛇" } ``` **Example:** ```bash curl -X POST "https://sisithefox-astrologybazi-api.hf.space/api/calendar/convert-lunar-to-solar" \\ -H "Content-Type: application/json" \\ -d '{"lunar_year": 2025, "lunar_month": 1, "lunar_day": 1, "is_leap_month": false}' ``` """ try: logger.info(f"🌙 Converting lunar date: {request.lunar_year}-{request.lunar_month}-{request.lunar_day} (leap: {request.is_leap_month})") # Create Lunar object # Note: For leap month, use negative month value month_value = -request.lunar_month if request.is_leap_month else request.lunar_month lunar = Lunar.fromYmd(request.lunar_year, month_value, request.lunar_day) # Convert to Solar solar = lunar.getSolar() # Get calendar info for the converted solar date target_date = date(solar.getYear(), solar.getMonth(), solar.getDay()) calendar_info = ChineseCalendarService.get_calendar_by_date(target_date) # Build response response = { "success": True, "solar_date": solar.toYmd(), "lunar_date": f"{request.lunar_year}-{request.lunar_month:02d}-{request.lunar_day:02d}", "solar_date_string": f"{solar.getYear()}年{solar.getMonth()}月{solar.getDay()}日", "lunar_date_string": calendar_info.get("lunar", {}).get("date_string", ""), "weekday": calendar_info.get("weekday", ""), "ganzhi": calendar_info.get("ganzhi", {}).get("full", ""), "zodiac": calendar_info.get("zodiac", {}).get("animal", ""), "is_leap_month": request.is_leap_month } logger.info(f"✅ Conversion successful: {response['lunar_date']} -> {response['solar_date']}") return response except Exception as e: logger.error(f"❌ Lunar to solar conversion failed: {e}") raise HTTPException( status_code=400, detail=f"Invalid lunar date or conversion error: {str(e)}" ) # ==================== Nine Flying Stars API ==================== class NineStarsRequest(BaseModel): """Request model for Nine Flying Stars analysis""" date: str = Field(..., description="Target date in YYYY-MM-DD format", example="2025-12-11") hour: int = Field(12, description="Hour of the day (0-23), default 12 (noon)", ge=0, le=23) @app.post("/api/nine-stars/analyze") def analyze_nine_stars( req: NineStarsRequest, api_key: str = Depends(verify_api_key) ): """ 🌟 Calculate and analyze Nine Flying Stars (九宫飞星) 🔒 Protected endpoint - Requires API key Based on traditional Xuan Kong Feng Shui (玄空风水) theory: - 《钦定协纪辨方书》 - 《紫白赋》 - 《沈氏玄空学》 **Parameters:** - `date`: Target date in YYYY-MM-DD format (e.g., "2025-12-11") - `hour`: Hour of the day (0-23), default 12 (noon) **Response:** - Yearly/Monthly/Daily/Hourly flying stars (年月日时飞星) - Nine palace distribution (九宫分布) - Fortune analysis (吉凶分析) - Lucky/Unlucky directions (吉凶方位) **Example Response:** ```json { "success": true, "data": { "date": "2025-12-11", "hour": 12, "yearly_star": { "number": 2, "name": "二黑巨门星", "fortune": "凶" }, "monthly_star": { "number": 8, "name": "八白左辅星", "fortune": "大吉" }, "daily_star": { "number": 6, "name": "六白武曲星", "fortune": "吉" }, "hourly_star": { "number": 3, "name": "三碧禄存星", "fortune": "凶" }, "palace_distribution": { "1": 6, "2": 7, "3": 8, "4": 9, "5": 1, "6": 2, "7": 3, "8": 4, "9": 5 }, "palace_details": {...}, "fortune_analysis": { "overall_score": 6.5, "overall_fortune": "吉", "description": "运势平稳向上,谨慎行事可获成功。", "lucky_directions": ["西北", "南", "东"], "avoid_directions": ["西南", "西", "东北"] } } } ``` """ try: logger.info(f"🌟 Analyzing Nine Flying Stars for: {req.date} at hour {req.hour}") # Parse and validate date try: target_date = datetime.strptime(req.date, "%Y-%m-%d").date() except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD (e.g., '2025-12-11')" ) # Validate hour if not 0 <= req.hour <= 23: raise HTTPException( status_code=400, detail="Invalid hour. Must be between 0 and 23" ) # Calculate Nine Flying Stars result = NineStarsCalculator.get_complete_analysis(target_date, req.hour) logger.info(f"✅ Nine Stars analysis successful: {result['fortune_analysis']['overall_fortune']}") return { "success": True, "data": result } except HTTPException: raise except Exception as e: logger.error(f"❌ Nine Stars calculation error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Nine Stars analysis failed: {str(e)}") @app.get("/api/nine-stars/today") def get_today_nine_stars( hour: int = 12, api_key: str = Depends(verify_api_key) ): """ 🌟 Get today's Nine Flying Stars analysis 🔒 Protected endpoint - Requires API key **Query Parameters:** - `hour`: Hour of the day (0-23), default 12 (noon) **Returns:** Same as `/api/nine-stars/analyze` but for today's date """ try: today = datetime.now().date() logger.info(f"🌟 Fetching today's Nine Flying Stars at hour {hour}") result = NineStarsCalculator.get_complete_analysis(today, hour) logger.info(f"✅ Today's Nine Stars retrieved: {result['fortune_analysis']['overall_fortune']}") return { "success": True, "data": result } except Exception as e: logger.error(f"❌ Error getting today's Nine Stars: {e}") raise HTTPException(status_code=500, detail=f"Nine Stars service error: {str(e)}") # ==================== 28 Lunar Mansions API ==================== class Constellation28Request(BaseModel): """Request model for 28 Constellations analysis""" date: str = Field(..., description="Target date in YYYY-MM-DD format", example="2025-12-11") activities: List[str] = Field( default=[], description="Activities to analyze (e.g., ['嫁娶', '开市', '安葬'])", example=["嫁娶", "开市"] ) @app.post("/api/28-constellations/analyze") def analyze_28_constellations( req: Constellation28Request, api_key: str = Depends(verify_api_key) ): """ ⭐ Analyze 28 Lunar Mansions (二十八星宿) for date selection 🔒 Protected endpoint - Requires API key Based on traditional Chinese astronomy and almanac selection: - 《钦定协纪辨方书》 - 《星宿择日法》 **Parameters:** - `date`: Target date in YYYY-MM-DD format - `activities`: List of activities to check (optional) **Response:** - Current constellation (当日星宿) - Four Symbols classification (四象) - Fortune level (吉凶等级 1-5) - Suitable/Avoid activities (宜忌) - Activity-specific recommendations (择日建议) **Example Response:** ```json { "success": true, "data": { "date": "2025-12-11", "constellation": { "name": "角", "pinyin": "Jiao", "symbol": "东方青龙", "animal": "蛟龙", "element": "木", "fortune": "吉", "level": 4, "description": "龙角,主功名、文书、升迁" }, "suitable_activities": ["祭祀", "嫁娶", "纳采", "开市"], "avoid_activities": ["安葬", "动土"], "fortune_analysis": { "recommendation": "今日角宿值日,大吉之日", "activity_analysis": [ { "activity": "嫁娶", "verdict": "大吉", "reason": "角宿宜嫁娶" } ] } } } ``` """ try: logger.info(f"⭐ Analyzing 28 Constellations for: {req.date}") # Parse date try: target_date = datetime.strptime(req.date, "%Y-%m-%d").date() except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD" ) # Get constellation for the date constellation_data = Constellation28Calculator.get_constellation(target_date) # Prepare response result = { "date": str(target_date), "constellation": constellation_data, "suitable_activities": constellation_data['suitable'], "avoid_activities": constellation_data['avoid'] } # Add fortune analysis if activities provided if req.activities: analysis = Constellation28Calculator.get_fortune_analysis( target_date, req.activities ) result['fortune_analysis'] = analysis logger.info(f"✅ 28 Constellations analysis successful: {constellation_data['name']}宿") return { "success": True, "data": result } except HTTPException: raise except Exception as e: logger.error(f"❌ 28 Constellations error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"28 Constellations analysis failed: {str(e)}") @app.get("/api/28-constellations/today") def get_today_constellation(api_key: str = Depends(verify_api_key)): """ ⭐ Get today's 28 Lunar Mansions constellation 🔒 Protected endpoint - Requires API key **Returns:** Today's constellation info with suitable/avoid activities """ try: today = datetime.now().date() logger.info(f"⭐ Fetching today's constellation") constellation_data = Constellation28Calculator.get_constellation(today) result = { "date": str(today), "constellation": constellation_data, "suitable_activities": constellation_data['suitable'], "avoid_activities": constellation_data['avoid'] } logger.info(f"✅ Today's constellation: {constellation_data['name']}宿") return { "success": True, "data": result } except Exception as e: logger.error(f"❌ Error getting today's constellation: {e}") raise HTTPException(status_code=500, detail=f"Constellation service error: {str(e)}") @app.post("/api/28-constellations/find-auspicious") def find_auspicious_dates( activity: str, start_date: str, end_date: str, min_level: int = 3, api_key: str = Depends(verify_api_key) ): """ ⭐ Find auspicious dates for specific activity (寻找吉日) 🔒 Protected endpoint - Requires API key **Query Parameters:** - `activity`: Activity name (e.g., "嫁娶", "开市", "安葬") - `start_date`: Start date (YYYY-MM-DD) - `end_date`: End date (YYYY-MM-DD) - `min_level`: Minimum fortune level (1-5), default 3 **Returns:** List of auspicious dates ranked by fortune level **Example:** ``` POST /api/28-constellations/find-auspicious { "activity": "嫁娶", "start_date": "2025-12-11", "end_date": "2026-01-11", "min_level": 4 } ``` """ try: logger.info(f"⭐ Finding auspicious dates for: {activity}") # Parse dates try: start = datetime.strptime(start_date, "%Y-%m-%d").date() end = datetime.strptime(end_date, "%Y-%m-%d").date() except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD" ) # Validate date range if end <= start: raise HTTPException( status_code=400, detail="end_date must be after start_date" ) if (end - start).days > 365: raise HTTPException( status_code=400, detail="Date range cannot exceed 1 year" ) # Find auspicious dates auspicious_dates = Constellation28Calculator.find_auspicious_dates( start, end, activity, min_level ) logger.info(f"✅ Found {len(auspicious_dates)} auspicious dates") return { "success": True, "data": { "activity": activity, "date_range": { "start": str(start), "end": str(end) }, "min_level": min_level, "found_count": len(auspicious_dates), "auspicious_dates": auspicious_dates[:30] # Limit to 30 results } } except HTTPException: raise except Exception as e: logger.error(f"❌ Error finding auspicious dates: {e}") raise HTTPException(status_code=500, detail=f"Auspicious date search failed: {str(e)}") # ======================================== # 🐉 Zodiac Fortune Endpoints (生肖运程) # ======================================== from zodiac_fortune_service import ZodiacFortuneService @app.get("/api/zodiac-fortune/today/{zodiac}") def get_today_zodiac_fortune(zodiac: str): """ 🐉 Get today's fortune for a specific zodiac (今日生肖运程) **Path Parameters:** - `zodiac`: Chinese zodiac animal (鼠/牛/虎/兔/龙/蛇/马/羊/猴/鸡/狗/猪) **Returns:** Complete fortune analysis for today **Example:** ```bash curl https://sisithefox-astrologybazi-api.hf.space/api/zodiac-fortune/today/鼠 ``` **Response:** ```json { "success": true, "date": "2025-12-11", "zodiac": "鼠", "overall": { "score": 9, "description": "今日与值日地支相合,贵人相助,万事顺遂" }, "career": {"score": 9, "advice": "贵人相助,可开展新业务"}, "wealth": {"score": 8, "advice": "财运亨通,可适当投资"}, "love": {"score": 9, "advice": "桃花运旺,单身者可主动出击"}, "health": {"score": 8, "advice": "精力充沛,身体健康"}, "lucky_hours": [...], "lucky_color": ["黑色", "蓝色"], "lucky_number": [1, 2] } ``` """ try: logger.info(f"🐉 Getting today's fortune for zodiac: {zodiac}") today = datetime.now().date() fortune = ZodiacFortuneService.generate_daily_fortune(zodiac, today) return fortune except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Error getting zodiac fortune: {e}") raise HTTPException(status_code=500, detail=f"Zodiac fortune service error: {str(e)}") @app.get("/api/zodiac-fortune/{zodiac}/{date_str}") def get_zodiac_fortune_by_date(zodiac: str, date_str: str): """ 🐉 Get fortune for a specific zodiac on a specific date (指定日期生肖运程) **Path Parameters:** - `zodiac`: Chinese zodiac animal (鼠/牛/虎/兔/龙/蛇/马/羊/猴/鸡/狗/猪) - `date_str`: Date in YYYY-MM-DD format **Returns:** Complete fortune analysis for the specified date **Example:** ```bash curl https://sisithefox-astrologybazi-api.hf.space/api/zodiac-fortune/龙/2026-01-01 ``` **Response:** Same format as `/api/zodiac-fortune/today/{zodiac}` """ try: # The generic two-segment route is registered before the historical # all-zodiac route. Delegate here so /all/today and /all/YYYY-MM-DD # cannot be mistaken for a single zodiac named "all". if zodiac == 'all': return get_all_zodiac_fortune(date_str) logger.info(f"🐉 Getting fortune for {zodiac} on {date_str}") # Parse date try: target_date = datetime.strptime(date_str, "%Y-%m-%d").date() except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD" ) fortune = ZodiacFortuneService.generate_daily_fortune(zodiac, target_date) return fortune except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except HTTPException: raise except Exception as e: logger.error(f"❌ Error getting zodiac fortune: {e}") raise HTTPException(status_code=500, detail=f"Zodiac fortune service error: {str(e)}") @app.get("/api/zodiac-fortune/all/{date_str}") def get_all_zodiac_fortune(date_str: str): """ 🐉 Get fortune for ALL 12 zodiacs on a specific date (12生肖运程) **Path Parameters:** - `date_str`: Date in YYYY-MM-DD format or "today" **Returns:** Fortune for all 12 Chinese zodiacs **Example:** ```bash curl https://sisithefox-astrologybazi-api.hf.space/api/zodiac-fortune/all/2026-01-01 curl https://sisithefox-astrologybazi-api.hf.space/api/zodiac-fortune/all/today ``` **Response:** ```json { "success": true, "date": "2026-01-01", "fortunes": { "鼠": {...}, "牛": {...}, ... } } ``` """ try: logger.info(f"🐉 Getting fortune for all zodiacs on {date_str}") # Parse date if date_str.lower() == "today": target_date = datetime.now().date() else: try: target_date = datetime.strptime(date_str, "%Y-%m-%d").date() except ValueError: raise HTTPException( status_code=400, detail="Invalid date format. Use YYYY-MM-DD or 'today'" ) # Generate fortune for all 12 zodiacs zodiacs = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'] fortunes = {} for zodiac in zodiacs: try: fortunes[zodiac] = ZodiacFortuneService.generate_daily_fortune(zodiac, target_date) except Exception as e: logger.error(f"Error generating fortune for {zodiac}: {e}") fortunes[zodiac] = {"success": False, "error": str(e)} return { "success": True, "date": str(target_date), "fortunes": fortunes } except HTTPException: raise except Exception as e: logger.error(f"❌ Error getting all zodiac fortunes: {e}") raise HTTPException(status_code=500, detail=f"Zodiac fortune service error: {str(e)}") # ==================== 八字合婚 API ==================== class MarriageCompatibilityRequest(BaseModel): """Request model for marriage compatibility analysis (八字合婚)""" male_birth_date: str = Field( ..., description="Male birth date in YYYY-MM-DD format", example="1990-01-15" ) male_birth_time: str = Field( ..., description="Male birth time in HH:MM format", example="08:30" ) male_is_lunar: bool = Field( default=False, description="True if male birth date is lunar calendar" ) female_birth_date: str = Field( ..., description="Female birth date in YYYY-MM-DD format", example="1992-06-20" ) female_birth_time: str = Field( ..., description="Female birth time in HH:MM format", example="14:00" ) female_is_lunar: bool = Field( default=False, description="True if female birth date is lunar calendar" ) @app.post("/api/marriage-compatibility") @app.post("/marriage-compatibility") def analyze_marriage_compatibility( req: MarriageCompatibilityRequest, api_key: str = Depends(verify_api_key) ): """ 💍 八字合婚分析 (Marriage Compatibility Analysis) 基于《男女能結合的奧祕》五要则评分系统: 1. 八字相似 (25%): 两人八字中相同、相生、相合之字 2. 禍福相同 (30%): 夫妻命运走向是否一致 3. 子女同步 (15%): 头胎子女性别是否一致 4. 年支同氣 (15%): 年支是否同为一方之气 5. 日干舒配 (15%): 日干五行配合 🔒 Protected endpoint - Requires API key **Request Body:** - `male_birth_date`: 男方出生日期 (YYYY-MM-DD) - `male_birth_time`: 男方出生时间 (HH:MM) - `female_birth_date`: 女方出生日期 (YYYY-MM-DD) - `female_birth_time`: 女方出生时间 (HH:MM) **Returns:** - 总分 (0-100) - 评级 (天作之合/上等婚配/中上婚配/中等婚配/中下婚配/下等婚配/不宜婚配) - 五要则详细分析 - 冲克分析 - 合婚建议 **Example:** ```json { "male_birth_date": "1990-01-15", "male_birth_time": "08:30", "female_birth_date": "1992-06-20", "female_birth_time": "14:00" } ``` """ try: logger.info(f"💍 Marriage compatibility analysis: Male={req.male_birth_date} Female={req.female_birth_date}") # 计算男方八字 male_bazi = BaZiCalculator.calculate_bazi( req.male_birth_date, req.male_birth_time, gender='male', is_lunar=req.male_is_lunar ) # 计算女方八字 female_bazi = BaZiCalculator.calculate_bazi( req.female_birth_date, req.female_birth_time, gender='female', is_lunar=req.female_is_lunar ) # 合婚分析 analyzer = MarriageCompatibilityAnalyzer(male_bazi, female_bazi) result = analyzer.analyze_compatibility() # 添加八字信息到结果 result['male_bazi'] = { 'birth_date': req.male_birth_date, 'birth_time': req.male_birth_time, 'bazi_chart': male_bazi.get('bazi_chart', {}) } result['female_bazi'] = { 'birth_date': req.female_birth_date, 'birth_time': req.female_birth_time, 'bazi_chart': female_bazi.get('bazi_chart', {}) } logger.info(f"✅ Marriage compatibility analysis complete: {result['total_score']}分 ({result['rating']})") return { "success": True, "data": result } except HTTPException: raise except Exception as e: logger.error(f"❌ Marriage compatibility error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Marriage compatibility analysis failed: {str(e)}") # ==================== Comprehensive Fortune Score API ==================== class FortuneScoreRequest(BaseModel): """Request model for comprehensive fortune score (0-120 scoring system)""" birth_date: str = Field( ..., description="Birth date in YYYY-MM-DD format", example="1998-03-15" ) birth_time: str = Field( ..., description="Birth time in HH:MM format", example="10:30" ) gender: str = Field( ..., description="Gender: 'male' or 'female'", example="female" ) is_lunar: bool = Field( default=False, description="True if birth date is lunar calendar" ) target_date: Optional[str] = Field( default=None, description="Target date for fortune analysis in YYYY-MM-DD format (default: today)", example="2026-06-15" ) include_western: bool = Field( default=True, description="Include Western astrology (Saturn/Jupiter Return) in scoring" ) @app.post("/api/fortune-score") @app.post("/fortune-score") def calculate_comprehensive_fortune_score( req: FortuneScoreRequest, api_key: str = Depends(verify_api_key) ): """ 🔮 综合运势评分 (Comprehensive Fortune Score) 算法融合东西方命理系统: - 东方 (70%): 八字身财关系 + 大运成败 + 流年吉凶 + 十神组合 - 西方 (30%): 土星回归 + 木星回归 + 行星过境 + 星体力量 评分体系: - 0-120分综合评分 - 5级评级:excellent (90+), good (70-89), average (50-69), challenging (30-49), difficult (<30) 特色功能: - ✨ 木星回归(12年周期):机会期加分 - ⚠️ 土星回归(29.5年周期):压力期减分 - 🎯 十神组合检测:10种关键吉凶组合 - 📊 精确身财关系:4步量化法(0-607分) """ try: logger.info(f"📊 Fortune score request: {req.birth_date} {req.birth_time} ({req.gender})") # 1. Calculate BaZi bazi_result = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, num_dayun=12 ) # 2. Analyze Genju (格局) genju_analyzer = GenjuAnalyzer(bazi_result) genju_analysis = genju_analyzer.analyze_genju() # 3. Analyze Dayun (大运) dayun_analyzer = DayunAnalyzer(bazi_result, genju_analysis) dayun_analysis = dayun_analyzer.analyze_all_dayun() # 4. Calculate comprehensive fortune score calculator = FortuneScoreCalculator( bazi_chart=bazi_result, genju_analysis=genju_analysis, dayun_analysis=dayun_analysis ) # Parse target date if req.target_date: target = datetime.strptime(req.target_date, '%Y-%m-%d').date() else: target = datetime.now().date() # Calculate score result = calculator.calculate_comprehensive_score( target_date=target, include_western=req.include_western ) logger.info(f"✅ Fortune score complete: {result['total_score']}/120 ({result['rating']})") return { "success": True, "data": result } except HTTPException: raise except Exception as e: logger.error(f"❌ Fortune score error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Fortune score calculation failed: {str(e)}") # ============================================================================ # 🌟 RITUALS API - Energy Rituals & Spiritual Practices (2026-01-25) # ============================================================================ class RitualSearchRequest(BaseModel): """仪式搜索请求""" query: Optional[str] = Field(None, description="关键词搜索") category: Optional[List[str]] = Field(None, description="类别筛选") difficulty: Optional[int] = Field(None, ge=1, le=5, description="难度筛选 (1-5)") ritual_type: Optional[str] = Field(None, description="类型筛选") max_time: Optional[int] = Field(None, description="最大时长(分钟)") include_premium: Optional[bool] = Field(True, description="是否包含Premium内容") class RitualRecommendRequest(BaseModel): """仪式推荐请求""" user_need: str = Field(..., description="用户需求") limit: Optional[int] = Field(5, ge=1, le=20, description="返回数量") birth_date: Optional[str] = Field(None, description="出生日期(可选,用于更精准推荐)") birth_time: Optional[str] = Field(None, description="出生时间") gender: Optional[str] = Field(None, description="性别") @app.get("/rituals") @app.get("/api/rituals") # 备用路由 def get_all_rituals(api_key: str = Depends(verify_api_key)): """ 获取所有能量仪式 Usage: GET /rituals Headers: X-API-Key: your_api_key Returns: { "success": true, "data": { "rituals": [...], "total": 100, "categories": [...], "statistics": {...} } } """ try: logger.info("🌟 Fetching all rituals") rituals = rituals_service.get_all_rituals() logger.info(f"✅ Retrieved {len(rituals)} rituals") return { "success": True, "data": { "rituals": rituals, "total": len(rituals), "categories": rituals_service.get_categories(), "statistics": rituals_service.get_statistics() } } except Exception as e: logger.error(f"❌ Get all rituals error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to fetch rituals: {str(e)}") @app.get("/rituals/statistics") @app.get("/api/rituals/statistics") # 备用路由 def get_rituals_statistics(api_key: str = Depends(verify_api_key)): """ 获取仪式统计信息 Usage: GET /rituals/statistics Headers: X-API-Key: your_api_key Returns: { "success": true, "data": { "total_rituals": 100, "by_category": {...}, "by_difficulty": {...}, "by_type": {...}, "average_duration": 30, "premium_count": 20, "free_count": 80 } } """ try: logger.info("🌟 Fetching rituals statistics") stats = rituals_service.get_statistics() logger.info(f"✅ Retrieved statistics for {stats['totalRituals']} rituals") return { "success": True, "data": stats } except Exception as e: logger.error(f"❌ Get statistics error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to fetch statistics: {str(e)}") # NOTE: /rituals/statistics is registered above this dynamic route on purpose — # FastAPI matches routes in registration order, and /rituals/{ritual_id} would # otherwise greedily swallow "statistics" as a ritual_id and 404. @app.get("/rituals/{ritual_id}") @app.get("/api/rituals/{ritual_id}") # 备用路由 def get_ritual_by_id(ritual_id: str, api_key: str = Depends(verify_api_key)): """ 获取单个仪式详情 Usage: GET /rituals/new_moon_abundance Headers: X-API-Key: your_api_key Parameters: ritual_id: 仪式ID Returns: { "success": true, "data": { "ritual": {...}, "related_rituals": [...] } } """ try: logger.info(f"🌟 Fetching ritual: {ritual_id}") ritual = rituals_service.get_ritual_by_id(ritual_id) if not ritual: raise HTTPException(status_code=404, detail=f"Ritual not found: {ritual_id}") # 获取相关仪式 related = rituals_service.get_related_rituals(ritual_id, limit=5) logger.info(f"✅ Retrieved ritual: {ritual.get('title_en')}") return { "success": True, "data": { "ritual": ritual, "related_rituals": related } } except HTTPException: raise except Exception as e: logger.error(f"❌ Get ritual error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to fetch ritual: {str(e)}") @app.post("/rituals/search") @app.post("/api/rituals/search") # 备用路由 def search_rituals(request: RitualSearchRequest, api_key: str = Depends(verify_api_key)): """ 搜索仪式 Usage: POST /rituals/search Headers: X-API-Key: your_api_key Body: { "query": "abundance", "category": ["prosperity", "healing"], "difficulty": 3, "max_time": 30, "include_premium": true } Parameters: query: 关键词搜索(可选) category: 类别筛选(可选) difficulty: 难度筛选 1-5(可选) ritual_type: 类型筛选(可选) max_time: 最大时长分钟数(可选) include_premium: 是否包含Premium内容(默认true) Returns: { "success": true, "data": { "rituals": [...], "total": 25, "filters_applied": {...} } } """ try: logger.info(f"🌟 Searching rituals with query: {request.query}") rituals = rituals_service.search_rituals( query=request.query, category=request.category, difficulty=request.difficulty, ritual_type=request.ritual_type, max_time=request.max_time, include_premium=request.include_premium ) logger.info(f"✅ Found {len(rituals)} matching rituals") return { "success": True, "data": { "rituals": rituals, "total": len(rituals), "filters_applied": { "query": request.query, "category": request.category, "difficulty": request.difficulty, "ritual_type": request.ritual_type, "max_time": request.max_time, "include_premium": request.include_premium } } } except Exception as e: logger.error(f"❌ Search rituals error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to search rituals: {str(e)}") @app.get("/rituals/category/{category}") @app.get("/api/rituals/category/{category}") # 备用路由 def get_rituals_by_category(category: str, api_key: str = Depends(verify_api_key)): """ 按类别获取仪式 Usage: GET /rituals/category/prosperity Headers: X-API-Key: your_api_key Parameters: category: 类别名称(prosperity, healing, protection, love, career, spiritual_growth) Returns: { "success": true, "data": { "category": "prosperity", "rituals": [...], "total": 15 } } """ try: logger.info(f"🌟 Fetching rituals for category: {category}") rituals = rituals_service.search_rituals(category=[category]) logger.info(f"✅ Found {len(rituals)} rituals in category: {category}") return { "success": True, "data": { "category": category, "rituals": rituals, "total": len(rituals) } } except Exception as e: logger.error(f"❌ Get category rituals error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to fetch category rituals: {str(e)}") @app.get("/rituals/moon-phase/current") @app.get("/api/rituals/moon-phase/current") # 备用路由 def get_current_moon_phase_rituals(api_key: str = Depends(verify_api_key)): """ 获取当前月相适合的仪式 Usage: GET /rituals/moon-phase/current Headers: X-API-Key: your_api_key Returns: { "success": true, "data": { "moon_phase": "waxing_crescent", "rituals": [...], "total": 8, "description": "适合显化和吸引的时期" } } """ try: logger.info("🌟 Fetching current moon phase rituals") result = rituals_service.get_current_moon_phase_rituals() logger.info(f"✅ Retrieved moon phase rituals: {result['currentPhase']}") return { "success": True, "data": result } except Exception as e: logger.error(f"❌ Get moon phase rituals error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to fetch moon phase rituals: {str(e)}") @app.post("/rituals/recommend") @app.post("/api/rituals/recommend") # 备用路由 def recommend_rituals(request: RitualRecommendRequest, api_key: str = Depends(verify_api_key)): """ 个性化仪式推荐 Usage: POST /rituals/recommend Headers: X-API-Key: your_api_key Body: { "user_need": "I need more abundance in my life", "limit": 5, "birth_date": "1990-05-15", "birth_time": "14:30", "gender": "female" } Parameters: user_need: 用户需求描述(必填) limit: 返回数量 1-20(默认5) birth_date: 出生日期(可选,用于更精准推荐) birth_time: 出生时间(可选) gender: 性别(可选) Returns: { "success": true, "data": { "recommendations": [...], "total": 5, "user_need": "...", "personalized": true } } """ try: logger.info(f"🌟 Recommending rituals for need: {request.user_need}") recommendations = rituals_service.recommend_rituals( user_need=request.user_need, limit=request.limit, birth_date=request.birth_date, birth_time=request.birth_time, gender=request.gender ) is_personalized = bool(request.birth_date) logger.info(f"✅ Generated {len(recommendations)} recommendations (personalized: {is_personalized})") return { "success": True, "data": { "recommendations": recommendations, "total": len(recommendations), "user_need": request.user_need, "personalized": is_personalized } } except Exception as e: logger.error(f"❌ Recommend rituals error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to recommend rituals: {str(e)}") @app.get("/rituals/related/{ritual_id}") @app.get("/api/rituals/related/{ritual_id}") # 备用路由 def get_related_rituals(ritual_id: str, limit: int = 5, api_key: str = Depends(verify_api_key)): """ 获取相关仪式 Usage: GET /rituals/related/new_moon_abundance?limit=5 Headers: X-API-Key: your_api_key Parameters: ritual_id: 仪式ID limit: 返回数量(默认5) Returns: { "success": true, "data": { "ritual_id": "new_moon_abundance", "related_rituals": [...], "total": 5 } } """ try: logger.info(f"🌟 Fetching related rituals for: {ritual_id}") related = rituals_service.get_related_rituals(ritual_id, limit=limit) logger.info(f"✅ Found {len(related)} related rituals") return { "success": True, "data": { "ritual_id": ritual_id, "related_rituals": related, "total": len(related) } } except Exception as e: logger.error(f"❌ Get related rituals error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to fetch related rituals: {str(e)}") # ============================================================================ # 🖐️ BODY READING API - Five Elements Body/Hand/Face Prediction (2026-02-16) # ============================================================================ class BodyReadingRequest(BaseModel): """体相分析请求""" birth_date: str = Field(..., description="出生日期 YYYY-MM-DD", example="1990-01-15") birth_time: str = Field(default="12:00", description="出生时间 HH:MM", example="08:30") gender: str = Field(default="male", description="性别 male/female") is_lunar: bool = Field(default=False, description="是否农历") @app.post("/analyze-body-reading") @app.post("/api/analyze-body-reading") def analyze_body_reading_endpoint( req: BodyReadingRequest, api_key: str = Depends(verify_api_key) ): """ 🖐️ 五行体相预测 (Five Elements Body Reading Prediction) 基于BaZi五行规则系统,预测手型/面相/体型/性格/健康 纯规则匹配(免费),不调用AI 用途:作为手相/面相付费分析的免费钩子 - 免费:五行体相预测(本API) - 付费:AI实拍掌纹/面相分析(vision AI) """ try: logger.info(f"🖐️ Body reading request: {req.birth_date} {req.birth_time} ({req.gender})") # 1. Calculate BaZi bazi_result = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, num_dayun=3 ) # 2. Analyze body reading result = analyze_body_reading(bazi_result) # 3. Add BaZi summary info chart = bazi_result.get('bazi_chart', {}) result['bazi_summary'] = { 'four_pillars': { 'year': {'stem': chart.get('year', {}).get('gan', ''), 'branch': chart.get('year', {}).get('zhi', '')}, 'month': {'stem': chart.get('month', {}).get('gan', ''), 'branch': chart.get('month', {}).get('zhi', '')}, 'day': {'stem': chart.get('day', {}).get('gan', ''), 'branch': chart.get('day', {}).get('zhi', '')}, 'hour': {'stem': chart.get('hour', {}).get('gan', ''), 'branch': chart.get('hour', {}).get('zhi', '')}, }, 'day_master': bazi_result.get('day_master', {}), } logger.info(f"✅ Body reading complete: dominant={result['dominant_element']}") return { "success": True, "data": result } except HTTPException: raise except Exception as e: logger.error(f"❌ Body reading error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Body reading analysis failed: {str(e)}") # ============================================================================ # 💕 LOVE CLARITY - Marriage Destiny Analysis (2026-02-28) # ============================================================================ class MarriageDestinyRequest(BaseModel): """Love Clarity 婚姻命运分析请求""" birth_date: str = Field( ..., description="Birth date in YYYY-MM-DD format", example="1992-08-15" ) birth_time: str = Field( ..., description="Birth time in HH:MM format", example="14:30" ) gender: str = Field( default="female", description="Gender: 'male' or 'female'", example="female" ) is_lunar: bool = Field( default=False, description="True if birth date is lunar calendar" ) relationship_status: Optional[str] = Field( default=None, description="Current relationship status for context", example="cold_war" ) children_status: Optional[str] = Field( default=None, description="Children status for enhanced analysis", example="teenager" ) @app.post("/analyze-marriage-destiny") @app.post("/api/analyze-marriage-destiny") def analyze_marriage_destiny( req: MarriageDestinyRequest, api_key: str = Depends(verify_api_key) ): """ 💕 Love & Marriage Clarity 婚姻命运深度分析 为 Love Clarity 产品 ($39/$99) 提供规则化命理数据,包含: 1. 日刃格检测 — 6个极端风险日柱 2. 增强桃花分析 — 5种类型(正桃花/野桃花/桃花劫/桃花煞/滚浪桃花) 3. 暗克分析 — 婚姻宫隐性冲克 4. 子女星分析 — 食伤星对婚姻影响 5. 2026流年影响 — 丙午年婚姻冲击 6. 冷读钩子 — 3个情绪共鸣锚点 + 完整的传统婚姻分析(配偶星、婚姻宫、100分评分、女命规则等) 🔒 Protected endpoint - Requires API key """ try: logger.info(f"💕 Marriage destiny analysis: {req.birth_date} {req.birth_time} ({req.gender})") # 1. Calculate complete BaZi with dayun bazi_result = BaZiCalculator.calculate_bazi_complete( birth_date=req.birth_date, birth_time=req.birth_time, gender=req.gender, is_lunar=req.is_lunar, num_dayun=8 # 80年大运 ) # 2. Traditional marriage analysis marriage_analyzer = MarriageAnalyzer(bazi_result) traditional_analysis = marriage_analyzer.analyze_marriage() # 3. Love Clarity enhanced analysis clarity_analyzer = LoveClarityAnalyzer(bazi_result) enhanced_analysis = clarity_analyzer.analyze_all() # 4. BaZi chart summary chart = bazi_result.get('bazi_chart', {}) bazi_summary = { 'four_pillars': { 'year': {'stem': chart.get('year', {}).get('gan', ''), 'branch': chart.get('year', {}).get('zhi', '')}, 'month': {'stem': chart.get('month', {}).get('gan', ''), 'branch': chart.get('month', {}).get('zhi', '')}, 'day': {'stem': chart.get('day', {}).get('gan', ''), 'branch': chart.get('day', {}).get('zhi', '')}, 'hour': {'stem': chart.get('hour', {}).get('gan', ''), 'branch': chart.get('hour', {}).get('zhi', '')}, }, 'day_master': bazi_result.get('day_master', {}), 'ten_gods': bazi_result.get('ten_gods', {}), } # 5. Combine all data result = { # Enhanced (new) 'day_blade_check': enhanced_analysis['day_blade_check'], 'peach_blossom_analysis': enhanced_analysis['peach_blossom_analysis'], 'hidden_clash_analysis': enhanced_analysis['hidden_clash_analysis'], 'children_star_analysis': enhanced_analysis['children_star_analysis'], 'annual_impact_2026': enhanced_analysis['annual_impact_2026'], 'cold_reading_hooks': enhanced_analysis['cold_reading_hooks'], # Traditional 'spouse_star_analysis': traditional_analysis['spouse_star_analysis'], 'marriage_palace_analysis': traditional_analysis['marriage_palace_analysis'], 'spouse_characteristics': traditional_analysis['spouse_characteristics'], 'marriage_quality': traditional_analysis['marriage_quality'], 'marriage_palace_score': traditional_analysis['marriage_palace_score'], 'marriage_fortune_years': traditional_analysis['marriage_fortune_years'], 'marriage_advice': traditional_analysis['marriage_advice'], 'female_marriage_rules': traditional_analysis.get('female_marriage_rules', []), 'spouse_direction': traditional_analysis.get('spouse_direction', {}), # Context 'bazi_summary': bazi_summary, 'user_context': { 'relationship_status': req.relationship_status, 'children_status': req.children_status, } } logger.info( f"✅ Marriage destiny complete: " f"palace_score={traditional_analysis['marriage_palace_score'].get('score', 'N/A')}, " f"day_blade={enhanced_analysis['day_blade_check']['detected']}, " f"peach_blossom={enhanced_analysis['peach_blossom_analysis']['has_peach_blossom']}" ) return { "success": True, "data": result } except HTTPException: raise except Exception as e: logger.error(f"❌ Marriage destiny analysis error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Marriage destiny analysis failed: {str(e)}") # ==================== Qimen Dunjia (奇门遁甲) ==================== class QimenChartRequest(BaseModel): """Request model for Qimen Dunjia chart calculation""" date: str = Field(..., description="Date in YYYY-MM-DD format", example="2026-04-14") hour: int = Field(..., description="Hour of the day (0-23)", ge=0, le=23, example=10) minute: int = Field(0, description="Minute (0-59)", ge=0, le=59, example=30) purpose: str = Field( "综合", description="Purpose: 事业/财运/感情/健康/学业/诉讼/出行/综合", example="事业" ) @app.post("/api/qimen/chart") @app.post("/qimen-chart") def calculate_qimen_chart( req: QimenChartRequest, api_key: str = Depends(verify_api_key) ): """ 🌀 Calculate Qimen Dunjia Chart (奇门遁甲排盘) 🔒 Protected endpoint - Requires API key Time-based Rotating Plate Method (时家转盘奇门) with: - Complete nine-palace grid (九宫格) with earth/sky stems - Nine Stars (九星), Eight Doors (八门), Eight Gods (八神) - Void palaces (旬空), Post Horse Star (驿马) - Zhi Fu (值符) and Zhi Shi (值使) positions - Fortune analysis per palace and overall recommendations **Parameters:** - `date`: Solar calendar date (YYYY-MM-DD) - `hour`: Hour (0-23) - `minute`: Minute (0-59), default 0 - `purpose`: 事业/财运/感情/健康/学业/诉讼/出行/综合 """ try: logger.info(f"🌀 Calculating Qimen chart for: {req.date} {req.hour}:{req.minute:02d}, purpose={req.purpose}") # Parse date try: parts = req.date.split("-") year, month, day = int(parts[0]), int(parts[1]), int(parts[2]) except (ValueError, IndexError): raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD") result = QimenCalculator.calculate( year=year, month=month, day=day, hour=req.hour, minute=req.minute, purpose=req.purpose ) logger.info( f"✅ Qimen chart: {result['chart_info']['full_name']}, " f"值符={result['zhi_fu']['star']}→{result['zhi_fu']['palace_name']}, " f"overall={result['analysis']['overall_fortune']}" ) return { "success": True, "data": result, "timestamp": datetime.now().isoformat() } except HTTPException: raise except Exception as e: logger.error(f"❌ Qimen calculation error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Qimen chart calculation failed: {str(e)}") @app.get("/api/qimen/now") @app.get("/qimen-now") def get_qimen_now( purpose: str = "综合", api_key: str = Depends(verify_api_key) ): """ 🌀 Get Qimen Dunjia chart for current moment (奇门遁甲 - 当前时刻) 🔒 Protected endpoint - Requires API key **Query Parameters:** - `purpose`: 事业/财运/感情/健康/学业/诉讼/出行/综合 **Returns:** Same as `/api/qimen/chart` but for the current moment """ try: logger.info(f"🌀 Calculating Qimen chart for NOW, purpose={purpose}") result = QimenCalculator.calculate_now(purpose=purpose) logger.info(f"✅ Qimen NOW: {result['chart_info']['full_name']}, overall={result['analysis']['overall_fortune']}") return { "success": True, "data": result, "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"❌ Qimen NOW error: {e}") import traceback logger.error(traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Qimen calculation failed: {str(e)}") class LiuYaoRequest(BaseModel): question: str = Field("", description="占卜问题", example="我的事业发展如何?") purpose: str = Field("综合", description="Purpose: 财运/感情/事业/健康/学业/出行/综合", example="事业") coins: Optional[List[int]] = Field( None, description="手动起卦:6个整数(每爻阳面铜钱数,由下到上),每个值0-3。不传则自动起卦", example=[3, 2, 1, 2, 3, 1] ) @field_validator("coins") @classmethod def validate_coins(cls, v: Optional[List[int]]) -> Optional[List[int]]: if v is None: return v if len(v) != 6: raise ValueError("coins must have exactly 6 elements (one per line, bottom to top)") if not all(0 <= c <= 3 for c in v): raise ValueError("each coin value must be 0–3 (count of yang-face coins)") return v def _liuyao_response(coins, question, purpose) -> dict: result = calculate_liuyao(coins=coins, question=question, purpose=purpose) logger.info( f"✅ Liu Yao: 本卦={result['origin_hexagram']['name']}, " f"变卦={result['variant_hexagram']['name'] if result['variant_hexagram'] else '无'}, " f"动爻={result['changed_positions']}" ) return {"success": True, "data": result, "timestamp": datetime.now().isoformat()} @app.post("/api/liuyao/divine") @app.post("/liuyao-divine") def divine_liuyao( req: LiuYaoRequest, api_key: str = Depends(verify_api_key) ): """ 🔮 Liu Yao (六爻) Divination Chart — 🔒 Requires API key Returns full chart: 本卦/变卦, 六爻(六亲/六神/地支/五行), 世应, 动爻, 空亡, 卦身/伏神. """ try: logger.info(f"🔮 Liu Yao: purpose={req.purpose}, manual_coins={req.coins is not None}") return _liuyao_response(req.coins, req.question, req.purpose) except HTTPException: raise except Exception as e: logger.error(f"❌ Liu Yao error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Liu Yao divination failed: {str(e)}") @app.get("/api/liuyao/now") @app.get("/liuyao-now") def get_liuyao_now( purpose: str = "综合", question: str = "", api_key: str = Depends(verify_api_key) ): """🔮 Liu Yao for current moment — 🔒 Requires API key""" try: logger.info(f"🔮 Liu Yao NOW: purpose={purpose}") return _liuyao_response(None, question, purpose) except Exception as e: logger.error(f"❌ Liu Yao NOW error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Liu Yao calculation failed: {str(e)}") # ==================== Fengshui (八宅法) ==================== from fengshui_calculator import FengshuiCalculator _fengshui = FengshuiCalculator() class FengshuiHomeRequest(BaseModel): facing_direction: str = Field(..., description="大门朝向,如:南/北/东/西/东南/东北/西南/西北") birth_year: Optional[int] = Field(None, description="主要居住者出生年份(可选)") gender: Optional[str] = Field(None, description="性别:male/female 或 男/女(可选)") class FengshuiMingGuaRequest(BaseModel): birth_year: int = Field(..., description="出生年份(公历)") gender: str = Field(..., description="性别:male/female 或 男/女") @app.post("/api/fengshui/home-analysis") @app.post("/fengshui-home-analysis") def fengshui_home_analysis( req: FengshuiHomeRequest, api_key: str = Depends(verify_api_key) ): """🏠 八宅法风水分析:宅卦+八方吉凶+房间建议+命宅匹配 — 🔒 Requires API key""" try: logger.info(f"🏠 Fengshui: facing={req.facing_direction}, birth_year={req.birth_year}") result = _fengshui.full_analysis( facing_direction=req.facing_direction, occupant_birth_year=req.birth_year, occupant_gender=req.gender, ) return {"success": True, "data": result} except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Fengshui home analysis error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Fengshui analysis failed: {str(e)}") @app.post("/api/fengshui/ming-gua") @app.post("/fengshui-ming-gua") def fengshui_ming_gua( req: FengshuiMingGuaRequest, api_key: str = Depends(verify_api_key) ): """🔮 计算个人命卦(八宅法)— 🔒 Requires API key""" try: ming = _fengshui.calculate_ming_gua(req.birth_year, req.gender) personal_dirs = _fengshui.get_personal_auspicious_directions(ming['gua_num']) return {"success": True, "data": {"ming_gua": ming, "personal_directions": personal_dirs}} except Exception as e: logger.error(f"❌ Ming gua error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Ming gua calculation failed: {str(e)}") @app.get("/api/fengshui/zhai-gua/{facing}") def fengshui_zhai_gua( facing: str, api_key: str = Depends(verify_api_key) ): """🏡 根据大门朝向查询宅卦+八方吉凶 — 🔒 Requires API key""" try: zhai = _fengshui.get_zhai_gua(facing) directions = _fengshui.get_eight_directions_fortune(zhai['gua_num']) rooms = _fengshui.get_room_recommendations(zhai['gua_num']) return {"success": True, "data": {"zhai_gua": zhai, "eight_directions": directions, "room_recommendations": rooms}} except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"❌ Zhai gua error: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Zhai gua lookup failed: {str(e)}") if __name__ == "__main__": import uvicorn import sys # Check for port argument port = 7860 # Default for Hugging Face Spaces if len(sys.argv) > 1 and sys.argv[1] == "--port" and len(sys.argv) > 2: port = int(sys.argv[2]) print(f"🚀 Starting server on port {port}") uvicorn.run(app, host="0.0.0.0", port=port)
"""
🔮 AstrologyBazi API - Complete Eastern Mysticism Platform
BaZi (八字) | Nine Flying Stars (九宫飞星) | 28 Lunar Mansions (二十八星宿) | Chinese Calendar (农历)
Powering https://astrologybazi.com
🔒 Protected with API Key Authentication
"""
from birth_time_context import BirthTimeRequest
from fastapi import FastAPI, HTTPException, Header, Depends
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field, field_validator
from typing import Optional, Dict, List, Any
import logging
import os
from datetime import date, datetime, timedelta
from dotenv import load_dotenv
# 🔧 Load environment variables from .env file
load_dotenv()
from bazi_calculator import BaZiCalculator
from lunar_python import Solar, Lunar
from nine_stars import NineStarsCalculator
from constellation_28 import Constellation28Calculator
from shensha_analyzer import ShenshaAnalyzer
from genju_analyzer import GenjuAnalyzer
from dayun_analyzer import DayunAnalyzer
from evidence import build_evidence, build_timing, build_meta, select_dayun_score_preview
from dayun_dynamic_assessment import build_dayun_dynamic_assessment
from ai_interpreter import AIInterpreter
from time_fortune_analyzer import TimeFortuneAnalyzer
from fortune_score_calculator import FortuneScoreCalculator
# 🎯 Phase 3 新增模块 (2025-12-24)
from marriage_analyzer import MarriageAnalyzer
from career_analyzer import CareerAnalyzer
from wealth_analyzer import WealthAnalyzer
from health_analyzer import HealthAnalyzer
from official_analyzer import OfficialAnalyzer # 官职分析
from legal_risk_analyzer import LegalRiskAnalyzer # 法律风险分析
from marriage_compatibility_analyzer import MarriageCompatibilityAnalyzer # 八字合婚
# 🌟 Rituals Service (2026-01-25)
from rituals_service import rituals_service
# 🖐️ Body Reading Analyzer (2026-02-16)
from body_reading_analyzer import analyze_body_reading
# 💕 Love Clarity Analyzer (2026-02-28)
from love_clarity_analyzer import LoveClarityAnalyzer
# 🌀 Qimen Dunjia Calculator (2026-04-14)
from qimen_calculator import QimenCalculator
# 🔮 Liu Yao Calculator (2026-04-17)
from liuyao_calculator import calculate_liuyao
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 🔐 API Key Configuration
# Support both naming conventions:
# - API_KEY: For HuggingFace Space settings (simple, since only one API key)
# - SISI_HF_API_KEY: For Cloudflare (clear distinction among many API keys)
# Fail closed: no hardcoded fallback — if neither env var is set, all
# protected endpoints refuse service instead of accepting a default key.
API_KEY = os.environ.get("API_KEY") or os.environ.get("SISI_HF_API_KEY")
if not API_KEY:
logger.error(
"🔒 API key is NOT configured (set API_KEY or SISI_HF_API_KEY). "
"All protected endpoints will return 503 until a key is set."
)
def verify_api_key(x_api_key: Optional[str] = Header(None)):
"""Verify API key from request header (fail closed when unconfigured)"""
if not API_KEY:
raise HTTPException(
status_code=503,
detail="🔒 API key is not configured on the server. Service unavailable."
)
if x_api_key != API_KEY:
raise HTTPException(
status_code=403,
detail="🔒 Invalid or missing API key. Please contact SisiTheFox for access."
)
return x_api_key
# ==================== Chinese Calendar Service ====================
class ChineseCalendarService:
"""Service for Chinese lunar calendar operations"""
# 地支到生肖的映射
ZHI_TO_ZODIAC = {
'子': '鼠', '丑': '牛', '寅': '虎', '卯': '兔',
'辰': '龙', '巳': '蛇', '午': '马', '未': '羊',
'申': '猴', '酉': '鸡', '戌': '狗', '亥': '猪'