v6.1.4: Orchestrator bridge + report_orchestrator integration

- 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__
This commit is contained in:
732642856
2026-06-07 14:47:57 +08:00
parent 0406723b7e
commit dae6d795bd
+325
View File
@@ -0,0 +1,325 @@
#!/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 也作为一个 TechniqueResultsentiment=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()