docs: 文档全面对齐 MCP 架构,并脱敏公开操作指南
接口与入门: - 新增 docs/MCP.md:注册方法(DSH patch + 通用 client)、返回信封、会话目录布局、标准流程、 硬约束与未实现清单;工具参考段由**运行时 input_schema 自动生成**,与代码同源不会漂移 - 删除 docs/API.md(856 行 Flask 路由/SSE 文档,描述的端点已不存在) - 重写根 README.md:为什么放弃自带前端、职责分工表、快速开始、注册配置、流程链、结构与风险 - 重写 AGENTS.md:命令表换成 mcp/mcp-smoke/upload;mypy 目标改 src;新增「提交纪律」 (pre-commit 的 stash 窗口会回滚未暂存改动)、COM 与 Playwright 线程约束、多进程共存提醒 包文档:src/README.md 与各包 README 按现状重写;新增 src/mcp/README.md(写工具的规矩、 宿主侧四个坑)与 src/upload/README.md(路由表与安全边界);tests/scripts/config 三处索引更新。 提示词迁移:src/infra/llm/ 整个删除,三份系统提示词迁到 .agents/skills/reimbursement/references/ (invoice-fields / travel-info-fields / normal-info-fields),删掉 can_submit 与 suggestion (save_info 也会主动剥离),「严格只输出 JSON」改为「交给对应工具落盘」;新增 SKILL.md 作为 宿主侧操作纪律(已被 DSH 热加载为 reimbursement 技能)。 作废处理:删除 .agents/docs/guides/系统事件流全景图.md(SSE 与 file_events/agent_events 通道已整体 移除,现在没有任何事件流);给项目架构全景图与架构分析-2026-06-15 加「已过时」横幅保留历史; config.example.json 去 consumable_storage 加 upload_pin;.env.example 去 LLM 段并说明已不再自动加载 .env。 安全:docs/报销操作指南.md 的示例配置里原来写着**真实姓名、16 位公务卡号、内网 LLM 地址与工号** (自 9fe5d70/2026-05-25 起在版本历史里),本次全部换成占位符。历史清洗与令牌轮换另议。 门禁(改动含 src/infra/llm 删除,故重跑全部门禁): ruff check PASS | ruff format --check PASS(42) | mypy src PASS(34) | deptry PASS(36) | pytest 107 passed | scripts/mcp_smoke.py PASS(17 工具 / instructions 1449 字节 / case_list ok) 申购单在依赖清理后另已实机复测:2.4s 生成,产物单元格读回正确。
This commit is contained in:
@@ -1,474 +1,125 @@
|
||||
# 财务报销自动化
|
||||
---
|
||||
last_reviewed: 2026-10-19
|
||||
---
|
||||
|
||||
自动从 PDF 发票或图片中提取信息,生成发票汇总表与易耗品出库单,并可选在财务系统中自动填报报销单。
|
||||
# 财务报销自动化(MCP 服务器)
|
||||
|
||||
**支持发票类型区分**:系统自动识别高铁票、酒店住宿等差旅发票与普通发票。差旅发票不生成易耗品出库单,走差旅报销流程;普通发票生成出库单,走普通报销流程。
|
||||
把发票、支付截图、出差申请单变成财务系统要的结构化数据,顺带生成低值易耗品申购单。
|
||||
|
||||
**它不是一个应用,而是一个 MCP 服务器**:读图、判断、决定下一步交给宿主 Agent(如 DSH),
|
||||
本项目只做确定性的活——PDF 渲染转正、金额匹配、规则校验、Excel 产物、浏览器填报。
|
||||
|
||||
> 为什么改:原来自带一整套前端 + 内置 LLM + 手写状态机(提取-校验-修正循环、轮次上限、SSE 事件流)。
|
||||
> 灵活性太差——想换个模型、加一类票据、改流程都得动代码。2026-10-19 起全部交给宿主 Agent 编排。
|
||||
|
||||
## 它做什么
|
||||
|
||||
| 环节 | 谁做 |
|
||||
|------|------|
|
||||
| 看发票、判断类型、抽字段 | **宿主 Agent**(多模态模型) |
|
||||
| 页面渲染、PDF 文本层、自动转正 | 本项目(PyMuPDF) |
|
||||
| 发票与刷卡记录金额匹配 | 本项目(确定性贪心 + 业务约束,输出待复核清单) |
|
||||
| 完整性校验 | 本项目(`config/validation_rules.json` 声明式规则) |
|
||||
| 申购单 Excel、财务系统填报 | 本项目(COM / Playwright) |
|
||||
| 流程编排、缺料追问、要不要提交 | **宿主 Agent** |
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
uv sync # 依赖(要求 Python >= 3.12)
|
||||
uv run playwright install chromium # 填报要用(P3 工具接入后需要)
|
||||
```
|
||||
|
||||
配好 `scripts/data/config.json`(参考 `config.example.json`:工号、密码、默认报销人、公务卡号、人员编号、上传 PIN)。
|
||||
该目录整体在 `.gitignore` 里,别提交。
|
||||
|
||||
把服务器注册到宿主(DSH profile patch):
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
- id: mcp-arb
|
||||
name: '@deepseek-ai/dsh-mcp-client'
|
||||
config:
|
||||
serverName: arb
|
||||
transport: stdio
|
||||
command: '<项目根>/.venv/Scripts/python.exe'
|
||||
args: ['-m', 'src.mcp.server']
|
||||
cwd: '<项目根>'
|
||||
```
|
||||
|
||||
自检(真的起一个子进程走 stdio 握手):
|
||||
|
||||
```bash
|
||||
make mcp-smoke # 或 python tasks.py mcp-smoke
|
||||
```
|
||||
|
||||
手机扫码上传材料(独立进程,必须单独起):
|
||||
|
||||
```bash
|
||||
make upload # 默认 0.0.0.0:5055;--local 只监听 127.0.0.1;--pin 加访问码
|
||||
```
|
||||
|
||||
`case_create` 会返回带令牌的手机页地址,上传落 `cases/<id>/inbox/`,之后由 Agent 收取。
|
||||
上传服务是只写不读的:没有列表也没有下载接口,扩展名白名单 + 25MB + 每会话 200 个文件封顶。
|
||||
|
||||
## 一次报销长什么样
|
||||
|
||||
```
|
||||
case_create → case_add_files → case_render_pages → case_read_text / 读 PNG
|
||||
→ case_save_extraction(逐张)→ match_payments(处理 review_required)
|
||||
→ info_skeleton → save_info → doc_requisition
|
||||
```
|
||||
|
||||
17 个工具的参数表与返回约定:[docs/MCP.md](docs/MCP.md)。操作步骤(财务口径):[docs/报销操作指南.md](docs/报销操作指南.md)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── pyproject.toml # 项目配置(依赖、工具链)
|
||||
├── uv.lock # 依赖锁定文件
|
||||
├── Makefile # 任务脚本(跨平台)
|
||||
├── tasks.py # 任务脚本(Windows 兼容)
|
||||
├── .pre-commit-config.yaml # pre-commit 钩子配置
|
||||
├── .env.example # 环境变量示例(SSO 地址、LLM 配置等)
|
||||
├── config.example.json # 用户配置示例
|
||||
├── 易耗品、出库单.doc # 易耗品出库单 Word 模板
|
||||
├── src/
|
||||
│ ├── __init__.py # 包初始化 / 日志器
|
||||
│ ├── config.py # 配置加载
|
||||
│ ├── exceptions.py # 异常定义
|
||||
│ ├── pipeline.py # CLI 流程编排
|
||||
│ ├── pipeline_core.py # CLI/Web 公共管道逻辑
|
||||
│ ├── main.py # CLI 入口
|
||||
│ ├── agent/ # Agent 调度模块
|
||||
│ │ ├── orchestrator.py # 总调度入口
|
||||
│ │ ├── coordinator.py # 校验-修正循环
|
||||
│ │ ├── session.py # 状态机与会话数据
|
||||
│ │ └── events.py # SSE 事件发射
|
||||
│ ├── core/ # 核心业务逻辑
|
||||
│ │ ├── extraction/ # 信息提取
|
||||
│ │ │ ├── extractor.py # 编排入口:串联文件扫描 → 提取 → 分类
|
||||
│ │ │ └── llm_extractor.py # LLM 多模态信息提取
|
||||
│ │ ├── matching/ # 金额匹配
|
||||
│ │ │ └── matcher.py # 支付记录与发票关联
|
||||
│ │ └── validation/ # 校验模块
|
||||
│ │ └── validator.py # 声明式校验器
|
||||
│ ├── infra/ # 基础设施层
|
||||
│ │ ├── browser/ # 浏览器自动化
|
||||
│ │ │ ├── base.py # BaseBot 基类
|
||||
│ │ │ ├── travel.py # 差旅报销填报流程
|
||||
│ │ │ └── normal.py # 普通报销填报流程
|
||||
│ │ ├── documents/ # 文档处理
|
||||
│ │ │ ├── invoice.py # 发票数据模型 + CSV 工具
|
||||
│ │ │ ├── pdf.py # PDF 图片渲染
|
||||
│ │ │ ├── consumable.py # 易耗品出库单填写(Word COM)
|
||||
│ │ │ └── requisition.py # 低值易耗品申购单填写(Excel COM)
|
||||
│ │ └── llm/ # LLM 接口
|
||||
│ │ ├── prompt.py # 提示词加载
|
||||
│ │ └── prompts/ # 提示词模板文件
|
||||
│ └── web/ # Web 界面模块
|
||||
│ ├── app.py # Flask 应用入口
|
||||
│ ├── routes.py # 路由定义
|
||||
│ ├── pipeline_web.py # Web 管道逻辑
|
||||
│ ├── sse_handler.py # SSE 日志流处理
|
||||
│ ├── templates/
|
||||
│ │ ├── index.html # PC 端主页
|
||||
│ │ └── mobile_upload.html # 移动端扫码上传
|
||||
│ ├── static/
|
||||
│ │ ├── css/ # 样式文件
|
||||
│ │ └── js/ # 前端脚本
|
||||
│ └── uploads/ # 按会话隔离的上传与产物目录
|
||||
├── scripts/ # CLI 数据目录
|
||||
│ ├── data/ # 发票源文件、config.json 与 .invoice_cache 缓存
|
||||
│ └── test_*.py # 测试脚本
|
||||
├── tests/ # 测试目录
|
||||
├── docs/ # 用户文档(API 说明、操作指南等)
|
||||
├── images/ # 浏览器调试截图
|
||||
└── *.pdf / *.jpg / *.png # 发票 PDF 或图片(CLI 模式,放在 scripts/data/)
|
||||
src/
|
||||
├── mcp/ 工具与协议层(server.py 注册;tools/ 分材料·抽取交接·分析·文档)
|
||||
├── cases.py 会话目录 cases/<id>/ 的唯一实现:阶段由产物推导,无状态机
|
||||
├── upload/ 扫码上传收件器(独立进程,绝不由 MCP 子进程拉起)
|
||||
├── core/ 确定性内核:extraction(缓存·渲染·转正)matching validation
|
||||
├── infra/ browser(Playwright 填报)documents(CSV·申购单 COM)
|
||||
├── config.py scripts/data/config.json + 环境变量
|
||||
└── exceptions.py 异常定义(MCP 层转成 ok:false 信封,不抛穿协议)
|
||||
.agents/skills/reimbursement/ 宿主侧操作手册与字段判读参考(从旧 LLM 提示词迁移)
|
||||
docs/MCP.md 工具契约
|
||||
config/validation_rules.json 声明式校验规则
|
||||
cases/ 会话产物(gitignore,含个人信息)
|
||||
```
|
||||
|
||||
## 声明式校验器
|
||||
|
||||
`src/core/validation/validator.py` 采用**规则配置与校验引擎分离**的设计模式,支持声明式定义校验规则:
|
||||
|
||||
### 设计特点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **声明式配置** | 校验规则以数据结构形式定义,无需编写代码 |
|
||||
| **统一路径定位** | 使用 `path` 统一定位字段,如 `["basic_info", "travel_purpose"]` |
|
||||
| **自定义校验函数** | 支持为字段定义自定义校验逻辑(日期格式、正数检查等) |
|
||||
| **数组元素校验** | 支持校验数组字段的最小元素数量及每个元素的必填字段 |
|
||||
| **向后兼容** | 支持简单格式 `["field1", "field2"]` 和详细格式 `{"path": [...], "custom_check": ...}` |
|
||||
|
||||
### 规则配置示例
|
||||
|
||||
```python
|
||||
# 差旅报销校验规则
|
||||
TRAVEL_VALIDATION_RULES = {
|
||||
"fields": [
|
||||
{"path": ["basic_info", "travel_purpose"], "description": "出差事由"},
|
||||
{"path": ["basic_info", "start_date"], "custom_check": _is_valid_date},
|
||||
],
|
||||
"arrays": [
|
||||
{
|
||||
"path": ["payment_methods"],
|
||||
"min_items": 1, # 至少1条支付记录
|
||||
"element_fields": [
|
||||
{"path": ["card_date"], "description": "刷卡日期"},
|
||||
{"path": ["card_amount"], "custom_check": _is_positive_number},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### 校验规则类型
|
||||
|
||||
| 规则类型 | 用途 | 关键字段 |
|
||||
|----------|------|----------|
|
||||
| `fields` | 顶层单值字段校验 | `path`, `custom_check`, `check_empty` |
|
||||
| `arrays` | 数组字段校验 | `path`, `min_items`, `element_fields` |
|
||||
|
||||
### 内置校验函数
|
||||
|
||||
- `_is_valid_date(value)` — 检查日期格式是否为 `YYYY-MM-DD`
|
||||
- `_is_positive_number(value)` — 检查值是否为正数
|
||||
|
||||
### 扩展自定义校验
|
||||
|
||||
```python
|
||||
# 定义自定义校验函数
|
||||
def check_vehicle_type(value):
|
||||
valid_types = ["飞机", "火车", "汽车", "打车"]
|
||||
return isinstance(value, str) and value.strip() in valid_types
|
||||
|
||||
# 在规则中使用
|
||||
{"path": ["vehicle_type"], "custom_check": check_vehicle_type}
|
||||
```
|
||||
|
||||
## 数据流
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
PDF[PDF 发票 / 图片] --> Extract[extractor 多模态提取]
|
||||
Extract --> Cache[(.invoice_cache/*.json)]
|
||||
|
||||
Cache --> Classify{发票类型分类}
|
||||
Classify -->|差旅发票| Travel[高铁票 / 酒店住宿]
|
||||
Classify -->|普通发票| General[普通发票]
|
||||
Classify -->|支付记录| Payment[支付截图]
|
||||
Classify -->|申请单| Application[出差事前申请单]
|
||||
|
||||
Travel --> Matcher[matcher 金额匹配]
|
||||
Payment --> Matcher
|
||||
Matcher --> MatchResult[(match_result.json)]
|
||||
|
||||
Cache --> TravelLLM[LLM 差旅信息提取]
|
||||
MatchResult --> TravelLLM
|
||||
TravelLLM --> TravelInfo[(travel_info.json)]
|
||||
|
||||
Cache --> NormalLLM[LLM 普通发票信息提取]
|
||||
MatchResult --> NormalLLM
|
||||
NormalLLM --> NormalInfo[(normal_info.json)]
|
||||
|
||||
TravelInfo -->|差旅基本信息| Bot_T[infra/browser/travel.py<br/>差旅填报流程]
|
||||
TravelInfo -->|报销明细| Bot_T
|
||||
TravelInfo -->|支付方式| Bot_T
|
||||
TravelInfo -->|补助清单| Bot_T
|
||||
TravelInfo -->|附件清单| Bot_T
|
||||
Bot_T --> Submit_T[差旅报销提交]
|
||||
|
||||
NormalInfo -->|报销说明| Bot_G[infra/browser/normal.py<br/>普通填报流程]
|
||||
NormalInfo -->|发票总数/金额| Bot_G
|
||||
NormalInfo -->|支付方式| Bot_G
|
||||
NormalInfo -->|附件清单| Bot_G
|
||||
Bot_G --> Submit_G[普通报销提交]
|
||||
|
||||
General --> CSV[(invoice_summary.csv)]
|
||||
CSV --> Fill[consumable.py]
|
||||
Fill --> Doc[易耗品、出库单.doc]
|
||||
```
|
||||
|
||||
### 关键中间产物
|
||||
|
||||
| 文件 | 生成阶段 | 作用 |
|
||||
|------|---------|------|
|
||||
| `.invoice_cache/*.json` | extractor 提取 | 单张发票/支付记录/申请单的结构化数据 |
|
||||
| `match_result.json` | matcher 匹配 | 支付截图与发票的关联关系(按金额匹配) |
|
||||
| `travel_info.json` | LLM 差旅信息提取 | 综合发票缓存 + 匹配结果,生成差旅报销所需的全部结构化数据 |
|
||||
| `normal_info.json` | LLM 普通发票信息提取 | 综合普通发票 + 匹配结果,生成普通报销所需的全部结构化数据 |
|
||||
| `invoice_summary.csv` | extractor 提取 | 普通发票汇总(用于生成易耗品出库单) |
|
||||
|
||||
### bot 模块架构
|
||||
|
||||
`infra/browser/` 包负责浏览器自动化填报,仅接收已提取的信息并执行填报操作,不承担信息提取职责:
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| `infra/browser/base.py` | `BaseBot` 基类:浏览器生命周期、登录、导航、截图 |
|
||||
| `infra/browser/travel.py` | 差旅填报流程:基本信息 → 差旅明细 → 支付方式 → 补助清单 → 附件上传 |
|
||||
| `infra/browser/normal.py` | 普通填报流程:基本信息 → 总明细 → 支付方式 → 附件上传 |
|
||||
| `infra/browser/__init__.py` | 入口函数:`run_bot()` / `run_bot_web()`,负责类型判断和流程路由 |
|
||||
|
||||
## 环境要求
|
||||
|
||||
- **Python 3.12+**
|
||||
- **uv** 包管理器([安装指南](https://docs.astral.sh/uv/getting-started/installation/))
|
||||
- Windows(易耗品出库单填写依赖 Microsoft Word + COM,仅 Windows 可用)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
## 开发
|
||||
|
||||
```bash
|
||||
# 同步所有依赖(运行时 + 开发工具)
|
||||
make install
|
||||
# Windows 上等效命令:
|
||||
python tasks.py install
|
||||
make install # uv sync + pre-commit
|
||||
make check # ruff check + format --check + mypy src + deptry
|
||||
make test # pytest + 覆盖率
|
||||
make upload # 上传收件器
|
||||
make mcp-smoke # stdio 自检
|
||||
```
|
||||
|
||||
项目使用 `uv` 管理依赖,所有包版本锁定在 `uv.lock` 中,确保可复现。
|
||||
测试覆盖:配置加载、发票分类与 CSV、金额匹配、会话模型、分析工具、骨架与规则一致性、申购单取值。
|
||||
|
||||
| 依赖 | 用途 |
|
||||
## 平台与风险
|
||||
|
||||
- **Windows-only**:申购单依赖 Microsoft Excel + `pywin32` COM;并发驱动 Office 会互抢,
|
||||
所以 COM 调用统一排队在单线程 actor(`src/infra/com_thread.py`)
|
||||
- 填报会打开 Chromium,自动化期间别手动点
|
||||
- `cases/` 与 `scripts/data/` 含身份证号、卡号、凭据,已在 `.gitignore`;发布前请自查历史
|
||||
- **提交前务必手动跑门禁**:本仓库 pre-commit 只跑 ruff,但它会 stash 未暂存改动,
|
||||
中途中断会把工作区文件回滚成 HEAD(本项目改造过程中真发生过,见方案文档 §11)
|
||||
|
||||
## 还没做完
|
||||
|
||||
向财务系统填报与提交(`browser_*`、`submit_*`)正在建设;在那之前本项目只到"产物齐备 + 校验通过"。
|
||||
Word 版易耗品出库单已下线(模板自 2026-08-31 起不在仓库,功能此前一直静默失败)。
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| PyMuPDF | PDF 图片渲染(供多模态 LLM 使用) |
|
||||
| llama-index | LLM 信息提取(发票识别、差旅信息提取) |
|
||||
| playwright | 财务系统浏览器自动化 |
|
||||
| flask | Web 服务 |
|
||||
| pywin32 | 填写 Word 出库单(`fill_consumable_doc`) |
|
||||
|
||||
### 2. 准备数据(CLI 模式)
|
||||
|
||||
将发票 PDF 或图片(`.jpg`、`.png`、`.webp`、`.bmp`)放在 `scripts/data/` 目录下。
|
||||
|
||||
### 3. 配置
|
||||
|
||||
编辑 `scripts/config.json`(参考 `config.example.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "你的工号",
|
||||
"password": "你的密码",
|
||||
"default_name": "默认报销人姓名",
|
||||
"default_card_no": "默认公务卡号",
|
||||
"default_person_id": "默认人员编号",
|
||||
"consumable_storage": "物料存储地"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `username` / `password` | 信息门户登录凭据 |
|
||||
| `default_name` | 默认报销人姓名 |
|
||||
| `default_card_no` | 默认公务卡号 |
|
||||
| `default_person_id` | 默认人员编号(工号) |
|
||||
| `consumable_storage` | 出库单「存放地点」列默认值(默认: `躬行楼 C205`) |
|
||||
|
||||
**服务端配置**(SSO 地址、报销系统 URL、LLM 参数)通过环境变量提供,有默认值,一般无需修改:
|
||||
|
||||
| 环境变量 | 默认值 | 说明 |
|
||||
|----------|--------|------|
|
||||
| `SSO_LOGIN_URL` | `https://tyrz.fynu.edu.cn/sso/login` | SSO 登录地址 |
|
||||
| `PORTAL_URL` | `https://tyrz.fynu.edu.cn/oshall` | 统一信息平台地址 |
|
||||
| `REIMBURSE_URL` | `http://210.45.32.214:8081` | 报销系统地址 |
|
||||
| `REIMBURSE_PAGE` | `/expen/common/common?v=4.0` | 普通报销页面路径 |
|
||||
| `TRAVEL_PAGE` | `/expen/travel/travel?v=4.0` | 差旅报销页面路径 |
|
||||
| `LLM_MODEL` | `qwen-vl-max` | LLM 模型名称 |
|
||||
| `LLM_API_BASE` | `http://localhost:8080/v1` | LLM API 地址 |
|
||||
| `LLM_API_KEY` | `lm-studio` | LLM API 密钥 |
|
||||
|
||||
### 4. 运行(CLI)
|
||||
|
||||
```bash
|
||||
# 全流程(发票提取 → 浏览器填报)
|
||||
make run
|
||||
# Windows 等效:
|
||||
python tasks.py run
|
||||
|
||||
# 仅执行某一步
|
||||
uv run python src/main.py --step invoice # 仅发票提取
|
||||
uv run python src/main.py --step submit # 仅浏览器填报
|
||||
|
||||
# 覆盖配置中的登录凭据
|
||||
uv run python src/main.py -u 工号 -p 密码
|
||||
```
|
||||
|
||||
### 5. 填写易耗品出库单(CLI)
|
||||
|
||||
需已生成 `invoice_summary.csv`,且本机已安装 **Microsoft Word**:
|
||||
|
||||
```bash
|
||||
uv run python -m src.infra.documents.consumable
|
||||
uv run python -m src.infra.documents.consumable --csv invoice_summary.csv --doc "易耗品、出库单.doc"
|
||||
uv run python -m src.infra.documents.consumable --config scripts/config.json # 指定配置文件
|
||||
uv run python -m src.infra.documents.consumable --no-backup # 不生成 .doc.bak 备份
|
||||
```
|
||||
|
||||
填写规则概要:
|
||||
|
||||
- 表头「日期」使用**填写当天**的日期(非发票开票日期)
|
||||
- 从 `spec_model` 解析品名、规格、单位、数量、单价;`card_amount` 写入金额列
|
||||
- 单价/金额保留两位小数;表格内统一为 **宋体五号(10.5 磅)**
|
||||
- 存放地点取自 `consumable_storage`(默认: `躬行楼 C205`);购货人/领用人签字、备注保持空白
|
||||
|
||||
### 6. 生成低值易耗品申购单(CLI)
|
||||
|
||||
从 Web 会话目录的发票缓存读取普通发票,自动填入「低值易耗品申购」Excel 模板。
|
||||
需本机已安装 **Microsoft Excel**(通过 COM 操作,仅 Windows 可用)。
|
||||
|
||||
```bash
|
||||
# 传入 Web 会话目录(含 .invoice_cache/ 与 agent_state.json)
|
||||
uv run python -m src.infra.documents.requisition --session src/web/uploads/35c63986686b
|
||||
|
||||
# 排除物品名称含指定关键词的发票(逗号分隔多个)
|
||||
uv run python -m src.infra.documents.requisition --session src/web/uploads/0c9b7e7ef1ac --exclude 千斤顶,台钻
|
||||
|
||||
# 指定模板 / 输出路径 / 实验实训项目
|
||||
uv run python -m src.infra.documents.requisition --session <会话目录> --template 模板.xls --output 输出.xls --project "项目名称"
|
||||
```
|
||||
|
||||
参数说明:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--session` | Web 会话目录,脚本自动读取其中的 `.invoice_cache/` 与 `agent_state.json` |
|
||||
| `--template` | 申购单 Excel 模板路径;默认取项目根目录下现有的 `低值易耗品申购*.xls` |
|
||||
| `--output` | 输出文件路径;默认同目录生成带时间戳的新文件,不覆盖模板 |
|
||||
| `--project` | 「对应实验实训项目」列内容;默认读 `agent_state.json` 的 `reimbursement_description` |
|
||||
| `--exclude` | 排除物品名称含这些关键词的发票,逗号分隔 |
|
||||
|
||||
填写规则概要:
|
||||
|
||||
- 仅收录 `invoice_type=general` 的普通发票(支付截图/备注文件自动排除),按发票号去重
|
||||
- 物品名称去除 `*分类*` 前缀(如 `*半导体*半导体分立器件` → `半导体分立器件`)
|
||||
- 规格:优先用发票提取的型号;无型号或为泛化名称(集成电路/电阻/连接器等)时,从名称提取封装/型号(如 `SOP-8`、`TO-220`、`STM32F103C8T6`),仍无法确定则填 `多品种`
|
||||
- 数量固定 `1`,单价(含税) = 金额 = 发票价税合计
|
||||
- 末尾自动追加「合计」行
|
||||
|
||||
## 执行步骤说明
|
||||
|
||||
| 步骤 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 发票提取 | `--step invoice` | 扫描 `scripts/data/` 目录的 PDF 和图片,生成 `invoice_summary.csv` |
|
||||
| 浏览器填报 | `--step submit` | 登录信息门户 → 报销系统 → 自动填单、上传附件 |
|
||||
|
||||
> 全流程执行时数据在内存中流转,CSV 为参考产物。分步执行时,缓存数据会自动成为下一步的输入。
|
||||
|
||||
### 缓存机制
|
||||
|
||||
系统使用 JSON 缓存作为数据中转站,串联整个处理流程:
|
||||
|
||||
```
|
||||
源文件 (PDF/图片) → LLM 多模态提取 → JSON 缓存 → 匹配/分类/填报
|
||||
```
|
||||
|
||||
| 缓存文件 | 说明 |
|
||||
|----------|------|
|
||||
| `<文件名>.json` | 每个源文件的 LLM 提取结果(发票信息、支付记录等) |
|
||||
| `match_result.json` | 发票与支付记录的匹配结果 |
|
||||
| `travel_info.json` | 差旅信息(事由、地点、时间等)提取结果 |
|
||||
|
||||
缓存位置:CLI 模式为 `scripts/data/.invoice_cache/`,Web 模式为 `src/web/uploads/<session_id>/.invoice_cache/`。缓存文件与源文件同名(如 `发票1.pdf` 对应 `.invoice_cache/发票1.json`),后续步骤(金额匹配、发票分类、浏览器填报)均从缓存读取结构化数据。删除缓存后下次处理会重新提取。
|
||||
|
||||
### CSV 字段说明
|
||||
|
||||
发票级别 CSV(`invoice_summary.csv`)使用英文列名:
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| `index` | 行号 |
|
||||
| `invoice_type` | 发票类型:`train` / `hotel` / `general` |
|
||||
| `invoice_number` | 电子发票号码 |
|
||||
| `invoice_date` | 开票日期 |
|
||||
| `item_name` | 货物或应税劳务名称 |
|
||||
| `spec_model` | 规格型号(差旅发票为出发站→到达站) |
|
||||
| `total_amount` | 发票含税金额 |
|
||||
| `seller_name` | 销方名称 |
|
||||
| `departure` / `arrival` | 出发站 / 到达站(高铁票专用) |
|
||||
| `train_no` / `ride_date` / `seat_class` | 车次 / 乘车日期 / 座位等级(高铁票专用) |
|
||||
| `person_name` | 人员姓名 |
|
||||
| `card_date` / `card_no` / `card_amount` | 刷卡日期 / 公务卡号 / 刷卡金额 |
|
||||
| `remark` | 备注 |
|
||||
| `person_id` | 工号 |
|
||||
|
||||
## Web 服务
|
||||
|
||||
提供浏览器界面:上传文件 → 自动处理 → 在线编辑 → 下载产物 → 可选提交财务系统。
|
||||
|
||||
```bash
|
||||
uv run python src/web/app.py
|
||||
```
|
||||
|
||||
访问 `http://localhost:5000`。
|
||||
|
||||
### 推荐使用流程
|
||||
|
||||
1. 上传 PDF 或图片(或上传已有 CSV)
|
||||
2. 填写配置(账号、密码、姓名、公务卡号、存放地点等),可上传 `config.json` 一键填充
|
||||
3. 点击 **开始处理** — 完成发票提取、生成 CSV,系统自动识别发票类型并分类统计
|
||||
4. **普通发票**:自动生成 **易耗品、出库单.doc**,可下载
|
||||
5. **差旅发票**(高铁票/酒店住宿):跳过出库单生成,直接进入差旅报销流程
|
||||
6. 在表格中核对、修改发票数据(提交财务系统前会自动保存)
|
||||
7. 确认无误后点击 **提交到财务系统**
|
||||
|
||||
### 处理流程
|
||||
|
||||
上传 PDF 或图片 → LLM 识别文档类型 → 结构化提取 → 分类处理
|
||||
|
||||
系统通过 LLM 多模态识别自动判断每张文档的类型,无需手动指定:
|
||||
|
||||
| 文档类型 | 处理方式 | 状态 |
|
||||
|----------|----------|------|
|
||||
| 发票(高铁票/酒店住宿/普通发票) | 提取发票信息 → 金额匹配 → 分类 | 已实现 |
|
||||
| 支付记录(刷卡截图) | 提取刷卡信息 → 与发票匹配 | 已实现 |
|
||||
| 出差事前申请单 | 提取出差事由、地点、时间 | 已实现 |
|
||||
| 飞机票 | 同高铁票处理流程 | 计划中 |
|
||||
|
||||
### 功能一览
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| 发票提取 | 上传 PDF 或图片后自动完成 |
|
||||
| 发票类型自动分类 | 高铁票/酒店住宿/普通发票,自动分流处理 |
|
||||
| 易耗品出库单 | 仅普通发票自动生成 Word,差旅发票跳过 |
|
||||
| 表格在线编辑 | 处理完成后可修改 CSV 各字段;保存后重新生成出库单 |
|
||||
| 财务系统填报 | 单独按钮触发,处理阶段不会自动提交 |
|
||||
| 实时日志 | SSE 推送处理进度 |
|
||||
| 配置上传 | 支持上传 `config.json` 填充表单 |
|
||||
| 手机扫码上传 | 二维码打开移动端页面,拍照上传,PC 端轮询同步 |
|
||||
|
||||
> Web 端浏览器填报以无头模式运行。未上传 PDF 或图片时,填报阶段会跳过附件上传。
|
||||
> 出库单生成需要 **Windows + Word + pywin32**;若失败,页面会显示具体原因,CSV 等其它产物仍可正常使用。
|
||||
|
||||
### 会话产物
|
||||
|
||||
每次上传生成独立会话,产物存放在 `src/web/uploads/<session_id>/`:
|
||||
|
||||
| 产物 | 说明 |
|
||||
|------|------|
|
||||
| `invoice_summary.csv` | 发票汇总数据(含 LLM 识别结果) |
|
||||
| `payment_records.csv` | 支付记录级别数据(含匹配结果) |
|
||||
| `travel_applications.json` | 出差事前申请单数据(JSON 格式) |
|
||||
| `易耗品、出库单.doc` | 自动填写的出库单(仅普通发票) |
|
||||
| `config.json` | 当次会话配置 |
|
||||
| `session.log` | 处理日志 |
|
||||
| `result.json` | 处理结果 |
|
||||
| `.invoice_cache/` | LLM 提取结果缓存(JSON 格式,避免重复处理) |
|
||||
|
||||
会话缓存机制与 CLI 模式相同,缓存目录中的 JSON 数据是后续匹配、分类和填报的唯一数据来源。
|
||||
|
||||
接口说明见 [API.md](./API.md)。
|
||||
|
||||
## 开发任务
|
||||
|
||||
项目提供统一的任务脚本,支持跨平台使用:
|
||||
|
||||
| 任务 | Makefile | tasks.py | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| 安装依赖 | `make install` | `python tasks.py install` | 同步依赖 + 安装 pre-commit |
|
||||
| 代码检查 | `make check` | `python tasks.py check` | Ruff lint + 格式化 + MyPy 类型检查 + deptry 依赖检查 |
|
||||
| 运行测试 | `make test` | `python tasks.py test` | pytest + 覆盖率报告 |
|
||||
| 运行 CLI | `make run` | `python tasks.py run` | 执行全流程 |
|
||||
| 清理缓存 | `make clean` | `python tasks.py clean` | 删除虚拟环境和缓存 |
|
||||
|
||||
## 代码质量
|
||||
|
||||
项目配置了完整的代码质量工具链:
|
||||
|
||||
- **Ruff** — 快速 lint 检查和代码格式化(替代 flake8 + isort + black)
|
||||
- **MyPy** — 严格模式类型检查(`strict = true`)
|
||||
- **deptry** — 检测未声明、未使用、过时依赖
|
||||
- **pre-commit** — 提交前自动运行 Ruff 检查和格式化
|
||||
|
||||
所有检查通过后方可提交代码。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 浏览器填报时会打开或使用 Chromium,请勿手动干扰自动化流程
|
||||
- 调试截图保存在 `images/` 目录
|
||||
- 项目根目录需保留 `易耗品、出库单.doc` 模板;Web 每次从模板复制到会话目录再填写,不修改原模板
|
||||
- `scripts/config.json` 含敏感信息,请勿提交到公开仓库
|
||||
- **发票类型区分**:差旅发票(高铁票/酒店住宿)不会生成易耗品出库单,差旅报销填报流程已完整实现(含差旅信息提取、明细录入、支付方式、补助清单、附件上传)
|
||||
| [docs/MCP.md](docs/MCP.md) | 17 个工具的参数、返回信封、会话目录、注册方法 |
|
||||
| [docs/报销操作指南.md](docs/报销操作指南.md) | 财务侧口径与材料准备要求 |
|
||||
| [AGENTS.md](AGENTS.md) | 给 Agent 的项目操作指南(开发命令、约束、坑) |
|
||||
| [.agents/docs/plans/MCP化改造方案-2026-10-19.md](.agents/docs/plans/MCP化改造方案-2026-10-19.md) | 改造决策、实测数据、踩坑记录 |
|
||||
|
||||
Reference in New Issue
Block a user