Files
hermes-skills/skills/legal/contract-editor/references/layer-revisions-on-user-revised-doc.md

5.3 KiB

在「用户已自行修订过」的合同上叠加我方修订

实战来源:金信大厦5层东部租赁合同(2026-06-25)。Maggie 本人已用修订模式改了 6 处(author="maggie jia"),要求小Maggie 在此基础上再补几处(模版比对后补不可抗力对等、装修残值公式、抵押救济),保留她的全部修订一字不动

何时用本配方

  • 收到的 docx 已带 track changes(settings.xml 有 <w:trackRevisions/>,文中有 author≠WB/小Maggie 的 w:ins/w:del)。
  • 任务是在用户既有修订之上追加几处,不是重审、不是从干净稿做。
  • 不重跑 workflow,也不用 ContractEditor 库——库的字符级 diff 引擎会把用户既有 w:ins/w:del 卷进来重算,破坏其修订。一律 zipfile+lxml 直接追加节点。

五步配方

1. 新修订 id 从 maxid+1000 起,防撞 + 便于事后过滤

maxid = 0
for el in root.iter():
    if el.tag in (Wq+"ins", Wq+"del"):
        v = el.get(Wq+"id")
        if v and v.isdigit(): maxid = max(maxid, int(v))
nextid = [maxid + 1000]            # 1000 间隔:本次新增 id 全 >1000,过滤/核验时一眼区分
def newid(): nextid[0]+=1; return str(nextid[0])

为什么 +1000 不是 +1:核验「我的修订」与「用户的修订」时,int(id)>1000 直接切分两批,不必记具体数字。

2. 作者:沿用文档既有修订线,不强行套 WB

金信大厦案文档既有修订 author="maggie jia",本次追加沿用同一 author(保持修订线一致、Maggie 看就是「她那条线的延续」)。

注意与「author 铁律=WB」的边界:WB 是 Doro 体系合同审查的署名;当文档已有用户自己的修订线、任务是「在她的修订上接着改」时,沿用她的 author 让修订归并到同一作者更自然。归属按文档既有线定,不是无脑套 WB。拿不准就问。

3. rPr:克隆「用户已渲染正确的 INS」当样板,预防中文字体回退坑

不要自己造 rPr。找一个用户已有的、中文显示正常的 w:ins run,读它的 rPr 当模板:

# 金信大厦案模板:<w:rFonts w:ascii="Times New Roman" w:eastAsiaTheme="minorEastAsia"
#                  w:hAnsi="Times New Roman" w:cs="Times New Roman" w:hint="eastAsia"/>
#                 <w:sz w:val="21"/><w:szCs w:val="21"/>
def make_rpr():
    rpr = etree.Element(Wq+"rPr")
    rf = etree.SubElement(rpr, Wq+"rFonts")
    rf.set(Wq+"ascii","Times New Roman"); rf.set(Wq+"eastAsiaTheme","minorEastAsia")
    rf.set(Wq+"hAnsi","Times New Roman"); rf.set(Wq+"cs","Times New Roman"); rf.set(Wq+"hint","eastAsia")
    sz = etree.SubElement(rpr, Wq+"sz"); sz.set(Wq+"val","21")
    etree.SubElement(rpr, Wq+"szCs").set(Wq+"val","21")
    return rpr

eastAsiaTheme="minorEastAsia"+hint="eastAsia" 让中文走主题回退(金信大厦回退到宋体),西文 Times New Roman——这是这类合同 INS 中文正常显示的关键,详见 SKILL.md「INS 中文字体」节。

4. 三种插入机制(按改动类型选)

  • 纯追加(句末补一句救济/公式):定位段落最后一个 normal run / 最后一个 ins,last.addnext(make_ins(text, rpr))
  • 删一段换对等表述(单向条款改双向):split 原 run → 原 run 文本保留前半、addnext(make_del(后半, 原rpr)) → 再 del.addnext(make_ins(对等表述, rpr))
  • 替换数值/词(6→12 个月、percent→百分之):字符级定位,DEL 旧 + INS 新。

make_del<w:del><w:r><w:delText>、克隆原 run 的 rPr 加 rsidDelmake_ins<w:ins><w:r><w:t xml:space="preserve">

5. 只换 document.xml(settings 已开 trackRevisions 就不动它)

with zipfile.ZipFile(SRC) as zin, zipfile.ZipFile(tmp,"w",zipfile.ZIP_DEFLATED) as zout:
    for it in zin.namelist():
        zout.writestr(it, new_doc if it=="word/document.xml" else zin.read(it))

四查验证(缺一不可)

  1. python-docx 能打开Document(out))——XML 合法。
  2. 接受所有修订后文本正确——抽出「保留 ins 内容、丢弃 del 内容」的纯文本,逐处核我改的几段语句通顺、内容对。
  3. 本次新增 INS(id>1000)含中文 run 字体非 Times New Roman——eastAsiaTheme=="minorEastAsia" or (ea and ea!="Times New Roman") 应全真。
  4. 🔴 用户原有修订逐 id 比对一字未动——把源文件与产物里 id<=maxid 的所有 ins/del 提成 (id, tag, author, 文本) 排序比对,必须完全相等。这是本配方的核心安全验证:证明我只追加、没碰用户的任何一处。

收口(与库路径相同)

scripts/accept-revisions-preview.py 生成干净版 → OnlyOffice 渲染 → vision 视觉验收。

  • vision 报「页底某句截断」先分清 PDF 分页 vs 真丢数据:金信大厦案 vision 报抵押救济句在 P7 底部截断,实为该句跨页接到 P8 开头——①数据层读该 INS 完整内容在;②P7+P8 拍平后 grep 完整句存在 → 确认是 PDF 分页跨页,OnlyOffice 滚动查看正常,不返工。判据同 contract-portfolio-analysis Pitfall:数据层完整+跨页搜得到=分页现象。
  • vision 对字体/‰%/小符号的误判同样适用——回数据层核,别据像素返工(见 SKILL.md「交付前视觉验收的两个已知误判」)。