94 lines
3.8 KiB
Markdown
94 lines
3.8 KiB
Markdown
# Multi-Version Contract Comparison Table (三版对比表)
|
|
|
|
## When to Use
|
|
When Maggie/Doro asks to compare multiple versions of a contract (typically: template / counterparty revision / our revision), produce a structured docx comparison table.
|
|
|
|
## Pattern (2026-07-03 模特合作协议 session)
|
|
|
|
### Document Setup
|
|
- **Landscape orientation** for 4-5 columns: `section.orientation = 1; page_width=Cm(29.7); page_height=Cm(21.0)`
|
|
- Narrow margins: 1.2-1.5cm all sides
|
|
- Font size 8.5-9pt for table cells (fits more content)
|
|
|
|
### Table Structure
|
|
| 条款 | 【模版】 | 版本A(对方修订) | 版本B(我方修订) | 双方协商一致 |
|
|
|------|---------|------------------|-----------------|-------------|
|
|
|
|
### Red Font for Differences
|
|
- Column N is red when its content differs from other versions
|
|
- Use `RGBColor(0xFF, 0x00, 0x00)` on the run
|
|
- "协商一致" column: red = current text doesn't match consensus → needs modification
|
|
|
|
### Yellow Background for Consensus Column
|
|
```python
|
|
def set_cell_shading(cell, color):
|
|
tc = cell._element
|
|
tcPr = tc.find(qn('w:tcPr'))
|
|
if tcPr is None:
|
|
tcPr = OxmlElement('w:tcPr')
|
|
tc.insert(0, tcPr)
|
|
shading = OxmlElement('w:shd')
|
|
shading.set(qn('w:fill'), color) # e.g. 'FFF8E1' for light yellow
|
|
shading.set(qn('w:val'), 'clear')
|
|
tcPr.append(shading)
|
|
```
|
|
|
|
### Header Row Styling
|
|
- Blue background (`D9E2F3`)
|
|
- Bold, centered, font size 8.5pt
|
|
|
|
### Data Structure in Code
|
|
```python
|
|
# Each row: (clause_name, col1_text, col2_text, col3_text, col4_text, col2_red, col3_red, col4_red)
|
|
rows = [
|
|
('条款名',
|
|
'模版内容',
|
|
'对方修订内容',
|
|
'我方修订内容',
|
|
'协商一致内容',
|
|
True, # col2 red? (differs from others)
|
|
False, # col3 red?
|
|
True), # col4 red? (doesn't match consensus)
|
|
]
|
|
```
|
|
|
|
### Legend at Bottom
|
|
Include a legend explaining what red means in each column:
|
|
- 版本A列红色 = 与模版/我方版不一致(对方的修改)
|
|
- 版本B列红色 = 与模版不一致(我方的修改)
|
|
- 协商一致列红色 = 当前文本与协商一致不符,需要修改
|
|
|
|
## Key Lessons
|
|
1. **Read all three files from Nextcloud** using `sudo find ~/nextcloud/data/data/...` path
|
|
2. **Extract paragraph text** using python-docx: `[(i, p.text.strip()) for i, p in enumerate(doc.paragraphs) if p.text.strip()]`
|
|
3. **Check tables** separately: `doc.tables` — contracts often have signature blocks and SNS account tables
|
|
4. **Align comparison by clause semantics**, not paragraph index — different versions may have different paragraph counts
|
|
5. Also upload to Nextcloud for viewing in OnlyOffice
|
|
|
|
## Per-Run Precision for Tracked Changes (Maggie's correction)
|
|
|
|
When applying tracked changes based on comparison results, **never replace entire paragraphs**. Instead:
|
|
|
|
1. Identify the specific runs containing text to change
|
|
2. For each run: create w:del wrapping a deepcopy (converting w:t → w:delText), create w:ins with new text and cloned rPr, swap in place
|
|
3. All surrounding runs remain untouched
|
|
|
|
```python
|
|
# Find specific run by text content
|
|
for r in para_element.findall(qn('w:r')):
|
|
text = ''.join(t.text or '' for t in r.findall(qn('w:t')))
|
|
if text == '¥700,000': # exact match on this run
|
|
r_parent = r.getparent()
|
|
r_idx = list(r_parent).index(r)
|
|
# Create del wrapping copy of this run
|
|
del_elem = make_del_run_from_existing(r)
|
|
# Create ins with new value, same rPr
|
|
ins_elem = make_ins_run('¥600,000', r.find(qn('w:rPr')))
|
|
r_parent.remove(r)
|
|
r_parent.insert(r_idx, ins_elem)
|
|
r_parent.insert(r_idx, del_elem)
|
|
break
|
|
```
|
|
|
|
This produces clean tracked changes where Word/OnlyOffice shows exactly which characters changed (e.g., ~~700,000~~ → 600,000) rather than entire-paragraph replacements.
|