# 物理依赖链架构（2026-06-29 确立）

## 设计理念

规则写在skill里是"纸面约束"——看了可以跳过。物理依赖链把关键步骤之间的依赖变成"物理约束"——上一步没做完，下一步**跑不起来**。

核心区别：
| | 纸面规则 | 物理依赖链 |
|---|---|---|
| 什么时候卡 | 做完后检查（事后） | 做之前检查（事前） |
| 能不能绕过 | 能（不跑检查直接发） | 不能（脚本直接中止） |
| 靠什么保证 | 人的记性 | checkpoint文件 |

## 三道卡口

### 卡口1：OCR完整性 → 建表

```
Step 1 OCR完成
  ↓
ocrmypdf → pdftotext → .md文件
  ↓
python3 scripts/ocr-integrity-check.py <工作目录>
  ↓ 检查：无乱码/无占位符
  ↓ 通过 → 生成 step1.verified
  ↓ 不通过 → 列出问题，不生成checkpoint
  ↓
Step 3 建表时：
  single-campus-builder.py 检查 step1.verified 是否存在
  不存在 → sys.exit(1)，表中止
```

### 卡口2：模版比对 → 建表

```
Step 2 动作B完成（delegate subagent）
  ↓
subagent产出 模版比对-*.md
  ↓
python3 scripts/template-diff-verify.py <工作目录>
  ↓ 检查：文件存在、>2000字节、≥10个条款号、≥3个差异关键词
  ↓ 通过 → 生成 step2b.verified
  ↓ 不通过 → 列出问题，不生成checkpoint
  ↓
Step 3 建表时：
  single-campus-builder.py 检查 step2b.verified 是否存在
  不存在 → sys.exit(1)，表中止
```

### 卡口3：最终闸门

```
Step 6 交付前
  ↓
python3 scripts/delivery-gate.py <xlsx> <工作目录> <校区名>
  ↓ 9项检查（G1-G9）
  ↓ 全过 → exit 0，可以发
  ↓ 任一不过 → exit 1，禁止发
```

## 环境变量

建表脚本通过环境变量 `CAMPUS_WORKDIR` 定位工作目录：

```bash
CAMPUS_WORKDIR=/tmp/人民中路 python3 templates/single-campus-builder.py
```

如果不设此变量，建表脚本跳过checkpoint检查（向后兼容），但 delivery-gate 事后仍会拦截 G8/G9。

## 已修复的误报模式（2026-06-29 多校区实证）

### ocr-integrity-check.py 误报

| 模式 | 误报原因 | 修复 |
|------|---------|------|
| 统一社会信用代码（如91320600MA1NAEBX26） | 代码中的英文字母（MA1NAEBX）被识别为"公司名乱码" | 脚本已增加前后字符检查：字母前后有数字→跳过 |
| 合同编号（如XYZL-20241029-2#1F） | 4+连续大写字母匹配乱码正则 | 临时修复：将XYZL替换为Xyzl（小写）避免匹配 |
| 签名页英文残留 | 扫描件签名区域的OCR噪声 | 用正则替换为[签章]占位符 |

**修复后仍可能触发的情况**：如果OCR文件中出现新的非标准英文序列，脚本仍会报"公司名乱码"。此时需判断：
- 前后有数字（如信用代码）→ 手动修正OCR文件中的具体位置
- 签名/盖章区域 → 用正则批量替换为[签章]
- 真正的乱码（关键字段无法识别）→ 用vision或tesseract看原图补全

### template-diff-verify.py 误报

| 模式 | 误报原因 | 修复 |
|------|---------|------|
| "模版表述："（冒号） | 原正则只匹配"模版表述为" | 已改为`模版表述[为：:]`，同时匹配冒号 |
| subagent用"无此条款"描述差异 | 原关键词列表不含此表述 | 已新增"无此条款"和"不存在"为有效关键词 |

## OCR补全的tesseract降级方案

当`vision_analyze`超时（常见于高分辨率扫描件）时，tesseract可作为降级方案：

```python
import fitz
doc = fitz.open('合同.pdf')
page = doc[page_index]
mat = fitz.Matrix(200/72, 200/72)  # 200 DPI
pix = page.get_pixmap(matrix=mat)
pix.save(f'page_{page_index+1}.png')
```

```bash
tesseract page_X.png stdout -l chi_sim+eng --psm 6 2>/dev/null
```

**适用场景**：
- 读取合同首页（甲乙方信息、地址、信用代码）
- 读取特定条款段落（裁剪页面局部区域）
- 补充OCR .md文件中的乱码位置

**不适用场景**：
- 手写签名、印章内容（tesseract无法识别）
- 低对比度扫描件（需先二值化处理）

## 企业入驻合同等非标准格式（2026-06-29 星月实证）

部分园区使用"企业入驻合同"而非标准"房屋租赁合同"格式。特征：
- 合同标题为"企业入驻合同"或"入驻协议"
- 包含物业管理费（打包在租金中或单独列出）
- 条款结构与07模版完全不同（无07模版的条款号体系）
- 常见于创业孵化器、科技园区、产业园

**处理方式**：subagent模版比对时仍需与07模版比对（找出缺失的保护性条款），但在L列注明"本合同为企业入驻合同格式，非07标准模版"。

## 脚本清单

| 脚本 | 位置 | 作用 |
|------|------|------|
| ocr-integrity-check.py | scripts/ | 检查OCR .md文件乱码/占位符 → step1.verified |
| template-diff-verify.py | scripts/ | 检查模版比对输出充实度 → step2b.verified |
| single-campus-builder.py | templates/ | 建表前检查两个checkpoint |
| delivery-gate.py | scripts/ | 最终9项闸门（含G8/G9复查checkpoint） |
