完善差旅报销归档流程
将附件整理到本次处理目录,并在填表后按行程信息归档,避免过程文件和报销附件污染根目录。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4e869ba4b3
commit
be21f0ab5a
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -1,3 +1,12 @@
|
|||
*.pdf
|
||||
*.jpg
|
||||
报销输出/
|
||||
*.jpeg
|
||||
*.png
|
||||
*.txt
|
||||
*.json
|
||||
*.xlsx
|
||||
报销输出/
|
||||
__pycache__/
|
||||
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/
|
||||
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-*/
|
||||
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_*/
|
||||
|
|
|
|||
99
README.md
99
README.md
|
|
@ -5,8 +5,8 @@
|
|||
当用户把本 `README.md` 提供给大模型,或让大模型在本目录处理差旅报销时,大模型应直接按下面流程开始执行,不需要用户再复制额外提示词:
|
||||
|
||||
1. 先阅读本说明,确认当前目录是报销目录。
|
||||
2. 检查当前目录是否存在基础文件:`差旅报销.py`、`差旅费明细表.xlsx`、本次报销 PDF。
|
||||
3. 如果 `报销输出/pdf_raw_text.txt` 不存在,自动运行:
|
||||
2. 检查当前目录是否存在基础文件:`差旅报销.py`、`差旅费明细表.xlsx`,以及用户放在根目录的本次报销 PDF/图片附件。
|
||||
3. 自动运行 `extract`,脚本会先把根目录的 PDF/JPG/JPEG/PNG 移动到处理当天目录(如 `20260529/`),再提取 PDF 原文:
|
||||
|
||||
```bash
|
||||
python3 差旅报销.py extract
|
||||
|
|
@ -18,23 +18,23 @@
|
|||
py 差旅报销.py extract
|
||||
```
|
||||
|
||||
4. 读取 `报销输出/pdf_raw_text.txt`,按本文的 JSON Schema 分析出差人、日期、事由、费用分类、金额和证据。
|
||||
4. 读取本次处理目录中的 `报销输出/pdf_raw_text.txt`,按本文的 JSON Schema 分析出差人、日期、事由、费用分类、金额和证据。
|
||||
5. 如果同一笔滴滴/打车费用同时有行程单和电子发票,金额只按电子发票计入一次;行程单只作为附件和行程明细使用,不要与发票重复计入金额。
|
||||
6. 如果出差人、出差事由、出发地、目的地、日期、金额等关键信息无法从原文可靠判断,必须先问用户;不要编造。
|
||||
7. 将最终 JSON 保存为 `报销输出/analyzed_result.json`。
|
||||
7. 将最终 JSON 保存为本次处理目录中的 `报销输出/analyzed_result.json`。
|
||||
8. 确认 `questions` 为空、金额合计一致、附件张数一致后,自动运行:
|
||||
|
||||
```bash
|
||||
python3 差旅报销.py fill 报销输出/analyzed_result.json
|
||||
python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
Windows 环境可改用:
|
||||
|
||||
```powershell
|
||||
py 差旅报销.py fill 报销输出/analyzed_result.json
|
||||
py 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
9. 最后告诉用户生成的 Excel 文件路径,以及是否有任何需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。
|
||||
9. 最后告诉用户生成的 Excel 文件路径和本次归档目录,以及是否有任何需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。
|
||||
|
||||
> 简单说:看到本 README 后,大模型应自动完成“提取 PDF 原文 → 分析生成 JSON → 填写 Excel”,只有在信息不确定时才暂停询问用户。
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ py -m pip install pdfplumber openpyxl
|
|||
|
||||
```powershell
|
||||
py 差旅报销.py extract
|
||||
py 差旅报销.py fill 报销输出/analyzed_result.json
|
||||
py 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
### Linux / macOS
|
||||
|
|
@ -112,39 +112,55 @@ python3 -m pip install pdfplumber openpyxl
|
|||
|
||||
```bash
|
||||
python3 差旅报销.py extract
|
||||
python3 差旅报销.py fill 报销输出/analyzed_result.json
|
||||
python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 标准目录结构
|
||||
|
||||
每次开始处理前,根目录只需要保留基础文件,并把本次报销附件直接放到根目录:
|
||||
|
||||
```text
|
||||
报销目录/
|
||||
├── 差旅费明细表.xlsx # 必需:模板
|
||||
├── 差旅报销.py # 必需:脚本
|
||||
├── README.md # 必需:说明
|
||||
├── 住宿.pdf # 本次报销 PDF
|
||||
├── 机票*.pdf # 本次报销 PDF
|
||||
├── 滴滴出行行程报销单*.pdf # 本次报销 PDF
|
||||
└── 滴滴电子发票*.pdf # 本次报销 PDF
|
||||
├── 住宿.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||||
├── 机票*.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||||
├── 滴滴出行行程报销单*.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||||
├── 滴滴电子发票*.pdf # 本次报销 PDF,运行 extract 后会被移动
|
||||
└── 打车截图.jpg # 本次报销图片附件,运行 extract 后会被移动
|
||||
```
|
||||
|
||||
脚本输出统一放到:
|
||||
|
||||
```text
|
||||
报销输出/
|
||||
```
|
||||
|
||||
运行后会形成:
|
||||
运行 `extract` 后会先形成处理当天目录,例如:
|
||||
|
||||
```text
|
||||
报销目录/
|
||||
└── 报销输出/
|
||||
├── pdf_raw_text.txt
|
||||
├── analyzed_result.json
|
||||
├── schema_example.json # 可选
|
||||
└── 差旅费明细表_xxxx_20260520_广州_售后维修.xlsx
|
||||
└── 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
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -163,23 +179,25 @@ Windows 可用:
|
|||
py 差旅报销.py extract
|
||||
```
|
||||
|
||||
输出:
|
||||
运行时会把根目录的 PDF/JPG/JPEG/PNG 移动到处理当天目录,例如 `20260529/`。如果当天目录已存在且已有附件,脚本会询问是否合并;不合并会创建 `20260529-1/`、`20260529-2/` 这样的新目录。
|
||||
|
||||
输出位于本次处理目录中,例如:
|
||||
|
||||
```text
|
||||
报销输出/pdf_raw_text.txt
|
||||
20260529/报销输出/pdf_raw_text.txt
|
||||
```
|
||||
|
||||
这个文件按 PDF 文件分块保存原始文本,例如:
|
||||
|
||||
```text
|
||||
============================================================
|
||||
文件: 住宿.pdf
|
||||
文件: 20260529/住宿.pdf
|
||||
============================================================
|
||||
电子发票(增值税专用发票)
|
||||
...
|
||||
|
||||
============================================================
|
||||
文件: 滴滴出行行程报销单A.pdf
|
||||
文件: 20260529/滴滴出行行程报销单A.pdf
|
||||
============================================================
|
||||
滴滴出行-行程单
|
||||
...
|
||||
|
|
@ -191,10 +209,10 @@ py 差旅报销.py extract
|
|||
|
||||
### Step 2:让大模型分析 TXT 并输出 JSON
|
||||
|
||||
把 `报销输出/pdf_raw_text.txt` 交给大模型,让大模型生成:
|
||||
把本次处理目录中的 `报销输出/pdf_raw_text.txt` 交给大模型,让大模型生成同目录下的 JSON,例如:
|
||||
|
||||
```text
|
||||
报销输出/analyzed_result.json
|
||||
20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
可以先查看 JSON 模板:
|
||||
|
|
@ -221,17 +239,17 @@ python3 差旅报销.py schema --output
|
|||
请根据本 README 自动处理当前目录的差旅报销。
|
||||
|
||||
你的任务:
|
||||
1. 如果 `报销输出/pdf_raw_text.txt` 不存在,先运行 `python3 差旅报销.py extract` 提取 PDF 原文。
|
||||
2. 读取 `报销输出/pdf_raw_text.txt`,从 PDF 原文中识别出差人、出差事由、出发日期、结束日期、出发地、目的地、出行方式、出差天数。
|
||||
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 文件路径,并列出需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。
|
||||
9. 将最终 JSON 保存到本次处理目录中的 `报销输出/analyzed_result.json`,JSON 必须符合 README 中的 schema。
|
||||
10. 确认 `questions` 为空、金额合计一致、附件张数一致后,运行 `python3 差旅报销.py fill 本次处理目录/报销输出/analyzed_result.json` 生成最终 Excel,并把本次目录重命名为 `出发日期-结束日期_出发地-目的地_事由简写` 格式。
|
||||
11. 最后告诉用户生成的 Excel 文件路径和本次归档目录,并列出需要人工复核的证据或假设;如果存在行程单和电子发票对应同一笔费用,必须明确提醒用户:行程单未重复计入金额,只作为附件和行程明细使用。
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -322,13 +340,13 @@ python3 差旅报销.py schema --output
|
|||
## Step 3:读取 JSON 并填表
|
||||
|
||||
```bash
|
||||
python3 差旅报销.py fill 报销输出/analyzed_result.json
|
||||
python3 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
Windows 可用:
|
||||
|
||||
```powershell
|
||||
py 差旅报销.py fill 报销输出/analyzed_result.json
|
||||
py 差旅报销.py fill 20260529/报销输出/analyzed_result.json
|
||||
```
|
||||
|
||||
脚本会:
|
||||
|
|
@ -338,7 +356,8 @@ py 差旅报销.py fill 报销输出/analyzed_result.json
|
|||
3. 校验附件张数是否一致
|
||||
4. 复制 `差旅费明细表.xlsx`
|
||||
5. 用 openpyxl 填入固定单元格
|
||||
6. 输出最终 XLSX 到 `报销输出/`
|
||||
6. 输出最终 XLSX 到本次处理目录的 `报销输出/`
|
||||
7. 将本次处理目录重命名为 `出发日期-结束日期_出发地-目的地_事由简写` 格式
|
||||
|
||||
输出文件命名规则:
|
||||
|
||||
|
|
@ -349,7 +368,7 @@ py 差旅报销.py fill 报销输出/analyzed_result.json
|
|||
示例:
|
||||
|
||||
```text
|
||||
报销输出/差旅费明细表_xxxx_20260520_广州_售后维修.xlsx
|
||||
20260520-20260522_北京-广州_售后维修/报销输出/差旅费明细表_xxxx_20260520_广州_售后维修.xlsx
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
170
差旅报销.py
170
差旅报销.py
|
|
@ -3,8 +3,8 @@
|
|||
差旅报销自动化脚本
|
||||
|
||||
Python 只做两类确定性工作:
|
||||
1. extract:提取 PDF 原文到 TXT
|
||||
2. fill:读取大模型生成的 JSON,填入 Excel 模板
|
||||
1. extract:整理输入附件,提取 PDF 原文到本次归档目录的 TXT
|
||||
2. fill:读取大模型生成的 JSON,填入 Excel 模板,并完成本次归档目录命名
|
||||
|
||||
中间的发票理解、金额分类、日期/事由/人员判断全部由大模型完成。
|
||||
模板使用 .xlsx,完全依赖 Python 包实现跨平台,不依赖 LibreOffice。
|
||||
|
|
@ -14,14 +14,17 @@ import argparse
|
|||
import json
|
||||
import re
|
||||
import shutil
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import pdfplumber
|
||||
|
||||
|
||||
OUTPUT_DIR = Path("报销输出")
|
||||
RAW_TEXT_FILE = OUTPUT_DIR / "pdf_raw_text.txt"
|
||||
OUTPUT_DIR_NAME = "报销输出"
|
||||
RAW_TEXT_FILENAME = "pdf_raw_text.txt"
|
||||
ANALYZED_RESULT_FILENAME = "analyzed_result.json"
|
||||
TEMPLATE_FILE = Path("差旅费明细表.xlsx")
|
||||
ATTACHMENT_SUFFIXES = {".pdf", ".jpg", ".jpeg", ".png"}
|
||||
|
||||
|
||||
SCHEMA_EXAMPLE = {
|
||||
|
|
@ -83,24 +86,24 @@ SCHEMA_EXAMPLE = {
|
|||
}
|
||||
|
||||
|
||||
def ensure_output_dir():
|
||||
OUTPUT_DIR.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def command_extract(_args):
|
||||
ensure_output_dir()
|
||||
pdf_files = sorted(Path(".").glob("*.pdf"))
|
||||
if not pdf_files:
|
||||
raise SystemExit("当前目录未找到 PDF 文件。")
|
||||
archive_dir = prepare_archive_dir()
|
||||
output_dir = archive_dir / OUTPUT_DIR_NAME
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
raw_text_file = output_dir / RAW_TEXT_FILENAME
|
||||
|
||||
with open(RAW_TEXT_FILE, "w", encoding="utf-8") as output:
|
||||
pdf_files = sorted(archive_dir.glob("*.pdf"))
|
||||
if not pdf_files:
|
||||
raise SystemExit(f"{archive_dir} 中未找到 PDF 文件。请把 PDF 放到根目录后重新运行 extract。")
|
||||
|
||||
with open(raw_text_file, "w", encoding="utf-8") as output:
|
||||
output.write("=" * 60 + "\n")
|
||||
output.write("PDF 原始内容汇总\n")
|
||||
output.write("=" * 60 + "\n\n")
|
||||
|
||||
for pdf_path in pdf_files:
|
||||
output.write(f"\n{'=' * 60}\n")
|
||||
output.write(f"文件: {pdf_path.name}\n")
|
||||
output.write(f"文件: {pdf_path}\n")
|
||||
output.write("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
|
|
@ -113,14 +116,93 @@ def command_extract(_args):
|
|||
except Exception as exc:
|
||||
output.write(f"\n[读取失败: {exc}]\n")
|
||||
|
||||
print(f"已提取 {len(pdf_files)} 个 PDF 文件到 {RAW_TEXT_FILE}")
|
||||
print(f"本次附件目录:{archive_dir}")
|
||||
print(f"已提取 {len(pdf_files)} 个 PDF 文件到 {raw_text_file}")
|
||||
print(f"请将分析结果保存为:{output_dir / ANALYZED_RESULT_FILENAME}")
|
||||
|
||||
|
||||
def prepare_archive_dir():
|
||||
root_attachments = find_root_attachments()
|
||||
today_dir = Path(date.today().strftime("%Y%m%d"))
|
||||
|
||||
if root_attachments:
|
||||
archive_dir = choose_archive_dir(today_dir)
|
||||
archive_dir.mkdir(exist_ok=True)
|
||||
for attachment in root_attachments:
|
||||
target = unique_file_path(archive_dir / attachment.name)
|
||||
shutil.move(str(attachment), target)
|
||||
print(f"已移动 {len(root_attachments)} 个输入文件到 {archive_dir}")
|
||||
return archive_dir
|
||||
|
||||
existing_today_dir = find_existing_today_dir(today_dir)
|
||||
if existing_today_dir:
|
||||
return existing_today_dir
|
||||
|
||||
raise SystemExit("当前根目录未找到 PDF/JPG/JPEG/PNG 文件。请把本次发票、行程单或截图放到根目录后再运行 extract。")
|
||||
|
||||
|
||||
def find_root_attachments():
|
||||
return sorted(
|
||||
path for path in Path(".").iterdir()
|
||||
if path.is_file() and path.suffix.lower() in ATTACHMENT_SUFFIXES
|
||||
)
|
||||
|
||||
|
||||
def choose_archive_dir(today_dir):
|
||||
if not today_dir.exists():
|
||||
return today_dir
|
||||
|
||||
if has_attachments(today_dir):
|
||||
answer = input(f"{today_dir} 已存在且包含附件,是否合并本次根目录附件?[y/N] ").strip().lower()
|
||||
if answer in {"y", "yes"}:
|
||||
return today_dir
|
||||
return next_numbered_dir(today_dir)
|
||||
|
||||
return today_dir
|
||||
|
||||
|
||||
def find_existing_today_dir(today_dir):
|
||||
candidates = [today_dir] + sorted(Path(".").glob(f"{today_dir.name}-*"))
|
||||
candidates = [path for path in candidates if path.is_dir() and list(path.glob("*.pdf"))]
|
||||
if not candidates:
|
||||
return None
|
||||
return candidates[-1]
|
||||
|
||||
|
||||
def has_attachments(directory):
|
||||
return any(
|
||||
path.is_file() and path.suffix.lower() in ATTACHMENT_SUFFIXES
|
||||
for path in directory.iterdir()
|
||||
)
|
||||
|
||||
|
||||
def next_numbered_dir(base_dir):
|
||||
index = 1
|
||||
while True:
|
||||
candidate = Path(f"{base_dir.name}-{index}")
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
index += 1
|
||||
|
||||
|
||||
def unique_file_path(path):
|
||||
if not path.exists():
|
||||
return path
|
||||
|
||||
index = 1
|
||||
while True:
|
||||
candidate = path.with_name(f"{path.stem}-{index}{path.suffix}")
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
index += 1
|
||||
|
||||
|
||||
def command_schema(args):
|
||||
ensure_output_dir()
|
||||
output_dir = Path(OUTPUT_DIR_NAME)
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
text = json.dumps(SCHEMA_EXAMPLE, ensure_ascii=False, indent=2)
|
||||
if args.output:
|
||||
schema_path = OUTPUT_DIR / "schema_example.json"
|
||||
schema_path = output_dir / "schema_example.json"
|
||||
schema_path.write_text(text + "\n", encoding="utf-8")
|
||||
print(f"已写入 {schema_path}")
|
||||
else:
|
||||
|
|
@ -132,9 +214,17 @@ def command_fill(args):
|
|||
validate_result(result)
|
||||
ensure_fill_dependencies()
|
||||
|
||||
output_file = fill_template(result)
|
||||
json_path = Path(args.json_path)
|
||||
output_dir = json_path.parent
|
||||
archive_dir = output_dir.parent if output_dir.name == OUTPUT_DIR_NAME else Path(".")
|
||||
|
||||
output_file = fill_template(result, output_dir)
|
||||
verify_output(output_file, result)
|
||||
print(f"完成:{output_file}")
|
||||
final_archive_dir = finalize_archive_dir(archive_dir, result)
|
||||
final_output_file = final_archive_dir / OUTPUT_DIR_NAME / output_file.name
|
||||
|
||||
print(f"完成:{final_output_file}")
|
||||
print(f"本次归档目录:{final_archive_dir}")
|
||||
|
||||
|
||||
def load_json(json_path):
|
||||
|
|
@ -213,13 +303,13 @@ def ensure_fill_dependencies():
|
|||
raise SystemExit("缺少 openpyxl,请运行:python -m pip install openpyxl")
|
||||
|
||||
|
||||
def fill_template(result):
|
||||
ensure_output_dir()
|
||||
def fill_template(result, output_dir):
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
import openpyxl
|
||||
|
||||
meta = result["meta"]
|
||||
expenses = result["expenses"]
|
||||
output_file = OUTPUT_DIR / build_output_filename(meta)
|
||||
output_file = output_dir / build_output_filename(meta)
|
||||
|
||||
shutil.copy(TEMPLATE_FILE, output_file)
|
||||
wb = openpyxl.load_workbook(output_file)
|
||||
|
|
@ -244,6 +334,38 @@ def fill_template(result):
|
|||
return output_file
|
||||
|
||||
|
||||
def finalize_archive_dir(archive_dir, result):
|
||||
if archive_dir == Path("."):
|
||||
return archive_dir
|
||||
|
||||
target_dir = unique_archive_dir(archive_dir.parent / build_archive_dir_name(result["meta"]))
|
||||
if archive_dir.resolve() == target_dir.resolve():
|
||||
return archive_dir
|
||||
|
||||
archive_dir.rename(target_dir)
|
||||
return target_dir
|
||||
|
||||
|
||||
def unique_archive_dir(path):
|
||||
if not path.exists():
|
||||
return path
|
||||
|
||||
index = 1
|
||||
while True:
|
||||
candidate = path.with_name(f"{path.name}-{index}")
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
index += 1
|
||||
|
||||
|
||||
def build_archive_dir_name(meta):
|
||||
start_date = normalize_date_for_filename(meta["出发日期"])
|
||||
end_date = normalize_date_for_filename(meta["结束日期"])
|
||||
route = sanitize_filename(f'{meta["出发地"]}-{meta["目的地"]}')
|
||||
reason = sanitize_filename(meta["事由简写"])
|
||||
return f"{start_date}-{end_date}_{route}_{reason}"
|
||||
|
||||
|
||||
def build_output_filename(meta):
|
||||
date_str = normalize_date_for_filename(meta["出发日期"])
|
||||
safe_parts = [
|
||||
|
|
@ -304,14 +426,14 @@ def build_parser():
|
|||
parser = argparse.ArgumentParser(description="差旅报销自动化脚本")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
extract_parser = subparsers.add_parser("extract", help="提取当前目录所有 PDF 原文到 报销输出/pdf_raw_text.txt")
|
||||
extract_parser = subparsers.add_parser("extract", help="整理根目录附件并提取 PDF 原文到本次归档目录")
|
||||
extract_parser.set_defaults(func=command_extract)
|
||||
|
||||
schema_parser = subparsers.add_parser("schema", help="输出大模型需要生成的 JSON schema 示例")
|
||||
schema_parser.add_argument("--output", action="store_true", help="写入 报销输出/schema_example.json,而不是打印到终端")
|
||||
schema_parser.set_defaults(func=command_schema)
|
||||
|
||||
fill_parser = subparsers.add_parser("fill", help="读取 JSON 并填入差旅费明细表模板")
|
||||
fill_parser = subparsers.add_parser("fill", help="读取 JSON 并填入差旅费明细表模板,再归档本次目录")
|
||||
fill_parser.add_argument("json_path", help="大模型生成的 analyzed_result.json 路径")
|
||||
fill_parser.set_defaults(func=command_fill)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user