- 新增 Step 3(复核):生成 JSON 后必须重新读取 pdf_raw_text.txt 逐项核对 - 自动执行入口新增复核步骤(第7条) - 给大模型的推荐提示词新增复核指令(第9条) - 详细列出 6 项复核清单:金额、人员、日期、费用分类、行程单处理、单据张数
548 lines
17 KiB
Markdown
548 lines
17 KiB
Markdown
# 差旅报销自动化流程
|
||
|
||
## 给大模型的自动执行入口
|
||
|
||
当用户把本 `README.md` 提供给大模型,或让大模型在本目录处理差旅报销时,大模型应直接按下面流程开始执行,不需要用户再复制额外提示词:
|
||
|
||
1. 先阅读本说明,确认当前目录是报销目录。
|
||
2. 检查当前目录是否存在基础文件:`差旅报销.py`、`差旅费明细表.xlsx`,以及用户放在根目录的本次报销 PDF/图片附件。
|
||
3. 自动运行 `extract`,脚本会先把根目录的 PDF/JPG/JPEG/PNG 移动到处理当天目录(如 `20260529/`),再提取 PDF 原文:
|
||
|
||
```bash
|
||
python3 差旅报销.py extract
|
||
```
|
||
|
||
Windows 环境可改用:
|
||
|
||
```powershell
|
||
py 差旅报销.py extract
|
||
```
|
||
|
||
4. 读取本次处理目录中的 `报销输出/pdf_raw_text.txt`,按本文的 JSON Schema 分析出差人、日期、事由、费用分类、金额和证据。
|
||
5. 如果同一笔滴滴/打车费用同时有行程单和电子发票,金额只按电子发票计入一次;行程单只作为附件和行程明细使用,不要与发票重复计入金额。
|
||
6. 如果出差人、出差事由、出发地、目的地、日期、金额等关键信息无法从原文可靠判断,必须先问用户;不要编造。
|
||
7. **复核**:重新读取 `pdf_raw_text.txt`,逐项核对生成的 JSON 是否与 PDF 原文中金额、日期、人员等信息一致;如果发现错误,修正后重新生成 JSON。
|
||
8. 将最终 JSON 保存为本次处理目录中的 `报销输出/analyzed_result.json`。
|
||
9. 确认 `questions` 为空、金额合计一致、附件张数一致后,自动运行:
|
||
|
||
```bash
|
||
python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||
```
|
||
|
||
Windows 环境可改用:
|
||
|
||
```powershell
|
||
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 包:
|
||
|
||
```text
|
||
pdfplumber # 提取 PDF 文本
|
||
openpyxl # 复制和填写 xlsx 模板
|
||
```
|
||
|
||
不再依赖 LibreOffice,也不需要系统命令做 `.xls` / `.xlsx` 转换。
|
||
|
||
如果你手里只有 `.xls` 模板,请先用 Excel、WPS 或 LibreOffice 手动另存为:
|
||
|
||
```text
|
||
差旅费明细表.xlsx
|
||
```
|
||
|
||
---
|
||
|
||
## 依赖安装
|
||
|
||
### 第一步:检测 Python 环境
|
||
|
||
在运行脚本前,应先确认 Python 和 pip 可用。以下是各平台的检测方法:
|
||
|
||
#### Windows
|
||
|
||
```powershell
|
||
# 检测 Python 是否可用
|
||
py --version
|
||
py -m pip --version
|
||
|
||
# 如果提示找不到 python,或版本异常,尝试:
|
||
where python
|
||
where pip
|
||
```
|
||
|
||
**注意**:Windows 应用商店的 Python(从 Microsoft Store 安装)可能有执行限制。如果 `py` 命令不可用或行为异常,请从 [python.org](https://www.python.org/downloads/) 或使用 conda 安装官方版本。
|
||
|
||
#### Linux / macOS
|
||
|
||
```bash
|
||
python3 --version
|
||
python3 -m pip --version
|
||
|
||
# 如果提示找不到 python3,尝试:
|
||
which python3
|
||
which pip3
|
||
```
|
||
|
||
---
|
||
|
||
### 第二步:安装依赖
|
||
|
||
确认 Python 可用后,安装所需的两个库:
|
||
|
||
#### Windows
|
||
|
||
```powershell
|
||
py -m pip install pdfplumber openpyxl
|
||
```
|
||
|
||
#### Linux / macOS
|
||
|
||
```bash
|
||
python3 -m pip install pdfplumber openpyxl
|
||
```
|
||
|
||
---
|
||
|
||
### 第三步:验证安装
|
||
|
||
```bash
|
||
python3 -c "import pdfplumber, openpyxl; print('依赖安装成功')"
|
||
```
|
||
|
||
如果提示 `ModuleNotFoundError`,说明安装失败,需要重新执行上一步。
|
||
|
||
---
|
||
|
||
### 常见问题
|
||
|
||
| 问题 | 解决方法 |
|
||
|------|----------|
|
||
| `py` 命令找不到 | 从 python.org 下载安装包,勾选 "Add Python to PATH" |
|
||
| `python3` 命令找不到 | `sudo apt install python3`(Linux)或从 python.org 安装(macOS)|
|
||
| 应用商店 Python 执行受限 | 卸载应用商店版本,安装官方 Python |
|
||
| pip 安装失败 | 尝试 `python3 -m pip install --upgrade pip` 先升级 pip |
|
||
|
||
---
|
||
|
||
## 标准目录结构
|
||
|
||
每次开始处理前,根目录只需要保留基础文件,并把本次报销附件直接放到根目录:
|
||
|
||
```text
|
||
报销目录/
|
||
├── 差旅费明细表.xlsx # 必需:模板
|
||
├── 差旅报销.py # 必需:脚本
|
||
├── README.md # 必需:说明
|
||
├── 住宿.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||
├── 机票*.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||
├── 滴滴出行行程报销单*.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||
├── 滴滴电子发票*.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||
└── 打车截图.jpg # 本次报销图片附件,运行 extract 后会被移动
|
||
```
|
||
|
||
运行 `extract` 后会先形成处理当天目录,例如:
|
||
|
||
```text
|
||
报销目录/
|
||
└── 20260529/
|
||
├── 住宿.pdf
|
||
├── 机票*.pdf
|
||
├── 滴滴出行行程报销单*.pdf
|
||
├── 滴滴电子发票*.pdf
|
||
├── 打车截图.jpg
|
||
└── 报销输出/
|
||
└── pdf_raw_text.txt
|
||
```
|
||
|
||
运行 `fill` 成功后,会把当天目录重命名为本次归档目录,并保留 `报销输出/` 结构:
|
||
|
||
```text
|
||
报销目录/
|
||
└── 20260520-20260522_北京-广州_售后维修/
|
||
├── 住宿.pdf
|
||
├── 机票*.pdf
|
||
├── 滴滴出行行程报销单*.pdf
|
||
├── 滴滴电子发票*.pdf
|
||
├── 打车截图.jpg
|
||
└── 报销输出/
|
||
├── pdf_raw_text.txt
|
||
├── analyzed_result.json
|
||
└── 差旅费明细表_xxxx_20260520_广州_售后维修.xlsx
|
||
```
|
||
|
||
---
|
||
|
||
## 使用流程
|
||
|
||
### Step 1:提取 PDF 原文
|
||
|
||
```bash
|
||
python3 差旅报销.py extract
|
||
```
|
||
|
||
Windows 可用:
|
||
|
||
```powershell
|
||
py 差旅报销.py extract
|
||
```
|
||
|
||
运行时会把根目录的 PDF/JPG/JPEG/PNG 移动到处理当天目录,例如 `20260529/`。如果当天目录已存在且已有附件,脚本会询问是否合并;不合并会创建 `20260529-1/`、`20260529-2/` 这样的新目录。
|
||
|
||
输出位于本次处理目录中,例如:
|
||
|
||
```text
|
||
20260529/报销输出/pdf_raw_text.txt
|
||
```
|
||
|
||
这个文件按 PDF 文件分块保存原始文本,例如:
|
||
|
||
```text
|
||
============================================================
|
||
文件: 20260529/住宿.pdf
|
||
============================================================
|
||
电子发票(增值税专用发票)
|
||
...
|
||
|
||
============================================================
|
||
文件: 20260529/滴滴出行行程报销单A.pdf
|
||
============================================================
|
||
滴滴出行-行程单
|
||
...
|
||
```
|
||
|
||
注意:这一步**不会**提取金额、不会判断类型、不会计算总额。
|
||
|
||
---
|
||
|
||
### Step 2:让大模型分析 TXT 并输出 JSON
|
||
|
||
把本次处理目录中的 `报销输出/pdf_raw_text.txt` 交给大模型,让大模型生成同目录下的 JSON,例如:
|
||
|
||
```text
|
||
20260529/报销输出/analyzed_result.json
|
||
```
|
||
|
||
可以先查看 JSON 模板:
|
||
|
||
```bash
|
||
python3 差旅报销.py schema
|
||
```
|
||
|
||
也可以直接输出模板文件:
|
||
|
||
```bash
|
||
python3 差旅报销.py schema --output
|
||
```
|
||
|
||
输出:
|
||
|
||
```text
|
||
报销输出/schema_example.json
|
||
```
|
||
|
||
### Step 3(复核):核对 JSON 与 PDF 原文
|
||
|
||
**重要**:生成 JSON 后,必须重新读取 `pdf_raw_text.txt`,逐项核对:
|
||
|
||
1. **金额核对**:每个 evidence.金额 是否与 PDF 原文中该文件的实际金额一致(检查"价税合计"或"合计"字段)。
|
||
2. **人员核对**:出差人姓名是否与 PDF 原文中一致。
|
||
3. **日期核对**:出发/结束日期是否与行程单或机票日期一致。
|
||
4. **费用分类核对**:每笔费用是否归类正确(住宿/交通/饮食/其他)。
|
||
5. **行程单处理**:同一笔滴滴费用同时有行程单和电子发票时,确认金额只计入了电子发票,行程单不重复计入。
|
||
6. **单据张数核对**:附件总张数是否与 evidence 条目数一致。
|
||
|
||
如果发现错误,修正 JSON 后重新核对,直到完全一致后再进入 Step 4。
|
||
|
||
---
|
||
|
||
### Step 4:读取 JSON 并填表
|
||
|
||
脚本会读取 JSON 并填入 Excel:
|
||
|
||
```bash
|
||
python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||
```
|
||
|
||
Windows 可用:
|
||
|
||
```powershell
|
||
py 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||
```
|
||
|
||
脚本会:
|
||
|
||
1. 校验 JSON 字段是否完整
|
||
2. 校验金额合计是否一致
|
||
3. 校验附件张数是否一致
|
||
4. 复制 `差旅费明细表.xlsx`
|
||
5. 用 openpyxl 填入固定单元格
|
||
6. 输出最终 XLSX 到本次处理目录的 `报销输出/`
|
||
7. 将本次处理目录重命名为 `出发日期-结束日期_出发地-目的地_事由简写` 格式
|
||
|
||
输出文件命名规则:
|
||
|
||
```text
|
||
差旅费明细表_[出差人]_[出发日期YYYYMMDD]_[目的地]_[事由简写].xlsx
|
||
```
|
||
|
||
示例:
|
||
|
||
```text
|
||
20260520-20260522_北京-广州_售后维修/报销输出/差旅费明细表_xxxx_20260520_广州_售后维修.xlsx
|
||
```
|
||
|
||
#### 给大模型的推荐提示词
|
||
|
||
```text
|
||
请根据本 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. **复核**:重新读取 `pdf_raw_text.txt`,逐项核对生成的 JSON 是否与 PDF 原文中金额、日期、人员等信息一致;如果发现错误,修正后重新生成 JSON。
|
||
10. 将最终 JSON 保存到本次处理目录中的 `报销输出/analyzed_result.json`,JSON 必须符合 README 中的 schema。
|
||
11. 确认 `questions` 为空、金额合计一致、附件张数一致后,运行 `python3 差旅报销.py fill 本次处理目录/报销输出/analyzed_result.json` 生成最终 Excel,并把本次目录重命名为 `出发日期-结束日期_出发地-目的地_事由简写` 格式。
|
||
12. 最后告诉用户生成的 Excel 文件路径和本次归档目录,并列出需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。
|
||
```
|
||
|
||
---
|
||
|
||
## JSON Schema
|
||
|
||
大模型必须输出如下结构:
|
||
|
||
```json
|
||
{
|
||
"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` | 如果大模型无法判断,可以列出问题;最终填表前应为空 |
|
||
|
||
---
|
||
|
||
## Excel 填写位置
|
||
|
||
脚本只填写这些单元格:
|
||
|
||
| 单元格 | 内容 |
|
||
|--------|------|
|
||
| `E3` | 出差事由 |
|
||
| `A6` | 出发日期-结束日期 |
|
||
| `B6` | 出发地-目的地 |
|
||
| `C6` | 出差天数 |
|
||
| `D6` | 出行方式 |
|
||
| `H6` / `I6` | 住宿单据张数 / 住宿金额 |
|
||
| `H7` / `I7` | 饮食单据张数 / 饮食金额 |
|
||
| `H8` / `I8` | 交通单据张数 / 交通金额 |
|
||
| `H9` / `I9` | 其他单据张数 / 其他金额 |
|
||
|
||
脚本**不会覆盖公式单元格**,例如:
|
||
|
||
| 单元格 | 公式 |
|
||
|--------|------|
|
||
| `H2` | 附件张数合计 |
|
||
| `F9` | 天数合计 |
|
||
| `C10` | 报销总额 |
|
||
| `H10` | 补领金额 |
|
||
| `H11` | 退还金额 |
|
||
|
||
---
|
||
|
||
## 校验规则
|
||
|
||
`fill` 命令会检查:
|
||
|
||
```text
|
||
summary.报销总额 = 住宿金额 + 交通金额 + 饮食金额 + 其他金额
|
||
summary.附件总张数 = 住宿单据张数 + 交通单据张数 + 饮食单据张数 + 其他单据张数
|
||
```
|
||
|
||
如果不一致,脚本会停止,不会写 Excel。
|
||
|
||
---
|
||
|
||
## 本次数据示例
|
||
|
||
```json
|
||
{
|
||
"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`
|
||
|
||
确认当前目录存在:
|
||
|
||
```text
|
||
差旅费明细表.xlsx
|
||
```
|
||
|
||
如果只有 `.xls`,请先用 Excel / WPS / LibreOffice 另存为 `.xlsx`。
|
||
|
||
### 2. `报销总额不一致`
|
||
|
||
说明 JSON 里的总额和分类费用合计不一致,检查:
|
||
|
||
```text
|
||
住宿 + 交通 + 饮食 + 其他
|
||
```
|
||
|
||
### 3. `附件总张数不一致`
|
||
|
||
说明 JSON 里的附件总数和分类单据张数合计不一致。
|
||
|
||
### 4. 缺少 Python 库
|
||
|
||
Windows:
|
||
|
||
```powershell
|
||
py -m pip install pdfplumber openpyxl
|
||
```
|
||
|
||
Linux / macOS:
|
||
|
||
```bash
|
||
python3 -m pip install pdfplumber openpyxl
|
||
```
|