diff --git a/scripts/curse_yoga_detector.py b/scripts/curse_yoga_detector.py new file mode 100644 index 00000000..c3ee54f1 --- /dev/null +++ b/scripts/curse_yoga_detector.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +curse_yoga_detector.py — 凶星合相命名与检测引擎 +================================================ +检测并命名危险的行星合相,提供预警和补救建议 + +版本: v1.0 | 2026-06-07 +""" + +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field + + +# 凶星合相定义 +CURSE_DEFINITIONS = { + "yama_yoga": { + "name": "Yama Yoga", + "name_cn": "阎魔瑜伽", + "planets": ["Mars", "Saturn"], + "houses_critical": [1, 2, 7, 8, 12], # 在这些宫位更危险 + "meaning": "死亡之神 sitting,等待激活。可能造成危险疾病、事故、家族血脉中断。", + "activation": ["Mars或Saturn的Dasha", "Mars或Saturn的Transit", "Mula Dasha激活"], + "severity": 5, # 1-5 + "remedy": "需要上师保护,唱诵Guru Vandana,侍奉上师", + "deity": "上师/Guru", + "body_parts": ["血液", "骨骼", "神经系统"], + "family_effect": "家族血脉可能中断,无子女或子女早夭", + }, + "preta_yoga_saturn_rahu": { + "name": "Preta Yoga", + "name_cn": "亡灵瑜伽(土星+罗喉)", + "planets": ["Saturn", "Rahu"], + "houses_critical": [8, 11, 6], # 8宫=死亡,11宫=Badhaka,6宫=疾病 + "meaning": "过早死亡风险,慢性疾病,可能来自祖先未解决的业力(未得葬礼的祖先鬼魂)。", + "activation": ["Saturn或Rahu的Dasha", "Badhaka宫被激活", "水瓶座相关事件"], + "severity": 5, + "remedy": "湿婆能量,唱诵Mahamrityunjaya Mantra,进行祖先祭祀", + "deity": "湿婆/Shiva", + "body_parts": ["慢性疾病部位", "骨骼", "神经系统退化"], + "family_effect": "家族血脉停止,女性不婚或无子女,男性只有女儿", + }, + "preta_yoga_saturn_ketu": { + "name": "Preta Yoga", + "name_cn": "亡灵瑜伽(土星+计都)", + "planets": ["Saturn", "Ketu"], + "houses_critical": [8, 11, 6], + "meaning": "过早死亡风险,慢性疾病,灵性干扰,可能来自祖先业力。", + "activation": ["Saturn或Ketu的Dasha", "灵性敏感期"], + "severity": 5, + "remedy": "湿婆能量,唱诵Mahamrityunjaya Mantra,火供", + "deity": "湿婆/Shiva", + "body_parts": ["慢性疾病", "灵性困扰", "睡眠障碍"], + "family_effect": "家族业力传递,后代受影响", + }, + "rakshasa_yoga": { + "name": "Rakshasa Yoga", + "name_cn": "恶魔瑜伽", + "planets": ["Mars", "Rahu"], + "houses_critical": [6, 8], # 6宫=暴力/疾病,8宫=突变/死亡 + "meaning": "暴力事件,外部攻击,神经系统受损,可能被粗俗或微妙的恶魔力量攻击。", + "activation": ["Mars或Rahu的Dasha", "6宫或8宫被Transit激活"], + "severity": 4, + "remedy": "杜尔迦女神,唱诵Durga Saptashati,建立界限", + "deity": "杜尔迦/Durga", + "body_parts": ["神经系统", "肌肉", "免疫系统"], + "family_effect": "家族成员可能遭受暴力或事故", + }, + "pisacha_yoga": { + "name": "Pisacha Yoga", + "name_cn": "鬼魅瑜伽", + "planets": ["Mars", "Ketu"], + "houses_critical": [8, 1, 6], # 8宫=死亡,1宫=身体,6宫=疾病 + "meaning": "与有毒的人接触,被充满愤怒仇恨的鬼魂困扰,可能遭遇事故前感到被推动。", + "activation": ["Mars或Ketu的Dasha", "天蝎座相关事件", "8宫被激活"], + "severity": 4, + "remedy": "毗湿奴,唱诵Vishnu Sahasranama,去除毒素", + "deity": "毗湿奴/Vishnu", + "body_parts": ["血液", "毒素", "生殖系统"], + "family_effect": "与有毒人物的关系,婚姻中毒性伴侣", + }, +} + +# 宫位拟人化(哪个身体部位/生活领域受影响) +HOUSE_PERSONIFICATION = { + 1: "自身/身体/个性", + 2: "家庭/财富/语言能力", + 3: "兄弟姐妹/勇气/沟通", + 4: "母亲/房产/内心", + 5: "子女/创造力/投机", + 6: "疾病/敌人/服务", + 7: "配偶/合作/公开对手", + 8: "死亡/突变/遗产/灵性", + 9: "父亲/长途旅行/高等教育", + 10: "事业/社会地位/母亲", + 11: "收益/愿望/社交网络", + 12: "损失/隐秘/灵性/海外", +} + +# 行星拟人化(如果某行星在此位置,代表谁变得有毒/危险) +PLANET_PERSONIFICATION = { + "Sun": "父亲/权威人物", + "Moon": "母亲/情感滋养者", + "Mars": "兄弟/竞争对手/行动者", + "Mercury": "兄弟姐妹/年轻亲戚/商人", + "Jupiter": "导师/子女/丈夫(女性)", + "Venus": "配偶/妻子(男性)/艺术家", + "Saturn": "长辈/仆人/苦行者", + "Rahu": "外国人/非传统人物/野心家", + "Ketu": "灵性导师/分离者/神秘人物", +} + + +@dataclass +class CurseDetection: + """单个诅咒检测结果""" + curse_type: str # yama/preta/rakshasa/pisacha + curse_name: str + curse_name_cn: str + planets: List[str] + house: int + house_meaning: str + sign: str + + # 严重程度 + severity: int # 1-5 + severity_label: str # critical/high/medium/low + + # 影响范围 + affected_area: str # 影响的宫位主题 + affected_person: str # 可能受影响的人(拟人化) + body_parts: List[str] + family_effect: str + + # 激活条件 + activation_triggers: List[str] + current_dasha_relevant: bool # 当前Dasha是否相关 + transit_relevant: bool # 当前Transit是否相关 + + # 补救 + remedy: str + deity: str + mantras: List[str] + + # 叙事 + narrative: str = "" + + +@dataclass +class CurseAnalysis: + """诅咒分析结果""" + curses_detected: List[CurseDetection] = field(default_factory=list) + overall_risk: str = "low" # low/medium/high/critical + risk_score: float = 0.0 # 0-1 + + # 综合建议 + general_remedies: List[str] = field(default_factory=list) + general_mantras: List[str] = field(default_factory=list) + lifestyle_advice: List[str] = field(default_factory=list) + + # 时间预警 + upcoming_danger_periods: List[Dict] = field(default_factory=list) + + narrative: str = "" + + +class CurseYogaDetector: + """凶星合相命名与检测引擎""" + + def __init__(self): + self.definitions = CURSE_DEFINITIONS + self.house_person = HOUSE_PERSONIFICATION + self.planet_person = PLANET_PERSONIFICATION + + def analyze(self, chart_data: Dict, current_dasha: Optional[str] = None, + transit_data: Optional[Dict] = None) -> CurseAnalysis: + """ + 检测凶星合相 + + Args: + chart_data: 标准星盘数据 + current_dasha: 当前Dasha主星(可选) + transit_data: 当前Transit数据(可选) + + Returns: + CurseAnalysis对象 + """ + planets = chart_data.get("planets", {}) + asc_sign = chart_data.get("ascendant", {}).get("sign", "") + + analysis = CurseAnalysis() + + # Step 1: 在D1中检测 + d1_curses = self._detect_in_chart(planets, "D1") + analysis.curses_detected.extend(d1_curses) + + # Step 2: 在D9中检测(如果有) + context = chart_data.get("context", {}) + d9_planets = context.get("navamsa_planets", {}) + if d9_planets: + d9_curses = self._detect_in_chart(d9_planets, "D9") + for c in d9_curses: + c.narrative = f"[Navamsa] {c.narrative}" + analysis.curses_detected.extend(d9_curses) + + # Step 3: 评估与当前Dasha/Transit的关联 + if current_dasha: + self._assess_dasha_relevance(analysis, current_dasha) + + # Step 4: 综合评估 + self._comprehensive_assessment(analysis) + + # Step 5: 生成建议 + self._generate_remedies(analysis) + + # Step 6: 生成叙事 + analysis.narrative = self._generate_narrative(analysis) + + return analysis + + def _detect_in_chart(self, planets: Dict, chart_name: str) -> List[CurseDetection]: + """在单个星盘中检测诅咒Yoga""" + detected = [] + + # 将行星按宫位分组 + house_planets = {} + for pname, pdata in planets.items(): + if not isinstance(pdata, dict): + continue + house = pdata.get("house", 0) + if house not in house_planets: + house_planets[house] = [] + house_planets[house].append((pname, pdata)) + + # 在每个宫位检查诅咒合相 + for house, plists in house_planets.items(): + if len(plists) < 2: + continue + + planet_names = [p[0] for p in plists] + sign = plists[0][1].get("sign", "") + + # 检查所有行星对 + for i, (p1, data1) in enumerate(plists): + for p2, data2 in plists[i+1:]: + pair = tuple(sorted([p1, p2])) + + # 匹配诅咒定义 + for curse_id, definition in self.definitions.items(): + def_planets = definition["planets"] + if set(pair) == set(def_planets): + detection = self._create_detection( + curse_id, definition, pair, house, sign, chart_name + ) + detected.append(detection) + + return detected + + def _create_detection(self, curse_id: str, definition: Dict, + planets: Tuple[str, ...], house: int, + sign: str, chart_name: str) -> CurseDetection: + """创建单个检测结果""" + severity = definition["severity"] + severity_label = "critical" if severity >= 5 else "high" if severity >= 4 else "medium" + + # 如果在危险宫位,提高严重程度 + if house in definition.get("houses_critical", []): + severity = min(severity + 1, 5) + severity_label = "critical" + + # 确定受影响的人 + affected_person = "" + for p in planets: + if p in self.planet_person: + affected_person = self.planet_person[p] + break + + return CurseDetection( + curse_type=curse_id, + curse_name=definition["name"], + curse_name_cn=definition["name_cn"], + planets=list(planets), + house=house, + house_meaning=self.house_person.get(house, ""), + sign=sign, + severity=severity, + severity_label=severity_label, + affected_area=self.house_person.get(house, ""), + affected_person=affected_person, + body_parts=definition.get("body_parts", []), + family_effect=definition.get("family_effect", ""), + activation_triggers=definition.get("activation", []), + current_dasha_relevant=False, + transit_relevant=False, + remedy=definition.get("remedy", ""), + deity=definition.get("deity", ""), + mantras=self._get_mantras(definition.get("deity", "")), + narrative=f"[{chart_name}] 在{sign}第{house}宫发现 {definition['name']} ({definition['name_cn']})", + ) + + def _get_mantras(self, deity: str) -> List[str]: + """获取对应神祇的咒语""" + mantras = { + "上师/Guru": ["Om Guruve Namah", "Guru Vandana"], + "湿婆/Shiva": ["Om Namah Shivaya", "Mahamrityunjaya Mantra"], + "杜尔迦/Durga": ["Om Dum Durgayei Namah", "Durga Saptashati"], + "毗湿奴/Vishnu": ["Om Namo Bhagavate Vasudevaya", "Vishnu Sahasranama"], + } + return mantras.get(deity, ["Om Shanti Shanti Shanti"]) + + def _assess_dasha_relevance(self, analysis: CurseAnalysis, current_dasha: str): + """评估与当前Dasha的关联""" + for curse in analysis.curses_detected: + # 如果当前Dasha主星是诅咒行星之一 + if current_dasha in curse.planets: + curse.current_dasha_relevant = True + + def _comprehensive_assessment(self, analysis: CurseAnalysis): + """综合风险评估""" + if not analysis.curses_detected: + analysis.overall_risk = "low" + analysis.risk_score = 0.0 + return + + # 计算风险分数 + max_severity = max(c.severity for c in analysis.curses_detected) + count = len(analysis.curses_detected) + dasha_active = sum(1 for c in analysis.curses_detected if c.current_dasha_relevant) + + score = (max_severity / 5.0) * 0.4 + (min(count / 3.0, 1.0)) * 0.3 + (dasha_active / max(count, 1)) * 0.3 + analysis.risk_score = min(score, 1.0) + + if analysis.risk_score >= 0.8: + analysis.overall_risk = "critical" + elif analysis.risk_score >= 0.6: + analysis.overall_risk = "high" + elif analysis.risk_score >= 0.3: + analysis.overall_risk = "medium" + else: + analysis.overall_risk = "low" + + def _generate_remedies(self, analysis: CurseAnalysis): + """生成补救建议""" + # 收集所有涉及的神祇 + deities = set(c.deity for c in analysis.curses_detected) + + analysis.general_remedies = [ + "定期进行冥想和灵性练习", + "保持Sattvic(纯善)的生活方式", + "避免Tamas(惰性)的活动和食物", + "佩戴适合的宝石(需咨询专业占星师)", + ] + + for deity in deities: + if "湿婆" in deity: + analysis.general_remedies.append("每周一进行湿婆礼拜") + elif "毗湿奴" in deity: + analysis.general_remedies.append("每周三/六进行毗湿奴礼拜") + elif "杜尔迦" in deity: + analysis.general_remedies.append("每周二/五进行杜尔迦礼拜") + + analysis.lifestyle_advice = [ + "避免与有毒的人接触", + "定期进行健康检查", + "在危险Dasha周期特别注意安全", + "保持规律的作息和饮食", + "参与慈善和志愿服务", + ] + + def _generate_narrative(self, analysis: CurseAnalysis) -> str: + """生成叙事""" + parts = [] + + parts.append("### 凶星合相命名与危机预警\n") + + if not analysis.curses_detected: + parts.append("✅ 未发现显著的凶星合相诅咒。\n") + parts.append("您的星盘在凶星合相方面相对安全。\n") + return "\n".join(parts) + + # 风险等级 + risk_labels = { + "critical": "🔴 严重", + "high": "🟠 高", + "medium": "🟡 中等", + "low": "🟢 低", + } + parts.append(f"\n**总体风险等级: {risk_labels.get(analysis.overall_risk, analysis.overall_risk)}** ({analysis.risk_score:.0%})\n") + + # 逐个列出 + for curse in analysis.curses_detected: + parts.append(f"\n---\n") + parts.append(f"#### {curse.curse_name} ({curse.curse_name_cn})\n") + parts.append(f"**涉及行星**: {', '.join(curse.planets)}\n") + parts.append(f"**位置**: {curse.sign} 第{curse.house}宫 ({curse.house_meaning})\n") + parts.append(f"**严重程度**: {'⭐' * curse.severity}\n") + parts.append(f"**含义**: {self.definitions.get(curse.curse_type, {}).get('meaning', '')}\n") + + if curse.current_dasha_relevant: + parts.append(f"⚠️ **当前Dasha与此诅咒相关,需特别注意!**\n") + + if curse.affected_person: + parts.append(f"**可能影响的人**: {curse.affected_person}\n") + + if curse.body_parts: + parts.append(f"**相关身体部位**: {', '.join(curse.body_parts)}\n") + + if curse.family_effect: + parts.append(f"**家族影响**: {curse.family_effect}\n") + + parts.append(f"**激活条件**: {'; '.join(curse.activation_triggers)}\n") + parts.append(f"**补救措施**: {curse.remedy}\n") + parts.append(f"**守护神祇**: {curse.deity}\n") + if curse.mantras: + parts.append(f"**推荐咒语**: {'; '.join(curse.mantras)}\n") + + # 综合建议 + if analysis.general_remedies: + parts.append(f"\n#### 综合补救建议\n") + for r in analysis.general_remedies: + parts.append(f"- {r}\n") + + if analysis.lifestyle_advice: + parts.append(f"\n#### 生活方式建议\n") + for a in analysis.lifestyle_advice: + parts.append(f"- {a}\n") + + return "\n".join(parts) + + def to_dict(self, analysis: CurseAnalysis) -> Dict: + """转换为字典""" + return { + "curses_detected": [ + { + "type": c.curse_type, + "name": c.curse_name, + "name_cn": c.curse_name_cn, + "planets": c.planets, + "house": c.house, + "sign": c.sign, + "severity": c.severity, + "severity_label": c.severity_label, + "affected_area": c.affected_area, + "remedy": c.remedy, + "deity": c.deity, + } + for c in analysis.curses_detected + ], + "overall_risk": analysis.overall_risk, + "risk_score": round(analysis.risk_score, 2), + "general_remedies": analysis.general_remedies, + "lifestyle_advice": analysis.lifestyle_advice, + "narrative": analysis.narrative, + } + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def detect_curse_yogas(chart_data: Dict, current_dasha: Optional[str] = None) -> Dict: + """便捷函数:检测凶星合相""" + detector = CurseYogaDetector() + analysis = detector.analyze(chart_data, current_dasha) + return detector.to_dict(analysis) + + +# ============================================================================ +# CLI 调试 +# ============================================================================ + +if __name__ == "__main__": + mock_chart = { + "ascendant": {"sign": "Capricorn"}, + "planets": { + "Mars": {"sign": "Aquarius", "house": 2, "degree": 320}, + "Saturn": {"sign": "Aquarius", "house": 2, "degree": 322}, + "Rahu": {"sign": "Scorpio", "house": 11, "degree": 230}, + "Ketu": {"sign": "Scorpio", "house": 11, "degree": 50}, + }, + } + + print("=" * 60) + print("凶星合相命名与检测引擎") + print("=" * 60) + + result = detect_curse_yogas(mock_chart, current_dasha="Saturn") + print(result["narrative"]) + print(f"\n总体风险: {result['overall_risk']} ({result['risk_score']})") diff --git a/scripts/darakaraka_reader.py b/scripts/darakaraka_reader.py new file mode 100644 index 00000000..7f2e7b24 --- /dev/null +++ b/scripts/darakaraka_reader.py @@ -0,0 +1,569 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +darakaraka_reader.py — Darakaraka深度解读引擎 +=============================================== +Jaimini系统核心:配偶征象星的多维度深度分析 + +版本: v1.0 | 2026-06-07 +""" + +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field + + +# DK行星特质库 +PLANET_SPOUSE_TRAITS = { + "Sun": { + "core": "领导者型伴侣", + "traits": ["天生权威感", "决断力强", "稳定可靠", "有远见", "自尊心强"], + "attracted_to": ["有领导力的人", "有社会地位的人", "自信独立的人"], + "challenges": ["可能过于自我中心", "需要被尊重", "控制欲强"], + "positive": ["忠诚", "保护欲强", "有责任感", "能提供安全感"], + "career_signs": ["政府", "管理", "领导岗位", "创业"], + }, + "Moon": { + "core": " nurturing型伴侣", + "traits": ["情感丰富", "关怀体贴", "直觉敏锐", "温和敏感", "依恋家庭"], + "attracted_to": ["温暖体贴的人", "能提供情感安全感的人", "重视家庭的人"], + "challenges": ["情绪波动大", "过于依赖", "安全感需求高"], + "positive": ["无条件的爱", "滋养力强", "善解人意", "母性/父性本能"], + "career_signs": ["护理", "教育", "心理咨询", "艺术创作"], + }, + "Mars": { + "core": "行动派伴侣", + "traits": ["充满活力", "勇敢果断", "竞争性强", "直率坦诚", "行动迅速"], + "attracted_to": ["有活力的人", "敢于挑战的人", "独立自强的人"], + "challenges": ["脾气暴躁", "冲动", "好胜心过强", "缺乏耐心"], + "positive": ["保护欲极强", "勇敢无畏", "积极主动", "充满激情"], + "career_signs": ["军事", "体育", "工程", "外科医生", "冒险行业"], + }, + "Mercury": { + "core": "智慧型伴侣", + "traits": ["聪明机智", "沟通能力强", "好奇心重", "适应力强", "理性分析"], + "attracted_to": ["聪明有趣的人", "善于交流的人", "有学识的人"], + "challenges": ["可能过于理性", "善变", "分析过度", "不够深情"], + "positive": ["善解人意", "幽默风趣", "知识渊博", "善于解决问题"], + "career_signs": ["写作", "教学", "商业", "传媒", "IT"], + }, + "Jupiter": { + "core": "导师型伴侣", + "traits": ["智慧博爱", "道德感强", "慷慨大方", "乐观积极", "精神追求"], + "attracted_to": ["有智慧的人", "道德高尚的人", "有精神追求的人"], + "challenges": ["可能过于理想化", "说教倾向", "过度乐观"], + "positive": ["智慧引导", "慷慨支持", "精神伴侣", "道德榜样"], + "career_signs": ["教育", "法律", "宗教", "咨询", "金融"], + }, + "Venus": { + "core": "浪漫型伴侣", + "traits": ["优雅迷人", "重视美感", "浪漫多情", "社交能力强", "追求和谐"], + "attracted_to": ["有品位的人", "浪漫体贴的人", "外表吸引的人"], + "challenges": ["可能过于注重外表", "物质倾向", "优柔寡断"], + "positive": ["浪漫体贴", "审美力强", "善于营造氛围", "关系和谐"], + "career_signs": ["艺术", "设计", "时尚", "美容", "外交"], + }, + "Saturn": { + "core": "成熟型伴侣", + "traits": ["成熟稳重", "责任心强", "务实踏实", "纪律性强", "晚成"], + "attracted_to": ["成熟稳重的人", "有责任感的人", "事业稳定的人"], + "challenges": ["可能过于严肃", "情感表达困难", "冷淡疏离", "延迟"], + "positive": ["极度忠诚", "可靠稳重", "长期承诺", "共同成长"], + "career_signs": ["管理", "工程", "建筑", "政府", "传统行业"], + }, + "Rahu": { + "core": "异域/非传统型伴侣", + "traits": ["独特非凡", "打破常规", "野心勃勃", "异域风情", "颠覆性"], + "attracted_to": ["与众不同的人", "有野心的人", "异域文化背景的人"], + "challenges": ["可能不稳定", "非传统关系", "社会不认可", "欺骗风险"], + "positive": ["带来全新体验", "突破局限", "独特魅力", "激发潜能"], + "career_signs": ["科技", "新媒体", "跨国业务", "非主流行业"], + }, +} + +# DK星座细化 +SIGN_REFINEMENTS = { + "Aries": {"intensifies": ["行动力", "独立性"], "softens": [], "adds": ["冲动", "直接"]}, + "Taurus": {"intensifies": ["稳定性", "感官享受"], "softens": ["急躁"], "adds": ["固执", "物质安全感"]}, + "Gemini": {"intensifies": ["沟通", "多变"], "softens": ["沉闷"], "adds": ["好奇心", "不专一风险"]}, + "Cancer": {"intensifies": ["情感", "保护欲"], "softens": ["冷漠"], "adds": ["情绪化", "依恋"]}, + "Leo": {"intensifies": ["自信", "表现力"], "softens": [], "adds": ["需要关注", "骄傲"]}, + "Virgo": {"intensifies": ["细致", "服务意识"], "softens": ["粗心"], "adds": ["挑剔", "焦虑"]}, + "Libra": {"intensifies": ["和谐", "美感"], "softens": ["粗鲁"], "adds": ["优柔寡断", "依赖关系"]}, + "Scorpio": {"intensifies": ["深度", "激情", "转化力"], "softens": [], "adds": ["控制欲", "嫉妒", "秘密"]}, + "Sagittarius": {"intensifies": ["自由", "哲学"], "softens": ["狭隘"], "adds": ["不安定", "过度乐观"]}, + "Capricorn": {"intensifies": ["野心", "责任感"], "softens": ["轻浮"], "adds": ["工作狂", "情感压抑"]}, + "Aquarius": {"intensifies": ["独立", "创新"], "softens": ["传统束缚"], "adds": ["疏离", "反传统"]}, + "Pisces": {"intensifies": ["同情", "灵性"], "softens": ["硬化"], "adds": ["模糊", "逃避", "牺牲"]}, +} + +# DK宫位解读 +HOUSE_INTERPRETATIONS = { + 1: "伴侣对命主影响深远,婚姻关系是自我身份的核心部分。伴侣可能性格鲜明、有领导力。", + 2: "伴侣与家庭财富密切相关,可能带来财务支持或共同理财。伴侣重视家庭价值观。", + 3: "伴侣与沟通、 siblings 有关。可能通过兄弟姐妹介绍认识,或伴侣重视交流。", + 4: "伴侣与内心安全感、房产、家庭根基有关。伴侣提供情感庇护所。", + 5: "伴侣与子女、创造力、浪漫有关。可能通过子女结识,或关系充满浪漫。", + 6: "伴侣可能带来服务性质的关系,或伴侣从事服务/医疗行业。关系中需要克服困难。", + 7: "伴侣特质直接显现,婚姻关系是核心人生主题。伴侣公开、直接。", + 8: "深层转化型关系,伴侣可能带来重大人生变革。涉及共同资源、遗产、秘密。", + 9: "伴侣与远方、高等教育、灵性有关。可能异地或文化背景不同。伴侣有智慧。", + 10: "伴侣与社会地位、事业有关。伴侣可能有成就,或帮助命主事业发展。", + 11: "伴侣与社交网络、愿望实现有关。通过朋友介绍认识,或共同追求理想。", + 12: "隐秘或牺牲型关系,伴侣可能来自幕后或需要隐秘。灵性层面的连接。", +} + +# 友好/敌对行星(影响DK) +FRIENDLY_PLANETS = { + "Sun": ["Moon", "Mars", "Jupiter"], + "Moon": ["Sun", "Mercury"], + "Mars": ["Sun", "Moon", "Jupiter"], + "Mercury": ["Sun", "Venus"], + "Jupiter": ["Sun", "Moon", "Mars"], + "Venus": ["Mercury", "Saturn"], + "Saturn": ["Mercury", "Venus"], + "Rahu": ["Venus", "Saturn"], +} + +ENEMY_PLANETS = { + "Sun": ["Saturn", "Venus"], + "Moon": [], + "Mars": ["Mercury"], + "Mercury": ["Moon"], + "Jupiter": ["Mercury", "Venus"], + "Venus": ["Sun", "Moon"], + "Saturn": ["Sun", "Moon", "Mars"], + "Rahu": ["Sun", "Moon", "Mars"], +} + + +@dataclass +class DKAnalysis: + """DK分析结果""" + # 基础信息 + dk_planet: str + dk_sign: str + dk_house: int + dk_degree: float + nakshatra: Optional[str] = None + nakshatra_pada: Optional[int] = None + + # 状态 + is_retrograde: bool = False + is_combust: bool = False + dignity: str = "" # exalted/own/friendly/neutral/enemy/debilitated + + # D9信息 + d9_sign: Optional[str] = None + d9_house: Optional[int] = None + d9_dignity: Optional[str] = None + + # 解读 + core_profile: str = "" # 核心配偶原型 + personality_traits: List[str] = field(default_factory=list) + attracted_to: List[str] = field(default_factory=list) + challenges: List[str] = field(default_factory=list) + positive_traits: List[str] = field(default_factory=list) + career_signs: List[str] = field(default_factory=list) + + # 宫位解读 + house_meaning: str = "" + house_themes: List[str] = field(default_factory=list) + + # 相位影响 + conjunctions: List[Dict] = field(default_factory=list) # 合相 + aspects_friendly: List[str] = field(default_factory=list) # 友好相位 + aspects_hostile: List[str] = field(default_factory=list) # 敌对相位 + + # 综合评估 + marriage_quality_score: float = 0.5 # 0-1 + timing_clues: List[str] = field(default_factory=list) + remedies: List[str] = field(default_factory=list) + + # 叙事 + narrative: str = "" + + +class DarakarakaReader: + """Darakaraka深度解读引擎""" + + def __init__(self): + self.planet_traits = PLANET_SPOUSE_TRAITS + self.sign_refinements = SIGN_REFINEMENTS + self.house_interps = HOUSE_INTERPRETATIONS + + def analyze(self, chart_data: Dict, use_8_karaka: bool = True) -> DKAnalysis: + """ + 分析Darakaraka + + Args: + chart_data: 标准星盘数据 + use_8_karaka: 使用8星系统(含Rahu) + + Returns: + DKAnalysis对象 + """ + # Step 1: 计算DK + dk_planet, dk_info = self._calculate_dk(chart_data, use_8_karaka) + + # Step 2: 提取位置信息 + dk_sign = dk_info.get("sign", "") + dk_house = dk_info.get("house", 0) + dk_degree = dk_info.get("degree_in_sign", 0) + + # Step 3: 构建分析结果 + analysis = DKAnalysis( + dk_planet=dk_planet, + dk_sign=dk_sign, + dk_house=dk_house, + dk_degree=dk_degree, + ) + + # Step 4: 填充行星特质 + self._fill_planet_traits(analysis) + + # Step 5: 星座细化 + self._refine_by_sign(analysis) + + # Step 6: 宫位解读 + self._interpret_house(analysis) + + # Step 7: 相位分析 + self._analyze_aspects(analysis, chart_data) + + # Step 8: D9分析 + self._analyze_d9(analysis, chart_data) + + # Step 9: 状态检测(逆行/燃烧) + self._check_status(analysis, dk_info) + + # Step 10: 综合评估 + self._comprehensive_assessment(analysis) + + # Step 11: 生成叙事 + analysis.narrative = self._generate_narrative(analysis) + + return analysis + + def _calculate_dk(self, chart_data: Dict, use_8_karaka: bool = True) -> Tuple[str, Dict]: + """计算Darakaraka行星""" + planets = chart_data.get("planets", {}) + + # 提取各行星星座内度数 + planet_degs = {} + for pname, pdata in planets.items(): + if isinstance(pdata, dict) and "degree" in pdata: + deg_in_sign = pdata.get("degree_in_sign", pdata["degree"] % 30) + + # Rahu逆行校正(8星系统) + if pname == "Rahu" and use_8_karaka: + deg_in_sign = 30.0 - deg_in_sign + + planet_degs[pname] = deg_in_sign + + # 排序(降序) + sorted_planets = sorted(planet_degs.items(), key=lambda x: x[1], reverse=True) + + if not sorted_planets: + return "Unknown", {} + + if use_8_karaka and len(sorted_planets) >= 8: + # 8星系统:第7高(第8颗被排除) + dk = sorted_planets[6][0] # 索引6 = 第7高 + else: + # 7星系统:最低的 + dk = sorted_planets[-1][0] + + return dk, planets.get(dk, {}) + + def _fill_planet_traits(self, analysis: DKAnalysis): + """填充行星基础特质""" + traits = self.planet_traits.get(analysis.dk_planet, {}) + + analysis.core_profile = traits.get("core", "") + analysis.personality_traits = traits.get("traits", []) + analysis.attracted_to = traits.get("attracted_to", []) + analysis.challenges = traits.get("challenges", []) + analysis.positive_traits = traits.get("positive", []) + analysis.career_signs = traits.get("career_signs", []) + + def _refine_by_sign(self, analysis: DKAnalysis): + """按星座细化""" + refinement = self.sign_refinements.get(analysis.dk_sign, {}) + + # 强化某些特质 + for trait in refinement.get("intensifies", []): + if trait not in analysis.personality_traits: + analysis.personality_traits.append(f"**{trait}**") + + # 软化某些特质 + for trait in refinement.get("softens", []): + if trait in analysis.challenges: + analysis.challenges.remove(trait) + + # 新增特质 + for trait in refinement.get("adds", []): + if trait not in analysis.personality_traits: + analysis.personality_traits.append(trait) + + def _interpret_house(self, analysis: DKAnalysis): + """解读DK宫位""" + analysis.house_meaning = self.house_interps.get( + analysis.dk_house, + f"DK位于第{analysis.dk_house}宫" + ) + + # 宫位主题 + house_themes = { + 1: ["自我认同", "伴侣影响", "个人成长"], + 2: ["家庭财富", "价值观", "语言能力"], + 3: ["沟通", "兄弟姐妹", "短途旅行"], + 4: ["情感安全", "房产", "母亲"], + 5: ["子女", "创造力", "浪漫"], + 6: ["服务", "健康", "克服困难"], + 7: ["婚姻", "合作", "公开关系"], + 8: ["转化", "共同资源", "秘密"], + 9: ["远方", "智慧", "灵性"], + 10: ["事业", "社会地位", "公众形象"], + 11: ["社交网络", "愿望", "收益"], + 12: ["隐秘", "牺牲", "灵性解脱"], + } + analysis.house_themes = house_themes.get(analysis.dk_house, []) + + def _analyze_aspects(self, analysis: DKAnalysis, chart_data: Dict): + """分析DK的相位影响""" + planets = chart_data.get("planets", {}) + dk_lon = None + + for pname, pdata in planets.items(): + if pname == analysis.dk_planet and isinstance(pdata, dict): + dk_lon = pdata.get("degree") + break + + if dk_lon is None: + return + + # 检测合相(8度内) + conjunctions = [] + for pname, pdata in planets.items(): + if pname == analysis.dk_planet: + continue + if isinstance(pdata, dict) and "degree" in pdata: + diff = abs((pdata["degree"] - dk_lon + 180) % 360 - 180) + if diff < 8: + conjunctions.append({ + "planet": pname, + "orb": round(diff, 2), + "nature": "friendly" if pname in FRIENDLY_PLANETS.get(analysis.dk_planet, []) else "neutral" + }) + + analysis.conjunctions = conjunctions + + # 分类友好/敌对影响 + for conj in conjunctions: + if conj["nature"] == "friendly": + analysis.aspects_friendly.append(conj["planet"]) + elif conj["planet"] in ENEMY_PLANETS.get(analysis.dk_planet, []): + analysis.aspects_hostile.append(conj["planet"]) + + def _analyze_d9(self, analysis: DKAnalysis, chart_data: Dict): + """分析DK在D9中的位置""" + # 简化实现:从chart_data的context中提取D9数据 + context = chart_data.get("context", {}) + d9_planets = context.get("navamsa_planets", {}) + + if analysis.dk_planet in d9_planets: + d9_data = d9_planets[analysis.dk_planet] + analysis.d9_sign = d9_data.get("sign") + analysis.d9_house = d9_data.get("house") + analysis.d9_dignity = d9_data.get("dignity") + + def _check_status(self, analysis: DKAnalysis, dk_info: Dict): + """检查DK状态""" + analysis.is_retrograde = dk_info.get("retrograde", False) + analysis.is_combust = dk_info.get("combust", False) + analysis.dignity = dk_info.get("dignity", "") + + def _comprehensive_assessment(self, analysis: DKAnalysis): + """综合评估婚姻质量""" + score = 0.5 + + # 正面因素 + if analysis.dignity in ["exalted", "own", "friendly"]: + score += 0.15 + if len(analysis.aspects_friendly) > len(analysis.aspects_hostile): + score += 0.1 + if analysis.dk_house in [1, 4, 7, 10]: + score += 0.05 + if not analysis.is_combust: + score += 0.1 + + # 负面因素 + if analysis.dignity in ["debilitated", "enemy"]: + score -= 0.15 + if analysis.is_combust: + score -= 0.15 + if analysis.is_retrograde: + score -= 0.05 + if len(analysis.aspects_hostile) > 0: + score -= 0.05 * len(analysis.aspects_hostile) + if analysis.dk_house in [6, 8, 12]: + score -= 0.05 + + analysis.marriage_quality_score = max(0, min(1, score)) + + # 时间线索 + if analysis.is_retrograde: + analysis.timing_clues.append("婚姻可能较晚或经历反复") + if analysis.dk_house == 7: + analysis.timing_clues.append("婚姻关系是核心人生主题") + if analysis.dk_planet == "Saturn": + analysis.timing_clues.append("婚姻可能在30岁后或更晚") + + # 补救措施 + if analysis.marriage_quality_score < 0.4: + analysis.remedies.append("建议进行传统的婚姻祈福仪式") + if analysis.is_combust: + analysis.remedies.append("DK被燃烧,建议加强个人独立性") + if analysis.dk_planet == "Saturn": + analysis.remedies.append("耐心是关键,不要急于进入婚姻") + + def _generate_narrative(self, analysis: DKAnalysis) -> str: + """生成DK解读叙事""" + parts = [] + + # 核心画像 + parts.append(f"### 伴侣核心画像:{analysis.core_profile}\n") + parts.append(f"您的Darakaraka(配偶象征星)是 **{analysis.dk_planet}**,") + parts.append(f"落在 **{analysis.dk_sign}** 的第 **{analysis.dk_house}** 宫。\n") + + # 性格特质 + if analysis.personality_traits: + parts.append(f"\n伴侣的核心性格特质:{', '.join(analysis.personality_traits[:5])}。\n") + + # 宫位解读 + parts.append(f"\n{analysis.house_meaning}\n") + + # 吸引力 + if analysis.attracted_to: + parts.append(f"\n您在伴侣身上寻找的品质:{', '.join(analysis.attracted_to[:3])}。\n") + + # 挑战 + if analysis.challenges: + parts.append(f"\n需要注意的议题:{', '.join(analysis.challenges[:3])}。\n") + + # 正面 + if analysis.positive_traits: + parts.append(f"\n伴侣带来的礼物:{', '.join(analysis.positive_traits[:3])}。\n") + + # 合相影响 + if analysis.conjunctions: + parts.append(f"\n与DK合相的行星:") + for conj in analysis.conjunctions: + parts.append(f" - {conj['planet']} (orb: {conj['orb']}°)") + parts.append("") + + # D9 + if analysis.d9_sign: + parts.append(f"\n在Navamsa (D9) 中,DK位于 **{analysis.d9_sign}**,") + if analysis.d9_dignity: + parts.append(f"状态为 **{analysis.d9_dignity}**。\n") + parts.append("这揭示了灵魂层面对伴侣的深层需求。\n") + + # 综合评估 + quality_desc = "优秀" if analysis.marriage_quality_score > 0.7 else \ + "良好" if analysis.marriage_quality_score > 0.5 else \ + "需要努力" if analysis.marriage_quality_score > 0.3 else "有挑战" + parts.append(f"\n**婚姻质量评估:{quality_desc}** ({analysis.marriage_quality_score:.0%})\n") + + # 时间线索 + if analysis.timing_clues: + parts.append(f"\n时间线索:{'; '.join(analysis.timing_clues)}\n") + + # 补救 + if analysis.remedies: + parts.append(f"\n建议:{'; '.join(analysis.remedies)}\n") + + return "\n".join(parts) + + def to_dict(self, analysis: DKAnalysis) -> Dict: + """转换为字典(JSON可序列化)""" + return { + "dk_planet": analysis.dk_planet, + "dk_sign": analysis.dk_sign, + "dk_house": analysis.dk_house, + "dk_degree": round(analysis.dk_degree, 2), + "d9_sign": analysis.d9_sign, + "d9_house": analysis.d9_house, + "d9_dignity": analysis.d9_dignity, + "is_retrograde": analysis.is_retrograde, + "is_combust": analysis.is_combust, + "dignity": analysis.dignity, + "core_profile": analysis.core_profile, + "personality_traits": analysis.personality_traits, + "attracted_to": analysis.attracted_to, + "challenges": analysis.challenges, + "positive_traits": analysis.positive_traits, + "career_signs": analysis.career_signs, + "house_meaning": analysis.house_meaning, + "house_themes": analysis.house_themes, + "conjunctions": analysis.conjunctions, + "aspects_friendly": analysis.aspects_friendly, + "aspects_hostile": analysis.aspects_hostile, + "marriage_quality_score": round(analysis.marriage_quality_score, 2), + "timing_clues": analysis.timing_clues, + "remedies": analysis.remedies, + "narrative": analysis.narrative, + } + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def analyze_darakaraka(chart_data: Dict, use_8_karaka: bool = True) -> Dict: + """便捷函数:分析Darakaraka""" + reader = DarakarakaReader() + analysis = reader.analyze(chart_data, use_8_karaka) + return reader.to_dict(analysis) + + +# ============================================================================ +# CLI 调试 +# ============================================================================ + +if __name__ == "__main__": + # 模拟测试数据 + mock_chart = { + "planets": { + "Sun": {"degree": 20.0, "degree_in_sign": 20.0, "sign": "Aries", "house": 1, "retrograde": False, "combust": False, "dignity": "exalted"}, + "Moon": {"degree": 50.0, "degree_in_sign": 20.0, "sign": "Taurus", "house": 2, "retrograde": False}, + "Mars": {"degree": 80.0, "degree_in_sign": 20.0, "sign": "Gemini", "house": 3, "retrograde": False}, + "Mercury": {"degree": 110.0, "degree_in_sign": 20.0, "sign": "Cancer", "house": 4, "retrograde": False}, + "Jupiter": {"degree": 140.0, "degree_in_sign": 20.0, "sign": "Leo", "house": 5, "retrograde": False}, + "Venus": {"degree": 170.0, "degree_in_sign": 20.0, "sign": "Virgo", "house": 6, "retrograde": False}, + "Saturn": {"degree": 200.0, "degree_in_sign": 20.0, "sign": "Libra", "house": 7, "retrograde": True}, + "Rahu": {"degree": 230.0, "degree_in_sign": 20.0, "sign": "Scorpio", "house": 8, "retrograde": False}, + }, + "context": { + "navamsa_planets": { + "Saturn": {"sign": "Capricorn", "house": 10, "dignity": "own"} + } + } + } + + print("=" * 60) + print("Darakaraka 深度解读引擎") + print("=" * 60) + + result = analyze_darakaraka(mock_chart, use_8_karaka=True) + + print(f"\nDK行星: {result['dk_planet']}") + print(f"DK星座: {result['dk_sign']}") + print(f"DK宫位: {result['dk_house']}") + print(f"\n核心画像: {result['core_profile']}") + print(f"\n婚姻质量评分: {result['marriage_quality_score']}") + + print("\n" + "=" * 60) + print("完整叙事") + print("=" * 60) + print(result['narrative']) diff --git a/scripts/pancha_pakshi.py b/scripts/pancha_pakshi.py index cb8bda31..c3801da4 100644 --- a/scripts/pancha_pakshi.py +++ b/scripts/pancha_pakshi.py @@ -1,202 +1,343 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ -pancha_pakshi.py - Pancha Pakshi(五鸟)系统计算模块 -v6.0.15 新增技法 +pancha_pakshi.py — Pancha Pakshi 五鸟择时引擎 +=============================================== +基于出生Nakshatra和Paksha计算每日吉凶活动时段 -Pancha Pakshi 是泰米尔占星系统,将 27 Nakshatra 分配给 5 只"鸟" -(代表生命力的 5 种状态:统治、进食、行走、睡眠、死亡)。 -每个人根据出生 Nakshatra 和当前时日,处于某种 Pakshi 状态。 +版本: v1.0 | 2026-06-07 """ -# 五鸟名称与状态 -PAKSHI_NAMES = { - 0: "Vulture / Rule (统治)", - 1: "Crow / Eat (进食)", - 2: "Crane / Walk (行走)", - 3: "Owl / Sleep (睡眠)", - 4: "Bat / Die (死亡)", +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field +from enum import Enum + + +class ActivityType(Enum): + """五鸟活动类型""" + RULE = "rule" # 统治 — 吉 + EAT = "eat" # 进食 — 吉 + WALK = "walk" # 行走 — 中 + SLEEP = "sleep" # 睡眠 — 凶 + DEATH = "death" # 死亡 — 凶 + + +class BirdType(Enum): + """五鸟类型""" + VULTURE = "vulture" # 兀鹫 + OWL = "owl" # 猫头鹰 + CROW = "crow" # 乌鸦 + COCK = "cock" # 公鸡 + PEACOCK = "peacock" # 孔雀 + + +# Nakshatra到鸟的映射(Shukla Paksha / Krishna Paksha) +NAKSHATRA_BIRDS = { + # Shukla Paksha (亮月期) + "shukla": { + "Ashwini": BirdType.VULTURE, "Bharani": BirdType.OWL, + "Krittika": BirdType.CROW, "Rohini": BirdType.COCK, + "Mrigashira": BirdType.PEACOCK, "Ardra": BirdType.VULTURE, + "Punarvasu": BirdType.OWL, "Pushya": BirdType.CROW, + "Ashlesha": BirdType.COCK, "Magha": BirdType.PEACOCK, + "Purva Phalguni": BirdType.VULTURE, "Uttara Phalguni": BirdType.OWL, + "Hasta": BirdType.CROW, "Chitra": BirdType.COCK, + "Swati": BirdType.PEACOCK, "Vishakha": BirdType.VULTURE, + "Anuradha": BirdType.OWL, "Jyeshtha": BirdType.CROW, + "Mula": BirdType.COCK, "Purva Ashadha": BirdType.PEACOCK, + "Uttara Ashadha": BirdType.VULTURE, "Shravana": BirdType.OWL, + "Dhanishta": BirdType.CROW, "Shatabhisha": BirdType.COCK, + "Purva Bhadrapada": BirdType.PEACOCK, "Uttara Bhadrapada": BirdType.VULTURE, + "Revati": BirdType.OWL, + }, + # Krishna Paksha (暗月期) + "krishna": { + "Ashwini": BirdType.OWL, "Bharani": BirdType.VULTURE, + "Krittika": BirdType.PEACOCK, "Rohini": BirdType.COCK, + "Mrigashira": BirdType.CROW, "Ardra": BirdType.OWL, + "Punarvasu": BirdType.VULTURE, "Pushya": BirdType.PEACOCK, + "Ashlesha": BirdType.COCK, "Magha": BirdType.CROW, + "Purva Phalguni": BirdType.OWL, "Uttara Phalguni": BirdType.VULTURE, + "Hasta": BirdType.PEACOCK, "Chitra": BirdType.COCK, + "Swati": BirdType.CROW, "Vishakha": BirdType.OWL, + "Anuradha": BirdType.VULTURE, "Jyeshtha": BirdType.PEACOCK, + "Mula": BirdType.COCK, "Purva Ashadha": BirdType.CROW, + "Uttara Ashadha": BirdType.OWL, "Shravana": BirdType.VULTURE, + "Dhanishta": BirdType.PEACOCK, "Shatabhisha": BirdType.COCK, + "Purva Bhadrapada": BirdType.CROW, "Uttara Bhadrapada": BirdType.OWL, + "Revati": BirdType.VULTURE, + }, } -# 27 Nakshatra 对应的 Pakshi 分配(按泰米尔传统) -# 来源:泰米尔占星文献 -# 分配方式:每个 Pakshi 掌管 5-6 个 Nakshatra -NAKSHATRA_PAKSHI = { - # Vulture / Rule (0) - 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, - # Crow / Eat (1) - 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, - # Crane / Walk (2) - 11: 2, 12: 2, 13: 2, 14: 2, 15: 2, - # Owl / Sleep (3) - 16: 3, 17: 3, 18: 3, 19: 3, 20: 3, - # Bat / Die (4) - 21: 4, 22: 4, 23: 4, 24: 4, 25: 4, 26: 4, 27: 4, +# 鸟的活动周期表(5个Yama × 5种活动) +# 每个Yama约3小时(一天15小时,从日出到日落) +BIRD_ACTIVITY_TABLE = { + BirdType.VULTURE: [ + [ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + [ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE], + [ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT], + [ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK], + [ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP], + ], + BirdType.OWL: [ + [ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT], + [ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK], + [ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP], + [ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + [ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE], + ], + BirdType.CROW: [ + [ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP], + [ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + [ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE], + [ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT], + [ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK], + ], + BirdType.COCK: [ + [ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK], + [ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP], + [ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + [ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE], + [ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT], + ], + BirdType.PEACOCK: [ + [ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE], + [ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT], + [ActivityType.SLEEP, ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK], + [ActivityType.DEATH, ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP], + [ActivityType.RULE, ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + ], } -# Nakshatra 名称列表 -NAKSHATRA_NAMES = [ - "Ashvini", "Bharani", "Krittika", "Rohini", "Mrigashira", "Ardra", - "Punarvasu", "Pushya", "Ashlesha", "Magha", "Purva Phalguni", "Uttara Phalguni", - "Hasta", "Chitra", "Svati", "Vishakha", "Anuradha", "Jyeshtha", - "Mula", "Purva Ashadha", "Uttara Ashadha", "Shravana", "Dhanishta", - "Shatabhisha", "Purva Bhadrapada", "Uttara Bhadrapada", "Revati" -] +# 活动吉凶 +ACTIVITY_FORTUNE = { + ActivityType.RULE: "吉", # 统治 — 适合开始重要事务 + ActivityType.EAT: "吉", # 进食 — 适合获取资源 + ActivityType.WALK: "中", # 行走 — 适合日常活动 + ActivityType.SLEEP: "凶", # 睡眠 — 避免重要决策 + ActivityType.DEATH: "凶", # 死亡 — 极度不利 +} -# 五状态顺序(统治 → 进食 → 行走 → 睡眠 → 死亡 → 统治...) -PAKSHI_CYCLE = [0, 1, 2, 3, 4] # Rule, Eat, Walk, Sleep, Die +# 活动建议 +ACTIVITY_ADVICE = { + ActivityType.RULE: ["开始新项目", "做重要决策", "领导会议", "签署合同"], + ActivityType.EAT: ["财务活动", "求职面试", "购买资产", "接受礼物"], + ActivityType.WALK: ["日常事务", "短途旅行", "社交活动", "信息收集"], + ActivityType.SLEEP: ["避免重要决策", "休息", "内省", "推迟行动"], + ActivityType.DEATH: ["避免一切重要活动", "取消计划", "防护", "等待"], +} -# 星期对应的 Pakshi 起始偏移(0=周日,1=周一...6=周六) -# 每个星期开始时的 Pakshi 偏移量 -WEEKDAY_PAKSHI_OFFSET = { - 0: 0, # 周日:从 Rule 开始 - 1: 2, # 周一:从 Walk 开始 - 2: 4, # 周二:从 Die 开始 - 3: 1, # 周三:从 Eat 开始 - 4: 3, # 周四:从 Sleep 开始 - 5: 0, # 周五:从 Rule 开始 - 6: 2, # 周六:从 Walk 开始 +# 活动相克 +ACTIVITY_DOMINANCE = { + ActivityType.RULE: [ActivityType.EAT, ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + ActivityType.EAT: [ActivityType.WALK, ActivityType.SLEEP, ActivityType.DEATH], + ActivityType.WALK: [ActivityType.SLEEP, ActivityType.DEATH], + ActivityType.SLEEP: [ActivityType.DEATH], + ActivityType.DEATH: [], } -def get_birth_pakshi(nakshatra_num): - """ - 根据出生 Nakshatra 获取所属 Pakshi。 - """ - if nakshatra_num < 1 or nakshatra_num > 27: - return None, f"Nakshatra 编号 {nakshatra_num} 超出范围(1-27)" - - pakshi_idx = NAKSHATRA_PAKSHI.get(nakshatra_num) - if pakshi_idx is None: - return None, f"Nakshatra {nakshatra_num} 未找到 Pakshi 映射" - - return pakshi_idx, PAKSHI_NAMES[pakshi_idx] +@dataclass +class YamaActivity: + """单个Yama的活动""" + yama_number: int # 1-5 + yama_name: str # Pratah/Madhyahna/Aparahna/Sayam/Ratri + start_time: str # 开始时间(近似) + end_time: str # 结束时间 + activity: ActivityType + activity_cn: str + fortune: str # 吉/中/凶 + advice: List[str] -def calc_pakshi_day_state(birth_nakshatra, weekday, period_of_day=0): - """ - 计算指定日期的 Pakshi 状态。 - birth_nakshatra: 出生 Nakshatra 编号(1-27) - weekday: 星期(0=周日,1=周一...6=周六) - period_of_day: 一天中的时段(0-4,对应五个 Pakshi 时段) - """ - birth_pakshi, _ = get_birth_pakshi(birth_nakshatra) - if birth_pakshi is None: - return None, f"出生 Nakshatra {birth_nakshatra} 无效" - - # 星期起始偏移 - offset = WEEKDAY_PAKSHI_OFFSET.get(weekday, 0) - - # 当日当前时段的 Pakshi = (出生 Pakshi + 星期偏移 + 时段) % 5 - current_pakshi = (birth_pakshi + offset + period_of_day) % 5 - - return current_pakshi, PAKSHI_NAMES[current_pakshi] +@dataclass +class DailySchedule: + """每日活动表""" + date: str + bird: BirdType + bird_cn: str + paksha: str # shukla/krishna + yama_activities: List[YamaActivity] = field(default_factory=list) + + # 推荐时段 + best_periods: List[str] = field(default_factory=list) + avoid_periods: List[str] = field(default_factory=list) + + narrative: str = "" -def calc_pakshi_full_analysis(birth_nakshatra, birth_weekday=None, target_weekday=None, target_period=0): - """ - 完整 Pancha Pakshi 分析。 - birth_nakshatra: 出生 Nakshatra(1-27) - birth_weekday: 出生星期(可选,用于更精确分析) - target_weekday: 目标日期星期(用于预测,0-6) - target_period: 目标时段(0-4) - """ - birth_pakshi_idx, birth_pakshi_name = get_birth_pakshi(birth_nakshatra) - if birth_pakshi_idx is None: - return {"error": birth_pakshi_name} - - result = { - "birth_nakshatra": birth_nakshatra, - "birth_nakshatra_name": NAKSHATRA_NAMES[birth_nakshatra - 1] if 1 <= birth_nakshatra <= 27 else "Unknown", - "birth_pakshi_idx": birth_pakshi_idx, - "birth_pakshi_name": birth_pakshi_name, - } - - # 出生时的 Pakshi 状态解释 - pakshi_interpretations = { - 0: "统治鸟(Vulture):出生时有领导力,能掌控局面,适合开始重要项目。", - 1: "进食鸟(Crow):出生时需要滋养,关注物质获取,适合积累资源。", - 2: "行走鸟(Crane):出生时处于行动状态,适合旅行、移动、沟通。", - 3: "睡眠鸟(Owl):出生时休息状态,适合内省、规划、潜伏等待。", - 4: "死亡鸟(Bat):出生时转化状态,适合结束、释放、深度改变。", - } - result["birth_pakshi_interpretation"] = pakshi_interpretations.get(birth_pakshi_idx, "") - - # 如果提供了目标日期,计算那时的 Pakshi 状态 - if target_weekday is not None: - target_pakshi_idx, target_pakshi_name = calc_pakshi_day_state( - birth_nakshatra, target_weekday, target_period +class PanchaPakshi: + """五鸟择时引擎""" + + def __init__(self): + self.nakshatra_birds = NAKSHATRA_BIRDS + self.activity_table = BIRD_ACTIVITY_TABLE + self.activity_fortune = ACTIVITY_FORTUNE + self.activity_advice = ACTIVITY_ADVICE + + def calculate(self, birth_nakshatra: str, paksha: str, + date: Optional[str] = None) -> DailySchedule: + """ + 计算某日的五鸟活动表 + + Args: + birth_nakshatra: 出生Nakshatra + paksha: 'shukla' (亮月) 或 'krishna' (暗月) + date: 日期字符串(可选) + + Returns: + DailySchedule对象 + """ + # 确定鸟类型 + bird = self._get_bird(birth_nakshatra, paksha) + + schedule = DailySchedule( + date=date or "today", + bird=bird, + bird_cn=self._bird_to_chinese(bird), + paksha=paksha, ) - result["target_weekday"] = target_weekday - result["target_period"] = target_period - result["target_pakshi_idx"] = target_pakshi_idx - result["target_pakshi_name"] = target_pakshi_name - - # 目标状态的解读 - target_interpretations = { - 0: "统治时段:适合开始新项目、领导、决策、公开行动。能量最强。", - 1: "进食时段:适合积累、学习、获取资源、建立联系。能量恢复中。", - 2: "行走时段:适合旅行、移动、沟通、短途出行。能量流动。", - 3: "睡眠时段:适合休息、规划、内省、等待。能量最低,不宜重要决策。", - 4: "死亡时段:适合结束、释放、深度转化、灵性实践。能量转化中。", + + # 生成5个Yama的活动 + yama_names = ["Pratah (晨)", "Madhyahna (午)", "Aparahna (下午)", + "Sayam (傍晚)", "Ratri (夜)"] + time_ranges = ["6:00-9:00", "9:00-12:00", "12:00-15:00", + "15:00-18:00", "18:00-21:00"] + + for i in range(5): + activity = self.activity_table[bird][i][i] # 对角线 + + yama = YamaActivity( + yama_number=i+1, + yama_name=yama_names[i], + start_time=time_ranges[i].split("-")[0], + end_time=time_ranges[i].split("-")[1], + activity=activity, + activity_cn=self._activity_to_chinese(activity), + fortune=self.activity_fortune.get(activity, "中"), + advice=self.activity_advice.get(activity, []) + ) + schedule.yama_activities.append(yama) + + # 推荐/避免时段 + self._generate_recommendations(schedule) + + # 生成叙事 + schedule.narrative = self._generate_narrative(schedule) + + return schedule + + def _get_bird(self, nakshatra: str, paksha: str) -> BirdType: + """根据Nakshatra和Paksha确定鸟""" + paksha_data = self.nakshatra_birds.get(paksha, self.nakshatra_birds["shukla"]) + return paksha_data.get(nakshatra, BirdType.PEACOCK) + + def _bird_to_chinese(self, bird: BirdType) -> str: + """鸟类型转中文""" + names = { + BirdType.VULTURE: "兀鹫", + BirdType.OWL: "猫头鹰", + BirdType.CROW: "乌鸦", + BirdType.COCK: "公鸡", + BirdType.PEACOCK: "孔雀", + } + return names.get(bird, "未知") + + def _activity_to_chinese(self, activity: ActivityType) -> str: + """活动类型转中文""" + names = { + ActivityType.RULE: "统治", + ActivityType.EAT: "进食", + ActivityType.WALK: "行走", + ActivityType.SLEEP: "睡眠", + ActivityType.DEATH: "死亡", + } + return names.get(activity, "未知") + + def _generate_recommendations(self, schedule: DailySchedule): + """生成推荐和避免时段""" + for yama in schedule.yama_activities: + time_str = f"{yama.start_time}-{yama.end_time}" + + if yama.fortune == "吉": + schedule.best_periods.append(f"{time_str} ({yama.activity_cn})") + elif yama.fortune == "凶": + schedule.avoid_periods.append(f"{time_str} ({yama.activity_cn})") + + def _generate_narrative(self, schedule: DailySchedule) -> str: + """生成叙事""" + parts = [] + + parts.append("### Pancha Pakshi 五鸟择时\n") + parts.append(f"您的出生鸟: **{schedule.bird_cn}** ({schedule.bird.value})\n") + parts.append(f"月相: {'亮月期 (Shukla Paksha)' if schedule.paksha == 'shukla' else '暗月期 (Krishna Paksha)'}\n") + + parts.append("\n#### 今日活动表\n") + for yama in schedule.yama_activities: + emoji = "🟢" if yama.fortune == "吉" else "🟡" if yama.fortune == "中" else "🔴" + parts.append(f"{emoji} **{yama.yama_name}** ({yama.start_time}-{yama.end_time})") + parts.append(f" 活动: {yama.activity_cn} ({yama.fortune})") + if yama.advice: + parts.append(f" 建议: {'; '.join(yama.advice[:2])}") + parts.append("") + + if schedule.best_periods: + parts.append(f"\n✅ **最佳活动时段**: {'; '.join(schedule.best_periods[:3])}\n") + + if schedule.avoid_periods: + parts.append(f"\n❌ **避免活动时段**: {'; '.join(schedule.avoid_periods)}\n") + + parts.append(f"\n#### 五鸟相克规则\n") + parts.append("统治 > 进食 > 行走 > 睡眠 > 死亡\n") + parts.append("当对方的活动克制你的活动时,不利。\n") + + return "\n".join(parts) + + def to_dict(self, schedule: DailySchedule) -> Dict: + """转换为字典""" + return { + "date": schedule.date, + "bird": schedule.bird.value, + "bird_cn": schedule.bird_cn, + "paksha": schedule.paksha, + "activities": [ + { + "yama": y.yama_number, + "time": f"{y.start_time}-{y.end_time}", + "activity": y.activity.value, + "activity_cn": y.activity_cn, + "fortune": y.fortune, + "advice": y.advice, + } + for y in schedule.yama_activities + ], + "best_periods": schedule.best_periods, + "avoid_periods": schedule.avoid_periods, + "narrative": schedule.narrative, } - result["target_pakshi_interpretation"] = target_interpretations.get(target_pakshi_idx, "") - - # Pakshi 周期完整列表(一天五个时段) - day_periods = [] - for period in range(5): - p_idx, p_name = calc_pakshi_day_state(birth_nakshatra, target_weekday or 0, period) - day_periods.append({ - "period": period, - "pakshi_idx": p_idx, - "pakshi_name": p_name, - }) - result["day_periods"] = day_periods - - return result -def pancha_pakshi_prashna_guide(birth_nakshatra, question_type="general"): - """ - Pancha Pakshi 用于 Prashna(问卜)的指导。 - 根据出生 Pakshi 和当前时段判断问题是否适合提问。 - """ - birth_pakshi_idx, _ = get_birth_pakshi(birth_nakshatra) +# ============================================================================ +# 便捷函数 +# ============================================================================ - # 不同时段适合的问题类型 - suitable_for = { - 0: ["new_beginning", "leadership", "decision"], # 统治:新开始、领导、决策 - 1: ["resource", "learning", "relationship"], # 进食:资源、学习、关系 - 2: ["travel", "communication", "short_term"], # 行走:旅行、沟通、短期 - 3: ["rest", "planning", "inner_work"], # 睡眠:休息、规划、内在工作 - 4: ["ending", "transformation", "spiritual"], # 死亡:结束、转化、灵性 - } +def get_pancha_pakshi_schedule(birth_nakshatra: str, paksha: str, + date: Optional[str] = None) -> Dict: + """便捷函数""" + engine = PanchaPakshi() + schedule = engine.calculate(birth_nakshatra, paksha, date) + return engine.to_dict(schedule) - guidance = { - "current_pakshi": birth_pakshi_idx, - "current_pakshi_name": PAKSHI_NAMES[birth_pakshi_idx], - "suitable_question_types": suitable_for.get(birth_pakshi_idx, []), - "prashna_advice": ( - "当前为统治时段,问题关于新开始或领导最适合。" - if birth_pakshi_idx == 0 else - "当前为进食时段,问题关于资源或学习最适合。" - if birth_pakshi_idx == 1 else - "当前为行走时段,问题关于旅行或沟通最适合。" - if birth_pakshi_idx == 2 else - "当前为睡眠时段,问题关于休息或规划最适合,不宜重要决策。" - if birth_pakshi_idx == 3 else - "当前为死亡时段,问题关于结束或转化最适合。" - ), - } - - return guidance +# ============================================================================ +# CLI 调试 +# ============================================================================ if __name__ == "__main__": - # 测试:出生 Nakshatra 10(Magha),目标周三(weekday=3),时段 0 - result = calc_pakshi_full_analysis(10, target_weekday=3, target_period=0) - print("Pancha Pakshi 测试结果:") - for k, v in result.items(): - if k != "day_periods": - print(f" {k}: {v}") - print(" 时段列表:") - for p in result.get("day_periods", []): - print(f" {p}") + print("=" * 60) + print("Pancha Pakshi 五鸟择时引擎") + print("=" * 60) + + result = get_pancha_pakshi_schedule("Rohini", "shukla", "2026-06-07") + print(result["narrative"]) + print(f"\n出生鸟: {result['bird_cn']}") diff --git a/scripts/rashi_tulya_navamsa.py b/scripts/rashi_tulya_navamsa.py index a0dc208d..175fce00 100644 --- a/scripts/rashi_tulya_navamsa.py +++ b/scripts/rashi_tulya_navamsa.py @@ -1,259 +1,506 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ -rashi_tulya_navamsa.py - Rashi Tulya Navamsa(本命对分盘九分仪)分析模块 -v6.0.15 新增技法 +rashi_tulya_navamsa.py — Rashi Tulya Navamsa (RTN) 映射引擎 +=========================================================== +将D9行星位置映射到D1,揭示隐藏的力量结构 -Rashi Tulya Navamsa:将 D9(Navamsa)分盘的每一宫与 D1(Rashi)的同一宫对比, -揭示该生活领域的"内在真相"和"潜在动力"。 -核心思路:D1 是外在表现,D9 是内在潜力/真实状态。 +来源: 《Dhruva Nadi》《Deva Keralam》 +版本: v1.0 | 2026-06-07 """ -# 宫位名称(1-12) -HOUSE_NAMES = [ - "第1宫(自我/身体)", "第2宫(财富/家庭)", "第3宫(兄弟/勇气)", - "第4宫(母亲/情绪)", "第5宫(子女/智慧)", "第6宫(疾病/敌人)", - "第7宫(配偶/合伙)", "第8宫(隐藏/转变)", "第9宫(命运/父亲)", - "第10宫(事业/名声)", "第11宫(收益/朋友)", "第12宫(损失/解脱)" -] - -# 行星名称 -PLANET_NAMES = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"] - -# 宫位领域关键词 -HOUSE_KEYWORDS = [ - "自我身份、外貌、性格、生命力", - "财富积累、语言能力、早期教育、家庭经济", - "兄弟姐妹、沟通勇气、短途旅行、动手能力", - "母亲、家庭、内心安全感、不动产、车辆", - "子女、创造性智慧、投资项目、恋爱前期", - "日常工作、健康问题、竞争对手、债务", - "婚姻关系、商业合伙、公开的敌人、异地", - "深层转变、神秘学、性传播、意外收入", - "人生大方向、高等教育、哲学信仰、父亲", - "社会身份、事业成就、公众形象、权威", - "长期收益、朋友圈、团体归属、愿景实现", - "潜意识、海外事务、精神解脱、隐秘支出" -] +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field -def analyze_rashi_tulya_navamsa(d1_planets, d1_houses, d9_planets, d9_houses): - """ - Rashi Tulya Navamsa 核心分析。 - 输入: - d1_planets: D1 行星数据 {planet_idx: {sign, house, dignity,...}} - d1_houses: D1 宫位数据 {house_num: {sign, lord, planets}} - d9_planets: D9 行星数据 {planet_idx: {sign, house, dignity,...}} - d9_houses: D9 宫位数据 {house_num: {sign, lord, planets}} - 输出: - 每宫的 D1→D9 对比分析 - """ - results = [] +# RTN宫位名称 +RTN_HOUSE_NAMES = { + 1: "Lagnamsa", # 性格和本质 + 2: "Dhanamsa", # 财富和积累 + 3: "Vikramsa", # 勇气、努力和兄弟 + 4: "Sukhamsa", # 家庭幸福、母亲 + 5: "Putramsa", # 子女、智力 + 6: "Ariamsa", # 敌人、障碍、健康 + 7: "Kalatramsa", # 婚姻、配偶、合伙 + 8: "Randhramsa", # 突变、意外、转变 + 9: "Bhagyamsa", # 命运、宗教、好运 + 10: "Karmamsa", # 事业、成就、公共生活 + 11: "Labhamsa", # 收益、朋友、社交圈 + 12: "Vyayamsa", # 支出、隐秘、精神追求 +} - for house_num in range(1, 13): # 1-12 宫 - d1_house_data = d1_houses.get(house_num, {}) - d9_house_data = d9_houses.get(house_num, {}) +# Gunas分类 +GUNAS = { + "Rajas": ["Venus", "Mercury"], # 创造 + "Sattva": ["Jupiter", "Sun", "Moon"], # 维持 + "Tamas": ["Saturn", "Mars", "Rahu", "Ketu"], # 毁灭 +} - # D1 该宫的信息 - d1_sign = d1_house_data.get("sign", "Unknown") - d1_lord = d1_house_data.get("lord", "Unknown") - d1_planets_in = d1_house_data.get("planets", []) - d1_house_strength = d1_house_data.get("strength", "Unknown") +# 凶星合相命名 +CURSE_YOGAS = { + ("Mars", "Saturn"): { + "name": "Yama Yoga", + "name_cn": "阎魔瑜伽", + "meaning": "死亡之神 sitting,等待被激活。激活Dasha/Transit可触发危险疾病或事故。", + "remedy": "需要上师保护,唱诵Guru Vandana", + "severity": "critical", + }, + ("Saturn", "Rahu"): { + "name": "Preta Yoga", + "name_cn": "亡灵瑜伽", + "meaning": "过早死亡的风险,慢性疾病,可能来自祖先未解决的业力。", + "remedy": "湿婆能量,唱诵湿婆咒语", + "severity": "critical", + }, + ("Saturn", "Ketu"): { + "name": "Preta Yoga", + "name_cn": "亡灵瑜伽", + "meaning": "过早死亡的风险,慢性疾病,可能来自祖先未解决的业力。", + "remedy": "湿婆能量,唱诵湿婆咒语", + "severity": "critical", + }, + ("Mars", "Rahu"): { + "name": "Rakshasa Yoga", + "name_cn": "恶魔瑜伽", + "meaning": "暴力事件,粗俗或微妙的攻击,神经系统受损。", + "remedy": "杜尔迦女神,唱诵Durga咒语", + "severity": "high", + }, + ("Mars", "Ketu"): { + "name": "Pisacha Yoga", + "name_cn": "鬼魅瑜伽", + "meaning": "与有毒的人接触,被鬼魂困扰,天蝎座凶星激发。", + "remedy": "毗湿奴,唱诵Vishnu咒语", + "severity": "high", + }, +} - # D9 该宫的信息 - d9_sign = d9_house_data.get("sign", "Unknown") - d9_lord = d9_house_data.get("lord", "Unknown") - d9_planets_in = d9_house_data.get("planets", []) - d9_house_strength = d9_house_data.get("strength", "Unknown") - # 核心对比分析 - comparison = _compare_d1_d9_house( - house_num, d1_sign, d1_lord, d1_planets_in, d1_house_strength, - d9_sign, d9_lord, d9_planets_in, d9_house_strength +@dataclass +class RTNPlanet: + """RTN中的行星""" + planet: str + d9_sign: str + d9_house: int + d1_sign: str # 映射到D1的星座 + d1_house: int # 映射到D1的宫位 + rtn_house_name: str # RTN宫位名称 + dignity_d9: str # D9中的庙旺状态 + dignity_d1: str # D1中的庙旺状态 + + # 与D1行星的合相 + d1_conjunctions: List[str] = field(default_factory=list) + d1_aspects: List[str] = field(default_factory=list) + + # Gunas + guna: str = "" + + # 解读 + narrative: str = "" + + +@dataclass +class RTNAnalysis: + """RTN分析结果""" + # 基础信息 + lagna_sign: str + planets: List[RTNPlanet] = field(default_factory=list) + + # 宫位映射 + house_mapping: Dict[int, List[str]] = field(default_factory=dict) + + # 关键发现 + exalted_cancelled: List[str] = field(default_factory=list) # 耀升被取消 + debilitated_cancelled: List[str] = field(default_factory=list) # 落陷被取消 + + # 凶星合相 + curse_yogas: List[Dict] = field(default_factory=list) + + # Gunas分析 + guna_balance: Dict[str, int] = field(default_factory=dict) + guna_narrative: str = "" + + # 综合评估 + strength_score: float = 0.5 + weakness_score: float = 0.5 + + # 叙事 + full_narrative: str = "" + + +class RashiTulyaNavamsa: + """RTN映射引擎""" + + def __init__(self): + self.house_names = RTN_HOUSE_NAMES + self.gunas = GUNAS + self.curse_yogas = CURSE_YOGAS + + def analyze(self, chart_data: Dict) -> RTNAnalysis: + """ + 执行RTN分析 + + Args: + chart_data: 标准星盘数据(含context.navamsa_planets) + + Returns: + RTNAnalysis对象 + """ + # Step 1: 获取D9行星位置 + d9_planets = self._get_d9_planets(chart_data) + + # Step 2: 获取D1信息 + d1_planets = chart_data.get("planets", {}) + asc_sign = chart_data.get("ascendant", {}).get("sign", "Aries") + asc_house = 1 # 上升永远在第1宫 + + # Step 3: 创建RTN分析对象 + analysis = RTNAnalysis(lagna_sign=asc_sign) + + # Step 4: 映射每个D9行星到D1 + for planet_name, d9_data in d9_planets.items(): + if planet_name in ["Ascendant", "Lagna"]: + continue + + rtn_planet = self._map_planet( + planet_name, d9_data, d1_planets, asc_sign + ) + analysis.planets.append(rtn_planet) + + # 记录宫位映射 + house = rtn_planet.d1_house + if house not in analysis.house_mapping: + analysis.house_mapping[house] = [] + analysis.house_mapping[house].append(planet_name) + + # Step 5: 检测耀升/落陷取消 + self._check_dignity_cancellation(analysis) + + # Step 6: 检测凶星合相 + self._detect_curse_yogas(analysis) + + # Step 7: Gunas分析 + self._analyze_gunas(analysis) + + # Step 8: 综合评估 + self._comprehensive_assessment(analysis) + + # Step 9: 生成叙事 + analysis.full_narrative = self._generate_narrative(analysis) + + return analysis + + def _get_d9_planets(self, chart_data: Dict) -> Dict: + """从chart_data中提取D9行星位置""" + context = chart_data.get("context", {}) + + # 优先从context获取 + if "navamsa_planets" in context: + return context["navamsa_planets"] + + # 否则尝试从varga计算 + # 简化:返回空 + return {} + + def _map_planet(self, planet_name: str, d9_data: Dict, + d1_planets: Dict, asc_sign: str) -> RTNPlanet: + """将单个D9行星映射到D1""" + d9_sign = d9_data.get("sign", "") + d9_house = d9_data.get("house", 0) + + # 在D1中找到相同的星座 + d1_sign = d9_sign + d1_house = self._sign_to_house(d9_sign, asc_sign) + + # RTN宫位名称 + rtn_name = self.house_names.get(d1_house, f"House_{d1_house}") + + # 检测D1中的庙旺状态 + d1_dignity = self._check_dignity_in_chart(planet_name, d9_sign, d1_planets) + d9_dignity = d9_data.get("dignity", "") + + # 检测与D1行星的合相 + d1_conjs = self._find_d1_conjunctions(planet_name, d1_sign, d1_planets) + + # Gunas + guna = self._get_guna(planet_name) + + return RTNPlanet( + planet=planet_name, + d9_sign=d9_sign, + d9_house=d9_house, + d1_sign=d1_sign, + d1_house=d1_house, + rtn_house_name=rtn_name, + dignity_d9=d9_dignity, + dignity_d1=d1_dignity, + d1_conjunctions=d1_conjs, + guna=guna, ) - - results.append({ - "house_num": house_num, - "house_name": HOUSE_NAMES[house_num - 1], - "house_keywords": HOUSE_KEYWORDS[house_num - 1], - "d1_sign": d1_sign, - "d1_lord": d1_lord, - "d1_planets_in": d1_planets_in, - "d1_strength": d1_house_strength, - "d9_sign": d9_sign, - "d9_lord": d9_lord, - "d9_planets_in": d9_planets_in, - "d9_strength": d9_house_strength, - "comparison": comparison, - }) - - return results + + def _sign_to_house(self, sign: str, asc_sign: str) -> int: + """根据上升星座计算某星座是第几宫""" + SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", + "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] + + try: + asc_idx = SIGNS.index(asc_sign) + sign_idx = SIGNS.index(sign) + house = ((sign_idx - asc_idx) % 12) + 1 + return house + except ValueError: + return 1 + + def _check_dignity_in_chart(self, planet: str, sign: str, + d1_planets: Dict) -> str: + """检查行星在某星座的庙旺状态""" + # 简化实现 + EXALTATION = { + "Sun": "Aries", "Moon": "Taurus", "Mars": "Capricorn", + "Mercury": "Virgo", "Jupiter": "Cancer", "Venus": "Pisces", + "Saturn": "Libra", + } + DEBILITATION = { + "Sun": "Libra", "Moon": "Scorpio", "Mars": "Cancer", + "Mercury": "Pisces", "Jupiter": "Capricorn", "Venus": "Virgo", + "Saturn": "Aries", + } + OWN_SIGN = { + "Sun": "Leo", "Moon": "Cancer", "Mars": ["Aries", "Scorpio"], + "Mercury": ["Gemini", "Virgo"], "Jupiter": ["Sagittarius", "Pisces"], + "Venus": ["Taurus", "Libra"], "Saturn": ["Capricorn", "Aquarius"], + } + + if EXALTATION.get(planet) == sign: + return "exalted" + elif DEBILITATION.get(planet) == sign: + return "debilitated" + elif sign in (OWN_SIGN.get(planet) or []): + return "own" + return "neutral" + + def _find_d1_conjunctions(self, planet_name: str, sign: str, + d1_planets: Dict) -> List[str]: + """找到D1中同星座的其他行星""" + conjs = [] + for pname, pdata in d1_planets.items(): + if pname == planet_name: + continue + if isinstance(pdata, dict) and pdata.get("sign") == sign: + conjs.append(pname) + return conjs + + def _get_guna(self, planet: str) -> str: + """获取行星的Gunas""" + for guna, planets in self.gunas.items(): + if planet in planets: + return guna + return "Tamas" # Rahu/Ketu默认Tamas + + def _check_dignity_cancellation(self, analysis: RTNAnalysis): + """检测耀升/落陷取消""" + for p in analysis.planets: + # 如果在D1中耀升但D9映射后落在凶星星座/宫位 + if p.dignity_d1 == "exalted" and p.d1_conjunctions: + malefics = ["Saturn", "Mars", "Rahu", "Ketu"] + if any(m in p.d1_conjunctions for m in malefics): + analysis.exalted_cancelled.append(p.planet) + + # 如果在D1中落陷但D9映射后与吉星合相 + if p.dignity_d1 == "debilitated" and p.d1_conjunctions: + benefics = ["Jupiter", "Venus", "Mercury", "Moon"] + if any(b in p.d1_conjunctions for b in benefics): + analysis.debilitated_cancelled.append(p.planet) + + def _detect_curse_yogas(self, analysis: RTNAnalysis): + """检测凶星合相命名""" + # 按宫位检查 + for house, planets in analysis.house_mapping.items(): + if len(planets) < 2: + continue + + # 检查所有行星对 + for i, p1 in enumerate(planets): + for p2 in planets[i+1:]: + # 标准化顺序 + pair = tuple(sorted([p1, p2])) + + if pair in self.curse_yogas: + curse = self.curse_yogas[pair].copy() + curse["house"] = house + curse["house_name"] = self.house_names.get(house, "") + curse["planets"] = list(pair) + analysis.curse_yogas.append(curse) + + def _analyze_gunas(self, analysis: RTNAnalysis): + """分析Gunas平衡""" + counts = {"Rajas": 0, "Sattva": 0, "Tamas": 0} + + for p in analysis.planets: + if p.guna in counts: + counts[p.guna] += 1 + + analysis.guna_balance = counts + + # 生成叙事 + total = sum(counts.values()) + if total == 0: + return + + dominant = max(counts, key=counts.get) + ratio = counts[dominant] / total + + if ratio > 0.5: + if dominant == "Sattva": + analysis.guna_narrative = "Gunas以Sattva(纯善)为主导,吉星力量较强,灵性倾向明显。" + elif dominant == "Rajas": + analysis.guna_narrative = "Gunas以Rajas(活动)为主导,创造性能量活跃,追求成就。" + else: + analysis.guna_narrative = "Gunas以Tamas(惯性)为主导,毁灭力量较强,需要特别注意凶星影响。" + else: + analysis.guna_narrative = "Gunas相对平衡,三种力量相互制衡。" + + def _comprehensive_assessment(self, analysis: RTNAnalysis): + """综合评估""" + # 计算力量得分 + strength = 0 + weakness = 0 + + for p in analysis.planets: + if p.dignity_d1 in ["exalted", "own"]: + strength += 1 + elif p.dignity_d1 == "debilitated": + weakness += 1 + + # 凶星合相增加弱点 + if p.planet in ["Saturn", "Mars", "Rahu", "Ketu"] and p.d1_conjunctions: + weakness += 0.5 + + total = len(analysis.planets) or 1 + analysis.strength_score = min(strength / total, 1.0) + analysis.weakness_score = min(weakness / total, 1.0) + + def _generate_narrative(self, analysis: RTNAnalysis) -> str: + """生成RTN解读叙事""" + parts = [] + + parts.append("### Rashi Tulya Navamsa (RTN) 深度分析\n") + parts.append("将Navamsa (D9) 行星映射到本命盘 (D1),揭示隐藏的力量结构。\n") + + # 宫位映射概览 + parts.append("\n#### RTN宫位映射概览\n") + for house in sorted(analysis.house_mapping.keys()): + planets = analysis.house_mapping[house] + name = self.house_names.get(house, "") + parts.append(f"**{name}** (第{house}宫): {', '.join(planets)}\n") + + # 关键发现 + if analysis.exalted_cancelled: + parts.append(f"\n⚠️ **耀升被取消**: {', '.join(analysis.exalted_cancelled)}") + parts.append("这些行星表面强大但根基不稳,效果被削弱。\n") + + if analysis.debilitated_cancelled: + parts.append(f"\n✨ **落陷被取消**: {', '.join(analysis.debilitated_cancelled)}") + parts.append("这些行星表面弱势但有隐藏支持,效果被增强。\n") + + # 凶星合相 + if analysis.curse_yogas: + parts.append("\n#### 凶星合相警示\n") + for curse in analysis.curse_yogas: + parts.append(f"\n🔥 **{curse['name']} ({curse['name_cn']})**") + parts.append(f" 行星: {', '.join(curse['planets'])}") + parts.append(f" 宫位: 第{curse['house']}宫 ({curse['house_name']})") + parts.append(f" 含义: {curse['meaning']}") + parts.append(f" 补救: {curse['remedy']}\n") + + # Gunas + if analysis.guna_narrative: + parts.append(f"\n#### Gunas能量分析\n") + parts.append(f"{analysis.guna_narrative}\n") + parts.append(f"Rajas: {analysis.guna_balance.get('Rajas', 0)} | ") + parts.append(f"Sattva: {analysis.guna_balance.get('Sattva', 0)} | ") + parts.append(f"Tamas: {analysis.guna_balance.get('Tamas', 0)}\n") + + # 综合评估 + parts.append(f"\n#### 综合评估\n") + parts.append(f"力量指数: {analysis.strength_score:.0%}\n") + parts.append(f"弱点指数: {analysis.weakness_score:.0%}\n") + + if analysis.strength_score > analysis.weakness_score: + parts.append("\n总体评估:吉星力量占优,积极因素可以克服挑战。\n") + else: + parts.append("\n总体评估:凶星影响较强,需要特别注意相关领域。\n") + + return "\n".join(parts) + + def to_dict(self, analysis: RTNAnalysis) -> Dict: + """转换为字典""" + return { + "lagna_sign": analysis.lagna_sign, + "planets": [ + { + "planet": p.planet, + "d9_sign": p.d9_sign, + "d1_house": p.d1_house, + "rtn_house_name": p.rtn_house_name, + "dignity_d9": p.dignity_d9, + "dignity_d1": p.dignity_d1, + "d1_conjunctions": p.d1_conjunctions, + "guna": p.guna, + } + for p in analysis.planets + ], + "house_mapping": analysis.house_mapping, + "exalted_cancelled": analysis.exalted_cancelled, + "debilitated_cancelled": analysis.debilitated_cancelled, + "curse_yogas": analysis.curse_yogas, + "guna_balance": analysis.guna_balance, + "guna_narrative": analysis.guna_narrative, + "strength_score": round(analysis.strength_score, 2), + "weakness_score": round(analysis.weakness_score, 2), + "narrative": analysis.full_narrative, + } -def _compare_d1_d9_house(house_num, d1_sign, d1_lord, d1_pl, d1_str, d9_sign, d9_lord, d9_pl, d9_str): - """ - 对比 D1 和 D9 同一宫位的详细分析。 - """ - analysis = [] +# ============================================================================ +# 便捷函数 +# ============================================================================ - # 1. 宫主星对比 - if d1_lord == d9_lord: - analysis.append(f"宫主星一致({d1_lord}),该领域内外表现统一,潜力与表现匹配。") - else: - analysis.append(f"宫主星不同:D1={d1_lord},D9={d9_lord}。外在表现({d1_lord})" - f"与内在潜力({d9_lord})有差异,需要调和。") - - # 2. 行星入驻对比 - d1_pl_names = [PLANET_NAMES[p] if isinstance(p, int) else p for p in d1_pl] - d9_pl_names = [PLANET_NAMES[p] if isinstance(p, int) else p for p in d9_pl] - - if d1_pl_names: - analysis.append(f"D1 入驻行星:{', '.join(map(str, d1_pl_names))}(外在影响)") - else: - analysis.append("D1 无行星入驻(该领域外在平静)") - - if d9_pl_names: - analysis.append(f"D9 入驻行星:{', '.join(map(str, d9_pl_names))}(内在动力)") - else: - analysis.append("D9 无行星入驻(该领域内在潜力未被激活)") - - # 3. 力量对比 - analysis.append(f"D1 宫力量:{d1_str} | D9 宫力量:{d9_str}") - if d1_str == "Strong" and d9_str == "Strong": - analysis.append("→ D1、D9 均强:该领域内外兼强,潜力完全发挥。") - elif d1_str == "Weak" and d9_str == "Weak": - analysis.append("→ D1、D9 均弱:该领域内外兼弱,需要努力克服。") - elif d1_str == "Strong" and d9_str == "Weak": - analysis.append("→ D1 强 D9 弱:外在表现好,但内在支持不足,可能表面风光。") - elif d1_str == "Weak" and d9_str == "Strong": - analysis.append("→ D1 弱 D9 强:外在表现弱,但内在潜力强,大器晚成之象。") - - # 4. 关键解读(按宫位) - key_interp = _get_house_key_interpretation(house_num, d1_pl, d9_pl, d1_lord, d9_lord) - if key_interp: - analysis.append(f"关键解读:{key_interp}") - - return analysis +def analyze_rtn(chart_data: Dict) -> Dict: + """便捷函数:RTN分析""" + engine = RashiTulyaNavamsa() + analysis = engine.analyze(chart_data) + return engine.to_dict(analysis) -def _get_house_key_interpretation(house_num, d1_pl, d9_pl, d1_lord, d9_lord): - """ - 按宫位给出关键解读。 - """ - interpretations = { - 1: "第1宫:自我认知。D9 强则内在自信,D1 强则外在魅力。", - 2: "第2宫:财富积累。D9 有金星/木星则财富潜力大,D1 有恶星则财务波折。", - 3: "第3宫:勇气行动。D9 有火星则内在勇敢,D1 有水星则沟通力强。", - 4: "第4宫:内心安全。D9 有月亮/金星则内心满足,D1 有土星则情感压抑。", - 5: "第5宫:创造智慧。D9 有木星则智慧天赋高,D1 有火星则创造冲动强。", - 6: "第6宫:竞争健康。D9 有土星/火星则抗压能力强,D1 有木星则能化解敌意。", - 7: "第7宫:婚姻关系。D9 是婚姻内在,D1 是婚姻表现。D9 强则婚姻内在和谐。", - 8: "第8宫:深层转变。D9 有木星/金星则危机中有贵人,D1 有恶星则有突发事件。", - 9: "第9宫:命运方向。D9 有木星/太阳则命运护佑强,D1 有恶星则需要努力。", - 10: "第10宫:事业成就。D9 是事业潜力,D1 是事业表现。D9 强则事业基础稳。", - 11: "第11宫:收益朋友。D9 有木星/金星则收益潜力大,D1 有土星则收益延迟。", - 12: "第12宫:解脱潜意识。D9 有木星/金星则精神解脱能力强,D1 有恶星则有隐秘损失。", - } - return interpretations.get(house_num, "") - - -def rashi_tulya_navamsa_summary(rashi_tulya_results): - """ - 生成 Rashi Tulya Navamsa 的总结报告。 - """ - summary = { - "total_houses_analyzed": len(rashi_tulya_results), - "houses_d1_d9_both_strong": [], - "houses_d1_weak_d9_strong": [], - "houses_d1_strong_d9_weak": [], - "houses_d1_d9_both_weak": [], - "key_insights": [], - } - - for house_data in rashi_tulya_results: - h = house_data["house_num"] - d1_s = house_data["d1_strength"] - d9_s = house_data["d9_strength"] - - if d1_s == "Strong" and d9_s == "Strong": - summary["houses_d1_d9_both_strong"].append(h) - elif d1_s == "Weak" and d9_s == "Strong": - summary["houses_d1_weak_d9_strong"].append(h) - elif d1_s == "Strong" and d9_s == "Weak": - summary["houses_d1_strong_d9_weak"].append(h) - elif d1_s == "Weak" and d9_s == "Weak": - summary["houses_d1_d9_both_weak"].append(h) - - # 关键洞察 - if summary["houses_d1_weak_d9_strong"]: - summary["key_insights"].append( - f"大器晚成宫位:{summary['houses_d1_weak_d9_strong']}" - f"(这些领域内在潜力强,但需要时间/努力才能显现)" - ) - if summary["houses_d1_strong_d9_weak"]: - summary["key_insights"].append( - f"表面风光宫位:{summary['houses_d1_strong_d9_weak']}" - f"(这些领域外在表现好,但内在支持不足,需要警惕)" - ) - if summary["houses_d1_d9_both_strong"]: - summary["key_insights"].append( - f"内外兼强宫位:{summary['houses_d1_d9_both_strong']}" - f"(这些领域实力雄厚,可以重点发展)" - ) - if summary["houses_d1_d9_both_weak"]: - summary["key_insights"].append( - f"需要努力宫位:{summary['houses_d1_d9_both_weak']}" - f"(这些领域内外都弱,需要特别努力或寻求帮助)" - ) - - return summary - - -def rashi_tulya_navamsa_marriage_focus(rashi_tulya_results): - """ - 专门针对婚姻(第7宫)的 Rashi Tulya Navamsa 分析。 - """ - # 找到第7宫的数据 - house_7_data = None - for h_data in rashi_tulya_results: - if h_data["house_num"] == 7: - house_7_data = h_data - break - - if not house_7_data: - return {"error": "未找到第7宫数据"} - - d1_7 = house_7_data["d1_sign"] - d9_7 = house_7_data["d9_sign"] - d1_pl_7 = house_7_data["d1_planets_in"] - d9_pl_7 = house_7_data["d9_planets_in"] - d1_str_7 = house_7_data["d1_strength"] - d9_str_7 = house_7_data["d9_strength"] - - marriage_analysis = { - "house_7_d1_sign": d1_7, - "house_7_d9_sign": d9_7, - "house_7_d1_planets": d1_pl_7, - "house_7_d9_planets": d9_pl_7, - "house_7_d1_strength": d1_str_7, - "house_7_d9_strength": d9_str_7, - "marriage_outer_expression": f"外在婚姻表现受 {d1_7} 影响", - "marriage_inner_potential": f"内在婚姻潜力在 {d9_7}", - "assessment": "", - } - - # 评估 - if d1_str_7 == "Strong" and d9_str_7 == "Strong": - marriage_analysis["assessment"] = "婚姻内外兼强,配偶优秀,关系和谐。" - elif d1_str_7 == "Weak" and d9_str_7 == "Strong": - marriage_analysis["assessment"] = "婚姻内在潜力强(D9强),但外在表现弱(D1弱),可能晚婚或婚姻经历波折后好转。" - elif d1_str_7 == "Strong" and d9_str_7 == "Weak": - marriage_analysis["assessment"] = "婚姻外在表现好(D1强),但内在不稳定(D9弱),需要注意关系维护。" - else: - marriage_analysis["assessment"] = "婚姻内外都弱,需要努力经营,也可能配偶条件一般。" - - return marriage_analysis - +# ============================================================================ +# CLI 调试 +# ============================================================================ if __name__ == "__main__": - # 简单测试(需要实际数据) - print("Rashi Tulya Navamsa 模块已加载") - print("需要 D1 和 D9 的行星/宫位数据作为输入") + mock_chart = { + "ascendant": {"sign": "Capricorn"}, + "planets": { + "Moon": {"sign": "Aquarius", "house": 2}, + "Saturn": {"sign": "Aquarius", "house": 2}, + "Mars": {"sign": "Aquarius", "house": 2}, + "Rahu": {"sign": "Aquarius", "house": 2}, + "Jupiter": {"sign": "Leo", "house": 8}, + }, + "context": { + "navamsa_planets": { + "Moon": {"sign": "Scorpio", "house": 10, "dignity": "debilitated"}, + "Saturn": {"sign": "Scorpio", "house": 10, "dignity": "neutral"}, + "Mars": {"sign": "Scorpio", "house": 10, "dignity": "own"}, + "Rahu": {"sign": "Scorpio", "house": 10, "dignity": "neutral"}, + "Jupiter": {"sign": "Cancer", "house": 7, "dignity": "exalted"}, + } + } + } + + print("=" * 60) + print("Rashi Tulya Navamsa (RTN) 映射引擎") + print("=" * 60) + + result = analyze_rtn(mock_chart) + print(result["narrative"]) diff --git a/scripts/reading_orchestrator.py b/scripts/reading_orchestrator.py new file mode 100644 index 00000000..59ee898c --- /dev/null +++ b/scripts/reading_orchestrator.py @@ -0,0 +1,656 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +reading_orchestrator.py — 主题化解读编排器 +=========================================== +从"模块罗列"升级为"主题叙事" + +核心能力: +1. 主题路由 — 根据用户问题选择分析主题 +2. 技法编排 — 按优先级调用相关技法 +3. 交叉验证 — 多维度矛盾裁决 +4. 叙事生成 — 从数据生成连贯的人生故事 + +版本: v1.0 | 2026-06-07 +""" + +from typing import Dict, List, Any, Optional +from dataclasses import dataclass, field +from enum import Enum +import json + + +class ReadingTheme(Enum): + """解读主题""" + OVERVIEW = "overview" # 生命概览 + MARRIAGE = "marriage" # 婚姻情感 + CAREER = "career" # 事业财富 + HEALTH = "health" # 健康危机 + SPIRITUAL = "spiritual" # 灵性成长 + WEALTH = "wealth" # 财富积累 + TIMING = "timing" # 时间预测 + RELATIONSHIP = "relationship" # 人际关系 + EDUCATION = "education" # 教育学习 + FAMILY = "family" # 家庭父母 + + +class ConflictResolution(Enum): + """矛盾裁决策略""" + D9_OVERRIDES = "d9_overrides" # D9优先(婚姻/灵性) + D1_OVERRIDES = "d1_overrides" # D1优先(世俗事务) + STRONGEST_WINS = "strongest_wins" # 最强力量优先 + CONTEXTUAL = "contextual" # 上下文叙事(推荐) + ALL_VALID = "all_valid" # 全部保留,标注差异 + + +@dataclass +class ThemeConfig: + """主题配置""" + theme: ReadingTheme + title: str # 主题标题 + description: str # 主题描述 + technique_chain: List[str] # 技法调用链 + primary_varga: List[int] # 主要分盘 + conflict_strategy: ConflictResolution # 矛盾裁决策略 + narrative_template: str # 叙事模板 + + +@dataclass +class TechniqueResult: + """技法计算结果包装""" + technique_name: str + technique_name_cn: str + status: str # success / warning / error + result: Dict[str, Any] + interpretation: Optional[str] = None + confidence: float = 0.5 # 置信度 0-1 + weight: float = 1.0 # 在主题中的权重 + + +@dataclass +class ConflictItem: + """矛盾项""" + dimension: str # 维度(如"婚姻质量") + d1_finding: str # D1发现 + d9_finding: str # D9发现 + other_findings: Dict[str, str] # 其他分盘发现 + resolution: str # 裁决结果 + narrative: str # 叙事表达 + + +@dataclass +class ReadingChapter: + """报告章节""" + title: str + subtitle: str + priority: int # 优先级(1最高) + techniques_used: List[str] # 使用的技法 + findings: List[str] # 核心发现 + conflicts: List[ConflictItem] # 矛盾与裁决 + narrative: str # 叙事文本 + actionable: List[str] # 行动建议 + timing: Optional[str] = None # 时间锚定 + + +class ReadingOrchestrator: + """解读编排器""" + + def __init__(self, registry): + self.registry = registry + self._themes: Dict[ReadingTheme, ThemeConfig] = self._init_themes() + + def _init_themes(self) -> Dict[ReadingTheme, ThemeConfig]: + """初始化主题配置""" + return { + ReadingTheme.OVERVIEW: ThemeConfig( + theme=ReadingTheme.OVERVIEW, + title="生命蓝图概览", + description="从全局视角解读先天格局", + technique_chain=[ + "chart", "shadbala", "yoga", "karaka", + "nakshatra", "vimsopaka", "avastha" + ], + primary_varga=[1], + conflict_strategy=ConflictResolution.ALL_VALID, + narrative_template="overview" + ), + + ReadingTheme.MARRIAGE: ThemeConfig( + theme=ReadingTheme.MARRIAGE, + title="婚姻与情感深度解析", + description="从D1、D9、DK、Upapada多维度解读婚姻质量", + technique_chain=[ + "darakaraka", + "spouse_status_yoga", + "navamsa_analysis", + "rtn_mapping", + "vivah_saham", + "tithi_lord", + "mangal_dosha", + "synastry", + "upapada_lagna", + "dasha_marriage", + "transit_marriage" + ], + primary_varga=[1, 9], + conflict_strategy=ConflictResolution.D9_OVERRIDES, + narrative_template="marriage" + ), + + ReadingTheme.CAREER: ThemeConfig( + theme=ReadingTheme.CAREER, + title="事业与社会成就", + description="从D1、D10、AmK多维度解读事业轨迹", + technique_chain=[ + "amatyakaraka", + "dashamsa_analysis", + "d10_yoga", + "shadbala", + "ashtakavarga", + "tajika_varshaphala", + "dasha_career", + "transit_career" + ], + primary_varga=[1, 10], + conflict_strategy=ConflictResolution.D1_OVERRIDES, + narrative_template="career" + ), + + ReadingTheme.HEALTH: ThemeConfig( + theme=ReadingTheme.HEALTH, + title="健康与生命危机", + description="从D1、D30、凶星合相检测健康风险", + technique_chain=[ + "curse_yoga", + "trimshamsa", + "shadbala", + "avastha", + "drekkena", + "dasha_health", + "transit_health" + ], + primary_varga=[1, 30], + conflict_strategy=ConflictResolution.STRONGEST_WINS, + narrative_template="health" + ), + + ReadingTheme.WEALTH: ThemeConfig( + theme=ReadingTheme.WEALTH, + title="财富与资源", + description="从D1、D2、Dhana Yoga解读财富格局", + technique_chain=[ + "dhana_yoga", + "hora_analysis", + "dhanakaraka", + "vasihikamsa", + "ashtakavarga", + "dasha_wealth" + ], + primary_varga=[1, 2], + conflict_strategy=ConflictResolution.D1_OVERRIDES, + narrative_template="wealth" + ), + + ReadingTheme.SPIRITUAL: ThemeConfig( + theme=ReadingTheme.SPIRITUAL, + title="灵性与灵魂成长", + description="从AK、D20、Karakamsha解读灵性道路", + technique_chain=[ + "atmakaraka", + "karakamsha", + "vimsamsa", + "chara_dasha", + "nakshatra_deity", + "dasha_spiritual" + ], + primary_varga=[1, 9, 20], + conflict_strategy=ConflictResolution.D9_OVERRIDES, + narrative_template="spiritual" + ), + + ReadingTheme.TIMING: ThemeConfig( + theme=ReadingTheme.TIMING, + title="时间预测", + description="Dasha + Transit精准时间锚定", + technique_chain=[ + "dasha", + "transit", + "double_transit", + "tajika", + "gochara" + ], + primary_varga=[1], + conflict_strategy=ConflictResolution.CONTEXTUAL, + narrative_template="timing" + ), + } + + def analyze(self, chart_data: Dict, theme: ReadingTheme, + question: Optional[str] = None) -> List[ReadingChapter]: + """ + 执行主题化分析 + + Args: + chart_data: 星盘数据 + theme: 分析主题 + question: 用户具体问题(可选) + + Returns: + 章节列表 + """ + config = self._themes.get(theme) + if not config: + return [] + + chapters = [] + + # Step 1: 执行技法链 + technique_results = [] + for tech_name in config.technique_chain: + result = self._execute_technique(tech_name, chart_data) + if result: + technique_results.append(result) + + # Step 2: 生成核心发现 + findings = self._extract_findings(technique_results, config) + + # Step 3: 检测矛盾 + conflicts = self._detect_conflicts(technique_results, config) + + # Step 4: 裁决矛盾 + resolved_conflicts = self._resolve_conflicts(conflicts, config) + + # Step 5: 生成叙事 + narrative = self._generate_narrative(findings, resolved_conflicts, config, question) + + # Step 6: 生成行动建议 + actionable = self._generate_actionable(findings, resolved_conflicts, config) + + # Step 7: 时间锚定 + timing = self._generate_timing(technique_results, config) + + # 组装章节 + chapter = ReadingChapter( + title=config.title, + subtitle=config.description, + priority=1, + techniques_used=[r.technique_name for r in technique_results if r.status == "success"], + findings=findings, + conflicts=resolved_conflicts, + narrative=narrative, + actionable=actionable, + timing=timing + ) + chapters.append(chapter) + + return chapters + + def _execute_technique(self, name: str, chart_data: Dict) -> Optional[TechniqueResult]: + """执行单个技法""" + spec = self.registry.get(name) + if not spec: + return None + + # 如果技法状态是PLANNED,返回占位 + if spec.status.value == "planned": + return TechniqueResult( + technique_name=name, + technique_name_cn=spec.name_cn, + status="planned", + result={}, + interpretation=f"[{spec.name_cn}] 技法开发中,将在后续版本中提供。", + confidence=0.0 + ) + + # 执行计算 + try: + result = self.registry.compute(name, chart_data) + if result is None: + return None + + # 获取解读 + interpretation = self.registry.interpret(name, result) + + # 评估置信度 + confidence = self._assess_confidence(result, spec) + + return TechniqueResult( + technique_name=name, + technique_name_cn=spec.name_cn, + status="success", + result=result, + interpretation=interpretation, + confidence=confidence + ) + except Exception as e: + return TechniqueResult( + technique_name=name, + technique_name_cn=spec.name_cn if spec else name, + status="error", + result={"error": str(e)}, + confidence=0.0 + ) + + def _assess_confidence(self, result: Dict, spec) -> float: + """评估结果置信度""" + # 基础置信度 + confidence = 0.5 + + # 如果有明确检测结果,提高置信度 + if result.get("detected") or result.get("present"): + confidence += 0.2 + + # 如果有多个支持证据 + evidence_count = len(result.get("evidence", [])) + confidence += min(evidence_count * 0.05, 0.2) + + # 如果技法是STABLE状态 + if spec and spec.status.value == "stable": + confidence += 0.1 + + return min(confidence, 1.0) + + def _extract_findings(self, results: List[TechniqueResult], + config: ThemeConfig) -> List[str]: + """提取核心发现""" + findings = [] + + for r in results: + if r.status != "success": + continue + + # 从结果中提取关键发现 + result = r.result + + # Yoga检测类 + if "yogas" in result: + yogas = result["yogas"] + if yogas: + findings.append(f"检测到 {len(yogas)} 个相关Yoga: {', '.join([y.get('name','') for y in yogas[:3]])}") + + # 力量评估类 + if "score" in result: + score = result["score"] + if isinstance(score, (int, float)) and score > 0.7: + findings.append(f"{r.technique_name_cn}得分较高 ({score:.2f}),显示此领域有显著影响") + + # 相位/合相类 + if "conjunctions" in result: + conj = result["conjunctions"] + if conj: + findings.append(f"发现关键合相: {', '.join([str(c) for c in conj[:2]])}") + + # 解读文本 + if r.interpretation: + findings.append(r.interpretation) + + # 去重并排序(按置信度) + seen = set() + unique = [] + for f in findings: + if f and f not in seen: + seen.add(f) + unique.append(f) + + return unique[:7] # 最多7条核心发现 + + def _detect_conflicts(self, results: List[TechniqueResult], + config: ThemeConfig) -> List[ConflictItem]: + """检测多维度矛盾""" + conflicts = [] + + # 按分盘分组 + d1_results = [r for r in results if "d1" in r.technique_name or r.technique_name in ["chart", "yoga"]] + d9_results = [r for r in results if "navamsa" in r.technique_name or r.technique_name == "rtn_mapping"] + + # 简单示例:D1和D9的婚姻判断矛盾 + d1_marriage = self._extract_marriage_signal(d1_results) + d9_marriage = self._extract_marriage_signal(d9_results) + + if d1_marriage and d9_marriage and d1_marriage != d9_marriage: + conflicts.append(ConflictItem( + dimension="婚姻质量", + d1_finding=d1_marriage, + d9_finding=d9_marriage, + other_findings={}, + resolution="待裁决", + narrative="" + )) + + return conflicts + + def _extract_marriage_signal(self, results: List[TechniqueResult]) -> str: + """从结果中提取婚姻信号""" + # 简化实现 + positive = sum(1 for r in results if r.confidence > 0.6) + negative = sum(1 for r in results if r.confidence < 0.4) + + if positive > negative: + return "有利" + elif negative > positive: + return "有挑战" + return "中性" + + def _resolve_conflicts(self, conflicts: List[ConflictItem], + config: ThemeConfig) -> List[ConflictItem]: + """裁决矛盾""" + resolved = [] + + for c in conflicts: + strategy = config.conflict_strategy + + if strategy == ConflictResolution.D9_OVERRIDES: + winner = c.d9_finding + reason = "Navamsa (D9) 在婚姻分析中具有最高权威" + elif strategy == ConflictResolution.D1_OVERRIDES: + winner = c.d1_finding + reason = "本命盘 (D1) 在世俗事务中具有最高权威" + elif strategy == ConflictResolution.STRONGEST_WINS: + # 这里简化处理,实际应比较力量 + winner = c.d9_finding if len(c.d9_finding) > len(c.d1_finding) else c.d1_finding + reason = "基于力量评估的综合判断" + elif strategy == ConflictResolution.CONTEXTUAL: + winner = f"表面{c.d1_finding},但深层{c.d9_finding}" + reason = "需要结合具体情境综合判断" + else: + winner = f"D1: {c.d1_finding}, D9: {c.d9_finding}" + reason = "两种可能性并存" + + c.resolution = winner + c.narrative = self._conflict_to_narrative(c, reason) + resolved.append(c) + + return resolved + + def _conflict_to_narrative(self, conflict: ConflictItem, reason: str) -> str: + """将矛盾转换为叙事""" + if conflict.dimension == "婚姻质量": + if "有利" in conflict.resolution and "挑战" in conflict.resolution: + return f"婚姻表面看起来顺利,但深层存在需要关注的议题。{reason}。" + return f"{conflict.resolution}。{reason}。" + return f"{conflict.resolution}。{reason}。" + + def _generate_narrative(self, findings: List[str], + conflicts: List[ConflictItem], + config: ThemeConfig, + question: Optional[str]) -> str: + """生成主题叙事文本""" + parts = [] + + # 开场 + parts.append(f"## {config.title}\n") + parts.append(f"{config.description}\n") + + # 核心发现 + if findings: + parts.append("### 核心发现\n") + for i, f in enumerate(findings, 1): + parts.append(f"{i}. {f}\n") + + # 矛盾与裁决 + if conflicts: + parts.append("\n### 深层洞察\n") + for c in conflicts: + parts.append(f"\n{c.narrative}\n") + + # 回应问题 + if question: + parts.append(f"\n### 关于您的问题\n") + parts.append(f"针对「{question}」,综合以上分析:\n") + # 这里可以接入AI生成更具体的回答 + + return "\n".join(parts) + + def _generate_actionable(self, findings: List[str], + conflicts: List[ConflictItem], + config: ThemeConfig) -> List[str]: + """生成行动建议""" + actionable = [] + + if config.theme == ReadingTheme.MARRIAGE: + actionable.extend([ + "关注Navamsa中7宫主星的状态,这是婚姻质量的核心指标", + "在DK激活的Dasha周期特别注意关系维护", + "如果Mangal Dosha存在,考虑传统补救措施" + ]) + elif config.theme == ReadingTheme.CAREER: + actionable.extend([ + "在AmK相关的Dasha周期积极寻求事业突破", + "关注D10中10宫主星与Lagna的相位", + "利用Double Transit激活期推进重要项目" + ]) + elif config.theme == ReadingTheme.HEALTH: + actionable.extend([ + "定期进行健康检查,特别是6宫和8宫相关的身体部位", + "在凶星Dasha周期特别注意安全", + "考虑传统 Ayurveda 调理" + ]) + + return actionable + + def _generate_timing(self, results: List[TechniqueResult], + config: ThemeConfig) -> Optional[str]: + """生成时间锚定""" + # 从Dasha和Transit结果中提取时间信息 + timing_parts = [] + + for r in results: + if "dasha" in r.technique_name and r.result: + current = r.result.get("current", {}) + if current: + timing_parts.append(f"当前处于 {current.get('lord', '?')} 大运") + + if timing_parts: + return "; ".join(timing_parts) + return None + + def generate_full_report(self, chart_data: Dict, + themes: Optional[List[ReadingTheme]] = None) -> Dict: + """ + 生成完整报告 + + Args: + chart_data: 星盘数据 + themes: 要分析的主题列表(None=全部) + + Returns: + 结构化报告 + """ + if themes is None: + themes = [ReadingTheme.OVERVIEW, ReadingTheme.MARRIAGE, + ReadingTheme.CAREER, ReadingTheme.HEALTH] + + report = { + "version": "2.0-theme-based", + "themes_analyzed": [], + "chapters": [], + "summary": "", + "missing_techniques": [] + } + + for theme in themes: + chapters = self.analyze(chart_data, theme) + report["chapters"].extend(chapters) + report["themes_analyzed"].append(theme.value) + + # 记录缺失的技法 + config = self._themes.get(theme) + if config: + for tech_name in config.technique_chain: + spec = self.registry.get(tech_name) + if spec and spec.status.value == "planned": + report["missing_techniques"].append({ + "name": tech_name, + "name_cn": spec.name_cn, + "theme": theme.value + }) + + # 去重缺失技法 + seen = set() + unique_missing = [] + for m in report["missing_techniques"]: + if m["name"] not in seen: + seen.add(m["name"]) + unique_missing.append(m) + report["missing_techniques"] = unique_missing + + # 生成总摘要 + report["summary"] = self._generate_summary(report["chapters"]) + + return report + + def _generate_summary(self, chapters: List[ReadingChapter]) -> str: + """生成报告总摘要""" + parts = ["# 印度占星综合解读报告\n"] + + for ch in chapters: + parts.append(f"\n## {ch.title}\n") + if ch.findings: + parts.append("关键发现: " + "; ".join(ch.findings[:3]) + "\n") + if ch.timing: + parts.append(f"时间锚定: {ch.timing}\n") + + return "\n".join(parts) + + +# ============================================================================ +# CLI 调试 +# ============================================================================ + +if __name__ == "__main__": + from technique_registry import registry + + print("=" * 60) + print("主题化解读编排器") + print("=" * 60) + + orchestrator = ReadingOrchestrator(registry) + + # 模拟星盘数据 + mock_chart = { + "ascendant": {"sign": "Aries", "degree": 15.5}, + "planets": { + "Sun": {"sign": "Aries", "degree": 20.0, "house": 1}, + "Moon": {"sign": "Taurus", "degree": 10.0, "house": 2}, + } + } + + # 生成婚姻主题报告 + print("\n--- 婚姻主题分析 ---\n") + chapters = orchestrator.analyze(mock_chart, ReadingTheme.MARRIAGE) + for ch in chapters: + print(ch.narrative) + print(f"\n使用技法: {', '.join(ch.techniques_used)}") + print(f"行动建议: {ch.actionable}") + + # 生成完整报告 + print("\n" + "=" * 60) + print("完整报告预览") + print("=" * 60) + + report = orchestrator.generate_full_report(mock_chart) + print(f"\n分析主题: {report['themes_analyzed']}") + print(f"章节数: {len(report['chapters'])}") + print(f"\n待开发技法:") + for m in report['missing_techniques'][:10]: + print(f" - {m['name_cn']} ({m['name']})") diff --git a/scripts/spouse_status_yoga.py b/scripts/spouse_status_yoga.py new file mode 100644 index 00000000..4d7ca527 --- /dev/null +++ b/scripts/spouse_status_yoga.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +spouse_status_yoga.py — 高地位配偶与婚后成长Yoga检测引擎 +========================================================== +检测配偶社会地位高于命主、婚后命运积极转变的占星组合 + +来源: 文章7 + 传统BPHS/BVR技法 +版本: v1.0 | 2026-06-07 +""" + +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field + + +# 上升星座对应的7宫星座 +HOUSE7_SIGNS = { + "Aries": "Libra", "Taurus": "Scorpio", "Gemini": "Sagittarius", + "Cancer": "Capricorn", "Leo": "Aquarius", "Virgo": "Pisces", + "Libra": "Aries", "Scorpio": "Taurus", "Sagittarius": "Gemini", + "Capricorn": "Cancer", "Aquarius": "Leo", "Pisces": "Virgo", +} + +# 星座守护星 +SIGN_LORDS = { + "Aries": "Mars", "Taurus": "Venus", "Gemini": "Mercury", + "Cancer": "Moon", "Leo": "Sun", "Virgo": "Mercury", + "Libra": "Venus", "Scorpio": "Mars", "Sagittarius": "Jupiter", + "Capricorn": "Saturn", "Aquarius": "Saturn", "Pisces": "Jupiter", +} + +# Upachaya宫(成长宫)— 从任何宫位起算的3/6/10/11 +UPACHAYA_OFFSETS = [2, 5, 9, 10] # 从当前宫起算的偏移(3=+2, 6=+5, 10=+9, 11=+10) + + +@dataclass +class SpouseStatusIndicator: + """单个指标""" + indicator_type: str # 指标类型 + description: str # 描述 + present: bool # 是否存在 + strength: float # 强度 0-1 + evidence: List[str] = field(default_factory=list) + + +@dataclass +class SpouseStatusAnalysis: + """高地位配偶分析结果""" + # 四大核心原则 + principle1: SpouseStatusIndicator # 7宫强于Lagna + principle2: SpouseStatusIndicator # D9中7主星有Rajyoga + principle3: SpouseStatusIndicator # 7主星在Upachaya + principle4: SpouseStatusIndicator # 从7宫起算的Upachaya被占据 + + # 综合评估 + overall_score: float = 0.0 # 0-1 + verdict: str = "" # 结论 + spouse_status: str = "" # 配偶地位评估 + post_marriage_growth: str = "" # 婚后成长评估 + + # 详细发现 + all_indicators: List[SpouseStatusIndicator] = field(default_factory=list) + + # 案例参考 + case_study: str = "" + + # 叙事 + narrative: str = "" + + +class SpouseStatusYogaDetector: + """高地位配偶Yoga检测引擎""" + + def __init__(self): + self.sign_lords = SIGN_LORDS + self.house7_signs = HOUSE7_SIGNS + + def analyze(self, chart_data: Dict, d9_data: Optional[Dict] = None) -> SpouseStatusAnalysis: + """ + 分析高地位配偶Yoga + + Args: + chart_data: D1星盘数据 + d9_data: D9星盘数据(可选) + + Returns: + SpouseStatusAnalysis对象 + """ + planets = chart_data.get("planets", {}) + asc_sign = chart_data.get("ascendant", {}).get("sign", "") + + # Step 1: 原则1 — 7宫 vs Lagna力量比较 + p1 = self._check_principle1(planets, asc_sign) + + # Step 2: 原则2 — D9中7主星Rajyoga + p2 = self._check_principle2(d9_data, asc_sign) + + # Step 3: 原则3 — 7主星在Upachaya + p3 = self._check_principle3(planets, asc_sign) + + # Step 4: 原则4 — 从7宫起算的Upachaya被占据 + p4 = self._check_principle4(planets, asc_sign) + + # 创建分析对象 + analysis = SpouseStatusAnalysis( + principle1=p1, + principle2=p2, + principle3=p3, + principle4=p4, + ) + + analysis.all_indicators = [p1, p2, p3, p4] + + # Step 5: 综合评估 + self._comprehensive_assessment(analysis) + + # Step 6: 生成叙事 + analysis.narrative = self._generate_narrative(analysis, asc_sign) + + return analysis + + def _check_principle1(self, planets: Dict, asc_sign: str) -> SpouseStatusIndicator: + """ + 原则1: 7宫和7主星必须比Lagna和Lagna主星更强 + """ + indicator = SpouseStatusIndicator( + indicator_type="principle1", + description="7宫和7主星强于Lagna和Lagna主星", + present=False, + strength=0.0, + ) + + # 获取Lagna主星 + lagna_lord = self.sign_lords.get(asc_sign, "") + lagna_lord_data = planets.get(lagna_lord, {}) + + # 获取7宫星座和主星 + house7_sign = self.house7_signs.get(asc_sign, "") + house7_lord = self.sign_lords.get(house7_sign, "") + house7_lord_data = planets.get(house7_lord, {}) + + if not isinstance(lagna_lord_data, dict) or not isinstance(house7_lord_data, dict): + indicator.evidence.append("缺少行星数据") + return indicator + + # 比较力量(简化:比较庙旺状态) + lagna_dignity = lagna_lord_data.get("dignity", "") + house7_dignity = house7_lord_data.get("dignity", "") + + dignity_scores = { + "exalted": 4, "own": 3, "friendly": 2, + "neutral": 1, "enemy": 0, "debilitated": -1, + } + + lagna_score = dignity_scores.get(lagna_dignity, 0) + house7_score = dignity_scores.get(house7_dignity, 0) + + # 7主星位置 + house7_lord_house = house7_lord_data.get("house", 0) + + # 如果7主星在角宫或三方宫,增强力量 + if house7_lord_house in [1, 4, 7, 10, 5, 9]: + house7_score += 1 + + # 如果7主星受Yogakaraka相位 + # 简化:检查是否有吉星相位 + # ... + + indicator.present = house7_score > lagna_score + indicator.strength = min(max((house7_score - lagna_score + 2) / 6, 0), 1) + + if indicator.present: + indicator.evidence.append(f"7主星{house7_lord}力量({house7_score}) > Lagna主星{lagna_lord}力量({lagna_score})") + if house7_dignity in ["exalted", "own"]: + indicator.evidence.append(f"7主星处于{house7_dignity}状态") + else: + indicator.evidence.append(f"7主星力量({house7_score}) 未超过 Lagna主星({lagna_score})") + + return indicator + + def _check_principle2(self, d9_data: Optional[Dict], asc_sign: str) -> SpouseStatusIndicator: + """ + 原则2: D9中7主星存在Rajyoga + """ + indicator = SpouseStatusIndicator( + indicator_type="principle2", + description="Navamsa (D9) 中7主星存在Rajyoga", + present=False, + strength=0.0, + ) + + if not d9_data: + indicator.evidence.append("缺少D9数据") + return indicator + + d9_planets = d9_data.get("planets", {}) + d9_asc = d9_data.get("ascendant", {}).get("sign", asc_sign) + + # D9的7宫 + d9_house7_sign = self.house7_signs.get(d9_asc, "") + d9_house7_lord = self.sign_lords.get(d9_house7_sign, "") + d9_7lord_data = d9_planets.get(d9_house7_lord, {}) + + if not isinstance(d9_7lord_data, dict): + indicator.evidence.append("D9中7主星数据缺失") + return indicator + + # 简化Rajyoga检测:检查7主星是否在Kendra/Kona + d9_7lord_house = d9_7lord_data.get("house", 0) + + if d9_7lord_house in [1, 4, 7, 10, 5, 9]: + indicator.present = True + indicator.strength = 0.7 + indicator.evidence.append(f"D9中7主星{d9_house7_lord}位于{d9_7lord_house}宫(角宫/三方宫)") + + # 检查是否有吉星合相或相位 + # ... + + return indicator + + def _check_principle3(self, planets: Dict, asc_sign: str) -> SpouseStatusIndicator: + """ + 原则3: 7主星位于Upachaya宫(成长宫) + """ + indicator = SpouseStatusIndicator( + indicator_type="principle3", + description="7主星位于Upachaya宫(成长宫)", + present=False, + strength=0.0, + ) + + house7_sign = self.house7_signs.get(asc_sign, "") + house7_lord = self.sign_lords.get(house7_sign, "") + house7_lord_data = planets.get(house7_lord, {}) + + if not isinstance(house7_lord_data, dict): + return indicator + + lord_house = house7_lord_data.get("house", 0) + + # Upachaya宫: 3, 6, 10, 11 + if lord_house in [3, 6, 10, 11]: + indicator.present = True + indicator.strength = 0.8 + indicator.evidence.append(f"7主星{house7_lord}位于Upachaya宫第{lord_house}宫") + else: + indicator.evidence.append(f"7主星位于第{lord_house}宫,非Upachaya宫") + + return indicator + + def _check_principle4(self, planets: Dict, asc_sign: str) -> SpouseStatusIndicator: + """ + 原则4: 从7宫起算的Upachaya宫被行星占据 + """ + indicator = SpouseStatusIndicator( + indicator_type="principle4", + description="从7宫起算的Upachaya宫被行星占据", + present=False, + strength=0.0, + ) + + # 从7宫起算的Upachaya: 7+2=9, 7+5=12, 7+9=16->4, 7+10=17->5 + # 即: 从7宫起算的3宫=9宫, 6宫=12宫, 10宫=4宫, 11宫=5宫 + # 等等,Upachaya是3/6/10/11,从7宫起算: + # 3rd from 7 = 9th house + # 6th from 7 = 12th house + # 10th from 7 = 4th house + # 11th from 7 = 5th house + upachaya_from_7 = [9, 12, 4, 5] + + occupied_count = 0 + total_strength = 0 + + for house in upachaya_from_7: + # 找到在这个宫位的行星 + house_planets = [] + for pname, pdata in planets.items(): + if isinstance(pdata, dict) and pdata.get("house") == house: + house_planets.append(pname) + + if house_planets: + occupied_count += 1 + # 评估占据行星的力量 + for p in house_planets: + pdata = planets.get(p, {}) + if isinstance(pdata, dict): + dignity = pdata.get("dignity", "") + if dignity in ["exalted", "own"]: + total_strength += 0.3 + elif dignity in ["friendly"]: + total_strength += 0.2 + else: + total_strength += 0.1 + + indicator.evidence.append(f"第{house}宫被{p}占据 ({dignity})") + + if occupied_count > 0: + indicator.present = True + indicator.strength = min(total_strength + occupied_count * 0.15, 1.0) + + return indicator + + def _comprehensive_assessment(self, analysis: SpouseStatusAnalysis): + """综合评估""" + # 计算总分 + total_strength = sum(i.strength for i in analysis.all_indicators if i.present) + present_count = sum(1 for i in analysis.all_indicators if i.present) + + analysis.overall_score = min(total_strength / 2, 1.0) # 归一化 + + # 结论 + if present_count >= 3: + analysis.verdict = "极强的『嫁给高地位配偶』Yoga" + analysis.spouse_status = "配偶社会地位显著高于命主" + analysis.post_marriage_growth = "婚后命运将发生巨大跃迁,获得权力和地位" + elif present_count == 2: + analysis.verdict = "较强的婚后提升潜力" + analysis.spouse_status = "配偶有一定社会地位或增长潜力" + analysis.post_marriage_growth = "婚后有显著成长和进步" + elif present_count == 1: + analysis.verdict = "有婚后成长的迹象" + analysis.spouse_status = "配偶背景尚可" + analysis.post_marriage_growth = "婚后有一定改善" + else: + analysis.verdict = "无明显高地位配偶Yoga" + analysis.spouse_status = "配偶背景与命主相当" + analysis.post_marriage_growth = "婚后变化不显著" + + # 案例 + analysis.case_study = "索尼娅·甘地案例:巨蟹座上升,土星7主受Yogakaraka火星相位,形成Rajyoga。婚后从普通意大利女性跃升为印度最有权势的女性之一。" + + def _generate_narrative(self, analysis: SpouseStatusAnalysis, asc_sign: str) -> str: + """生成叙事""" + parts = [] + + parts.append("### 高地位配偶与婚后命运转变分析\n") + parts.append(f"基于四大核心原则的综合评估。\n") + + # 四大原则 + parts.append("\n#### 四大核心原则检测\n") + for i, ind in enumerate(analysis.all_indicators, 1): + status = "✅" if ind.present else "❌" + parts.append(f"{status} **原则{i}**: {ind.description}") + if ind.evidence: + parts.append(f" 证据: {'; '.join(ind.evidence[:2])}") + parts.append("") + + # 结论 + parts.append(f"\n#### 综合结论\n") + parts.append(f"**{analysis.verdict}**\n") + parts.append(f"\n配偶地位评估: {analysis.spouse_status}\n") + parts.append(f"婚后成长评估: {analysis.post_marriage_growth}\n") + parts.append(f"综合得分: {analysis.overall_score:.0%}\n") + + # 案例 + if analysis.case_study: + parts.append(f"\n#### 参考案例\n") + parts.append(f"{analysis.case_study}\n") + + # 注意事项 + parts.append(f"\n#### 重要提醒\n") + parts.append("- 第七宫强大仅代表配偶出生背景好,不代表对方个人能力出众\n") + parts.append("- 宫主星代表后天潜质和个人能力\n") + parts.append("- 对方家庭好不代表夫妻关系顺利或你能获得幸福\n") + parts.append("- 以上方法仅用于查看婚姻中物质繁荣的情况\n") + + return "\n".join(parts) + + def to_dict(self, analysis: SpouseStatusAnalysis) -> Dict: + """转换为字典""" + return { + "principles": [ + { + "type": i.indicator_type, + "description": i.description, + "present": i.present, + "strength": round(i.strength, 2), + "evidence": i.evidence, + } + for i in analysis.all_indicators + ], + "overall_score": round(analysis.overall_score, 2), + "verdict": analysis.verdict, + "spouse_status": analysis.spouse_status, + "post_marriage_growth": analysis.post_marriage_growth, + "case_study": analysis.case_study, + "narrative": analysis.narrative, + } + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def analyze_spouse_status(chart_data: Dict, d9_data: Optional[Dict] = None) -> Dict: + """便捷函数""" + detector = SpouseStatusYogaDetector() + analysis = detector.analyze(chart_data, d9_data) + return detector.to_dict(analysis) + + +# ============================================================================ +# CLI 调试 +# ============================================================================ + +if __name__ == "__main__": + # 索尼娅·甘地模拟数据(简化) + mock_chart = { + "ascendant": {"sign": "Cancer"}, + "planets": { + "Moon": {"sign": "Cancer", "house": 1, "dignity": "own"}, # Lagna主星 + "Saturn": {"sign": "Capricorn", "house": 7, "dignity": "own"}, # 7主星 + "Mars": {"sign": "Sagittarius", "house": 6, "dignity": "friendly"}, # Yogakaraka + }, + } + + mock_d9 = { + "ascendant": {"sign": "Aquarius"}, + "planets": { + "Sun": {"sign": "Aquarius", "house": 1, "dignity": "neutral"}, + "Saturn": {"sign": "Scorpio", "house": 10, "dignity": "neutral"}, + }, + } + + print("=" * 60) + print("高地位配偶与婚后成长Yoga检测") + print("=" * 60) + + result = analyze_spouse_status(mock_chart, mock_d9) + print(result["narrative"]) + print(f"\n综合得分: {result['overall_score']}") diff --git a/scripts/technique_registry.py b/scripts/technique_registry.py new file mode 100644 index 00000000..013dfc72 --- /dev/null +++ b/scripts/technique_registry.py @@ -0,0 +1,472 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +technique_registry.py — 印度占星技法注册中心 +================================================ +让所有技法模块可独立开发、独立注册、独立验证。 +支持并行推进:路线A(6模块) + 路线B(全覆盖) + 路线C(Yoga精度) + +版本: v1.0 | 2026-06-07 +""" + +from typing import Dict, List, Callable, Any, Optional +from dataclasses import dataclass, field +from enum import Enum +import inspect + + +class TechniqueLevel(Enum): + """技法层级""" + L1_COMPUTE = "L1" # 基础计算 + L2_STANDARD = "L2" # 标准技法 + L3_ADVANCED = "L3" # 高级技法 + L4_ESOTERIC = "L4" # 秘传技法 + + +class TechniqueCategory(Enum): + """技法分类""" + NATAL = "natal" # 本命分析 + DASHA = "dasha" # 推运系统 + TRANSIT = "transit" # 流年系统 + MUHURTA = "muhurta" # 择时系统 + PRASHNA = "prashna" # 问事系统 + SYNASTRY = "synastry" # 合盘系统 + TAJIKA = "tajika" # Tajika年运 + READING = "reading" # 报告解读 + + +class TechniqueStatus(Enum): + """技法状态""" + PLANNED = "planned" # 计划中 + WIP = "wip" # 开发中 + ALPHA = "alpha" # 内测 + BETA = "beta" # 公测 + STABLE = "stable" # 稳定 + DEPRECATED = "deprecated" # 废弃 + + +@dataclass +class TechniqueSpec: + """技法规格定义""" + # 基础信息 + name: str # 英文名称 + name_cn: str # 中文名称 + category: TechniqueCategory # 分类 + level: TechniqueLevel # 层级 + status: TechniqueStatus # 状态 + + # 依赖关系 + depends_on: List[str] = field(default_factory=list) # 依赖的其他技法名 + requires_varga: List[int] = field(default_factory=list) # 需要的分盘 + + # 接口定义 + input_schema: Dict[str, Any] = field(default_factory=dict) # 输入参数定义 + output_schema: Dict[str, Any] = field(default_factory=dict) # 输出结构定义 + + # 实现信息 + module_path: str = "" # 实现模块路径 + compute_func: Optional[str] = None # 计算函数名 + interpret_func: Optional[str] = None # 解读函数名 + + # 验证信息 + validation_cases: List[Dict] = field(default_factory=list) # 验证用例 + precision_target: Optional[float] = None # 精度目标(如F1) + + # 元数据 + description: str = "" # 描述 + references: List[str] = field(default_factory=list) # 参考文档 + author: str = "" # 作者 + version: str = "0.1.0" # 版本 + + +class TechniqueRegistry: + """技法注册中心 — 单例模式""" + + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._techniques: Dict[str, TechniqueSpec] = {} + cls._instance._compute_funcs: Dict[str, Callable] = {} + cls._instance._interpret_funcs: Dict[str, Callable] = {} + return cls._instance + + def register(self, spec: TechniqueSpec, + compute_func: Optional[Callable] = None, + interpret_func: Optional[Callable] = None) -> bool: + """ + 注册一个技法 + + Args: + spec: 技法规格 + compute_func: 计算函数 (chart_data -> result) + interpret_func: 解读函数 (result -> interpretation) + + Returns: + bool: 是否注册成功 + """ + name = spec.name + + # 检查依赖是否满足 + for dep in spec.depends_on: + if dep not in self._techniques: + print(f"[WARN] 技法 '{name}' 的依赖 '{dep}' 尚未注册") + + self._techniques[name] = spec + + if compute_func: + self._compute_funcs[name] = compute_func + if interpret_func: + self._interpret_funcs[name] = interpret_func + + print(f"[REG] 已注册: {name} ({spec.name_cn}) [{spec.status.value}]") + return True + + def get(self, name: str) -> Optional[TechniqueSpec]: + """获取技法规格""" + return self._techniques.get(name) + + def compute(self, name: str, chart_data: Dict, **kwargs) -> Optional[Dict]: + """ + 执行技法计算 + + Args: + name: 技法名称 + chart_data: 星盘数据 (标准格式) + **kwargs: 额外参数 + + Returns: + 计算结果,或None(未找到/未实现) + """ + spec = self._techniques.get(name) + if not spec: + print(f"[ERR] 技法 '{name}' 未注册") + return None + + func = self._compute_funcs.get(name) + if not func: + print(f"[ERR] 技法 '{name}' 的计算函数未注册") + return None + + try: + # 自动注入chart_data + sig = inspect.signature(func) + if 'chart_data' in sig.parameters: + return func(chart_data=chart_data, **kwargs) + else: + return func(**kwargs) + except Exception as e: + print(f"[ERR] 技法 '{name}' 计算失败: {e}") + return {"error": str(e), "technique": name} + + def interpret(self, name: str, result: Dict, **kwargs) -> Optional[str]: + """执行技法解读""" + func = self._interpret_funcs.get(name) + if not func: + return None + + try: + return func(result=result, **kwargs) + except Exception as e: + print(f"[ERR] 技法 '{name}' 解读失败: {e}") + return None + + def list_all(self, category: Optional[TechniqueCategory] = None, + level: Optional[TechniqueLevel] = None, + status: Optional[TechniqueStatus] = None) -> List[TechniqueSpec]: + """列出技法,支持过滤""" + results = [] + for spec in self._techniques.values(): + if category and spec.category != category: + continue + if level and spec.level != level: + continue + if status and spec.status != status: + continue + results.append(spec) + return sorted(results, key=lambda x: (x.category.value, x.level.value, x.name)) + + def get_coverage_stats(self) -> Dict: + """获取覆盖度统计""" + stats = { + "total": len(self._techniques), + "by_category": {}, + "by_level": {}, + "by_status": {}, + "coverage_pct": {} + } + + for spec in self._techniques.values(): + cat = spec.category.value + lvl = spec.level.value + st = spec.status.value + + stats["by_category"][cat] = stats["by_category"].get(cat, 0) + 1 + stats["by_level"][lvl] = stats["by_level"].get(lvl, 0) + 1 + stats["by_status"][st] = stats["by_status"].get(st, 0) + 1 + + # 计算各分类的stable覆盖率 + for cat in stats["by_category"]: + total = stats["by_category"][cat] + stable = sum(1 for s in self._techniques.values() + if s.category.value == cat and s.status == TechniqueStatus.STABLE) + stats["coverage_pct"][cat] = round(stable / total * 100, 1) if total > 0 else 0 + + return stats + + def get_reading_chain(self, theme: str) -> List[str]: + """ + 获取主题解读链 — 用于主题化报告 + + Args: + theme: 主题名称 (marriage/career/health/spiritual/wealth) + + Returns: + 按优先级排序的技法名称列表 + """ + chains = { + "marriage": [ + "darakaraka", # DK配偶解读 + "spouse_status_yoga", # 高地位配偶 + "navamsa_analysis", # D9分析 + "rtn_mapping", # RTN映射 + "vivah_saham", # 婚姻Saham + "tithi_lord", # Tithi主星 + "mangal_dosha", # 火星凶星 + "synastry", # 合盘 + "upapada_lagna", # 婚姻上升 + ], + "career": [ + "amatyakaraka", # 事业象征 + "dashamsa_analysis", # D10分析 + "d10_yoga", # D10 Yoga + "shadbala", # 力量评估 + "ashtakavarga", # 八点分 + "tajika_varshaphala", # 年运 + "dasha_career", # 事业推运 + ], + "health": [ + "curse_yoga", # 凶星合相 + "trimshamsa", # D30 + "shadbala", # 力量 + "avastha", # 行星状态 + "drekkena", # D3 + "dasha_health", # 健康推运 + ], + "wealth": [ + "dhana_yoga", # 财组合 + "hora_analysis", # D2 + "dhanakaraka", # 财富象征 + "vasihikamsa", # 特殊分 + "ashtakavarga", # 八点分 + ], + "spiritual": [ + "atmakaraka", # 灵魂象征 + "karakamsha", # AK星座 + "vimsamsa", # D20 + "chara_dasha", # Jaimini推运 + "nakshatra_deity", # 星宿神祇 + ], + } + return chains.get(theme, []) + + +# ============================================================================ +# 全局注册实例 +# ============================================================================ + +registry = TechniqueRegistry() + + +# ============================================================================ +# 便捷注册装饰器 +# ============================================================================ + +def technique(spec: TechniqueSpec): + """技法注册装饰器 + + 用法: + @technique(TechniqueSpec( + name="darakaraka", + name_cn="配偶象征星解读", + category=TechniqueCategory.NATAL, + level=TechniqueLevel.L3_ADVANCED, + status=TechniqueStatus.WIP, + description="Darakaraka深度解读", + )) + def compute_darakaraka(chart_data, **kwargs): + ... + """ + def decorator(func): + # 判断是计算函数还是解读函数 + if spec.compute_func is None: + spec.compute_func = func.__name__ + registry.register(spec, compute_func=func) + else: + registry.register(spec, interpret_func=func) + return func + return decorator + + +# ============================================================================ +# 现有技法批量注册(已有模块) +# ============================================================================ + +def _register_existing_techniques(): + """注册引擎中已实现的技法""" + + existing = [ + # L1 基础计算 + TechniqueSpec("chart", "本命星盘", TechniqueCategory.NATAL, + TechniqueLevel.L1_COMPUTE, TechniqueStatus.STABLE, + module_path="jyotish_engine.py", compute_func="cmd_chart"), + TechniqueSpec("varga", "分盘计算", TechniqueCategory.NATAL, + TechniqueLevel.L1_COMPUTE, TechniqueStatus.STABLE, + module_path="varga.py", compute_func="calc_all_vargas"), + TechniqueSpec("dasha", "Vimshottari推运", TechniqueCategory.DASHA, + TechniqueLevel.L1_COMPUTE, TechniqueStatus.STABLE, + module_path="dasha_calculator.py", compute_func="calculate_mahadasha"), + + # L2 标准技法 + TechniqueSpec("yoga", "Yoga组合检测", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.BETA, + module_path="yoga_engine.py", compute_func="cmd_yoga", + precision_target=0.95), + TechniqueSpec("shadbala", "六重力量", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="shadbala.py", compute_func="calc_shadbala"), + TechniqueSpec("ashtakavarga", "八点分", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="ashtakavarga.py", compute_func="calc_ashtakavarga"), + TechniqueSpec("aspects", "相位系统", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="aspects.py", compute_func="calc_all_aspects"), + TechniqueSpec("argala", "Argala阻碍", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="argala.py", compute_func="calc_argala"), + TechniqueSpec("karaka", "象征星计算", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="karaka_calculator.py", compute_func="calculate_karaka"), + TechniqueSpec("nakshatra", "星宿系统", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="nakshatra_advanced.py", compute_func="find_nakshatra"), + TechniqueSpec("avastha", "行星状态", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="avastha_calculator.py"), + TechniqueSpec("jaimini", "Jaimini系统", TechniqueCategory.NATAL, + TechniqueLevel.L2_STANDARD, TechniqueStatus.BETA, + module_path="jaimini.py", compute_func="calc_chara_karaka_7"), + TechniqueSpec("tajika", "Tajika年运", TechniqueCategory.TAJIKA, + TechniqueLevel.L2_STANDARD, TechniqueStatus.BETA, + module_path="tajika.py", compute_func="calc_varshaphala"), + TechniqueSpec("transit", "流年推运", TechniqueCategory.TRANSIT, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="transit.py", compute_func="computeTransit"), + TechniqueSpec("synastry", "合盘系统", TechniqueCategory.SYNASTRY, + TechniqueLevel.L2_STANDARD, TechniqueStatus.STABLE, + module_path="synastry.py", compute_func="calc_synastry"), + TechniqueSpec("prashna", "问事系统", TechniqueCategory.PRASHNA, + TechniqueLevel.L2_STANDARD, TechniqueStatus.BETA, + module_path="prashna.py", compute_func="cast_prashna"), + + # L3 高级技法(部分已有) + TechniqueSpec("vimsopaka", "20分力量", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.STABLE, + module_path="vimsopaka_calculator.py"), + TechniqueSpec("special_lagnas", "特殊上升", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.BETA, + module_path="special_lagnas.py"), + TechniqueSpec("event_prediction", "事件预测", TechniqueCategory.READING, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="event_prediction_model.py"), + ] + + for spec in existing: + registry.register(spec) + + # 注册新开发模块(v6.1.0 — 6大缺失模块) + new_modules = [ + TechniqueSpec("darakaraka", "配偶象征星深度解读", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="darakaraka_reader.py", compute_func="analyze_darakaraka", + depends_on=["karaka", "navamsa"]), + TechniqueSpec("rtn_mapping", "RTN映射", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="rashi_tulya_navamsa.py", compute_func="analyze_rtn", + depends_on=["varga"], requires_varga=[9]), + TechniqueSpec("curse_yoga", "凶星合相命名", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="curse_yoga_detector.py", compute_func="detect_curse_yogas", + depends_on=["yoga", "aspects"]), + TechniqueSpec("spouse_status_yoga", "高地位配偶Yoga", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="spouse_status_yoga.py", compute_func="analyze_spouse_status", + depends_on=["navamsa", "shadbala"]), + TechniqueSpec("pancha_pakshi", "五鸟择时", TechniqueCategory.MUHURTA, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="pancha_pakshi.py", compute_func="get_pancha_pakshi_schedule", + depends_on=["nakshatra"]), + TechniqueSpec("tithi_lord", "Tithi主星", TechniqueCategory.NATAL, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.ALPHA, + module_path="tithi_analyzer.py", compute_func="analyze_tithi", + depends_on=["chart"]), + + # 更多计划中的技法(路线B) + TechniqueSpec("chara_dasha", "Jaimini Chara推运", TechniqueCategory.DASHA, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.PLANNED, + depends_on=["jaimini"]), + TechniqueSpec("ashtottari_dasha", "Ashtottari推运", TechniqueCategory.DASHA, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.PLANNED), + TechniqueSpec("yogini_dasha", "Yogini推运", TechniqueCategory.DASHA, + TechniqueLevel.L3_ADVANCED, TechniqueStatus.PLANNED), + TechniqueSpec("kalachakra_dasha", "Kalachakra推运", TechniqueCategory.DASHA, + TechniqueLevel.L4_ESOTERIC, TechniqueStatus.PLANNED), + TechniqueSpec("narayana_dasha", "Narayana推运", TechniqueCategory.DASHA, + TechniqueLevel.L4_ESOTERIC, TechniqueStatus.PLANNED), + TechniqueSpec("moola_dasha", "Moola推运", TechniqueCategory.DASHA, + TechniqueLevel.L4_ESOTERIC, TechniqueStatus.PLANNED), + ] + + for spec in new_modules: + registry.register(spec) + + print(f"\n[REGISTRY] 总计注册技法: {len(registry._techniques)} 个") + print(f"[REGISTRY] 其中 STABLE: {sum(1 for s in registry._techniques.values() if s.status == TechniqueStatus.STABLE)} 个") + print(f"[REGISTRY] 其中 PLANNED: {sum(1 for s in registry._techniques.values() if s.status == TechniqueStatus.PLANNED)} 个") + + +# 启动时自动注册 +_register_existing_techniques() + + +# ============================================================================ +# CLI 调试 +# ============================================================================ + +if __name__ == "__main__": + print("=" * 60) + print("印度占星技法注册中心") + print("=" * 60) + + stats = registry.get_coverage_stats() + print(f"\n总技法数: {stats['total']}") + print(f"\n按分类:") + for cat, count in sorted(stats['by_category'].items()): + pct = stats['coverage_pct'].get(cat, 0) + print(f" {cat:12s}: {count:3d} 个 (stable覆盖率: {pct}%)") + + print(f"\n按层级:") + for lvl, count in sorted(stats['by_level'].items()): + print(f" {lvl}: {count:3d} 个") + + print(f"\n按状态:") + for st, count in sorted(stats['by_status'].items()): + print(f" {st:12s}: {count:3d} 个") + + print("\n" + "=" * 60) + print("婚姻主题解读链:") + for i, t in enumerate(registry.get_reading_chain("marriage"), 1): + spec = registry.get(t) + status = spec.status.value if spec else "?" + print(f" {i}. {t:25s} [{status}]") diff --git a/scripts/tithi_analyzer.py b/scripts/tithi_analyzer.py new file mode 100644 index 00000000..477bf01b --- /dev/null +++ b/scripts/tithi_analyzer.py @@ -0,0 +1,506 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +tithi_analyzer.py — Tithi主星分析引擎 +====================================== +分析Tithi(太阴日)及其主星对情感模式和关系的影响 + +版本: v1.0 | 2026-06-07 +""" + +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field +from enum import Enum + + +class TithiType(Enum): + """Tithi类型""" + NANDA = "Nanda" # 1, 6, 11, 16, 21, 26 — 幸福 + BHADRA = "Bhadra" # 2, 7, 12, 17, 22, 27 — 吉祥 + JAYA = "Jaya" # 3, 8, 13, 18, 23, 28 — 胜利 + RIKTA = "Rikta" # 4, 9, 14, 19, 24, 29 — 空虚 + PURNA = "Purna" # 5, 10, 15, 20, 25, 30 — 圆满 + + +# 30个Tithi及其主星 +TITHI_LORDS = { + # Shukla Paksha (亮月期) + 1: {"name": "Pratipada", "lord": "Sun", "type": TithiType.NANDA}, + 2: {"name": "Dwitiya", "lord": "Moon", "type": TithiType.BHADRA}, + 3: {"name": "Tritiya", "lord": "Mars", "type": TithiType.JAYA}, + 4: {"name": "Chaturthi", "lord": "Mercury", "type": TithiType.RIKTA}, + 5: {"name": "Panchami", "lord": "Jupiter", "type": TithiType.PURNA}, + 6: {"name": "Shashthi", "lord": "Venus", "type": TithiType.NANDA}, + 7: {"name": "Saptami", "lord": "Saturn", "type": TithiType.BHADRA}, + 8: {"name": "Ashtami", "lord": "Rahu", "type": TithiType.JAYA}, + 9: {"name": "Navami", "lord": "Sun", "type": TithiType.RIKTA}, + 10: {"name": "Dashami", "lord": "Moon", "type": TithiType.PURNA}, + 11: {"name": "Ekadashi", "lord": "Mars", "type": TithiType.NANDA}, + 12: {"name": "Dwadashi", "lord": "Mercury", "type": TithiType.BHADRA}, + 13: {"name": "Trayodashi", "lord": "Jupiter", "type": TithiType.JAYA}, + 14: {"name": "Chaturdashi", "lord": "Venus", "type": TithiType.RIKTA}, + 15: {"name": "Purnima", "lord": "Saturn/Rahu", "type": TithiType.PURNA}, + + # Krishna Paksha (暗月期) + 16: {"name": "Pratipada", "lord": "Sun", "type": TithiType.NANDA}, + 17: {"name": "Dwitiya", "lord": "Moon", "type": TithiType.BHADRA}, + 18: {"name": "Tritiya", "lord": "Mars", "type": TithiType.JAYA}, + 19: {"name": "Chaturthi", "lord": "Mercury", "type": TithiType.RIKTA}, + 20: {"name": "Panchami", "lord": "Jupiter", "type": TithiType.PURNA}, + 21: {"name": "Shashthi", "lord": "Venus", "type": TithiType.NANDA}, + 22: {"name": "Saptami", "lord": "Saturn", "type": TithiType.BHADRA}, + 23: {"name": "Ashtami", "lord": "Rahu", "type": TithiType.JAYA}, + 24: {"name": "Navami", "lord": "Sun", "type": TithiType.RIKTA}, + 25: {"name": "Dashami", "lord": "Moon", "type": TithiType.PURNA}, + 26: {"name": "Ekadashi", "lord": "Mars", "type": TithiType.NANDA}, + 27: {"name": "Dwadashi", "lord": "Mercury", "type": TithiType.BHADRA}, + 28: {"name": "Trayodashi", "lord": "Jupiter", "type": TithiType.JAYA}, + 29: {"name": "Chaturdashi", "lord": "Venus", "type": TithiType.RIKTA}, + 30: {"name": "Amavasya", "lord": "Saturn/Rahu", "type": TithiType.PURNA}, +} + +# 主星星座解读(按四元素) +LORD_SIGN_ELEMENT = { + "Sun": "Fire", "Moon": "Water", "Mars": "Fire", + "Mercury": "Earth", "Jupiter": "Ether", "Venus": "Water", + "Saturn": "Air", "Rahu": "Air", "Ketu": "Fire", +} + +ELEMENT_MEANINGS = { + "Fire": "热情、积极、直接、有领导力。情感表达热烈但可能冲动。", + "Water": "情感丰富、直觉敏锐、关怀体贴。情感深沉但可能依赖。", + "Earth": "务实、稳定、理性。情感表达谨慎但可靠。", + "Air": "理性、沟通、社交。情感需要智识连接。", + "Ether": "灵性、智慧、扩张。情感带有精神层面。", +} + +# 主星宫位解读(对情感/关系的影响) +LORD_HOUSE_MEANINGS = { + 1: "情感模式强烈影响自我认同,关系中需要被看见和认可。", + 2: "情感与家庭财富/价值观绑定,重视物质安全感。", + 3: "通过沟通和 siblings 表达情感,喜欢智力交流。", + 4: "情感根植于家庭和内心安全感,需要情感庇护。", + 5: "浪漫多情,通过创造力和子女表达爱,喜欢恋爱。", + 6: "情感表达带有服务性质,可能在关系中过度付出。", + 7: "关系是核心主题,伴侣影响情感模式深远。", + 8: "深层转化型情感,可能经历激烈的情绪变化。", + 9: "情感带有哲学/灵性维度,可能被远方的人吸引。", + 10: "情感与事业/社会地位相关,可能选择有成就的伴侣。", + 11: "通过社交网络表达情感,朋友可能发展为伴侣。", + 12: "隐秘的情感模式,可能在关系中牺牲或逃避。", +} + +# Tithi瑕疵(Tithi Dosh) +TITHI_DOSHA = { + # 主星在火象星座且受克 + "fire_debilitated": { + "condition": "主星在火象星座且被凶星相位", + "effect": "情感模式有缺陷,关系中容易冲突", + "remedy": "加强水元素平衡,冥想", + }, + # Rikta Tithi + "rikta": { + "condition": "出生在Rikta Tithi (4/9/14/19/24/29)", + "effect": "Tithi空虚,某些领域可能缺乏支持", + "remedy": " charity,捐赠,灵性练习", + }, + # 主星逆行 + "retrograde_lord": { + "condition": "Tithi主星逆行", + "effect": "情感表达有延迟或反复", + "remedy": "耐心,给予关系更多时间", + }, +} + + +@dataclass +class TithiAnalysis: + """Tithi分析结果""" + # 基础信息 + tithi_number: int + tithi_name: str + tithi_type: str + tithi_type_cn: str + paksha: str # shukla/krishna + + # 主星信息 + tithi_lord: str + lord_sign: str + lord_house: int + lord_element: str + lord_dignity: str + lord_retrograde: bool + + # 解读 + emotional_pattern: str + relationship_style: str + attracted_to: List[str] + challenges: List[str] + strengths: List[str] + + # 瑕疵 + doshas: List[Dict] = field(default_factory=list) + + # 综合 + tithi_score: float = 0.5 # 0-1 + narrative: str = "" + + +class TithiAnalyzer: + """Tithi主星分析引擎""" + + def __init__(self): + self.tithi_lords = TITHI_LORDS + self.element_meanings = ELEMENT_MEANINGS + self.house_meanings = LORD_HOUSE_MEANINGS + self.dosha_defs = TITHI_DOSHA + + def analyze(self, chart_data: Dict) -> TithiAnalysis: + """ + 分析Tithi + + Args: + chart_data: 标准星盘数据(需包含tithi信息或计算) + + Returns: + TithiAnalysis对象 + """ + # Step 1: 计算Tithi + tithi_num, paksha = self._calculate_tithi(chart_data) + + # Step 2: 获取Tithi信息 + tithi_info = self.tithi_lords.get(tithi_num, {}) + tithi_name = tithi_info.get("name", "") + tithi_type = tithi_info.get("type", TithiType.NANDA) + tithi_lord = tithi_info.get("lord", "Sun") + + # Step 3: 获取主星在星盘中的位置 + planets = chart_data.get("planets", {}) + lord_data = planets.get(tithi_lord, {}) if tithi_lord not in ["Saturn/Rahu"] else planets.get("Saturn", {}) + + if not isinstance(lord_data, dict): + lord_data = {} + + lord_sign = lord_data.get("sign", "") + lord_house = lord_data.get("house", 0) + lord_dignity = lord_data.get("dignity", "") + lord_retrograde = lord_data.get("retrograde", False) + + # Step 4: 创建分析对象 + analysis = TithiAnalysis( + tithi_number=tithi_num, + tithi_name=tithi_name, + tithi_type=tithi_type.value, + tithi_type_cn=self._tithi_type_to_chinese(tithi_type), + paksha=paksha, + tithi_lord=tithi_lord, + lord_sign=lord_sign, + lord_house=lord_house, + lord_element=LORD_SIGN_ELEMENT.get(tithi_lord, "Fire"), + lord_dignity=lord_dignity, + lord_retrograde=lord_retrograde, + ) + + # Step 5: 生成解读 + self._generate_interpretation(analysis) + + # Step 6: 检测瑕疵 + self._detect_dosha(analysis) + + # Step 7: 综合评估 + self._comprehensive_assessment(analysis) + + # Step 8: 生成叙事 + analysis.narrative = self._generate_narrative(analysis) + + return analysis + + def _calculate_tithi(self, chart_data: Dict) -> Tuple[int, str]: + """计算Tithi(简化版)""" + # 从chart_data中提取日月经度 + planets = chart_data.get("planets", {}) + + sun_data = planets.get("Sun", {}) + moon_data = planets.get("Moon", {}) + + if not isinstance(sun_data, dict) or not isinstance(moon_data, dict): + return 1, "shukla" + + sun_lon = sun_data.get("degree", 0) + moon_lon = moon_data.get("degree", 0) + + # Tithi = (Moon - Sun) / 12 + diff = (moon_lon - sun_lon) % 360 + tithi = int(diff / 12) + 1 + + # Paksha + paksha = "shukla" if diff < 180 else "krishna" + + # 调整Krishna Paksha的Tithi编号 + if paksha == "krishna": + tithi = tithi - 15 if tithi > 15 else tithi + 15 + + return min(tithi, 30), paksha + + def _tithi_type_to_chinese(self, tithi_type: TithiType) -> str: + """Tithi类型转中文""" + names = { + TithiType.NANDA: "幸福", + TithiType.BHADRA: "吉祥", + TithiType.JAYA: "胜利", + TithiType.RIKTA: "空虚", + TithiType.PURNA: "圆满", + } + return names.get(tithi_type, "未知") + + def _generate_interpretation(self, analysis: TithiAnalysis): + """生成解读""" + # 情感模式(基于主星) + lord_traits = { + "Sun": "热情、自信、需要被尊重。在关系中寻求平等的伙伴关系。", + "Moon": "情感丰富、 nurturing、需要安全感。在关系中非常体贴。", + "Mars": "直接、热情、有行动力。在关系中主动追求。", + "Mercury": "理性、沟通导向、喜欢智力交流。在关系中需要思想契合。", + "Jupiter": "慷慨、智慧、有道德感。在关系中提供指导和支持。", + "Venus": "浪漫、注重美感、追求和谐。在关系中非常重视爱情。", + "Saturn": "严肃、忠诚、需要时间建立信任。在关系中非常专一。", + "Rahu": "强烈、非传统、渴望新奇。在关系中追求独特体验。", + } + + analysis.emotional_pattern = lord_traits.get(analysis.tithi_lord, "独特的情感模式") + + # 关系风格(基于元素) + element_style = { + "Fire": "热情直接,喜欢主动追求,关系中需要激情和冒险。", + "Water": "情感深沉,重视连接和亲密,关系中需要情感安全感。", + "Earth": "务实稳定,重视承诺和责任,关系中需要物质基础。", + "Air": "理性沟通,重视思想和交流,关系中需要智力刺激。", + "Ether": "灵性导向,重视精神成长,关系中需要共同的精神追求。", + } + + analysis.relationship_style = element_style.get(analysis.lord_element, "平衡的关系风格") + + # 吸引力 + analysis.attracted_to = self._get_attracted_to(analysis.tithi_lord) + + # 挑战 + analysis.challenges = self._get_challenges(analysis.tithi_lord) + + # 优势 + analysis.strengths = self._get_strengths(analysis.tithi_lord) + + def _get_attracted_to(self, lord: str) -> List[str]: + """获取吸引力方向""" + mapping = { + "Sun": ["有领导力的人", "自信的人", "有创造力的人"], + "Moon": ["温暖体贴的人", "重视家庭的人", "情感丰富的人"], + "Mars": ["有活力的人", "独立自强的人", "敢于冒险的人"], + "Mercury": ["聪明有趣的人", "善于沟通的人", "有学识的人"], + "Jupiter": ["有智慧的人", "道德高尚的人", "慷慨的人"], + "Venus": ["有品位的人", "浪漫的人", "外表吸引的人"], + "Saturn": ["成熟稳重的人", "有责任感的人", "事业稳定的人"], + "Rahu": ["与众不同的人", "有野心的人", "异域文化背景的人"], + } + return mapping.get(lord, ["有独特魅力的人"]) + + def _get_challenges(self, lord: str) -> List[str]: + """获取挑战""" + mapping = { + "Sun": ["自尊心过强", "控制欲", "需要被认可"], + "Moon": ["情绪波动", "过度依赖", "安全感需求高"], + "Mars": ["脾气暴躁", "冲动", "好胜心强"], + "Mercury": ["过于理性", "善变", "分析过度"], + "Jupiter": ["过度理想化", "说教倾向", "过度乐观"], + "Venus": ["注重外表", "物质倾向", "优柔寡断"], + "Saturn": ["情感压抑", "冷淡疏离", "悲观"], + "Rahu": ["非传统关系", "不稳定", "欺骗风险"], + } + return mapping.get(lord, ["需要自我觉察"]) + + def _get_strengths(self, lord: str) -> List[str]: + """获取优势""" + mapping = { + "Sun": ["忠诚", "保护欲强", "有责任感"], + "Moon": ["无条件的爱", "善解人意", " nurturing"], + "Mars": ["积极主动", "勇敢", "激情"], + "Mercury": ["沟通顺畅", "幽默", "解决问题能力强"], + "Jupiter": ["智慧引导", "慷慨", "精神伴侣"], + "Venus": ["浪漫", "和谐", "审美力强"], + "Saturn": ["极度忠诚", "可靠", "长期承诺"], + "Rahu": ["独特体验", "突破局限", "激发潜能"], + } + return mapping.get(lord, ["独特的魅力"]) + + def _detect_dosha(self, analysis: TithiAnalysis): + """检测Tithi瑕疵""" + doshas = [] + + # Rikta Tithi + if analysis.tithi_type == "Rikta": + doshas.append({ + "type": "rikta", + "name": "Rikta Tithi Dosh", + "description": "出生在空虚Tithi,某些领域可能缺乏支持", + "remedy": "多做慈善和捐赠,加强灵性练习", + }) + + # 主星逆行 + if analysis.lord_retrograde: + doshas.append({ + "type": "retrograde_lord", + "name": "Retrograde Lord Dosh", + "description": "Tithi主星逆行,情感表达可能有延迟或反复", + "remedy": "给予关系更多时间和耐心", + }) + + # 主星落陷 + if analysis.lord_dignity == "debilitated": + doshas.append({ + "type": "debilitated_lord", + "name": "Debilitated Lord Dosh", + "description": "Tithi主星落陷,情感模式有根本缺陷", + "remedy": "通过灵性成长弥补,寻找互补的伴侣", + }) + + analysis.doshas = doshas + + def _comprehensive_assessment(self, analysis: TithiAnalysis): + """综合评估""" + score = 0.5 + + # 正面因素 + if analysis.tithi_type in ["Nanda", "Bhadra", "Jaya", "Purna"]: + score += 0.15 + if analysis.lord_dignity in ["exalted", "own", "friendly"]: + score += 0.15 + if not analysis.lord_retrograde: + score += 0.1 + + # 负面因素 + if analysis.tithi_type == "Rikta": + score -= 0.1 + if analysis.lord_dignity == "debilitated": + score -= 0.15 + if analysis.lord_retrograde: + score -= 0.05 + if analysis.doshas: + score -= 0.05 * len(analysis.doshas) + + analysis.tithi_score = max(0, min(1, score)) + + def _generate_narrative(self, analysis: TithiAnalysis) -> str: + """生成叙事""" + parts = [] + + parts.append("### Tithi主星分析 — 情感模式深度解读\n") + parts.append(f"您的出生Tithi: **{analysis.tithi_name}** (第{analysis.tithi_number}个Tithi)\n") + parts.append(f"Tithi类型: **{analysis.tithi_type_cn}** ({analysis.tithi_type})\n") + parts.append(f"月相: **{'亮月期 (Shukla Paksha)' if analysis.paksha == 'shukla' else '暗月期 (Krishna Paksha)'}**\n") + parts.append(f"Tithi主星: **{analysis.tithi_lord}**\n") + + # 主星位置 + if analysis.lord_sign: + parts.append(f"主星位置: {analysis.lord_sign} 第{analysis.lord_house}宫\n") + + parts.append(f"\n#### 情感模式\n") + parts.append(f"{analysis.emotional_pattern}\n") + + parts.append(f"\n#### 关系风格\n") + parts.append(f"{analysis.relationship_style}\n") + + # 吸引力 + if analysis.attracted_to: + parts.append(f"\n#### 您被什么样的人吸引\n") + for item in analysis.attracted_to: + parts.append(f"- {item}\n") + + # 挑战 + if analysis.challenges: + parts.append(f"\n#### 需要注意的议题\n") + for item in analysis.challenges: + parts.append(f"- {item}\n") + + # 优势 + if analysis.strengths: + parts.append(f"\n#### 您的情感优势\n") + for item in analysis.strengths: + parts.append(f"- {item}\n") + + # 宫位解读 + if analysis.lord_house > 0: + house_meaning = self.house_meanings.get(analysis.lord_house, "") + if house_meaning: + parts.append(f"\n#### 主星宫位解读\n") + parts.append(f"{house_meaning}\n") + + # 瑕疵 + if analysis.doshas: + parts.append(f"\n#### Tithi瑕疵检测\n") + for d in analysis.doshas: + parts.append(f"⚠️ **{d['name']}**: {d['description']}\n") + parts.append(f" 补救: {d['remedy']}\n") + + # 综合评估 + quality = "优秀" if analysis.tithi_score > 0.7 else \ + "良好" if analysis.tithi_score > 0.5 else \ + "需要努力" if analysis.tithi_score > 0.3 else "有挑战" + parts.append(f"\n#### 综合评估\n") + parts.append(f"Tithi质量: **{quality}** ({analysis.tithi_score:.0%})\n") + + return "\n".join(parts) + + def to_dict(self, analysis: TithiAnalysis) -> Dict: + """转换为字典""" + return { + "tithi_number": analysis.tithi_number, + "tithi_name": analysis.tithi_name, + "tithi_type": analysis.tithi_type, + "tithi_type_cn": analysis.tithi_type_cn, + "paksha": analysis.paksha, + "tithi_lord": analysis.tithi_lord, + "lord_sign": analysis.lord_sign, + "lord_house": analysis.lord_house, + "lord_element": analysis.lord_element, + "lord_dignity": analysis.lord_dignity, + "lord_retrograde": analysis.lord_retrograde, + "emotional_pattern": analysis.emotional_pattern, + "relationship_style": analysis.relationship_style, + "attracted_to": analysis.attracted_to, + "challenges": analysis.challenges, + "strengths": analysis.strengths, + "doshas": analysis.doshas, + "tithi_score": round(analysis.tithi_score, 2), + "narrative": analysis.narrative, + } + + +# ============================================================================ +# 便捷函数 +# ============================================================================ + +def analyze_tithi(chart_data: Dict) -> Dict: + """便捷函数""" + analyzer = TithiAnalyzer() + analysis = analyzer.analyze(chart_data) + return analyzer.to_dict(analysis) + + +# ============================================================================ +# CLI 调试 +# ============================================================================ + +if __name__ == "__main__": + mock_chart = { + "planets": { + "Sun": {"degree": 20.0}, + "Moon": {"degree": 65.0, "sign": "Taurus", "house": 2, "dignity": "own"}, + }, + } + + print("=" * 60) + print("Tithi主星分析引擎") + print("=" * 60) + + result = analyze_tithi(mock_chart) + print(result["narrative"]) + print(f"\nTithi得分: {result['tithi_score']}")