# Same-Template Revision Transfer (同模板修订参照) When Doro says "参照X合同的修订进行修订" — apply the same WB revisions from a reference contract to another contract using the same template. ## ⚠️ Doro 强制验证纪律(2026-07-08 明确要求) Doro 明确要求做同模板修订参照时必须走完以下步骤,缺一不可: 1. **先确认模板一致性**:逐段对比两份合同原文(去掉修订后的文本),确认段落数一致、差异仅限业务内容(项目名称、单价等),其余结构完全相同。打印差异段数/总段数(如"9/62段有差异")。 2. **参照修订**:提取参照合同的WB修订→适配目标合同业务语境→应用 3. **格式/字体/编号全检**:所有INS的rPr必须与前后邻居run一致(逐个检查sz/rFonts/bold)。遵守workflow规则(author=WB、精准到字、不整段del+ins) 4. **全文阅读审查合理性**:渲染accept后全文,逐段通读确认修订逻辑合理、不破坏上下文语义 5. **交付前检查**:python-docx可打开、无异常字符、修订数与参照合同一致、他人修订保持不动 **不能跳步直接做修订然后上传。** Doro原话:"你先确认:两个合同是不是模板一样,内容一样;如果一样,参照修改;你修改的格式、字体、编号等,都要遵守workflow的规则;全文阅读,修订是否合理。最后检查交付。" ## Workflow ### Step 1: Extract revisions from reference contract ```python import zipfile from lxml import etree ns = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main' def extract_wb_revisions(filepath): """Extract all WB-authored INS and DEL from a contract.""" with zipfile.ZipFile(filepath, 'r') as z: content = z.read('word/document.xml') tree = etree.fromstring(content) revisions = [] for ins in tree.iter(f'{{{ns}}}ins'): if ins.get(f'{{{ns}}}author') != 'WB': continue texts = [t.text for t in ins.iter(f'{{{ns}}}t') if t.text] # Get parent paragraph for context parent_p = ins while parent_p is not None and parent_p.tag != f'{{{ns}}}p': parent_p = parent_p.getparent() p_texts = [t.text for t in parent_p.iter(f'{{{ns}}}t') if t.text] if parent_p is not None else [] revisions.append({ 'type': 'INS', 'text': ''.join(texts), 'para_context': ''.join(p_texts)[:120] }) for d in tree.iter(f'{{{ns}}}del'): if d.get(f'{{{ns}}}author') != 'WB': continue texts = [t.text for t in d.iter(f'{{{ns}}}delText') if t.text] parent_p = d while parent_p is not None and parent_p.tag != f'{{{ns}}}p': parent_p = parent_p.getparent() p_texts = [t.text for t in parent_p.iter(f'{{{ns}}}t') if t.text] if parent_p is not None else [] revisions.append({ 'type': 'DEL', 'text': ''.join(texts), 'para_context': ''.join(p_texts)[:120] }) return revisions ``` ### Step 2: Identify modification patterns Group INS/DEL pairs by paragraph context to understand what was changed: - Simple text replacement: DEL "协议" + INS "合同" in same paragraph - Text insertion: INS without corresponding DEL (e.g., data ownership sentence) - Prefix insertion: INS "上海市" before existing text ### Step 3: Context adaptation When the template is shared but service content differs, adapt context-specific terms: - "体检服务" → "口腔检查服务" - "学生个人信息、健康检查结果" → "个人信息、检查结果" - Keep legal boilerplate identical (e.g., "归甲方所有", "合同期满") ### Step 4: Apply to target contract (zipfile+lxml) Use three operations: #### A. Tracked replace (DEL old + INS new) ```python def do_tracked_replace(para, find_text, replace_text): """Find text in paragraph runs, create DEL + INS.""" # Build character map from runs (skip ins/del elements) char_map = [] for elem in para: if elem.tag == f'{{{ns}}}r': t = elem.find(f'{{{ns}}}t') if t is not None and t.text: for ci in range(len(t.text)): char_map.append((elem, t, ci)) full_text = ''.join(cm[1].text[cm[2]] for cm in char_map) pos = full_text.find(find_text) if pos == -1: return False # Verify single-run containment, then split into before/DEL/INS/after # ... (see session code for full implementation) ``` #### B. Insert before text ```python def do_tracked_insert_before(para, anchor_text, insert_text): """Insert INS element right before anchor_text.""" for elem in para: if elem.tag == f'{{{ns}}}r': t = elem.find(f'{{{ns}}}t') if t is not None and t.text and anchor_text in t.text: # Split run, insert INS before anchor portion ... ``` #### C. Insert after text ```python def do_tracked_insert_after(para, anchor_text, insert_text): """Insert INS element right after anchor_text.""" for elem in para: if elem.tag == f'{{{ns}}}r': t = elem.find(f'{{{ns}}}t') if t is not None and t.text and anchor_text in t.text: # Split run, insert INS after anchor portion ... ``` ## Data Attribution Rule (2026-07-08 Doro clarification) When writing data ownership/attribution clauses across same-template contracts: **LOCKED (identical across all contracts)**: 归属表述 = "归甲方或相关权利方所有" - NOT "归甲方所有" (excludes data subjects' rights under PIPL) - The phrase "甲方或相关权利方" covers both: data甲方 owns (aggregated stats, service outputs) AND personal info that belongs to data subjects **NOT LOCKED (varies by contract)**: The descriptive content before the attribution phrase - 体检合同: "乙方在提供体检服务过程中获取和产生的全部数据(包括但不限于学生个人信息、健康检查结果等)" - 口腔检查合同: "乙方在提供口腔检查服务过程中获取和产生的全部数据(包括但不限于个人信息、检查结果等)" - Other contracts: adapt to the specific service/data context **Doro原话**: "我只需要涉及到数据权利的归属时,把归属谁改成'归甲方或相关权利方所有',其他的内容不同合同会不同,所以你不能写死。" **Rule source**: `review-rules.md` §4 保密/数据 (updated 2026-07-08) ## Pitfalls ### 1. `` splits runs Text like "青浦区练塘镇" may be split into multiple runs separated by `` elements: ```xml 青浦区练塘 镇社区卫生服务中心 ``` **Fix**: Search at individual run level (`for elem in para: if elem.tag == w:r`), not at full paragraph text level. Insert INS before the run containing the anchor, not at a text position within full paragraph text. ### 2. "达成如下协议" — not all "协议" should be replaced In the reference contract, "达成如下协议:" was NOT changed (it's a formulaic expression meaning "reached the following agreement"). Only contextual uses of "协议" meaning "this agreement/contract" were changed to "合同"/"本合同". **Rule**: Compare reference contract's accepted text to determine which instances were changed and which were left alone. ### 3. INS rPr must clone from target run (not reference) The target contract's runs may have different formatting than the reference. Always clone rPr from the **target paragraph's existing run**, not from the reference contract. ### 4. Order of operations matters Do replacements BEFORE insertions. Insertions change paragraph structure and character positions, which can break subsequent text searches. Recommended order: 1. All `do_tracked_replace` calls (these only split existing runs) 2. All `do_tracked_insert_after` / `do_tracked_insert_before` calls (these add new elements) ### 5. Verify with accept-all view After applying all revisions, verify by building accepted text (skip DEL, include INS) for key paragraphs and comparing against the reference contract's accepted text. ## 2026-07-08 实证:练塘口腔检查合同 Reference: 【修】2026年学生体检外包合同--练塘(1).docx Target: 2026年口腔检查外包合同--练塘.docx Modifications applied: | # | Type | Content | Adaptation | |---|------|---------|------------| | 1 | INS before "青浦区" | "上海市" | None (identical) | | 2a | INS after "保密义务。" | Data ownership sentence | "体检服务"→"口腔检查服务", "学生个人信息、健康检查结果"→"个人信息、检查结果" | | 2b | DEL "协议" + INS "合同" | "协议期满"→"合同期满" | None | | 2c | DEL "服务协议" + INS "本合同" | "服务协议解除"→"本合同解除" | None | | 2d | DEL "本协议" + INS "本合同" | "本协议的履行"→"本合同的履行" | None | | 3 | DEL "本协议" + INS "本合同" | "本协议一式"→"本合同一式" | None | proofErr pitfall encountered: "青浦区练塘" split by `` — had to insert at run level rather than text-position level.