Go to file
copper-xw 327b745055 填表完成后清理 Python 缓存
在差旅报销填表成功后删除 __pycache__,避免运行脚本产生的缓存目录残留在项目根目录。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-29 11:53:53 +08:00
.gitignore 完善差旅报销归档流程 2026-05-29 11:50:12 +08:00
README.md 完善差旅报销归档流程 2026-05-29 11:50:12 +08:00
差旅报销.py 填表完成后清理 Python 缓存 2026-05-29 11:53:53 +08:00
差旅费明细表.xlsx 保存差旅报销自动化基础文件 2026-05-28 19:28:23 +08:00

差旅报销自动化流程

给大模型的自动执行入口

当用户把本 README.md 提供给大模型,或让大模型在本目录处理差旅报销时,大模型应直接按下面流程开始执行,不需要用户再复制额外提示词:

  1. 先阅读本说明,确认当前目录是报销目录。

  2. 检查当前目录是否存在基础文件:差旅报销.py、差旅费明细表.xlsx,以及用户放在根目录的本次报销 PDF/图片附件。

  3. 自动运行 extract,脚本会先把根目录的 PDF/JPG/JPEG/PNG 移动到处理当天目录(如 20260529/),再提取 PDF 原文:

    python3 差旅报销.py extract
    

    Windows 环境可改用:

    py 差旅报销.py extract
    
  4. 读取本次处理目录中的 报销输出/pdf_raw_text.txt,按本文的 JSON Schema 分析出差人、日期、事由、费用分类、金额和证据。

  5. 如果同一笔滴滴/打车费用同时有行程单和电子发票,金额只按电子发票计入一次;行程单只作为附件和行程明细使用,不要与发票重复计入金额。

  6. 如果出差人、出差事由、出发地、目的地、日期、金额等关键信息无法从原文可靠判断,必须先问用户;不要编造。

  7. 将最终 JSON 保存为本次处理目录中的 报销输出/analyzed_result.json。

  8. 确认 questions 为空、金额合计一致、附件张数一致后,自动运行:

    python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json
    

    Windows 环境可改用:

    py 差旅报销.py fill 20260529/报销输出/analyzed_result.json
    
  9. 最后告诉用户生成的 Excel 文件路径和本次归档目录,以及是否有任何需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。

简单说:看到本 README 后,大模型应自动完成“提取 PDF 原文 → 分析生成 JSON → 填写 Excel”,只有在信息不确定时才暂停询问用户。


核心思想

这个流程分成三段:

  1. Python 提取原文:只把 PDF 内容提取成 TXT,不理解、不计算、不猜测。
  2. 大模型分析:读取 TXT,判断出差人、日期、事由、费用分类和金额,输出标准 JSON。
  3. Python 填表:只读取 JSON,把数据填入 Excel 模板,生成最终 XLSX。

也就是说:Python 只做机械衔接,大模型负责理解发票内容。


基础三件套

每次复用这个流程,只需要保留下面三个基础文件:

文件 作用
差旅费明细表.xlsx 原始 Excel 模板,脚本会复制它,不直接修改
差旅报销.py 唯一 Python 脚本,提供 extract / schema / fill 三个命令
README.md 本说明,给人和大模型看

PDF 发票、pdf_raw_text.txt、analyzed_result.json、最终 XLSX 都是每次报销产生的过程文件或结果文件。


为什么使用 XLSX

为了跨平台,模板统一使用 .xlsx。

这样脚本只依赖 Python 包:

pdfplumber  # 提取 PDF 文本
openpyxl    # 复制和填写 xlsx 模板

不再依赖 LibreOffice,也不需要系统命令做 .xls / .xlsx 转换。

如果你手里只有 .xls 模板,请先用 Excel、WPS 或 LibreOffice 手动另存为:

差旅费明细表.xlsx

依赖安装

Windows

py -m pip install pdfplumber openpyxl

运行时使用:

py 差旅报销.py extract
py 差旅报销.py fill 20260529/报销输出/analyzed_result.json

Linux / macOS

python3 -m pip install pdfplumber openpyxl

运行时使用:

python3 差旅报销.py extract
python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json

标准目录结构

每次开始处理前,根目录只需要保留基础文件,并把本次报销附件直接放到根目录:

报销目录/
├── 差旅费明细表.xlsx      # 必需:模板
├── 差旅报销.py             # 必需:脚本
├── README.md              # 必需:说明
├── 住宿.pdf               # 本次报销 PDF,运行 extract 后会被移动
├── 机票*.pdf              # 本次报销 PDF,运行 extract 后会被移动
├── 滴滴出行行程报销单*.pdf  # 本次报销 PDF,运行 extract 后会被移动
├── 滴滴电子发票*.pdf       # 本次报销 PDF,运行 extract 后会被移动
└── 打车截图.jpg            # 本次报销图片附件,运行 extract 后会被移动

运行 extract 后会先形成处理当天目录,例如:

报销目录/
└── 20260529/
    ├── 住宿.pdf
    ├── 机票*.pdf
    ├── 滴滴出行行程报销单*.pdf
    ├── 滴滴电子发票*.pdf
    ├── 打车截图.jpg
    └── 报销输出/
        └── pdf_raw_text.txt

运行 fill 成功后,会把当天目录重命名为本次归档目录,并保留 报销输出/ 结构:

报销目录/
└── 20260520-20260522_北京-广州_售后维修/
    ├── 住宿.pdf
    ├── 机票*.pdf
    ├── 滴滴出行行程报销单*.pdf
    ├── 滴滴电子发票*.pdf
    ├── 打车截图.jpg
    └── 报销输出/
        ├── pdf_raw_text.txt
        ├── analyzed_result.json
        └── 差旅费明细表_xxxx_20260520_广州_售后维修.xlsx

使用流程

Step 1:提取 PDF 原文

python3 差旅报销.py extract

Windows 可用:

py 差旅报销.py extract

运行时会把根目录的 PDF/JPG/JPEG/PNG 移动到处理当天目录,例如 20260529/。如果当天目录已存在且已有附件,脚本会询问是否合并;不合并会创建 20260529-1/、20260529-2/ 这样的新目录。

输出位于本次处理目录中,例如:

20260529/报销输出/pdf_raw_text.txt

这个文件按 PDF 文件分块保存原始文本,例如:

============================================================
文件: 20260529/住宿.pdf
============================================================
电子发票(增值税专用发票)
...

============================================================
文件: 20260529/滴滴出行行程报销单A.pdf
============================================================
滴滴出行-行程单
...

注意:这一步不会提取金额、不会判断类型、不会计算总额。


Step 2:让大模型分析 TXT 并输出 JSON

把本次处理目录中的 报销输出/pdf_raw_text.txt 交给大模型,让大模型生成同目录下的 JSON,例如:

20260529/报销输出/analyzed_result.json

可以先查看 JSON 模板:

python3 差旅报销.py schema

也可以直接输出模板文件:

python3 差旅报销.py schema --output

输出:

报销输出/schema_example.json

给大模型的推荐提示词

请根据本 README 自动处理当前目录的差旅报销。

你的任务:
1. 先运行 `python3 差旅报销.py extract`,脚本会把根目录 PDF/JPG/JPEG/PNG 移动到本次处理目录,并提取 PDF 原文。
2. 读取 extract 输出提示中的 `报销输出/pdf_raw_text.txt`,从 PDF 原文中识别出差人、出差事由、出发日期、结束日期、出发地、目的地、出行方式、出差天数。
3. 将费用分为:住宿、交通、饮食、其他。
4. 每个费用类别都要给出金额、单据张数、说明。
5. 计算附件总张数、报销总额、预借差旅费、补领金额、退还金额。
6. 每个金额必须写入 evidence,说明来自哪个文件、依据是哪段文本。
7. 如果同一笔滴滴/打车费用同时有行程单和电子发票,金额只按电子发票计入一次;行程单只作为附件和行程明细使用,不要与发票重复计入金额。
8. 如果某个字段无法可靠判断,必须先问用户,不要编造;在未确认前不要填表。
9. 将最终 JSON 保存到本次处理目录中的 `报销输出/analyzed_result.json`,JSON 必须符合 README 中的 schema。
10. 确认 `questions` 为空、金额合计一致、附件张数一致后,运行 `python3 差旅报销.py fill 本次处理目录/报销输出/analyzed_result.json` 生成最终 Excel,并把本次目录重命名为 `出发日期-结束日期_出发地-目的地_事由简写` 格式。
11. 最后告诉用户生成的 Excel 文件路径和本次归档目录,并列出需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。

JSON Schema

大模型必须输出如下结构:

{
  "meta": {
    "出差人": "xxxx",
    "出差事由": "前往广州进行售后维修工作",
    "出发日期": "2026/5/20",
    "结束日期": "2026/5/22",
    "出发地": "北京",
    "目的地": "广州",
    "出行方式": "飞机",
    "出差天数": 3,
    "事由简写": "售后维修"
  },
  "expenses": {
    "住宿": {
      "金额": 510.0,
      "单据张数": 1,
      "说明": "广州市番禺明乐宾馆住宿费"
    },
    "交通": {
      "金额": 2013.77,
      "单据张数": 5,
      "说明": "机票1690.00 + 滴滴323.77"
    },
    "饮食": {
      "金额": 0.0,
      "单据张数": 0,
      "说明": "未发现餐饮发票"
    },
    "其他": {
      "金额": 0.0,
      "单据张数": 0,
      "说明": "无其他费用"
    }
  },
  "summary": {
    "附件总张数": 6,
    "报销总额": 2523.77,
    "预借差旅费": 0.0,
    "补领金额": 2523.77,
    "退还金额": 0.0
  },
  "evidence": [
    {
      "文件": "住宿.pdf",
      "类别": "住宿",
      "金额": 510.0,
      "依据": "价税合计(小写)¥510.00"
    },
    {
      "文件": "机票预订出行订单尾号87377629-电子普通发票.pdf",
      "类别": "交通",
      "金额": 1690.0,
      "依据": "价税合计(小写)¥1690.00"
    }
  ],
  "questions": []
}

字段说明

字段 说明
meta.出差人 出差人姓名,不确定就问用户
meta.出差事由 出差目的,不确定就问用户
meta.出发日期 / meta.结束日期 格式建议为 YYYY/M/D
meta.出差天数 含首尾日期,例如 5/20 至 5/22 为 3 天
expenses.住宿 酒店住宿类费用
expenses.交通 机票、火车、打车等交通类费用
expenses.饮食 餐饮费用,没有就 0
expenses.其他 不属于前三类的费用,没有就 0
summary.附件总张数 四类费用单据张数之和
summary.报销总额 四类费用金额之和
evidence 每笔金额的来源证据,便于人工核对
questions 如果大模型无法判断,可以列出问题;最终填表前应为空

Step 3:读取 JSON 并填表

python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json

Windows 可用:

py 差旅报销.py fill 20260529/报销输出/analyzed_result.json

脚本会:

  1. 校验 JSON 字段是否完整
  2. 校验金额合计是否一致
  3. 校验附件张数是否一致
  4. 复制 差旅费明细表.xlsx
  5. 用 openpyxl 填入固定单元格
  6. 输出最终 XLSX 到本次处理目录的 报销输出/
  7. 将本次处理目录重命名为 出发日期-结束日期_出发地-目的地_事由简写 格式

输出文件命名规则:

差旅费明细表_[出差人]_[出发日期YYYYMMDD]_[目的地]_[事由简写].xlsx

示例:

20260520-20260522_北京-广州_售后维修/报销输出/差旅费明细表_xxxx_20260520_广州_售后维修.xlsx

Excel 填写位置

脚本只填写这些单元格:

单元格 内容
E3 出差事由
A6 出发日期-结束日期
B6 出发地-目的地
C6 出差天数
D6 出行方式
H6 / I6 住宿单据张数 / 住宿金额
H7 / I7 饮食单据张数 / 饮食金额
H8 / I8 交通单据张数 / 交通金额
H9 / I9 其他单据张数 / 其他金额

脚本不会覆盖公式单元格,例如:

单元格 公式
H2 附件张数合计
F9 天数合计
C10 报销总额
H10 补领金额
H11 退还金额

校验规则

fill 命令会检查:

summary.报销总额 = 住宿金额 + 交通金额 + 饮食金额 + 其他金额
summary.附件总张数 = 住宿单据张数 + 交通单据张数 + 饮食单据张数 + 其他单据张数

如果不一致,脚本会停止,不会写 Excel。


本次数据示例

{
  "meta": {
    "出差人": "xxxx",
    "出差事由": "前往广州进行售后维修工作",
    "出发日期": "2026/5/20",
    "结束日期": "2026/5/22",
    "出发地": "北京",
    "目的地": "广州",
    "出行方式": "飞机",
    "出差天数": 3,
    "事由简写": "售后维修"
  },
  "expenses": {
    "住宿": {"金额": 510.0, "单据张数": 1, "说明": "住宿费"},
    "交通": {"金额": 2013.77, "单据张数": 5, "说明": "机票1690.00 + 滴滴323.77"},
    "饮食": {"金额": 0.0, "单据张数": 0, "说明": "无餐饮发票"},
    "其他": {"金额": 0.0, "单据张数": 0, "说明": "无其他费用"}
  },
  "summary": {
    "附件总张数": 6,
    "报销总额": 2523.77,
    "预借差旅费": 0.0,
    "补领金额": 2523.77,
    "退还金额": 0.0
  },
  "evidence": [],
  "questions": []
}

常见错误

1. 找不到模板文件:差旅费明细表.xlsx

确认当前目录存在:

差旅费明细表.xlsx

如果只有 .xls,请先用 Excel / WPS / LibreOffice 另存为 .xlsx。

2. 报销总额不一致

说明 JSON 里的总额和分类费用合计不一致,检查:

住宿 + 交通 + 饮食 + 其他

3. 附件总张数不一致

说明 JSON 里的附件总数和分类单据张数合计不一致。

4. 缺少 Python 库

Windows:

py -m pip install pdfplumber openpyxl

Linux / macOS:

python3 -m pip install pdfplumber openpyxl