dae6d795bd
- scripts/orchestrator_bridge.py: bridge between reading_orchestrator and report_orchestrator - Converts ReadingChapter -> List[ReportTechniqueResult] - Theme mapping (ReadingTheme <-> ThemeName) - End-to-end generate_full_report() entry point - Unified narrative builder - Sentiment/strength inference from finding text - Demo runnable via __main__
326 lines
12 KiB
Python
326 lines
12 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
|
||
import json
|
||
|
||
# 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 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,
|
||
}
|
||
"""
|
||
if themes is None:
|
||
themes = [
|
||
ReadingTheme.MARRIAGE,
|
||
ReadingTheme.CAREER,
|
||
ReadingTheme.WEALTH,
|
||
ReadingTheme.HEALTH,
|
||
ReadingTheme.SPIRITUAL,
|
||
]
|
||
|
||
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,
|
||
}
|
||
|
||
# ── 内部辅助 ──
|
||
|
||
@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 _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():
|
||
"""演示桥接功能(使用模拟数据)。"""
|
||
from report_orchestrator import MockDataFactory
|
||
|
||
# 创建模拟星盘数据
|
||
chart_data = MockDataFactory.create_sample_chart()
|
||
|
||
# 创建 orchestrators
|
||
# NOTE: reading_orchestrator 需要 registry,这里用空 registry 演示
|
||
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()
|