feat: export core Hermes skills
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# 在「用户已自行修订过」的合同上叠加我方修订
|
||||
|
||||
实战来源:金信大厦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` 起,防撞 + 便于事后过滤
|
||||
```python
|
||||
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 当模板:
|
||||
```python
|
||||
# 金信大厦案模板:<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 加 `rsidDel`;`make_ins` 用 `<w:ins><w:r><w:t xml:space="preserve">`。
|
||||
|
||||
### 5. 只换 document.xml(settings 已开 trackRevisions 就不动它)
|
||||
```python
|
||||
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「交付前视觉验收的两个已知误判」)。
|
||||
Reference in New Issue
Block a user