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,323 @@
# 纯扫描件合同 OCR 配方(image-only PDF → 可读文本)
> 适用:南通新东方多数校区合同是**纯扫描件**——`pdftotext` 文字层字符数 ≈ 0(个位数),
> 必须 OCR 才能读。人民中路、跃龙路实证此配方干净好用,优先于 deepseek_ocr 路径。
## 配方(tesseract 引擎,中英混排)
```bash
# 1) ocrmypdf 给扫描件加文字层(force-ocr 强制重做,image-dpi 300 提清晰度)
ocrmypdf -l chi_sim+eng --force-ocr --image-dpi 300 租赁.pdf 租赁-ocr.pdf
# 2) pdftotext -layout 抽文本(-layout 保留表格的列对齐,读租金表/费用表更可靠)
pdftotext -layout 租赁-ocr.pdf 租赁-OCR.md
```
- `-l chi_sim+eng`:中文简体+英文,合同里身份证号/账号/英文条款都能认。
- **为什么用 ocrmypdf 而非 deepseek/裸 tesseract**:① 一步产出**带文字层的 PDF**(后续 x2t 渲染、人工查阅都能用);② `pdftotext -layout` 出来的文本**保留空间布局**,财务表格的列不会糊成一行;③ tesseract 对扫描合同的正文条款识别率足够高。
- **大文件后台跑**:10MB+ 的扫描 PDF(如跃龙路租赁 10.9MB)OCR 要几分钟,写脚本 `terminal(background=true, notify_on_complete=true)`,发出后并行做别的(见 SKILL Pitfall 4)。
- **验证**:OCR 后 `wc -c 租赁-OCR.md` 字符数应从 ~0 跳到**数千~数万**(人民中路租赁 8 页→31420 字符、物业 3 页→6111;跃龙路 6+6 页同量级)。仍是个位数=OCR 没生效,回查 ocrmypdf 报错。
## 两个必防的 OCR 失真(扫描件通病)
### ① 财务表格"大写金额"几乎必乱码 → 以阿拉伯数字为准
正文条款 OCR 可读,但租金表里的**中文大写金额**("壹拾壹万玖仟…")常被识别成乱码(`SRSAU``SRSAUT SAAR` 之类)。处置:
- **关键金额一律以阿拉伯数字栏为准**(如 `119,190.75`),大写乱码忽略,**不照抄乱码进表**。
- 数额存疑/阿拉伯数字也不清 → 回原图 `vision_analyze` 核,或走金额数学交叉验证(不含税×(1+税率)=含税,见 SKILL「4d/数学交叉验证」)。
### ② 抬头/公司名乱码 → 裁高清局部图 vision 逐笔辨认 + 多处交叉核
扫描件首页抬头(甲方/乙方公司全称)常因字号小/印章压字而 OCR 乱码,**且这是梳理表的关键基础项,不能蒙**。处置:
```bash
pdftoppm -png -f 1 -l 1 -r 400 租赁.pdf hd_p1 # 400dpi 高清渲染首页
# python: PIL 裁抬头区(高度~4%-16%) → resize 宽1200 → 存 jpg quality≥90 (<400KB 防 vision 超时)
```
- 把抬头局部图喂 `vision_analyze`,要求**逐字辨认、看不清就明说哪个字看不清、禁止猜测填充**。
- **多处交叉核**:公司名在合同里通常出现 3+ 处(抬头、第三条收付款户名、落款、骑缝章),用整页+高清局部两次独立辨认互证;**与旧梳理表/兄弟合同的记法对撞**——不一致就是该名称 OCR 不稳的信号,必须核到底。
- 人民中路实证:旧表(0622)甲方记"南通琳大鞍房屋…",本次 vision 高清逐笔辨认为"南通**森大蒂**房屋建设开发有限公司"("森"=品字三木,确非"琳"),纠正了旧表的误识。**OCR 与旧表都可能错,回高清原图 vision 核才是真值。**
## ③ tesseract 空白输出救援:二值化(binarization)回退(2026-06-26 通州金鹰实证)
### 症状
`ocrmypdf` 整份跑完,`pdftotext` 抽出来的文本**只有前几页、后几页完全空白**(80 字节的 `\f` 换页符)。拆出空白页单独 `tesseract` 直接读 PNG 也**一字不输出**——但 PNG 文件大小正常(2-3MB),`numpy` 统计非白像素 1000 万+,**页面有内容、只是 tesseract 读不出来**。
### 根因
扫描件对比度低/底色不均/噪点多,tesseract 默认的灰度处理在纹理复杂的旧扫描件上找不到文字边界。
### 正解:二值化(threshold=128)→ tesseract
```python
from PIL import Image
img = Image.open('page.png')
gray = img.convert('L') # 灰度
bw = gray.point(lambda x: 0 if x < 128 else 255, '1') # 阈值 128 二值化
bw.save('page_bin.png')
```
```bash
tesseract page_bin.png stdout -l chi_sim+eng
```
二值化后 tesseract 能正常识别,输出与正常 OCR 页面同质量。
### 必做步骤
-`pdftoppm -r 200 -png` 渲染空白页 → 二值化 → tesseract(通州金鹰 p4-p8 五页全救回)
- **不要增加二值化阈值试错**——128 是标准阈值,低对比度扫描件用 128 即可;若 128 仍不输出,优先怀疑页面本身无文字(检查 `numpy` 非白像素数),而非阈值问题
- 二值化后 OCR 出来的文本与正常 `ocrmypdf` 产出同质量,直接合并使用
## ④ 特定乱码字段核实:tesseract → browser_vision 降级链(2026-06-29 解放中路实证)
### 问题场景
OCR/tesseract 能读出大部分合同文本,但**特定字段仍然乱码**(如"壹"被读成"过"、数字被吃0、条款号后的填空值模糊)。需要 vision 核实具体字符,但 `vision_analyze` 反复超时。
### 诊断:fitz 文字层检测
```python
import fitz
doc = fitz.open('合同.pdf')
for i in range(doc.page_count):
text = doc[i].get_text()
if not text.strip():
print(f'Page {i+1}: 纯扫描页(无文字层)')
else:
print(f'Page {i+1}: {len(text)} chars')
```
纯扫描件 `fitz.get_text()` 返回空字符串 → 必须先 OCR。OCR 后的 PDF 有文字层但部分字符乱码 → 进入 vision 核实。
### vision_analyze 超时问题
`vision_analyze` 对合同页面图片(即使压缩到 325KB/400×565px 的 72 DPI JPEG)**连续超时 5 次**(2026-06-29 解放中路实证)。全页 300 DPI PNG(2.5MB)更不用说。**不要反复重试 vision_analyze——它对这个场景不可靠。**
### 正解:tesseract 全页 + browser_vision 局部裁图
**第一层:tesseract 全页 OCR(已做,处理 90% 文本)**
```bash
# 300 DPI 渲染每页 → tesseract 逐页读
python3 -c "
import fitz
doc = fitz.open('合同.pdf')
for i in range(doc.page_count):
pix = doc[i].get_pixmap(matrix=fitz.Matrix(300/72, 300/72))
pix.save(f'page_{i+1}.png')
"
tesseract page_N.png stdout -l chi_sim+eng --psm 6
```
tesseract 对中文合同正文条款识别率高(解放中路第六条完整读出、第十二条大部分可读)。
**第二层:browser_vision 局部裁图(处理 tesseract 仍乱码的具体字符)**
```python
from PIL import Image
# 1. 打开 300 DPI 页面图
img = Image.open('page_3.png') # 2481x3510
w, h = img.size
# 2. 裁剪目标区域(如第12.1条第4项,约在页面25-35%高度)
crop = img.crop((100, int(h*0.25), w-100, int(h*0.45)))
# 3. 缩放到 600-800px 宽,存 JPEG quality=80
crop_resized = crop.convert('RGB').resize(
(600, int(crop.height * 600 / crop.width)), Image.LANCZOS)
crop_resized.save('crop_target.jpg', 'JPEG', quality=80)
```
```
# 4. browser_navigate file:///tmp/xxx/crop_target.jpg
# 5. browser_vision 问具体问题(如"第4项中'拖欠租金累计达几个月的'数字是多少")
```
### 关键要点
- **browser_vision 和 vision_analyze 是不同的后端**——browser_vision 通过浏览器截图+视觉模型,对小图片处理更稳定,本 session 一次成功
- 裁图要**精准定位目标条款区域**,不要裁全页(太大)也不要裁太窄(缺上下文)
- 问题要**具体**:"第4项中拖欠租金累计达几个月的数字是什么"比"请读出全部文字"效果好
- 中文大写数字(壹/贰/叁)在扫描件中常被 tesseract 误识为形近字,**必须 vision 确认**
- 文件大小控制在 50-100KB(JPEG quality=75-80, 600px 宽),避免超时
### 完整降级链总结
```
fitz.get_text() 空 → ocrmypdf 全篇 OCR → pdftotext 取文本
↓ tesseract 能读的 → 直接用
↓ tesseract 乱码的具体字段 → 裁区域 + browser_vision
↓ browser_vision 也超时 → 如实告诉 Maggie,请她核对原件
↓ 绝不能 → 用旧表数据填 + 手动创建 checkpoint(Pitfall 39)
```
## ⑤ 统一社会信用代码 OCR 乱码修复 + 网络交叉验证(2026-06-29 通大附+凤凰文化实证)
### 问题场景
OCR 把统一社会信用代码中的英文字母部分读乱(如 `MA1NAEBX26``MA INAEBX26`,数字间插入空格;`MADQRFT66P` 被完整读出但无法确认是否正确)。`ocr-integrity-check.py` 将 3+ 连续大写字母匹配为"公司名乱码"——但信用代码中的字母部分是合法的。
### ocr-integrity-check.py 修复(已落地 2026-06-29)
脚本增加信用代码排除逻辑:检测匹配到的字母序列前后是否有数字,有则判定为信用代码的一部分并跳过。
### 网络交叉验证技巧
OCR 信用代码不完整时,用企查查/天眼查 web search 验证:
```
web_search: "公司全称" 统一社会信用代码
```
已验证案例:
- 南通青创企业管理咨询有限公司:91320600MA1NAEBX26(企查查)
- 南通新东方教育科技有限公司:91320602MADQRFT66P(校验位验证通过)
- 南通业玖物业管理有限责任公司:企查查未查到(可能是小公司或名称细微差异),但 tesseract 重跑清晰读出
### 信用代码校验位验证
```python
weights = [1,3,9,27,19,26,16,17,20,29,25,13,8,24,10,30,28]
chars = '0123456789ABCDEFGHJKLMNPQRTUWXY'
code_map = {c:i for i,c in enumerate(chars)}
total = sum(code_map[code[i]] * weights[i] for i in range(17))
check = 31 - (total % 31)
expected = chars[check] if check < len(chars) else '?'
# expected == code[17] → 校验通过
```
## ⑥ 签名页/签章区域乱码处理(2026-06-29 多校区实证)
### 问题场景
合同最后 1-2 页(签名盖章页)OCR 产出大量无意义英文字母序列(`AAA``LIV``BREE``RUE``ANON` 等),是印章/手写签名/骑缝章被 OCR 引擎误识别的产物。
### 处理方式
```python
import re
# 替换 3+ 连续大写字母为 [签章] 占位符
lines[idx] = re.sub(r'[A-Z]{3,}', '[签章]', lines[idx])
```
### 判断标准
- 出现在签名页(通常最后 1-2 页)
- 上下文含"甲方签字""乙方盖章""日期"等关键词
- 这些区域不含合同实质条款,替换后不影响完整性检查
- 替换后 `ocr-integrity-check.py` 不再报这些位置的问题
## ⑦ vision 全不可用时的 delegate_task 降级(2026-06-29 跃龙路实证)
### 问题场景
`vision_analyze` 连续超时 7+ 次(即使图片压缩到 119KB JPEG),`browser_vision` 也间歇性超时。所有视觉工具在同一 session 中同时不可用。
### 正解:delegate_task 批量分发 OCR 核实
```python
delegate_task(tasks=[
{"goal": "Read scanned contract page and transcribe ALL text. Focus on: ...",
"toolsets": ["vision", "file"]},
# 最多 3 个并行
])
```
- **subagent 的 vision 后端可能与父 agent 不同**——同一时段父 agent vision 全挂,subagent 有 2/3 成功读取(跃龙路实证:task 1 超时但 task 2、3 成功)
- **混合策略**:给每个 subagent 不同的页面 + 具体的提取目标(不要笼统"读全文"),提高单次成功率
- **subagent 也超时时的兜底**:用已有 tesseract OCR 输出 + 上下文推理重建文本,标注哪些段落是推断而非 vision 确认的
### 关键要点
- delegate_task 的 vision 调用走独立的模型实例,timeout 行为不总与父 agent 一致
- 把最关键的乱码页(租金表、签名页、违约金条款)优先分配给 subagent
- 返回结果里 subagent 会自报"vision failed, used OCR instead"——**检查 status 和 summary**,不能假设全部成功
## ⑧ 单位后缀误读(天→月、年→月等)——数学交叉验证必抓(2026-07-01 凤凰文化实证)
### 问题场景
OCR 将"0.4元/㎡/**天**"误读为"0.4 a/R 元/㎡/月"——单位"天"被吞掉或变成乱码,导致后续模版比对报告错误地认为物业费(0.4元/月)与租金(12.167元/月)是两个不同标准。实际上:0.4元/天 × 365÷12 = **12.167元/月**,两者完全一致。
### 根因
扫描件中"天"字笔画简单(仅3画),在低质量扫描+OCR识别中极易丢失或被误读为符号碎片(如"a/R")。类似地,"月""年"等单位后缀也可能被吃掉。
### 检测方法:数学交叉验证
合同中单价通常后面紧跟一个**月总额表格**。验证公式:
```python
stated_unit_price = 0.4 # OCR 读出的数字
area = 562 # 面积
table_monthly = 6837.854 # 表格中月费
# 如果是 元/㎡/月:
if_monthly = stated_unit_price * area # 0.4 × 562 = 224.8 ≠ 6837.854 → 不匹配!
# 如果是 元/㎡/天:
if_daily = stated_unit_price * area * 365 / 12 # 0.4 × 562 × 30.417 = 6837.85 ✓ 匹配!
```
**匹配失败 = 单位被误读**。必须回原图 vision 确认真实单位。
### 铁律
- 凡 OCR 读出单价+单位(元/㎡/月、元/㎡/天、元/吨等),必须用**表格中的月总额**做交叉验证
- 不匹配时,优先怀疑单位被OCR吃掉/替换,而非合同本身矛盾
- 模版比对报告中若涉及"费率标准不同"的结论,必须先过此验证
### 连锁影响
此类误读会导致模版比对报告产出错误结论(如"物业费0.4元/月与租金12.167元/月**不同**"),进而误导汇总表K列风险判断。修正路径:OCR产出后、写比对报告前,所有涉及金额/费率的字段过一遍数学交叉验证。
## ⑨ OCR严重乱码段落对模版比对的污染(2026-07-01 凤凰文化8.5-8.8实证)
### 问题场景
扫描件第6页8.5-8.8区域OCR产出如下乱码:
```
8.5 甲方按照部 门 标准 及水、电 指 和
定为 :水 国家 电价 计收
8.6 Fak ) 租赁 期限保持 一致
```
基于此写出的模版比对报告将8.5概括为"水电费按部门标准及国家电价计收"(丢失具体费率),将8.6描述为"网络、电话、防火门等配套设施约定"(完全搞错——8.6只是说物业期限=租赁期限,网络电话防火门内容在8.7-8.8)。
### 真实内容(vision核实)
- **8.5**:水费按**4.2元/吨**计收;电费按**国家电价+0.53元/度服务费**(随国家调价联动)
- **8.6**:物业服务期限与租赁期限一致,随解除/终止而终止(仅此一句)
- **8.7-8.8**:消防远程联网、疏散楼梯、二消改造等详细消防义务
### 铁律
- **模版比对报告中,每一条差异的描述必须基于可读的OCR文本或vision核实结果**
- 如果某条款区域OCR输出含3+个连续乱码词(如"Fak""peMet iy ecsapae"),该区域必须先vision确认真实内容,再写入比对报告
- **不得对乱码文本做"合理推测"后写入比对报告**——推测错误比留空更有害
- 比对报告中遇到OCR不可读段落,标注"[OCR不可读,需原件核实]"比写错误描述强
## ⑩ 重做校区时 delegate_task 必须携带 vision 修正值(2026-07-01 凤凰文化实证)
### 问题场景
发现OCR质量问题后重做某校区,重新发 delegate_task 让 subagent 做模版比对。如果 context 只给 OCR 文件路径,subagent 读到的仍是乱码文本,会重复产出错误结论(如"物业费0.4元/月"、"8.6为网络电话条款")。
### 正解:在 delegate_task context 中显式列出所有 vision 修正
```python
delegate_task(
goal="对比租赁合同与07模版,生成逐条差异清单...",
context="""
文件路径:
- 07模版: /tmp/.../07模版-全文.txt
- 租赁合同OCR: /tmp/.../租赁合同-OCR.md
关键修正信息(vision逐页核实,OCR中这些地方有乱码需用修正版):
- 8.4条: 物业费为0.4元/㎡/天(不是/月),换算=12.167元/㎡/月
- 8.5条: 水费4.2元/吨;电费=国家电价+0.53元/度服务费
- 8.6条: 仅"物业期限=租赁期限"一句,不涉及网络电话
- ...
""",
toolsets=["file", "terminal"]
)
```
### 铁律
- **OCR修正后重做比对 ≠ 只重发 delegate_task**——必须把修正值写进 context
- subagent 不会自动跑 vision,它只能读文件;如果文件本身有乱码而 context 没给修正,subagent 就会按乱码理解
- 修正信息的格式:"条款号: 真实内容(不是XXX)"——明确标注哪里被误读、正确值是什么
- 这同样适用于首次做比对时发现OCR乱码区域——先vision核实,再发subagent,context带修正值
## ⑪ OCR产出md文件事后核实流程(2026-07-13 桃坞路实证)
### 场景
md文件已产出并保存,但用户(或后续任务)质疑准确度,需要系统性核实而非重跑OCR。
### 核实方法:vision逐页比对
```bash
# 1. 把PDF关键页渲染成图片(200dpi够用,关键是覆盖含数据的页面)
pdftoppm -png -r 200 -f 1 -l 1 合同.pdf verify/p1
pdftoppm -png -r 200 -f 2 -l 2 合同.pdf verify/p2
# 2. vision_analyze 每张图,问具体数据点(金额、面积、日期、当事人)
# 3. 与md文件对应段落逐项比对
```
### 桃坞路4份合同核实发现的典型OCR失真模式
| 失真类型 | 频率 | 影响 | 处置 |
|---------|------|------|------|
| 封面公章区乱码(前10-16行) | 每份必现 | 不影响条款提取 | 可忽略 |
| 大写金额全乱 | 物业合同必现 | 中等——引用时必须用小写数字 | 以阿拉伯数字为准(已有①) |
| 收款账户段排版错乱 | 偶发 | 低——信息可拼凑 | 核对账号12位数字即可 |
| 个别汉字形近误识(崇→喧、饰→4) | 偶发 | 低——不影响数据 | 已知正确值时直接修正 |
| 手写填空处空白被OCR虚构文字 | 偶发 | 中——会产生虚假数据 | vision确认原件是否真的填写了 |
### 核实策略(效率优先)
1. **不需逐字全文核对**——重点核对:金额、面积、日期、费率、百分比、当事人名称、账号
2. **优先核对第2页**(通常含租金/期限/保证金等核心条款),封面页和签字页优先级最低
3. **数学交叉验证仍是最快手段**:如首期=年租金÷2?物业年费=单价×面积×365?通过=可信
4. **只需vision核对md中有乱码嫌疑的段落**——正常可读的中文条款OCR准确率>98%无需逐字核
5. **结论模板**:核实后向用户报告"核心数据准确+已发现的具体问题列表",让用户知道哪些可信哪些需注意
### 与全流程的关系
- 新做校区:OCR后立即跑 `ocr-integrity-check.py`(自动检测乱码)→ 高危区vision核实 → 写入md
- 事后核实(本场景):直接 pdftoppm + vision 比对关键页,快速出结论
- 两者互补:integrity-check 抓格式异常,vision比对抓语义错误(如空白处虚构文字)
## 与既有规则的关系
- 这是「第一铁律·逐字通读」「OCR 不可信回原件核」在**扫描件取文本**环节的落地配方。
- 费率符号(‰ vs %)的双跑/裁图核对走 `references/ocr-rate-symbol-verification.md`;本文管的是**整篇取文本 + 抬头/金额两类高频失真 + 二值化救援 + 信用代码修复 + 签章乱码处理 + 单位误读 + 乱码段落处理**。