「发票报销」Skill:把贴发票这件事交给 AI

一条命令,从一堆零散的电子发票 PDF、微信截图、扫描水单,变成一份可以直接提交审批的报销汇总 PDF。本文记录这个 WorkBuddy Skill 的设计思路与技术实现。

一、痛点:报销季的手工地狱

每次出差回来,报销材料的整理都是一场修行:
  • 电子发票 PDF 若干张(数电票,各平台下载)
  • 微信里保存的发票截图、酒店水单照片若干张
  • 夸克扫描王扫的纸质收据若干份
手工流程是:逐张打开 → 抄发票号、日期、金额 → 拼成 Excel → 排版打印 → 贴票。一次报销少则半小时,多则一小时,而且抄错一个发票号就要重来。
这类任务的特点恰好是 AI 的舒适区:模式固定、规则明确、重复劳动。于是就有了「发票报销」Skill。

二、设计思路:文件夹即报销期间

Skill 约定了一个极简的目录协议,用户唯一要做的事是把同一批报销的材料丢进同一个文件夹:
在此基础上,整个流水线只需要一条命令:
(目录参数可省略,默认 ~/Desktop/发票管理)

三条核心设计决策

  1. 文件夹名即业务标识:文件夹名(如日期区间)直接成为报告标题和输出文件名,无需额外配置。
  1. .done 后缀实现幂等:处理完的文件夹改名为 20260718-20260723.done,重复执行自动跳过,中断重跑零成本。
  1. 双类型分流:电子发票(有文本层的 PDF)走正则解析;扫描件/图片走多模态视觉识别。两条路径各司其职。

三、流水线全景

notion image
整条流水线四个步骤、两次分流:入口处按"PDF 有无文本层"分成正则解析与多模态识别双路径(确定性归代码,智能归模型);出口处按"审批用还是打印用"分成汇总 PDF 与水单 PDF 双输出。JSON 作为中间产物把提取与生成解耦,.done 后缀形成幂等闭环。

四、技术拆解

Skill 由三个 Python 脚本组成,串成一个数据流水线(JSON 作为中间产物):
脚本
职责
scripts/extract_invoices.py
扫描目录,提取发票信息,输出 JSON
scripts/generate_summary_pdf.py
读取 JSON,为每个文件夹生成 PDF 汇总报告
scripts/rename_folders.py
将已处理文件夹重命名加 .done
依赖:Python 3.6+、PyMuPDF(fitz)、Pillow、reportlab。

4.1 步骤一:双路径提取(extract_invoices.py)

核心是一个分流判断——PDF 里有没有文本层:
路径 A:电子发票正则解析。 数电票的文本层结构非常规整,发票号(20 位数字)、开票日期、购买方/销售方、三段金额(金额/税额/价税合计)都能用正则稳定提取:
路径 B:扫描件/图片多模态识别。 银行水单、酒店收据这类扫描件没有文本层,脚本只负责把图片从 PDF 里抽出来(存到 /tmp),打上 needs_ocr: true 标记,剩下的交给 AI 的视觉能力逐张识别。
这里我沿用了 ayee-picture2markdown 沉淀的 Strict Faithfulness(严格忠实)铁律:
  • 只输出图片上实际可见的内容,绝不推断、绝不臆造
  • 看不清的字用 □ 或 [?] 占位,并标注「识别不确定」
  • 宁可留空,不可编造——报销单上一个编造的发票号比一个空格严重得多

4.2 步骤二:生成 PDF 报告(generate_summary_pdf.py)

每个文件夹生成两份 PDF,分而治之是刻意设计:
  • 汇总 PDF(20260718-20260723.pdf):发票表格(序号/文件名/类型/发票号/公司/金额)+ 合计金额 + 电子发票原件图片。审批人一页看完所有信息,附上原件,免翻附件。
  • 水单 PDF(20260724-20260726_水单.pdf):扫描件(水单、收据)单独成册,方便出纳查看打印。
中文字体是 reportlab 的经典坑。解决方案是按优先级依次尝试 macOS 系统字体:

4.3 步骤三:.done 幂等收尾(rename_folders.py)

处理完即打标。下次执行时 extract_invoices.py 里的目录过滤自动跳过:
这是整个 Skill 里我最喜欢的一行代码——幂等性把"中途失败要不要重跑"这个问题从设计上消灭了。

五、最终输出

一个细节:20260723-20260724(缺发票) 这种文件夹名里的中文备注会被原样保留到输出文件名——文件夹命名协议天然支持用户自由扩展,无需额外字段。

六、已发布 SkillHub:一行命令装走

这个 Skill 已经发布到 SkillHub:@user_ff7200d6/expensereimbursement(v1.0.0)。任何 AI Coding 客户端都可以直接安装使用。

安装方法

先装 SkillHub CLI(macOS / Linux 一行命令):
然后按你所用客户端的 skills 目录安装,以 WorkBuddy 为例:
各客户端的 skills 目录对照(来自 SkillHub 安装文档):
AI Client
Skills 路径
Claude Code
~/.claude/skills/
Cursor
~/.cursor/skills/
Windsurf
~/.codeium/windsurf/skills/
Codex
~/.codex/skills/ 或 .agents/skills/
Gemini CLI
~/.gemini/skills/
WorkBuddy
~/.workbuddy/skills/
注意:--dir 是必需参数,必须指向你客户端的真实 skills 目录,否则装到默认的 ./skills/ 不会被识别;安装后需重启会话生效。
装好之后,一句 /发票报销 就能跑起来整条流水线——这也是我理解 Skill 生态的正确打开方式:把踩过的坑、定下的铁律、写好的脚本打包成一个可分发的工件,别人拿到的不是一段要自己复现的教程,而是五秒钟内可用的能力。

七、经验总结

  1. 让确定性归代码,让智能归模型。 正则能稳定搞定的(电子发票文本层)绝不用大模型;正则无能为力的(扫描件视觉理解)才交给多模态。这条边界划清楚后,准确率和速度兼得。
  1. 幂等性是批处理脚本的生命线。 一个 .done 后缀胜过十页容错逻辑。
  1. Strict Faithfulness 在财务场景是刚需不是洁癖。 识别不确定就标注不确定,AI 输出可信度低于 100% 时,宁可让用户人工补一眼。
  1. JSON 作为流水线中间产物。 提取和生成分离,任何一步都可以单独重跑、单独调试。

本文对应 Skill 已发布至 SkillHub(@user_ff7200d6/expensereimbursement),关联阅读:ayee-picture2markdown(图片识别忠实原则的来源)、ayee-arch-designer(Skill 的架构设计规范)。
Skill-oushudb_explain模板说明
Loading...