- 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 无重复
88 lines
3.9 KiB
Python
88 lines
3.9 KiB
Python
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.5:H1 起章,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 == []
|