#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 南印度占星盘图可视化渲染器 v1.0 生成南印度风格 (4×4方格) 的可视化星盘SVG。 支持:D1(本命)/D9(Navamsa)/D10(Dasamsa)等分盘 """ from typing import Dict, List, Optional SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'] # 南印度盘星座固定布局(从左上角顺时针读数) # 星座位置固定不变,行星散布其中 SOUTH_INDIAN_GRID = [ # Row 0: Pisces, Aries, Taurus, Gemini [11, 0, 1, 2], # Row 1: Aquarius, (CENTER), (CENTER), Cancer [10, -1, -1, 3], # Row 2: Capricorn, (CENTER), (CENTER), Leo [9, -1, -1, 4], # Row 3: Sagittarius, Scorpio, Libra, Virgo [8, 7, 6, 5], ] PLANET_SYMBOLS = { 'Sun': '☉', 'Moon': '☽', 'Mars': '♂', 'Mercury': '☿', 'Jupiter': '♃', 'Venus': '♀', 'Saturn': '♄', 'Rahu': '☊', 'Ketu': '☋', 'Asc': 'Asc', } PLANET_COLORS = { 'Sun': '#E65100', 'Moon': '#1565C0', 'Mars': '#C62828', 'Mercury': '#2E7D32', 'Jupiter': '#F9A825', 'Venus': '#6A1B9A', 'Saturn': '#37474F', 'Rahu': '#00695C', 'Ketu': '#BF360C', } SIGN_NAMES_CN = { 'Aries': '白羊', 'Taurus': '金牛', 'Gemini': '双子', 'Cancer': '巨蟹', 'Leo': '狮子', 'Virgo': '处女', 'Libra': '天秤', 'Scorpio': '天蝎', 'Sagittarius': '射手', 'Capricorn': '摩羯', 'Aquarius': '水瓶', 'Pisces': '双鱼', } SIGN_ABBREV = { 'Aries': 'Ar', 'Taurus': 'Ta', 'Gemini': 'Ge', 'Cancer': 'Cn', 'Leo': 'Le', 'Virgo': 'Vi', 'Libra': 'Li', 'Scorpio': 'Sc', 'Sagittarius': 'Sg', 'Capricorn': 'Cp', 'Aquarius': 'Aq', 'Pisces': 'Pi', } CELL_SIZE = 80 PADDING = 10 GRID_OFFSET_X = 50 GRID_OFFSET_Y = 50 SVG_WIDTH = GRID_OFFSET_X * 2 + CELL_SIZE * 4 SVG_HEIGHT = GRID_OFFSET_Y * 2 + CELL_SIZE * 4 + 60 def render_south_indian_chart(planets: Dict, asc_sign: str, title: str = "D1 — Rashi Chart") -> str: """ 生成南印度风格星盘SVG字符串。 Args: planets: {planet: sign_name} 或 {planet: {'sign': name, 'degree': float}} asc_sign: 上升星座名称 title: 图表标题 Returns: 完整SVG字符串 """ lines = [] lines.append(f'') # 背景 lines.append(f'') lines.append(f'{title}') # 绘制网格 for row in range(4): for col in range(4): sign_idx = SOUTH_INDIAN_GRID[row][col] if sign_idx < 0: continue x = GRID_OFFSET_X + col * CELL_SIZE y = GRID_OFFSET_Y + row * CELL_SIZE sign_name = SIGNS[sign_idx] abbrev = SIGN_ABBREV[sign_name] # 单元格背景(上升星座高亮) is_asc = sign_name == asc_sign bg = '#fff3e0' if is_asc else '#fff' lines.append(f'') # 星座缩写(左上角) lines.append(f'{abbrev}') # 上升标记 if is_asc: lines.append(f'') # 行星 planet_y = y + 30 for pname in ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu']: pdata = planets.get(pname) if not pdata: continue p_sign = pdata if isinstance(pdata, str) else pdata.get('sign', '') p_deg = "" if isinstance(pdata, str) else f" {pdata.get('degree',0)%30:.0f}°" if p_sign == sign_name: symbol = PLANET_SYMBOLS.get(pname, pname[:2]) color = PLANET_COLORS.get(pname, '#333') lines.append(f'{pname}{symbol}{p_deg}') planet_y += 16 # 中心区域(传统上写星盘信息) cx = GRID_OFFSET_X + 1.5 * CELL_SIZE cy = GRID_OFFSET_Y + 1.5 * CELL_SIZE lines.append(f'Rasi Chart') lines.append(f'(D1)') lines.append('') return '\n'.join(lines) def render_html_report(birth_data: Dict, chart_data: Dict, analysis: Dict, title: str = "印度占星分析报告") -> str: """ 生成完整的HTML分析报告。 Args: birth_data: {'name': str, 'date': str, 'time': str, 'place': str} chart_data: 星盘数据(用于生成图表) analysis: 分析结果 {'career': {...}, 'relationship': {...}, 'remedies': {...}, ...} title: 报告标题 Returns: 完整HTML字符串 """ html = f''' {title}

🔮 {title}

印度占星 (Jyotish) · 基于BPHS标准 · Lahiri岁差

📋 出生信息

姓名:{birth_data.get('name', '—')}
日期:{birth_data.get('date', '—')}
时间:{birth_data.get('time', '—')}
地点:{birth_data.get('place', '—')}
''' # 星盘图 if chart_data: svg_chart = render_south_indian_chart( chart_data.get('planets', {}), chart_data.get('asc_sign', 'Aries'), "D1 — Rashi Chart (本命盘)" ) html += f'

🌟 本命盘

{svg_chart}
' # 事业分析 if analysis.get('career'): c = analysis['career'] html += '

💼 事业分析

' html += f'

评估:{c.get("assessment", "")}

' if c.get('fields'): html += '

推荐领域:
' for f in c['fields'][:5]: perc = int(f.get('score', 0) / max(1, max(x.get('score', 0) for x in c['fields'] or [{'score': 1}])) * 100) color = '#1a237e' if perc > 70 else '#283593' if perc > 40 else '#5c6bc0' html += f'{f["field"]} ' html += '

' html += '
' # 感情分析 if analysis.get('relationship'): r = analysis['relationship'] html += '

💕 感情分析

' html += f'

评估:{r.get("assessment", "")}

' if r.get('partnership_style'): for s in r['partnership_style'][:1]: html += f'

Venus:{s.get("expression", "")}

' html += '
' # 补救建议 if analysis.get('remedies'): rem = analysis['remedies'] html += '

🪷 补救建议

' html += f'

{rem.get("summary", "")}

' recs = rem.get('recommendations', {}) if recs.get('gems'): html += '

宝石建议:
' for g in recs['gems'][:3]: html += f'{g["planet"]}: {g["gem"]} ' html += '

' if recs.get('mantras'): html += '

咒语建议:
' for m in recs['mantras'][:3]: html += f'{m["mantra"]} × {m["repetitions"]} ' html += '

' html += '
' html += '' html += '
' return html