1061 lines
47 KiB
Python
1061 lines
47 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
orchestrator_bridge.py — 编排器桥接层
|
||
=====================================
|
||
打通 reading_orchestrator (技法执行层) 与 report_orchestrator (叙事生成层)。
|
||
|
||
职责:
|
||
1. 将 ReadingChapter 转换为 report_orchestrator 可消费的 TechniqueResult
|
||
2. 映射主题(ReadingTheme ↔ ThemeName)
|
||
3. 统一冲突裁决的表达方式
|
||
4. 提供端到端的一体化报告生成入口
|
||
|
||
版本: v1.0 | 2026-06-07
|
||
"""
|
||
|
||
from typing import Dict, List, Any, Optional
|
||
from datetime import datetime
|
||
import json
|
||
|
||
# Import dasha systems (handle relative imports)
|
||
try:
|
||
from ashtottari_dasha import calculate_ashtottari_dasha
|
||
from yogini_dasha import calculate_yogini_dasha
|
||
from kalachakra_dasha import calculate_kalachakra_dasha
|
||
except ImportError:
|
||
from scripts.ashtottari_dasha import calculate_ashtottari_dasha
|
||
from scripts.yogini_dasha import calculate_yogini_dasha
|
||
from scripts.kalachakra_dasha import calculate_kalachakra_dasha
|
||
|
||
# Import both orchestrators (handle relative imports)
|
||
try:
|
||
from reading_orchestrator import (
|
||
ReadingOrchestrator, ReadingTheme, ReadingChapter,
|
||
TechniqueResult as ReadingTechniqueResult,
|
||
)
|
||
except ImportError:
|
||
from scripts.reading_orchestrator import (
|
||
ReadingOrchestrator, ReadingTheme, ReadingChapter,
|
||
TechniqueResult as ReadingTechniqueResult,
|
||
)
|
||
|
||
try:
|
||
from report_orchestrator import (
|
||
ThematicReportOrchestrator as ReportOrchestrator,
|
||
ThemeName, ThemeReport,
|
||
TechniqueResult as ReportTechniqueResult,
|
||
StrengthLevel, TimingAnchor,
|
||
)
|
||
except ImportError:
|
||
from scripts.report_orchestrator import (
|
||
ThematicReportOrchestrator as ReportOrchestrator,
|
||
ThemeName, ThemeReport,
|
||
TechniqueResult as ReportTechniqueResult,
|
||
StrengthLevel, TimingAnchor,
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 主题映射
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
THEME_MAPPING: Dict[ReadingTheme, ThemeName] = {
|
||
ReadingTheme.MARRIAGE: ThemeName.MARRIAGE,
|
||
ReadingTheme.CAREER: ThemeName.CAREER,
|
||
ReadingTheme.WEALTH: ThemeName.WEALTH,
|
||
ReadingTheme.HEALTH: ThemeName.HEALTH,
|
||
ReadingTheme.SPIRITUAL: ThemeName.SPIRITUALITY,
|
||
}
|
||
|
||
REVERSE_THEME_MAPPING: Dict[ThemeName, ReadingTheme] = {
|
||
v: k for k, v in THEME_MAPPING.items()
|
||
}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 桥接转换器
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
class OrchestratorBridge:
|
||
"""
|
||
编排器桥接器
|
||
|
||
将 reading_orchestrator 的输出(ReadingChapter)转换为
|
||
report_orchestrator 的输入(TechniqueResult + ThemeReport)。
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
reading_orchestrator: ReadingOrchestrator,
|
||
report_orchestrator: ReportOrchestrator,
|
||
):
|
||
self.ro = reading_orchestrator
|
||
self.rpo = report_orchestrator
|
||
|
||
# ── ReadingChapter → ReportTechniqueResult ──
|
||
|
||
@staticmethod
|
||
def chapter_to_technique_results(chapter: ReadingChapter) -> List[ReportTechniqueResult]:
|
||
"""
|
||
将 ReadingChapter 拆解为 report_orchestrator 的 TechniqueResult 列表。
|
||
|
||
策略:
|
||
- findings → 每个 finding 对应一个 TechniqueResult
|
||
- conflicts → 每个 conflict 也作为一个 TechniqueResult(sentiment=neutral,标注矛盾)
|
||
- timing → 如果时间锚定存在,也生成一个 TechniqueResult
|
||
"""
|
||
results: List[ReportTechniqueResult] = []
|
||
|
||
# 1. Findings → positive/negative TechniqueResult
|
||
for finding in chapter.findings:
|
||
sentiment = _infer_sentiment(finding)
|
||
strength = _infer_strength(finding, chapter.conflicts)
|
||
results.append(ReportTechniqueResult(
|
||
technique=f"finding_{len(results)}",
|
||
chart="D1", # default; can be refined
|
||
conclusion=finding,
|
||
sentiment=sentiment,
|
||
strength=strength,
|
||
))
|
||
|
||
# 2. Conflicts → neutral TechniqueResult with details
|
||
for conflict in chapter.conflicts:
|
||
results.append(ReportTechniqueResult(
|
||
technique="conflict_resolution",
|
||
chart="D1",
|
||
conclusion=conflict.narrative,
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={
|
||
"dimension": conflict.dimension,
|
||
"d1_finding": conflict.d1_finding,
|
||
"d9_finding": conflict.d9_finding,
|
||
"resolution": conflict.resolution,
|
||
},
|
||
))
|
||
|
||
# 3. Timing → TechniqueResult
|
||
if chapter.timing:
|
||
results.append(ReportTechniqueResult(
|
||
technique="timing_anchor",
|
||
chart="Dasha",
|
||
conclusion=chapter.timing,
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
))
|
||
|
||
return results
|
||
|
||
# ── 推运系统注入 ──
|
||
|
||
def inject_dasha_results(self, chart_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""
|
||
计算并注入 Ashtottari / Yogini / Kalachakra Dasha 结果。
|
||
|
||
从 chart_data 提取 birth_info,调用三个推运模块,
|
||
将当前推运周期转换为 TimingAnchor 注入 report_orchestrator。
|
||
|
||
Returns:
|
||
三个推运系统的原始计算结果,供后续使用。
|
||
"""
|
||
# 从 chart_data 提取 birth_info
|
||
birth_info = self._extract_birth_info(chart_data)
|
||
if not birth_info:
|
||
return {}
|
||
|
||
results: Dict[str, Any] = {}
|
||
|
||
# 1. Ashtottari Dasha (条件性推运)
|
||
try:
|
||
ash = calculate_ashtottari_dasha(birth_info)
|
||
results["ashtottari"] = ash
|
||
if ash.get("applicable") and ash.get("current"):
|
||
self._add_dasha_timing_anchor(
|
||
system="Ashtottari",
|
||
current=ash["current"],
|
||
total_cycle=ash.get("total_cycle", 108),
|
||
)
|
||
except Exception as e:
|
||
import logging
|
||
logging.warning(f"[orchestrator] ashtottari dasha calculation failed: {e}")
|
||
|
||
# 2. Yogini Dasha (普遍适用)
|
||
try:
|
||
yog = calculate_yogini_dasha(birth_info)
|
||
results["yogini"] = yog
|
||
if yog.get("current"):
|
||
self._add_dasha_timing_anchor(
|
||
system="Yogini",
|
||
current=yog["current"],
|
||
total_cycle=yog.get("total_cycle", 36),
|
||
)
|
||
except Exception as e:
|
||
import logging
|
||
logging.warning(f"[orchestrator] yogini dasha calculation failed: {e}")
|
||
|
||
# 3. Kalachakra Dasha (条件性推运)
|
||
try:
|
||
kal = calculate_kalachakra_dasha(birth_info)
|
||
results["kalachakra"] = kal
|
||
if kal.get("current"):
|
||
self._add_dasha_timing_anchor(
|
||
system="Kalachakra",
|
||
current=kal["current"],
|
||
total_cycle=kal.get("total_cycle", 0),
|
||
)
|
||
except Exception as e:
|
||
import logging
|
||
logging.warning(f"[orchestrator] kalachakra dasha calculation failed: {e}")
|
||
|
||
# 4. 将推运结果也作为 TechniqueResult 注入所有主题
|
||
self._inject_dasha_technique_results(results)
|
||
|
||
return results
|
||
|
||
def _extract_birth_info(self, chart_data: Any) -> Optional[Dict[str, Any]]:
|
||
"""从 chart_data 提取推运计算所需的 birth_info。
|
||
|
||
支持 dict 和 dataclass 对象(如 BirthChartData)。
|
||
如果数据不足,返回 None(推运注入将静默跳过)。
|
||
"""
|
||
import dataclasses
|
||
|
||
# 统一转为 dict
|
||
if hasattr(chart_data, "__dataclass_fields__"):
|
||
data = dataclasses.asdict(chart_data)
|
||
elif isinstance(chart_data, dict):
|
||
data = chart_data
|
||
else:
|
||
return None
|
||
|
||
info: Dict[str, Any] = {}
|
||
|
||
# birth_datetime
|
||
birth_dt = data.get("birth_datetime")
|
||
if birth_dt is None:
|
||
chart = data.get("chart") or data.get("natal_chart")
|
||
if chart:
|
||
birth_dt = chart.get("birth_datetime")
|
||
if isinstance(birth_dt, str):
|
||
birth_dt = datetime.fromisoformat(birth_dt.replace("Z", "+00:00"))
|
||
info["birth_datetime"] = birth_dt
|
||
|
||
# moon_nakshatra_index
|
||
moon_nak = data.get("moon_nakshatra_index")
|
||
if moon_nak is None:
|
||
chart = data.get("chart") or data.get("natal_chart")
|
||
if chart:
|
||
nakshatras = chart.get("nakshatras") or chart.get("nakshatra")
|
||
if nakshatras and "Moon" in nakshatras:
|
||
moon_nak = nakshatras["Moon"].get("index")
|
||
if moon_nak is not None:
|
||
info["moon_nakshatra_index"] = int(moon_nak)
|
||
|
||
# is_shukla_paksha (从Tithi推断)
|
||
tithi = data.get("tithi")
|
||
if tithi is None:
|
||
chart = data.get("chart") or data.get("natal_chart")
|
||
if chart:
|
||
tithi = chart.get("tithi")
|
||
if tithi is not None:
|
||
tithi_num = int(tithi) if isinstance(tithi, (int, float, str)) else 15
|
||
info["is_shukla_paksha"] = 1 <= tithi_num <= 15
|
||
|
||
# lagna_rashi_index
|
||
lagna = data.get("ascendant") or data.get("lagna")
|
||
if lagna is None:
|
||
chart = data.get("chart") or data.get("natal_chart")
|
||
if chart:
|
||
lagna = chart.get("ascendant") or chart.get("lagna")
|
||
if lagna is not None:
|
||
if isinstance(lagna, str):
|
||
sign_names = [
|
||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||
]
|
||
if lagna in sign_names:
|
||
info["lagna_rashi_index"] = sign_names.index(lagna)
|
||
elif isinstance(lagna, dict):
|
||
sign = lagna.get("sign") or lagna.get("rashi")
|
||
if sign:
|
||
sign_names = [
|
||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||
]
|
||
if sign in sign_names:
|
||
info["lagna_rashi_index"] = sign_names.index(sign)
|
||
degree = lagna.get("degree")
|
||
if degree is not None:
|
||
info["lagna_rashi_index"] = int(degree / 30) % 12
|
||
|
||
# moon_pada (for Kalachakra)
|
||
moon_pada = data.get("moon_pada")
|
||
if moon_pada is None:
|
||
chart = data.get("chart") or data.get("natal_chart")
|
||
if chart:
|
||
nakshatras = chart.get("nakshatras") or chart.get("nakshatra")
|
||
if nakshatras and "Moon" in nakshatras:
|
||
moon_pada = nakshatras["Moon"].get("pada")
|
||
if moon_pada is not None:
|
||
info["moon_pada"] = int(moon_pada)
|
||
else:
|
||
info["moon_pada"] = 1
|
||
|
||
return info if info.get("birth_datetime") else None
|
||
|
||
def _add_dasha_timing_anchor(
|
||
self,
|
||
system: str,
|
||
current: Dict[str, Any],
|
||
total_cycle: int,
|
||
) -> None:
|
||
"""将单个推运当前周期转换为 TimingAnchor 并注入 report_orchestrator。"""
|
||
start_date = current.get("start_date", "")
|
||
end_date = current.get("end_date", "")
|
||
|
||
# 提取年份
|
||
try:
|
||
start_year = int(start_date[:4]) if isinstance(start_date, str) else datetime.now().year
|
||
end_year = int(end_date[:4]) if isinstance(end_date, str) else (start_year + 5)
|
||
except (ValueError, TypeError):
|
||
start_year = datetime.now().year
|
||
end_year = start_year + 5
|
||
|
||
# 确定行星名称
|
||
planet = current.get("planet") or current.get("yogini") or current.get("lord") or "Unknown"
|
||
|
||
# 构建激活描述
|
||
activation = f"{system}推运中,{planet}主运带来人生阶段的转换与业力展现"
|
||
if system == "Yogini":
|
||
activation = f"Yogini推运中,{planet}女神主导当前周期,影响心理与事件层面"
|
||
elif system == "Kalachakra":
|
||
rashi = current.get("rashi", "")
|
||
mode = current.get("mode", "")
|
||
activation = f"Kalachakra {mode}模式推运中,{planet}主宰{rashi}宫阶段"
|
||
|
||
anchor = TimingAnchor(
|
||
dasha_period=f"{system}-{planet}",
|
||
start_year=start_year,
|
||
end_year=end_year,
|
||
activation_description=activation,
|
||
is_current=True,
|
||
)
|
||
|
||
# 注入到所有5个主题
|
||
for tn in ThemeName:
|
||
self.rpo.add_timing(tn, anchor)
|
||
|
||
def _inject_dasha_technique_results(self, dasha_results: Dict[str, Any]) -> None:
|
||
"""将推运结果作为 TechniqueResult 注入所有主题。"""
|
||
for system, result in dasha_results.items():
|
||
if not result:
|
||
continue
|
||
current = result.get("current")
|
||
if not current:
|
||
continue
|
||
|
||
planet = current.get("planet") or current.get("yogini") or current.get("lord") or "Unknown"
|
||
system_zh = {"ashtottari": "Ashtottari推运", "yogini": "Yogini推运", "kalachakra": "Kalachakra推运"}.get(system, system)
|
||
|
||
conclusion = f"{system_zh}:当前处于{planet}主运周期"
|
||
if system == "ashtottari" and result.get("applicable") is False:
|
||
conclusion = f"{system_zh}:不适用(出生条件不符合)"
|
||
|
||
tech_result = ReportTechniqueResult(
|
||
technique=f"{system}_dasha",
|
||
chart="Dasha",
|
||
conclusion=conclusion,
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={
|
||
"system": system,
|
||
"current_period": current,
|
||
"total_cycle": result.get("total_cycle"),
|
||
"applicable": result.get("applicable", True),
|
||
},
|
||
)
|
||
|
||
for tn in ThemeName:
|
||
self.rpo.add_technique(tn, tech_result)
|
||
|
||
# ── full-reading.modules → ReportTechniqueResult ──
|
||
|
||
def inject_full_reading_modules(self, chart_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""
|
||
将 `jyotish_engine.py full-reading` 的真实模块输出注入主题化报告。
|
||
|
||
v6.1.8 之前,桥接层主要依赖 reading_orchestrator 的 registry;当 registry
|
||
未注册真实执行函数时,主题报告容易退化为少量模拟/占位信息。本方法直接消费
|
||
full-reading.modules 中已经计算完成的模块,把 Yoga、分盘、Ashtakavarga、
|
||
Shadbala、特殊上升点、Vivah Saham、Transit、多系统 Dasha Convergence 等
|
||
转成 report_orchestrator 可叙事的 TechniqueResult。
|
||
"""
|
||
modules = _extract_modules(chart_data)
|
||
if not modules:
|
||
return {"injected": False, "reason": "no full-reading modules found", "counts": {}}
|
||
|
||
counts = {theme.value: 0 for theme in ThemeName}
|
||
|
||
def add(theme: ThemeName, result: ReportTechniqueResult) -> None:
|
||
self.rpo.add_technique(theme, result)
|
||
counts[theme.value] += 1
|
||
|
||
self._inject_yoga_module(modules, add)
|
||
self._inject_varga_modules(modules, add)
|
||
self._inject_strength_modules(modules, add)
|
||
self._inject_jaimini_relationship_modules(modules, add)
|
||
self._inject_special_points(modules, add)
|
||
self._inject_dasa_convergence_module(modules, add)
|
||
self._inject_transit_module(modules, add)
|
||
|
||
return {
|
||
"injected": True,
|
||
"source": "full-reading.modules",
|
||
"counts": counts,
|
||
"source_modules": sorted(k for k in modules.keys() if k in {
|
||
"yoga", "varga_full", "d9_navamsa_expanded", "ashtakavarga", "shadbala",
|
||
"jaimini", "rashi_tulya_navamsa", "special_lagnas", "vivah_saham",
|
||
"dasa_convergence", "transit_multi_reference",
|
||
"dasha", "yogini_dasha", "ashtottari_dasha", "kalachakra_dasha",
|
||
}),
|
||
}
|
||
|
||
def _inject_yoga_module(self, modules: Dict[str, Any], add) -> None:
|
||
yoga = modules.get("yoga") or {}
|
||
yogas = yoga.get("yogas") or yoga.get("detected_yogas") or []
|
||
if not isinstance(yogas, list):
|
||
return
|
||
|
||
added_per_theme = {theme: 0 for theme in ThemeName}
|
||
for y in yogas:
|
||
if not isinstance(y, dict):
|
||
continue
|
||
themes = _themes_for_yoga(y)
|
||
if not themes:
|
||
continue
|
||
strength = _strength_from_text(y.get("strength"), default=StrengthLevel.MODERATE)
|
||
sentiment = _sentiment_from_yoga(y)
|
||
effects = y.get("effects") or []
|
||
if isinstance(effects, list):
|
||
effect_text = "、".join(str(e) for e in effects[:4])
|
||
else:
|
||
effect_text = str(effects)
|
||
conclusion = f"{y.get('name_cn') or y.get('name') or 'Yoga'}:{y.get('combination', '检测到相关组合')}"
|
||
if effect_text:
|
||
conclusion += f";传统效应:{effect_text}"
|
||
|
||
for theme in themes:
|
||
if added_per_theme[theme] >= 8:
|
||
continue
|
||
add(theme, ReportTechniqueResult(
|
||
technique=f"yoga:{y.get('rule_id') or y.get('name') or 'unknown'}",
|
||
chart="D1",
|
||
conclusion=conclusion,
|
||
sentiment=sentiment,
|
||
strength=strength,
|
||
details={"source_module": "yoga", "raw": y},
|
||
))
|
||
added_per_theme[theme] += 1
|
||
|
||
def _inject_varga_modules(self, modules: Dict[str, Any], add) -> None:
|
||
d9 = modules.get("d9_navamsa_expanded") or {}
|
||
if isinstance(d9, dict) and d9:
|
||
asc = d9.get("Ascendant") or {}
|
||
venus = d9.get("Venus") or {}
|
||
jupiter = d9.get("Jupiter") or {}
|
||
parts = []
|
||
if asc:
|
||
parts.append(f"D9上升落{asc.get('sign_cn') or asc.get('sign')},主星{asc.get('lord', '未知')}")
|
||
if venus:
|
||
parts.append(f"Venus在D9落{venus.get('sign_cn') or venus.get('sign')},尊严={venus.get('dignity', '未知')}")
|
||
if jupiter:
|
||
parts.append(f"Jupiter在D9落{jupiter.get('sign_cn') or jupiter.get('sign')},尊严={jupiter.get('dignity', '未知')}")
|
||
add(ThemeName.MARRIAGE, ReportTechniqueResult(
|
||
technique="d9_navamsa_expanded",
|
||
chart="D9",
|
||
conclusion=";".join(parts[:3]) or "D9 Navamsa 已完成展开",
|
||
sentiment=_sentiment_from_dignities([venus.get("dignity"), jupiter.get("dignity")]),
|
||
strength=StrengthLevel.STRONG,
|
||
details={"source_module": "d9_navamsa_expanded", "ascendant": asc, "venus": venus, "jupiter": jupiter},
|
||
))
|
||
|
||
varga = modules.get("varga_full") or {}
|
||
if not isinstance(varga, dict):
|
||
return
|
||
mapping = [
|
||
("D2_Hora", ThemeName.WEALTH, "D2", "Hora财富分盘"),
|
||
("D10_Dasamsa", ThemeName.CAREER, "D10", "Dashamsha事业分盘"),
|
||
("D20_Vimsamsa", ThemeName.SPIRITUALITY, "D20", "Vimsamsa灵性分盘"),
|
||
("D30_Trimsamsa", ThemeName.HEALTH, "D30", "Trimsamsa健康/灾厄分盘"),
|
||
("D60_Shashtyamsa", ThemeName.SPIRITUALITY, "D60", "Shashtyamsha业力分盘"),
|
||
]
|
||
for key, theme, chart, label in mapping:
|
||
data = varga.get(key) or {}
|
||
if not isinstance(data, dict) or not data:
|
||
continue
|
||
asc = data.get("Ascendant") or {}
|
||
dignity = data.get("_dignity") or {}
|
||
strong = [p for p, d in dignity.items() if str(d).lower() in {"exalted", "own sign", "moolatrikona"}]
|
||
weak = [p for p, d in dignity.items() if "debil" in str(d).lower()]
|
||
conclusion = f"{label}:上升落{asc.get('sign', '未知')};强势行星{strong[:3] or '未突出'};需留意行星{weak[:3] or '无明显落陷'}"
|
||
add(theme, ReportTechniqueResult(
|
||
technique=f"varga_full:{key}",
|
||
chart=chart,
|
||
conclusion=conclusion,
|
||
sentiment="positive" if len(strong) > len(weak) else ("negative" if weak else "neutral"),
|
||
strength=StrengthLevel.STRONG,
|
||
details={"source_module": "varga_full", "varga": key, "ascendant": asc, "dignity": dignity},
|
||
))
|
||
|
||
def _inject_strength_modules(self, modules: Dict[str, Any], add) -> None:
|
||
shadbala = modules.get("shadbala") or {}
|
||
if isinstance(shadbala, dict) and shadbala:
|
||
strongest = shadbala.get("strongest") or []
|
||
weakest = shadbala.get("weakest") or []
|
||
conclusion = f"Shadbala相对力量:最强={strongest};最弱={weakest}。该模块当前用于相对强弱排序。"
|
||
result = ReportTechniqueResult(
|
||
technique="shadbala_relative_strength",
|
||
chart="D1",
|
||
conclusion=conclusion,
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "shadbala", "strongest": strongest, "weakest": weakest, "status": shadbala.get("status")},
|
||
)
|
||
for theme in [ThemeName.CAREER, ThemeName.HEALTH, ThemeName.SPIRITUALITY]:
|
||
add(theme, result)
|
||
|
||
av = modules.get("ashtakavarga") or {}
|
||
if isinstance(av, dict) and av:
|
||
house_scores = av.get("house_scores") or av.get("house_scores_full") or {}
|
||
wealth_scores = _pick_house_scores(house_scores, [2, 11])
|
||
health_scores = _pick_house_scores(house_scores, [6, 8, 12])
|
||
career_scores = _pick_house_scores(house_scores, [10])
|
||
for theme, houses, label in [
|
||
(ThemeName.WEALTH, wealth_scores, "2/11宫财富"),
|
||
(ThemeName.HEALTH, health_scores, "6/8/12宫健康压力"),
|
||
(ThemeName.CAREER, career_scores, "10宫事业"),
|
||
]:
|
||
if not houses:
|
||
continue
|
||
avg = sum(houses.values()) / len(houses)
|
||
add(theme, ReportTechniqueResult(
|
||
technique=f"ashtakavarga:{label}",
|
||
chart="AV",
|
||
conclusion=f"Ashtakavarga {label}分值:{houses},均值约{avg:.1f}",
|
||
sentiment="positive" if avg >= 28 else ("negative" if avg <= 24 else "neutral"),
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "ashtakavarga", "scores": houses},
|
||
))
|
||
|
||
def _inject_jaimini_relationship_modules(self, modules: Dict[str, Any], add) -> None:
|
||
"""Inject relationship evidence from Jaimini DK and RTN modules.
|
||
|
||
v6.1.10 connects two previously underused P0 relationship layers:
|
||
`modules.jaimini.darakaraka` and `modules.rashi_tulya_navamsa`.
|
||
Both are deterministic full-reading outputs; the bridge translates them into
|
||
marriage/health/spirituality evidence so thematic reports can narrate them.
|
||
"""
|
||
jaimini = modules.get("jaimini") or {}
|
||
darakaraka = jaimini.get("darakaraka") if isinstance(jaimini, dict) else None
|
||
if isinstance(darakaraka, dict) and darakaraka and not darakaraka.get("error"):
|
||
score = darakaraka.get("marriage_quality_score")
|
||
score_text = f",质量评分约{score:.0%}" if isinstance(score, (int, float)) else ""
|
||
conclusion = (
|
||
f"Darakaraka:{darakaraka.get('dk_planet', '未知')}落{darakaraka.get('dk_sign', '未知')}"
|
||
f"第{darakaraka.get('dk_house', '?')}宫,伴侣原型={darakaraka.get('core_profile', '未标注')}{score_text}。"
|
||
)
|
||
if darakaraka.get("d9_sign"):
|
||
conclusion += f" D9中落{darakaraka.get('d9_sign')},尊严={darakaraka.get('d9_dignity', '未知')}。"
|
||
add(ThemeName.MARRIAGE, ReportTechniqueResult(
|
||
technique="jaimini:darakaraka_deep_reader",
|
||
chart="D1/D9",
|
||
conclusion=conclusion,
|
||
sentiment="positive" if isinstance(score, (int, float)) and score >= 0.6 else ("negative" if isinstance(score, (int, float)) and score < 0.4 else "neutral"),
|
||
strength=StrengthLevel.STRONG,
|
||
details={"source_module": "jaimini.darakaraka", "raw": darakaraka},
|
||
))
|
||
|
||
rtn_module = modules.get("rashi_tulya_navamsa") or {}
|
||
rtn = rtn_module.get("analysis") if isinstance(rtn_module, dict) and isinstance(rtn_module.get("analysis"), dict) else rtn_module
|
||
if isinstance(rtn, dict) and rtn and not rtn.get("error"):
|
||
house_mapping = rtn.get("house_mapping") or {}
|
||
curse_yogas = rtn.get("curse_yogas") or []
|
||
strength_score = rtn.get("strength_score")
|
||
weakness_score = rtn.get("weakness_score")
|
||
marriage_planets = house_mapping.get(7) or house_mapping.get("7") or []
|
||
summary_parts = []
|
||
if marriage_planets:
|
||
summary_parts.append(f"RTN第7宫映射行星={marriage_planets}")
|
||
if curse_yogas:
|
||
summary_parts.append(f"检测到{len(curse_yogas)}个凶星合相命名")
|
||
if isinstance(strength_score, (int, float)) and isinstance(weakness_score, (int, float)):
|
||
summary_parts.append(f"隐藏力量={strength_score:.0%},弱点={weakness_score:.0%}")
|
||
conclusion = ";".join(summary_parts) or "Rashi Tulya Navamsa 已完成 D1/D9 映射分析。"
|
||
sentiment = "negative" if curse_yogas or (isinstance(weakness_score, (int, float)) and weakness_score > strength_score) else "positive"
|
||
add(ThemeName.MARRIAGE, ReportTechniqueResult(
|
||
technique="rashi_tulya_navamsa:relationship_mapping",
|
||
chart="D1/D9",
|
||
conclusion=conclusion,
|
||
sentiment=sentiment,
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "rashi_tulya_navamsa", "raw": rtn},
|
||
))
|
||
if curse_yogas:
|
||
add(ThemeName.HEALTH, ReportTechniqueResult(
|
||
technique="rashi_tulya_navamsa:curse_yogas",
|
||
chart="D1/D9",
|
||
conclusion=f"RTN凶星合相提示健康/危机压力:{[c.get('name') for c in curse_yogas[:3]]}",
|
||
sentiment="negative",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "rashi_tulya_navamsa", "curse_yogas": curse_yogas},
|
||
))
|
||
guna = rtn.get("guna_narrative")
|
||
if guna:
|
||
add(ThemeName.SPIRITUALITY, ReportTechniqueResult(
|
||
technique="rashi_tulya_navamsa:guna_balance",
|
||
chart="D1/D9",
|
||
conclusion=f"RTN Gunas:{guna}",
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "rashi_tulya_navamsa", "guna_balance": rtn.get("guna_balance")},
|
||
))
|
||
|
||
def _inject_special_points(self, modules: Dict[str, Any], add) -> None:
|
||
special = modules.get("special_lagnas") or {}
|
||
if isinstance(special, dict):
|
||
upapada = special.get("Upapada_Lagna") or {}
|
||
a10 = special.get("A10_Karma_Pada") or {}
|
||
arudha = special.get("Arudha_Lagna") or {}
|
||
if upapada:
|
||
add(ThemeName.MARRIAGE, ReportTechniqueResult(
|
||
technique="special_lagnas:upapada_lagna",
|
||
chart="D1",
|
||
conclusion=f"Upapada Lagna落{upapada.get('sign', '未知')}第{upapada.get('house', '?')}宫,提示婚姻的社会呈现与伴侣形象。",
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.STRONG,
|
||
details={"source_module": "special_lagnas", "upapada_lagna": upapada},
|
||
))
|
||
if a10:
|
||
add(ThemeName.CAREER, ReportTechniqueResult(
|
||
technique="special_lagnas:a10_karma_pada",
|
||
chart="D1",
|
||
conclusion=f"A10/Karma Pada落{a10.get('sign', '未知')}第{a10.get('house', '?')}宫,用于观察事业名声与业力方向。",
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.STRONG,
|
||
details={"source_module": "special_lagnas", "a10_karma_pada": a10},
|
||
))
|
||
if arudha:
|
||
add(ThemeName.CAREER, ReportTechniqueResult(
|
||
technique="special_lagnas:arudha_lagna",
|
||
chart="D1",
|
||
conclusion=f"Arudha Lagna落{arudha.get('sign', '未知')}第{arudha.get('house', '?')}宫,显示公众形象与外界评价。",
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "special_lagnas", "arudha_lagna": arudha},
|
||
))
|
||
|
||
vivah = modules.get("vivah_saham") or {}
|
||
if isinstance(vivah, dict) and vivah:
|
||
point = vivah.get("vivah_saham") or vivah
|
||
add(ThemeName.MARRIAGE, ReportTechniqueResult(
|
||
technique="vivah_saham",
|
||
chart="Tajika",
|
||
conclusion=f"Vivah Saham婚姻敏感点落{point.get('sign', vivah.get('saham_sign', '未知'))}第{point.get('house', vivah.get('saham_house', '?'))}宫;适合与Transit触发联合判断婚恋窗口。",
|
||
sentiment="neutral",
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "vivah_saham", "raw": vivah},
|
||
))
|
||
|
||
def _inject_dasa_convergence_module(self, modules: Dict[str, Any], add) -> None:
|
||
convergence = modules.get("dasa_convergence") or {}
|
||
if not isinstance(convergence, dict) or not convergence:
|
||
return
|
||
|
||
# 从 full-reading 的 Vimshottari current antar 注入真实时间锚,避免报告退回模拟 TimingAnchorBuilder。
|
||
systems_summary = convergence.get("systems_summary") or {}
|
||
vim = systems_summary.get("vimshottari") or {}
|
||
antar = vim.get("antar") or {}
|
||
if isinstance(antar, dict):
|
||
start_year = _year_from_date(antar.get("start"), datetime.now().year)
|
||
end_year = _year_from_date(antar.get("end"), start_year + 3)
|
||
maha = vim.get("maha") or "Unknown"
|
||
antar_lord = antar.get("lord") or "Unknown"
|
||
anchor = TimingAnchor(
|
||
dasha_period=f"Vimshottari-{maha}-{antar_lord}",
|
||
start_year=start_year,
|
||
end_year=end_year,
|
||
activation_description="full-reading 五系统 Dasa Convergence 已完成,当前 Vimshottari 主/副运作为主题报告时间锚。",
|
||
is_current=bool(antar.get("is_current", True)),
|
||
)
|
||
for tn in ThemeName:
|
||
self.rpo.add_timing(tn, anchor)
|
||
|
||
domain_map = {
|
||
"marriage_partnership": ThemeName.MARRIAGE,
|
||
"career_status": ThemeName.CAREER,
|
||
"wealth_family": ThemeName.WEALTH,
|
||
"health_service": ThemeName.HEALTH,
|
||
"fortune_dharma": ThemeName.SPIRITUALITY,
|
||
"transformation": ThemeName.SPIRITUALITY,
|
||
}
|
||
domains = convergence.get("domain_activations") or {}
|
||
for domain, data in domains.items():
|
||
theme = domain_map.get(domain)
|
||
if not theme or not isinstance(data, dict):
|
||
continue
|
||
activations = data.get("activations") or []
|
||
systems = sorted({a.get("system") for a in activations if isinstance(a, dict) and a.get("system")})
|
||
conclusion = f"Dasa Convergence:{domain} 获得{data.get('system_count', len(systems))}个系统激活({', '.join(systems) or '未列出'}),收敛等级{data.get('convergence_level', '未知')},概率{data.get('probability', '未标注')}。"
|
||
add(theme, ReportTechniqueResult(
|
||
technique=f"dasa_convergence:{domain}",
|
||
chart="Dasha",
|
||
conclusion=conclusion,
|
||
sentiment="positive" if data.get("system_count", 0) >= 2 else "neutral",
|
||
strength=StrengthLevel.STRONG if data.get("system_count", 0) >= 3 else StrengthLevel.MODERATE,
|
||
details={"source_module": "dasa_convergence", "domain": domain, "raw": data},
|
||
))
|
||
|
||
def _inject_transit_module(self, modules: Dict[str, Any], add) -> None:
|
||
transit = modules.get("transit_multi_reference") or {}
|
||
if not isinstance(transit, dict) or not transit:
|
||
return
|
||
target_date = transit.get("target_date")
|
||
analysis = transit.get("transit_analysis") or {}
|
||
for planet in ["Jupiter", "Saturn", "Rahu", "Ketu"]:
|
||
pdata = analysis.get(planet)
|
||
if not isinstance(pdata, dict):
|
||
continue
|
||
house_map = pdata.get("house_from_ref") or {}
|
||
lagna_house = (house_map.get("Lagna") or {}).get("house")
|
||
if not lagna_house:
|
||
continue
|
||
themes = _themes_for_transit_house(int(lagna_house))
|
||
for theme in themes:
|
||
add(theme, ReportTechniqueResult(
|
||
technique=f"transit_multi_reference:{planet}",
|
||
chart="Transit",
|
||
conclusion=f"{target_date or '当前'}真实过境:{planet}从Lagna参考点落第{lagna_house}宫,并需结合Chandra/Arudha/Navamsa多参考点校验。",
|
||
sentiment="positive" if planet == "Jupiter" and lagna_house in [2, 5, 7, 9, 10, 11] else ("negative" if planet in ["Saturn", "Rahu", "Ketu"] and lagna_house in [6, 8, 12] else "neutral"),
|
||
strength=StrengthLevel.MODERATE,
|
||
details={"source_module": "transit_multi_reference", "planet": planet, "raw": pdata},
|
||
))
|
||
|
||
# ── 端到端报告生成 ──
|
||
|
||
def generate_full_report(
|
||
self,
|
||
chart_data: Dict[str, Any],
|
||
themes: Optional[List[ReadingTheme]] = None,
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
端到端报告生成:从星盘数据 → ReadingChapter → ThemeReport。
|
||
|
||
Args:
|
||
chart_data: 星盘完整数据
|
||
themes: 要分析的主题列表,None=全部5大主题
|
||
|
||
Returns:
|
||
{
|
||
"reading_chapters": Dict[ReadingTheme, List[ReadingChapter]],
|
||
"theme_reports": Dict[ThemeName, ThemeReport],
|
||
"unified_narrative": str,
|
||
"dasha_results": Dict[str, Any],
|
||
}
|
||
"""
|
||
if themes is None:
|
||
themes = [
|
||
ReadingTheme.MARRIAGE,
|
||
ReadingTheme.CAREER,
|
||
ReadingTheme.WEALTH,
|
||
ReadingTheme.HEALTH,
|
||
ReadingTheme.SPIRITUAL,
|
||
]
|
||
|
||
# Step 0: 注入推运结果与 full-reading 真实模块(在所有主题分析之前)
|
||
dasha_results = self.inject_dasha_results(chart_data)
|
||
full_reading_injection = self.inject_full_reading_modules(chart_data)
|
||
|
||
reading_chapters: Dict[str, List[ReadingChapter]] = {}
|
||
theme_reports: Dict[str, Any] = {}
|
||
|
||
for rt in themes:
|
||
# Step 1: reading_orchestrator 执行技法
|
||
chapters = self.ro.analyze(chart_data, rt)
|
||
reading_chapters[rt.value] = chapters
|
||
|
||
# Step 2: 映射到 ThemeName
|
||
tn = THEME_MAPPING.get(rt)
|
||
if not tn:
|
||
continue
|
||
|
||
# Step 3: 将 chapters 转换为 TechniqueResults 并喂给 report_orchestrator
|
||
for ch in chapters:
|
||
tech_results = self.chapter_to_technique_results(ch)
|
||
for tr in tech_results:
|
||
self.rpo.add_technique(tn, tr)
|
||
|
||
# Step 4: 生成 ThemeReport
|
||
report = self.rpo.generate_report(tn)
|
||
theme_reports[tn.value] = report.to_dict()
|
||
|
||
# Step 5: 生成统一叙事
|
||
unified = self._build_unified_narrative(theme_reports)
|
||
|
||
return {
|
||
"reading_chapters": {
|
||
k: [self._chapter_to_dict(ch) for ch in v]
|
||
for k, v in reading_chapters.items()
|
||
},
|
||
"theme_reports": theme_reports,
|
||
"unified_narrative": unified,
|
||
"dasha_results": dasha_results,
|
||
"full_reading_injection": full_reading_injection,
|
||
}
|
||
|
||
# ── 内部辅助 ──
|
||
|
||
@staticmethod
|
||
def _build_unified_narrative(theme_reports: Dict[str, Any]) -> str:
|
||
"""将多个 ThemeReport 拼接为统一的人生叙事。"""
|
||
parts = []
|
||
order = ["marriage", "career", "wealth", "health", "spirituality"]
|
||
for theme_key in order:
|
||
if theme_key in theme_reports:
|
||
r = theme_reports[theme_key]
|
||
parts.append(f"\n## {r.get('summary', theme_key)}")
|
||
parts.append(r.get("narrative", ""))
|
||
if r.get("recommendations"):
|
||
parts.append("\n**建议:**")
|
||
for rec in r["recommendations"]:
|
||
parts.append(f"- {rec}")
|
||
return "\n".join(parts)
|
||
|
||
@staticmethod
|
||
def _chapter_to_dict(ch: ReadingChapter) -> Dict[str, Any]:
|
||
return {
|
||
"title": ch.title,
|
||
"subtitle": ch.subtitle,
|
||
"priority": ch.priority,
|
||
"techniques_used": ch.techniques_used,
|
||
"findings": ch.findings,
|
||
"conflicts": [
|
||
{
|
||
"dimension": c.dimension,
|
||
"d1_finding": c.d1_finding,
|
||
"d9_finding": c.d9_finding,
|
||
"resolution": c.resolution,
|
||
"narrative": c.narrative,
|
||
}
|
||
for c in ch.conflicts
|
||
],
|
||
"narrative": ch.narrative,
|
||
"actionable": ch.actionable,
|
||
"timing": ch.timing,
|
||
}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 辅助函数
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
def _extract_modules(chart_data: Any) -> Dict[str, Any]:
|
||
"""Return full-reading modules from either a full report dict or a modules dict."""
|
||
if not isinstance(chart_data, dict):
|
||
return {}
|
||
modules = chart_data.get("modules")
|
||
if isinstance(modules, dict):
|
||
return modules
|
||
# Allow passing modules directly in tests.
|
||
known = {
|
||
"yoga", "varga_full", "d9_navamsa_expanded", "ashtakavarga", "shadbala",
|
||
"jaimini", "rashi_tulya_navamsa", "special_lagnas", "vivah_saham",
|
||
"dasa_convergence", "transit_multi_reference",
|
||
"dasha", "yogini_dasha", "ashtottari_dasha", "kalachakra_dasha",
|
||
}
|
||
if any(k in chart_data for k in known):
|
||
return chart_data
|
||
return {}
|
||
|
||
|
||
def _strength_from_text(value: Any, default: StrengthLevel = StrengthLevel.MODERATE) -> StrengthLevel:
|
||
text = str(value or "").lower()
|
||
if any(x in text for x in ["强", "strong", "high", "极"]):
|
||
return StrengthLevel.STRONG
|
||
if any(x in text for x in ["弱", "weak", "low"]):
|
||
return StrengthLevel.WEAK
|
||
return default
|
||
|
||
|
||
def _sentiment_from_yoga(yoga: Dict[str, Any]) -> str:
|
||
category = str(yoga.get("category") or yoga.get("name") or yoga.get("name_cn") or "").lower()
|
||
negative_tokens = ["darid", "dharidhra", "kapata", "arista", "dosha", "curse", "poverty", "roga", "mrityu", "凶", "贫", "病", "灾", "欺"]
|
||
positive_tokens = ["raja", "dhana", "lakshmi", "gaja", "kesari", "amala", "yoga", "财富", "王", "吉", "成就"]
|
||
if any(t in category for t in negative_tokens):
|
||
return "negative"
|
||
if any(t in category for t in positive_tokens):
|
||
return "positive"
|
||
return "neutral"
|
||
|
||
|
||
def _themes_for_yoga(yoga: Dict[str, Any]) -> List[ThemeName]:
|
||
text = " ".join(str(yoga.get(k, "")) for k in ["name", "name_cn", "category", "rule_id", "combination"])
|
||
effects = yoga.get("effects") or []
|
||
if isinstance(effects, list):
|
||
text += " " + " ".join(str(e) for e in effects)
|
||
lower = text.lower()
|
||
themes: List[ThemeName] = []
|
||
keyword_map = [
|
||
(ThemeName.WEALTH, ["dhana", "lakshmi", "wealth", "money", "income", "财富", "财", "收入", "资源"]),
|
||
(ThemeName.CAREER, ["raja", "career", "profession", "status", "authority", "power", "事业", "权力", "地位", "名声", "成就"]),
|
||
(ThemeName.MARRIAGE, ["marriage", "spouse", "venus", "vivah", "partner", "婚", "配偶", "伴侣", "金星"]),
|
||
(ThemeName.HEALTH, ["arista", "roga", "disease", "health", "illness", "saturn-mars", "病", "健康", "灾", "凶"]),
|
||
(ThemeName.SPIRITUALITY, ["moksha", "jupiter", "ketu", "spiritual", "dharma", "智慧", "灵性", "木星", "解脱"]),
|
||
]
|
||
for theme, keywords in keyword_map:
|
||
if any(k in lower or k in text for k in keywords):
|
||
themes.append(theme)
|
||
if not themes and yoga.get("category") in ["raja", "bvr"]:
|
||
themes.append(ThemeName.CAREER)
|
||
return themes[:3]
|
||
|
||
|
||
def _sentiment_from_dignities(values: List[Any]) -> str:
|
||
text = " ".join(str(v or "").lower() for v in values)
|
||
if any(k in text for k in ["exalted", "own", "moola", "friend"]):
|
||
return "positive"
|
||
if any(k in text for k in ["debil", "enemy"]):
|
||
return "negative"
|
||
return "neutral"
|
||
|
||
|
||
def _pick_house_scores(house_scores: Any, houses: List[int]) -> Dict[int, float]:
|
||
if not isinstance(house_scores, dict):
|
||
return {}
|
||
picked: Dict[int, float] = {}
|
||
for house in houses:
|
||
value = None
|
||
for key in [house, str(house), f"house_{house}", f"H{house}"]:
|
||
if key in house_scores:
|
||
value = house_scores[key]
|
||
break
|
||
if isinstance(value, dict):
|
||
value = value.get("score") or value.get("bindus") or value.get("sav")
|
||
if isinstance(value, (int, float)):
|
||
picked[house] = float(value)
|
||
return picked
|
||
|
||
|
||
def _year_from_date(value: Any, default: int) -> int:
|
||
if isinstance(value, int):
|
||
return value
|
||
if isinstance(value, str) and len(value) >= 4:
|
||
try:
|
||
return int(value[:4])
|
||
except ValueError:
|
||
return default
|
||
return default
|
||
|
||
|
||
def _themes_for_transit_house(house: int) -> List[ThemeName]:
|
||
mapping = {
|
||
1: [ThemeName.HEALTH],
|
||
2: [ThemeName.WEALTH],
|
||
5: [ThemeName.WEALTH, ThemeName.SPIRITUALITY],
|
||
6: [ThemeName.HEALTH, ThemeName.CAREER],
|
||
7: [ThemeName.MARRIAGE],
|
||
8: [ThemeName.HEALTH, ThemeName.SPIRITUALITY],
|
||
9: [ThemeName.SPIRITUALITY, ThemeName.CAREER],
|
||
10: [ThemeName.CAREER],
|
||
11: [ThemeName.WEALTH, ThemeName.CAREER],
|
||
12: [ThemeName.HEALTH, ThemeName.SPIRITUALITY],
|
||
}
|
||
return mapping.get(house, [])
|
||
|
||
|
||
def _infer_sentiment(text: str) -> str:
|
||
"""从 finding 文本推断 sentiment。"""
|
||
negative_keywords = [
|
||
"阻碍", "困难", "挑战", "风险", "不利", "凶", "克", "弱", "陷",
|
||
"冲突", "矛盾", "破坏", "损失", "障碍", "延迟", "问题",
|
||
]
|
||
positive_keywords = [
|
||
"有利", "吉", "旺", "强", "助力", "支持", "机遇", "突破",
|
||
"成就", "成功", "和谐", "稳定", "增益", "提升",
|
||
]
|
||
text_lower = text.lower()
|
||
neg_score = sum(1 for w in negative_keywords if w in text_lower)
|
||
pos_score = sum(1 for w in positive_keywords if w in text_lower)
|
||
if neg_score > pos_score:
|
||
return "negative"
|
||
if pos_score > neg_score:
|
||
return "positive"
|
||
return "neutral"
|
||
|
||
|
||
def _infer_strength(text: str, conflicts: List[Any]) -> StrengthLevel:
|
||
"""从 finding 文本和冲突情况推断 strength。"""
|
||
strong_keywords = ["非常", "极强", "显著", "明确", "主导", "绝对"]
|
||
weak_keywords = ["轻微", "略有", "潜在", "可能", "模糊", "微弱"]
|
||
text_lower = text.lower()
|
||
if any(w in text_lower for w in strong_keywords):
|
||
return StrengthLevel.STRONG
|
||
if any(w in text_lower for w in weak_keywords):
|
||
return StrengthLevel.WEAK
|
||
if conflicts:
|
||
return StrengthLevel.MODERATE
|
||
return StrengthLevel.MODERATE
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# CLI / 测试
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
def demo():
|
||
"""演示桥接功能。
|
||
|
||
默认使用 MockDataFactory;如果传入一个 full-reading JSON 路径,则直接消费
|
||
`modules` 真实结果,验证 v6.1.8 的真实模块接线。
|
||
"""
|
||
import sys
|
||
from report_orchestrator import MockDataFactory
|
||
|
||
# 创建星盘数据:优先使用 full-reading JSON,否则使用模拟数据
|
||
if len(sys.argv) > 1:
|
||
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||
chart_data = json.load(f)
|
||
else:
|
||
chart_data = MockDataFactory.create_sample_chart()
|
||
|
||
# 创建 orchestrators
|
||
# NOTE: reading_orchestrator 需要 registry,这里用空 registry 演示;真实证据由 full-reading 注入层提供
|
||
class DummyRegistry:
|
||
def get(self, name):
|
||
return None
|
||
|
||
ro = ReadingOrchestrator(DummyRegistry())
|
||
rpo = ReportOrchestrator(chart_data)
|
||
|
||
# 桥接
|
||
bridge = OrchestratorBridge(ro, rpo)
|
||
result = bridge.generate_full_report(chart_data)
|
||
|
||
print("=" * 60)
|
||
print("Orchestrator Bridge Demo")
|
||
print("=" * 60)
|
||
print(f"\n生成主题报告数: {len(result['theme_reports'])}")
|
||
for theme, report in result["theme_reports"].items():
|
||
print(f"\n [{theme}]")
|
||
print(f" 总结: {report.get('summary', 'N/A')}")
|
||
print(f" 强度: {report.get('strength', 'N/A')}")
|
||
print(f" 证据数: {len(report.get('evidence', []))}")
|
||
|
||
print("\n" + "=" * 60)
|
||
print("统一叙事(前500字):")
|
||
print("=" * 60)
|
||
print(result["unified_narrative"][:500] + "...")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
demo()
|