渲染后强制 QA 清单
适用时点:fill_template.py 渲染完成 → 交付用户前,必须完整走完本清单。 血泪来源:250612 案(22-著作权案由)连续 5 轮返工,以下每一条都是实际踩过的坑。
一、问题归属速查表(排障先分责)
| 来源 | 已实测的问题 | 判别方法 |
|---|---|---|
| 入库模板库(法〔2025〕82号源,OLE2→docx 转换遗留) | ①主体拆成 4 张 w:tbl + 表间空段;②表宽 9344 twips 溢出 A4 可用宽 8306(法院发放件实宽约 7008);③字段被切进多个 tc,短正则跨 tc 匹配必失败 | 解包 word/document.xml,数 body 直接子级 w:tbl 数量、sum(gridCol) |
| 渲染引擎(fill_template 规则) | ①22 等知产案由 _ip_specifics 只覆盖费用字段,客体/权项/行为方式/停止侵权/住所地等全不填;②代理人"有□/特别授权□"无勾选规则;③make_amount_sentence 重写时吞掉左列标签("2. 赔偿经济损失"消失);④gender/字段规则 occurrence 会把被告信息串填进**第三人(自然人)**块;⑤elements["勾选"] 的 key 必须是骨架"段落原文子串",写错则静默不勾(skipped 不报错) |
对比 rules applied/skipped 数与骨架字段清单;grep 输出全文找空标签 |
| 人工 XML 修补(Agent 后处理) | ①正则 repl 里写 \1 反引用——lxml 手写替换不做 re.sub 展开,字面量入文(是☑\1否□);②宽松正则(登记时?\s*间?)匹配到相邻标签("版权登记:"被插值);③行内 tc 宽度全匹配 fullmatch 过严漏填 |
全文 grep 字面 \\1;逐项 20 项自动断言 |
基准原则:几何与形态以法院实际发放件(如该案历史 .doc)为准,不以入库模板为准——两者已被证实不一致。
⚠️ v1.1 勘误(2026-08-28):v1 所记"法院发放件实宽约 7008"实测有误——法院件表宽就是 9344, 装得下是因为页边距窄(L1133/R1417,可用 9356),不是表窄。据此衍生出的"缩表到 7008"修补路线 是错的(列宽等比缩 25% → 证件号视觉断行、整体形态偏离法院件)。正确做法:保 9344 表宽 + 收窄页边距。v0.14 起 merge_sections_and_normalize 已改为宽度感知边距;22 案由模板已整体替换 为法院件基准(DEC-011),本清单步骤 1 的"目标 ~7008"不再适用(按"可用宽 ≥ 表宽"判即可)。
二、渲染后六步 QA(顺序执行)
步骤 1|几何
-
sum(tblGrid gridCol)≤ 页宽−左右边距(A4/11906−3600=8306;目标 ~7008 对齐法院件) - 不满足则全表等比缩放(grid + 全部
tcPr/tcW[@type=dxa]同乘系数,末列吸收余数)
步骤 2|结构
- 表数=3:主体连体大表(说明+当事人+诉请+事实+关联)/意愿表/证据清单表
- 合并方法:各子表列实为同一主网格的切分,按"行内 tc 宽度累积→主网格边界换算 gridSpan"逐行换算并入壳表(22 案由主网格 [2270,611,1153,526,2097,2687])
- 行宽校验:每行 ΣtcW = 主网格总宽(22 案由 34/34 行全过为准出条件)
- 删表间/页顶空段;意愿页与证据页各保留单个分页符段
步骤 3|勾选(逐项 grep 输出全文)
- 性别:原告勾对应侧、被告勾对应侧、第三人两□全空(引擎串填高发点)
- 代理人有☑ + 特别授权☑(引擎无此规则,必手补)
- 停止侵权☑、合理费用有☑、诉讼费是☑
- 案由大勾选群(如著作权:署名权/复制权/信息网络传播权/类别美术建筑/主体作者/通知否×2/行为方式12.其他/关联无)
- 调解块:了解×6 + 先行调解是☑
步骤 4|字段(引擎不填的高发清单,按案由骨架对漏)
- 双方当事人住所地(户籍所在地)与经常居住地逐个非空核查(250612 案两处全空)
- 客体五要素:作品名称/完成时间/首次发表/登记是☑+登记时间/作品类别
- 金额:标签行("2. 赔偿经济损失")存在 + 数值行非空 + 模板残留空"经济损失 元"已清
- 时间地点、其他请求、侵权链接
步骤 5|唯一性与残留
- 被告姓名/电话/身份证号全文各出现 1 次(第三人块串填检测)
- 字面
\\1反引用残留 = 0 - 旧当事人串(--verify-residual 已做,人工再加案号/旧律师名)
步骤 6|20 项自动断言(python 模板)
t = textutil 全文
checks = [('原告住所地', '住所地(户籍所在地):<值>' in t), ...] # 按1106案实测清单展开
ok = sum(v for _, v in checks); print(f'{ok}/{len(checks)}')
# 全过才允许覆盖目标文件;任何一项失败→回到对应步骤修,禁止带病交付三、人工修补铁律(Agent 后处理 docx 时)
- 只能在单元格内改文本,不得增删行/拆表
- 替换用"跨 run 累积文本定位 + 首 run 写入新文 + 其余 run 截除"的 sp() 模式(见本文件附录);repl 一律纯字符串,禁止含
\1等反引用 - 正则锚点必须含足够上下文(标签全文),禁止
字段名?\s*这类可匹配相邻标签的宽松式 - 修改在 /tmp 完成并通过步骤 6 后一次性覆盖目标件;禁止对目标件多轮就地覆写
四、引擎层待修清单(v0.14 已清偿,2026-08-28)
-
_ip_specifics/22 规则链:客体五要素、权项勾选(含前置/尾随框新原语 make_swap_rule)、行为方式、停止侵权、其他请求、时间地点、关联无 ✅ v0.14 - 代理人有☑/特别授权☑勾选规则(通用层代理人块窗口化)✅ v0.14
- 当事人字段串填第三人:22 换法院件基准模板后窗口定位天然正确;住所地全空根因为字段名映射 bug(标签"住所地(户籍所在地)"≠键"住所地"),已修 ✅ v0.14
-
make_amount_sentence/费用行吞结构:make_fee_row 改结构保持+布局无关 ✅ v0.14 - 几何后处理:改宽度感知边距(非缩表;见 v1.1 勘误)✅ v0.14
- skipped 规则默认打印明细 ✅ v0.14
附录|sp() 跨 run 替换参考实现(lxml)
def sp(el, pattern, repl): # el = w:p 或 w:tc
ts = el.findall('.//w:t', NS)
full = ''.join(t.text or '' for t in ts)
m = re.search(pattern, full)
if not m: return False
start, end = m.span(); pos = 0; done = False
for t in ts:
txt = t.text or ''; a, b = pos, pos + len(txt); pos = b
if b <= start or a >= end: continue
kb = txt[:max(0, start - a)]; ka = txt[max(0, min(len(txt), end - a)):]
if not done: t.text = kb + repl + ka; done = True
else: t.text = kb + ka
return True变更记录:v1.0(2026-08-28)首版,源自 250612 武景怡案实战复盘。