单据图片按表整理、盖章、合成可交 PDF
目的
把一堆散的单据图片,按一张「清单表」的行列顺序排好、盖章、合成一本带连续页码的 PDF,并自动标出哪些序号缺图。核心前提是「图片多 + 有张清单要对齐」。
何时使用
- 用户有一批单据/凭证/回单照片,要按某张清单(结算表、报销明细、流水)的序号排序、盖章、合成一本能直接交出去的 PDF。
- 用户要一份缺图清单(双缺/缺用款单/缺回单)或证据目录表(编号|证据名称|证据形式|页码|证明内容)。
- 典型场景:诉讼/仲裁证据整理、工程结算三方核账、报销单据归档、审计资料提交、年度凭证装订、个人账目票据整理。
- 不适用:只有几张图不用排序、纯文字表无单据图、图无任何序号且不愿 OCR/预命名。
输入
- 一张清单表(xls/xlsx),列含序号、日期、收付款人、金额(借贷)、方向(收/付)、备注。
- 图片来源(六种形态之一,见「图片源形态」)。
- 可选流水表(用于补凭证编号列、按对方+金额+日期对碰)。
输出(四件套 + 成册)
- 一份合并 PDF(各分册并成一个文件、逐页连续编页码)——最终交付件。
- 对照表 / 页码映射(序号→页码→图类)。
- 缺图清单(三档:双缺 / 缺用款单 / 缺回单)。
- 目录表(证据清单)(编号|证据名称|证据形式|页码|证明内容)。
核心流程(6 步)
详细实现见 references/methodology.md。要点如下。
步骤 1 读表成任务清单
- 列映射参数化(序号/日期/对方/金额/方向列由输入指定,不写死)。
- 方向判定:贷方>0=付款(该有用款单+回单);借方>0=收款(仅回单,无用款单);负数反向。
- 剔除金额=0 或对方为空的父行(其单据归在父序号下,不计入对账笔数)。
步骤 2 抽图 + 三要素匹配
- 图片源六型(见下)。任何源最终落到映射 M:序号→[{图类,文件}](图类=用款单/回单/附件N)。
- 匹配用三要素(非纯序号,序号会重号/一对多):对方名归一化(剥「外壳」取内层全称)+ 金额±0.5 + 日期窗(参数
date_window_days,默认 ±120 天,可按项目实际日期跨度收紧;但收得太紧(如 10 天)会漏掉日期差更大的回单,须权衡)。撞号用付款日期就近配对。 - 回单对方名常写本公司视角,须跨行(同项目+同金额+同日期)补查,不能只卡对方名。
步骤 3 排序重命名
- 一张多图加
_2、_3。 - 排序用自然序(用
scripts/util_io.py的 natural_sort_key):1-1 < 1-10 < 2-1,勿用字符串或浮点版本号比较。
步骤 4 盖章
- 两行红底白字(项目名/标题 + 付款编号),长名缩字号;竖横自适应。
- 图片转正(拍照侧图必做):手机/微信导出的单据照常是侧躺的(EXIF
Orientation=8≈ 旋转 90°)。盖章前必须ImageOps.exif_transpose照 EXIF 标记把像素转正——与用户在文件管理器里看到的朝向完全一致,是金标准。不要依赖 OCR 角度分类器自动转正(它会把侧图扶正后才识别,让人误以为已正、实则章盖错位);也不要手工逐张旋转(千张级不现实,且易漏转)。 /Rotate=180须先建正向页(show_pdf_page(rotate=180))再叠章。- 银行回单必做二维码避让:探测二维码 bbox,把章贴到「标题右缘与二维码左缘之间空白」,缩章并下让日期行(章压二维码=整块遮挡收款人账号,废章)。
步骤 5 合成 + 三件套
- pypdf 6+ 用 PdfWriter(无 PdfMerger),合并前先拷分片再删临时目录(否则静默丢成品)。
- 对照表逐编号记页码+图类;缺图清单三档(收款行永不计缺用款单/双缺)。
步骤 6 成册 + 目录表
- 各分册合并成一个 PDF,逐页连续编页码。
- 目录表页码必须来自合并后真实页序(先生成页码映射再回填),否则「页数对≠页序对」整张目录作废。
图片源形态(前置,详见 references/methodology.md 步骤2前置)
- A 内嵌流水:DISPIMG 挂在流水行,xls 解压取媒体 / xlsx 取 xl/media,按行+行内位置还原图类;大文件防 OOM。
- B 文件夹按序号(默认):文件名含序号,glob 解析入 M,无需流水表。
- C 子文件夹=章名:文件名=编号+单据类型,扫描目录取编号核对入 M。
- D 回单 PDF:一页多张独立回单,先按槽位裁切再三要素匹配,勿整页盖章。
- E 扫描 PDF:每页多张用暗度掩膜(gray<235)裁切,页尺寸统一。
- F 无命名图:OCR 预命名——F1 全自动(本地引擎)/ F2 半自动+抽验(推荐)/ F3 借流水反查免命名。
关键坑(必看)
- 大文件 OOM:1G+ Excel 只读/分批,分钟级任务后台跑。
- 页数对≠页序对:必须逐编号抽页校验。
- 章压二维码:银行回单必须避让。
- 版本号不覆盖:每版另存。
- 改 Excel 前先备份 .bak。
- 对方名「外壳」须剥(结算表写「甲方(某单位全称)」而流水写简称/真名)。
- 流水项目字段可能不单列,需建项目映射。
- 回单对方名写本公司,须跨行补查。
- 并发写同文件污染:重算脚本单进程。
- 图片侧躺(EXIF 方向):拍照/微信导出的单据多为侧图(EXIF=8)。盖章前必须
exif_transpose照 EXIF 自动转正;别信 OCR 自动转正(掩盖真实朝向)、别手工逐张转(不现实)。
复用脚本
scripts/util_io.py:natural_sort_key()(序号自然序)、safe_name()(去除 Windows/PDF 不支持的特殊字符<>:"|?*)。每次项目都用,直接 import 复用,不重写。
执行纪律(用户铁律)
- 版本号不覆盖:迭代产物每版另存,绝不覆盖原扫描件。
- 反对过度设计:能用半自动+抽验就别上全自动 OCR;脚本复制上版改 4 常量(SRC/OUTDIR/PROJ/输出名)即可。
- 抽验不能省:先抽验 20-30 笔确认口径再全量;合并 PDF 后逐编号抽页校验。
- 脱敏:写进 skill 的文档/脚本不得出现真实单位名、银行名、项目真名(用「甲方/某银行/本案例」),不写死真实主体。