将章节拼接成Document IR的简单装订器。 作用: - 按order排序章节,补充默认chapterId; - 防止anchor重复,生成全局唯一锚点; - 注入 IR 版本与生成时间戳。
| 13 | |
| 14 | |
| 15 | class DocumentComposer: |
| 16 | """ |
| 17 | 将章节拼接成Document IR的简单装订器。 |
| 18 | |
| 19 | 作用: |
| 20 | - 按order排序章节,补充默认chapterId; |
| 21 | - 防止anchor重复,生成全局唯一锚点; |
| 22 | - 注入 IR 版本与生成时间戳。 |
| 23 | """ |
| 24 | |
| 25 | def __init__(self): |
| 26 | """初始化装订器并记录已使用的锚点,避免重复""" |
| 27 | self._seen_anchors: Set[str] = set() |
| 28 | |
| 29 | def build_document( |
| 30 | self, |
| 31 | report_id: str, |
| 32 | metadata: Dict[str, object], |
| 33 | chapters: List[Dict[str, object]], |
| 34 | ) -> Dict[str, object]: |
| 35 | """ |
| 36 | 把所有章节按order排序并注入唯一锚点,形成整本IR。 |
| 37 | |
| 38 | 同时合并 metadata/themeTokens/assets,供渲染器直接消费。 |
| 39 | |
| 40 | 参数: |
| 41 | report_id: 本次报告ID。 |
| 42 | metadata: 全局元信息(标题、主题、toc等)。 |
| 43 | chapters: 章节payload列表。 |
| 44 | |
| 45 | 返回: |
| 46 | dict: 满足渲染器需求的Document IR。 |
| 47 | """ |
| 48 | # 构建从chapterId到toc anchor的映射 |
| 49 | toc_anchor_map = self._build_toc_anchor_map(metadata) |
| 50 | |
| 51 | ordered = sorted(chapters, key=lambda c: c.get("order", 0)) |
| 52 | for idx, chapter in enumerate(ordered, start=1): |
| 53 | chapter.setdefault("chapterId", f"S{idx}") |
| 54 | |
| 55 | # 优先级:1. 目录配置的anchor 2. 章节自带的anchor 3. 默认anchor |
| 56 | chapter_id = chapter.get("chapterId") |
| 57 | anchor = ( |
| 58 | toc_anchor_map.get(chapter_id) or |
| 59 | chapter.get("anchor") or |
| 60 | f"section-{idx}" |
| 61 | ) |
| 62 | chapter["anchor"] = self._ensure_unique_anchor(anchor) |
| 63 | chapter.setdefault("order", idx * 10) |
| 64 | if chapter.get("errorPlaceholder"): |
| 65 | self._ensure_heading_block(chapter) |
| 66 | |
| 67 | document = { |
| 68 | "version": IR_VERSION, |
| 69 | "reportId": report_id, |
| 70 | "metadata": { |
| 71 | **metadata, |
| 72 | "generatedAt": metadata.get("generatedAt") |
no outgoing calls