Files
2026Technology-Competition/tests/test_phase5_template_mapper.py
lhl 0a498e4f7a fix(writer): 无锚点子章归并父章生成 + 注入后裸重复子节去重
- template_mapper: 按 design §6.5 仅 Heading level<=1 起章,H2/H3 归入
  父章 sub_headings(此前每个 heading 独立成章,无 {{section:id}} 锚点的
  6 个子章生成后被静默丢弃)
- models: ChapterSpec.sub_headings 字段;to_vars 暴露 sub_headings 变量
- writer_agent: prompt 新增【小节约束】(有子节时按小节顺序以 level=2
  heading 组织,不得遗漏或新增)
- docx_injector: 注入后删除同章内与已生成标题同名且完全无内容的模板裸
  H2/H3(保守策略:带内容的模板子节保留)
- 真实试运行验证:7 章 / 无静默丢弃告警 / 14 个 H2 无重复
2026-08-24 12:23:35 +08:00

88 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from genesis.data_models import ChapterMarker, ParsedTemplate
from genesis.writer.models import ChapterSpec
from genesis.writer.template_mapper import map_template
def _parsed():
sections = [
ChapterMarker(type="heading", name="はじめに", level=1),
ChapterMarker(type="placeholder", name="section:introduction", level=0),
ChapterMarker(type="heading", name="DB 設計", level=1),
ChapterMarker(type="placeholder", name="section:db_design", level=0),
ChapterMarker(type="bookmark", name="bm1", level=0),
]
return ParsedTemplate(file_name="t.docx", sections=sections, placeholders={"section:introduction": "x"}, styles={})
def test_map_template_ordered_and_assigns_ids():
specs = map_template(_parsed())
assert [s.title for s in specs] == ["はじめに", "DB 設計"]
assert specs[0].chapter_id == "introduction"
assert specs[0].section_placeholder == "section:introduction"
assert specs[1].chapter_id == "db_design"
assert specs[1].section_placeholder == "section:db_design"
assert all(isinstance(s, ChapterSpec) for s in specs)
def test_map_template_falls_back_without_placeholder():
sections = [ChapterMarker(type="heading", name="附録", level=1)]
parsed = ParsedTemplate(file_name="t.docx", sections=sections, placeholders={}, styles={})
specs = map_template(parsed)
assert specs[0].chapter_id == "chapter_1"
assert specs[0].section_placeholder is None
def test_map_template_section_re_case_insensitive():
# 宽容:占位符键名大小写不敏感(解析端已归一,防御性双保险)
sections = [
ChapterMarker(type="heading", name="画面一覧", level=1),
ChapterMarker(type="placeholder", name="Section:2", level=0),
]
parsed = ParsedTemplate(file_name="t.docx", sections=sections, placeholders={}, styles={})
specs = map_template(parsed)
assert specs[0].chapter_id == "2"
assert specs[0].section_placeholder == "Section:2"
# ---------- design.md §6.5H1 起章,H2/H3 归入本章作为子节(不再独立成章被丢弃) ----------
def test_h2_headings_merge_into_parent_chapter():
sections = [
ChapterMarker(type="heading", name="2. 機能一覧", level=1),
ChapterMarker(type="placeholder", name="section:function_list", level=0),
ChapterMarker(type="heading", name="2.1 機能一覧表", level=2),
ChapterMarker(type="heading", name="2.2 機能詳細", level=2),
]
parsed = ParsedTemplate(file_name="t.docx", sections=sections, placeholders={}, styles={})
specs = map_template(parsed)
assert len(specs) == 1
s = specs[0]
assert s.chapter_id == "function_list"
assert s.title == "2. 機能一覧"
assert s.sub_headings == ["2.1 機能一覧表", "2.2 機能詳細"]
def test_h3_headings_also_merge():
sections = [
ChapterMarker(type="heading", name="5. DB設計", level=1),
ChapterMarker(type="placeholder", name="section:db_design", level=0),
ChapterMarker(type="heading", name="5.1 テーブル一覧", level=2),
ChapterMarker(type="heading", name="5.1.1 補足", level=3),
]
parsed = ParsedTemplate(file_name="t.docx", sections=sections, placeholders={}, styles={})
specs = map_template(parsed)
assert len(specs) == 1
assert specs[0].sub_headings == ["5.1 テーブル一覧", "5.1.1 補足"]
def test_leading_h2_without_parent_starts_chapter():
"""异常模板防御:任何 H1 之前出现 H2 → 回落为独立章(不崩溃、不静默丢弃)。"""
sections = [
ChapterMarker(type="heading", name="孤立节", level=2),
ChapterMarker(type="heading", name="1. はじめに", level=1),
]
parsed = ParsedTemplate(file_name="t.docx", sections=sections, placeholders={}, styles={})
specs = map_template(parsed)
assert [s.title for s in specs] == ["孤立节", "1. はじめに"]
assert specs[0].sub_headings == []