feat: export core Hermes skills

This commit is contained in:
2026-07-15 02:45:56 +00:00
parent a028b63eda
commit 54711fee2a
308 changed files with 41310 additions and 1 deletions
@@ -0,0 +1,298 @@
# Multi-Party Revision Reconciliation (多方修订对账)
## 场景
合同经多方修订(如WB、华诚-Z、Adon hase等不同修订人),需要:
1. 核对叠加修订后的最终效果是否符合协商一致的商业条件
2. 对比最终版与模板的差异
3. 评估差异对特定方权利义务的影响
4. 统一修订人署名
## 技术方法
### 1. 提取带修订标记的全文(含作者归属)
```python
from docx import Document
from lxml import etree
ns = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
W = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
doc = Document('contract.docx')
body = doc.element.body
# 统计修订作者
authors = set()
for elem in body.iter():
author = elem.get(f'{W}author')
if author:
authors.add(author)
# 分作者统计插入/删除
ins_by_author = {}
del_by_author = {}
for ins in body.findall(f'.//{W}ins'):
a = ins.get(f'{W}author', 'unknown')
ins_by_author[a] = ins_by_author.get(a, 0) + 1
for d in body.findall(f'.//{W}del'):
a = d.get(f'{W}author', 'unknown')
del_by_author[a] = del_by_author.get(a, 0) + 1
```
### 2. 带修订标记的文本提取
格式:`[+作者: 插入文本]` / `[-作者: 删除文本]` / 普通文本
```python
def get_text_with_revisions(body):
result = []
for para in body.findall(f'.//{W}p'):
para_text = []
for elem in para.iter():
if elem.tag == f'{W}ins':
author = elem.get(f'{W}author', '?')
texts = [t.text for t in elem.findall(f'.//{W}t') if t.text]
if texts:
para_text.append(f"[+{author}: {''.join(texts)}]")
elif elem.tag == f'{W}del':
author = elem.get(f'{W}author', '?')
texts = [t.text for t in elem.findall(f'.//{W}delText') if t.text]
if texts:
para_text.append(f"[-{author}: {''.join(texts)}]")
elif elem.tag == f'{W}t':
in_revision = False
p = elem
while p is not None:
if p.tag in [f'{W}ins', f'{W}del']:
in_revision = True
break
p = p.getparent()
if not in_revision and elem.text:
para_text.append(elem.text)
if para_text:
result.append(''.join(para_text))
return result
```
### 3. 三版对比分析
对比维度:
- **模板** → 我方标准条款(基准线)
- **对方修订版** → 对方修改后接受所有修订的版本(ins=0, del=0 说明已全部接受)
- **当前叠加版** → 在WB修订基础上加入另一方修改
### 4. 协商一致核对表
按商业条件逐项核对:
| 协商条件 | 条款位置 | 当前版本内容 | 是否符合 |
|---|---|---|---|
| 费用40/60/60/80/80 | 3.3条 | 具体金额 | ✅/❌ |
| 违约责任按我方 | 第4条 | ... | ✅/⚠️ |
### 5. 模板偏差影响评估
对每处偏差评级:
- ✅ 对己方有利(扩大了己方权利/对方义务)
- ⚠️ 中等影响(条件调整但不改变核心权利义务)
- ❗ 重要(实质性削弱己方权利/扩大己方义务/给对方逃出合同的通道)
## 修订人统一规则
当需要将多个修订人统一为一个(如统一为WB):
### 处理优先级
1. A修订了B的修订 → **以A为准**(接受A对B的修改)
2. A和B各自独立修订 → 两者都保留,统一署名
### 核心技术挑战:嵌套修订
**关键结构:B(华诚-Z)在A(WB)的 `w:ins` 内部做了 `w:del`**
XML表现为:
```xml
<w:ins w:author="WB">
<w:r><w:t>个月未给乙方安排工作的,</w:t></w:r>
<w:del w:author="华诚-Z">
<w:r><w:delText>拍摄</w:delText></w:r>
</w:del>
</w:ins>
```
含义:WB插入了"个月未给乙方安排拍摄工作的,",华诚-Z在WB的插入中删除了"拍摄",最终效果="个月未给乙方安排工作的,"。
### 三步统一流程(2026-07-03 MCN模特合同实证)
**Step 1: 接受嵌套删除**(B对A修订的修改)
```python
def accept_nested_deletions(body, inner_author='华诚-Z', outer_author='WB'):
"""接受inner_author对outer_author修订的修改(删除嵌套del元素)"""
for ins_elem in body.findall(f'.//{W}ins'):
if ins_elem.get(f'{W}author') != outer_author:
continue
# 找到outer_author的ins内部,inner_author做的del
for del_elem in ins_elem.findall(f'.//{W}del'):
if del_elem.get(f'{W}author') == inner_author:
parent = del_elem.getparent()
parent.remove(del_elem)
accept_nested_deletions(body)
```
**Step 2: 清除空壳元素**
接受嵌套删除后,某些WB的ins可能变空(内容全被华诚-Z删了,华诚-Z在旁边插入了替代文本):
```python
def remove_empty_ins(body):
"""删除没有任何文本内容的ins元素"""
for ins_elem in body.findall(f'.//{W}ins'):
has_text = False
for t in ins_elem.findall(f'.//{W}t'):
if t.text and t.text.strip():
has_text = True
break
if not has_text:
parent = ins_elem.getparent()
if parent is not None:
parent.remove(ins_elem)
remove_empty_ins(body)
```
**Step 3: 统一作者名**
```python
def rename_author(body, old_author, new_author):
"""修改所有修订元素的author属性"""
count = 0
for elem in body.iter():
if elem.get(f'{W}author') == old_author:
elem.set(f'{W}author', new_author)
count += 1
return count
count = rename_author(body, '华诚-Z', 'WB')
```
### 在统一后的文件上追加模板修改
如果发现与模板有偏差需要修正(如恢复模板中的固定金额违约金选项、自动续约条件等),在已统一的文件上新增tracked changes:
```python
from copy import deepcopy
def get_rPr_from_run(run):
"""获取run的格式属性用于新增修订"""
rpr = run.find(f'{W}rPr')
return deepcopy(rpr) if rpr is not None else None
def make_ins(text, rPr=None, author='WB', date='2026-07-03T06:00:00Z'):
"""创建tracked insertion"""
ins = etree.Element(f'{W}ins')
ins.set(f'{W}id', str(abs(hash(text)) % 100000))
ins.set(f'{W}author', author)
ins.set(f'{W}date', date)
r = etree.SubElement(ins, f'{W}r')
if rPr is not None:
r.append(deepcopy(rPr))
t = etree.SubElement(r, f'{W}t')
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = text
return ins
def make_del(text, rPr=None, author='WB', date='2026-07-03T06:00:00Z'):
"""创建tracked deletion"""
d = etree.Element(f'{W}del')
d.set(f'{W}id', str(abs(hash(text + 'del')) % 100000))
d.set(f'{W}author', author)
d.set(f'{W}date', date)
r = etree.SubElement(d, f'{W}r')
if rPr is not None:
r.append(deepcopy(rPr))
dt = etree.SubElement(r, f'{W}delText')
dt.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
dt.text = text
return d
# 修改模式:找到目标run → 替换为del(旧) + ins(新)
target_run = ... # 找到包含目标文本的w:r元素
rPr = get_rPr_from_run(target_run)
old_text = target_run.find(f'.//{W}t').text
del_elem = make_del(old_text, rPr)
ins_elem = make_ins(new_text, rPr)
idx = list(para).index(target_run)
para.remove(target_run)
para.insert(idx, del_elem)
para.insert(idx + 1, ins_elem)
```
### 恢复已被删除的内容(撤回WB之前的del)
当需要把WB之前删除的内容恢复回来(因为模板中有这个内容):
```python
# 找到WB的del元素
for child in para:
if child.tag == f'{W}del' and child.get(f'{W}author') == 'WB':
del_texts = ''.join(dt.text for dt in child.findall(f'.//{W}delText') if dt.text)
if '目标文本' in del_texts:
# 策略:删除这个del,插入一个ins代替(因为原文已经"被删"了)
inner_r = child.find(f'.//{W}r')
inner_rPr = get_rPr_from_run(inner_r)
ins_restore = make_ins(del_texts, inner_rPr)
idx = list(para).index(child)
para.remove(child)
para.insert(idx, ins_restore)
break
```
## 验证步骤
统一修订人后必须验证:
```python
# 1. 确认只剩一个作者
authors = set()
for elem in body.iter():
a = elem.get(f'{W}author')
if a:
authors.add(a)
assert authors == {'WB'}, f"Unexpected authors: {authors}"
# 2. 获取接受所有修订后的最终文本
def get_accepted_text(para):
texts = []
for elem in para.iter():
if elem.tag == f'{W}t':
in_del = False
p = elem
while p is not None:
if p.tag == f'{W}del':
in_del = True
break
p = p.getparent()
if not in_del and elem.text:
texts.append(elem.text)
return ''.join(texts)
# 3. 逐条核对关键条款的最终文本
```
## 输出格式
报告分三部分:
1. **协商一致核对** — 逐项确认商业条件是否落实(✅/⚠️/❌)
2. **模板差异表** — 与标准模板的偏差点 + 对己方权利义务的影响评估
3. **结论与建议** — 哪些已符合、哪些需关注、是否需要进一步协商
## 注意事项
- 对方修订版如果修订已全部接受(ins=0, del=0),说明是"接受所有修订后"的干净版本
- 比较时需要分别提取:(1)当前版本接受所有修订后的最终文本;(2)带修订标记的过程文本
- 对方修改了措辞但实质不变的情况(如"整容"→"形象变化"),需评估措辞变化是否改变法律效果——措辞更宽泛时分析对哪方有利
- 费用条款的结构性改写(如合并年度/拆分年度)要验证数学正确性(年总额×期数=合同总额)
- 处理段落29-31连读场景:华诚-Z可能将原来的多个年度段落(第三、四年/第五年分开写)合并为一个(第四、第五年),导致中间段落被del清空,需要把前后段落连起来读才能看到完整的费用结构
- **修订人统一后的检查清单**:作者唯一性、ins/del总数合理、关键条款最终文本正确、文件可正常打开