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:
-856
@@ -1,856 +0,0 @@
|
||||
---
|
||||
last_reviewed: 2026-06-13
|
||||
---
|
||||
|
||||
# 财务报销自动化 — API 文档
|
||||
|
||||
> 基础地址: `http://localhost:5000`
|
||||
> 启动: `uv run python src/web/app.py`
|
||||
|
||||
## 总览
|
||||
|
||||
| # | 方法 | 路径 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | GET | `/` | PC 端主页 |
|
||||
| 2 | GET | `/mobile/<session_id>` | 移动端上传页面 |
|
||||
| 3 | POST | `/api/session` | 创建会话 |
|
||||
| 4 | POST | `/api/upload/<session_id>` | 上传文件(PDF/图片) |
|
||||
| 5 | GET | `/api/files/<session_id>` | 列出会话目录中的文件 |
|
||||
| 6 | GET | `/api/download/<session_id>/<filename>` | 下载生成的文件 |
|
||||
| 7 | POST | `/api/mobile-upload/<session_id>` | 移动端上传图片 |
|
||||
| 8 | GET | `/api/config/<session_id>` | 获取会话配置 |
|
||||
| 9 | GET | `/api/data/<session_id>` | 获取发票数据(JSON) |
|
||||
| 10 | POST | `/api/save/<session_id>` | 保存编辑后的发票数据 |
|
||||
| 11 | POST | `/api/process/<session_id>` | 启动管道处理(仅发票提取,不自动提交) |
|
||||
| 12 | GET | `/api/logs/<session_id>` | SSE 日志流 |
|
||||
| 13 | POST | `/api/submit-financial/<session_id>` | 提交到财务系统 |
|
||||
| **14** | **GET** | **`/api/agent/state/<session_id>`** | **获取 Agent 会话状态** |
|
||||
| **15** | **POST** | **`/api/agent/process/<session_id>`** | **启动 Agent 多轮处理(主入口)** |
|
||||
| **16** | **POST** | **`/api/agent/supplement/<session_id>`** | **补充文件后重新分析** |
|
||||
| **17** | **POST** | **`/api/agent/user-supplement/<session_id>`** | **通过文字补充信息** |
|
||||
| **18** | **POST** | **`/api/agent/force-submit/<session_id>`** | **强制提交,跳过校验** |
|
||||
|
||||
> 加粗条目为 Agent 多轮校验流程新增接口。
|
||||
|
||||
### 入口选择建议
|
||||
|
||||
- **推荐使用** `/api/agent/process`:完整流程,包含发票提取、LLM 信息校验、自动提交财务系统。
|
||||
- **仅发票提取** `/api/process`:跳过 Agent 校验,只做文档解析和发票分类。适合调试发票提取本身,或仅需导出 CSV 的场景。
|
||||
|
||||
---
|
||||
|
||||
## 会话与目录
|
||||
|
||||
- 调用 `POST /api/session` 获得 `session_id`
|
||||
- 该会话下所有文件存放在 `src/web/uploads/<session_id>/`
|
||||
- 典型产物:`invoice_summary.csv`、`payment_records.csv`、`易耗品、出库单.doc`、`config.json`、`session.log`、`result.json`、`agent_state.json`、`agent_events.log`、`file_events.log`、`llm_stream.log`
|
||||
|
||||
---
|
||||
|
||||
## 接口详情
|
||||
|
||||
### 1. 创建会话
|
||||
|
||||
```
|
||||
POST /api/session
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{ "session_id": "a1b2c3d4e5f6" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 上传文件(PDF/图片)
|
||||
|
||||
```
|
||||
POST /api/upload/<session_id>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| file | File | PDF 发票或支付截图 |
|
||||
|
||||
**响应(成功):**
|
||||
|
||||
```json
|
||||
{ "ok": true, "filename": "1. 电容一批.pdf" }
|
||||
```
|
||||
|
||||
**响应(失败):**
|
||||
|
||||
```json
|
||||
{ "error": "未选择文件" }
|
||||
```
|
||||
|
||||
HTTP `400`
|
||||
|
||||
---
|
||||
|
||||
### 3. 列出会话文件
|
||||
|
||||
```
|
||||
GET /api/files/<session_id>
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{ "name": "1. 电容一批.pdf", "type": "pdf", "size": 12345 },
|
||||
{ "name": "payment_01.jpg", "type": "image", "size": 67890 }
|
||||
],
|
||||
"pdfs": ["1. 电容一批.pdf"],
|
||||
"images": ["payment_01.jpg"]
|
||||
}
|
||||
```
|
||||
|
||||
`images` 包含扩展名:`.png`、`.jpg`、`.jpeg`、`.bmp`、`.webp`。
|
||||
|
||||
---
|
||||
|
||||
### 4. 下载文件
|
||||
|
||||
```
|
||||
GET /api/download/<session_id>/<filename>
|
||||
```
|
||||
|
||||
**响应:** 文件二进制流,带 `Content-Disposition: attachment` 与 UTF-8 文件名。
|
||||
|
||||
| 扩展名 | Content-Type |
|
||||
|--------|----------------|
|
||||
| `.csv` | `text/csv; charset=utf-8` |
|
||||
| `.doc` | `application/msword` |
|
||||
| 其它 | `application/octet-stream` |
|
||||
|
||||
**常见文件名:**
|
||||
|
||||
| 文件名 | 说明 |
|
||||
|--------|------|
|
||||
| `invoice_summary.csv` | 发票汇总 |
|
||||
| `payment_records.csv` | 支付记录 |
|
||||
| `易耗品、出库单.doc` | 自动填写的出库单 |
|
||||
| `travel_applications.json` | 差旅申请信息 |
|
||||
| `result.json` | 处理结果 |
|
||||
| `agent_state.json` | Agent 会话状态 |
|
||||
|
||||
**错误:**
|
||||
|
||||
```json
|
||||
{ "error": "文件不存在" }
|
||||
```
|
||||
|
||||
HTTP `404`。`filename` 仅允许会话目录内的文件名(防止路径穿越)。
|
||||
|
||||
---
|
||||
|
||||
### 5. 移动端上传页面
|
||||
|
||||
```
|
||||
GET /mobile/<session_id>
|
||||
```
|
||||
|
||||
返回移动端 HTML 页面。
|
||||
|
||||
---
|
||||
|
||||
### 6. 移动端上传图片
|
||||
|
||||
```
|
||||
POST /api/mobile-upload/<session_id>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| file | File | 图片文件 |
|
||||
|
||||
逻辑与 `POST /api/upload/<session_id>` 相同。
|
||||
|
||||
---
|
||||
|
||||
### 7. 获取会话配置
|
||||
|
||||
```
|
||||
GET /api/config/<session_id>
|
||||
```
|
||||
|
||||
获取当前会话的配置,供前端回填表单。优先读取会话目录下的 `config.json`,未找到则使用项目全局配置。
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "",
|
||||
"password": "",
|
||||
"default_name": "",
|
||||
"default_card_no": "",
|
||||
"default_person_id": "",
|
||||
"consumable_storage": ""
|
||||
}
|
||||
```
|
||||
|
||||
注意:`password` 字段始终返回空字符串。
|
||||
|
||||
---
|
||||
|
||||
### 8. 获取发票数据
|
||||
|
||||
```
|
||||
GET /api/data/<session_id>
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"csv_filename": "payment_records.csv",
|
||||
"fields": [
|
||||
"序号", "发票号码", "开票日期", "项目名称", "规格型号",
|
||||
"价税合计", "销售方名称", "人员姓名", "刷卡日期",
|
||||
"公务卡号", "刷卡金额", "备注", "工号"
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"__row": 0,
|
||||
"序号": "1",
|
||||
"发票号码": "26442000005432755951",
|
||||
"价税合计": "2900.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
读取优先级:`payment_records.csv` → `invoice_summary.csv` → 任意 `.csv` 文件。
|
||||
|
||||
- `fields`:列顺序
|
||||
- `data[].__row`:内部行索引(保存时不需要提交,服务端按数组顺序写回)
|
||||
|
||||
**错误:** `404` 未找到 CSV;`500` 读取失败。
|
||||
|
||||
---
|
||||
|
||||
### 9. 保存编辑后的发票数据
|
||||
|
||||
```
|
||||
POST /api/save/<session_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"csv_filename": "invoice_summary.csv",
|
||||
"data": [
|
||||
{
|
||||
"序号": "1",
|
||||
"发票号码": "26442000005432755951",
|
||||
"开票日期": "2026/05/18",
|
||||
"项目名称": "...",
|
||||
"规格型号": "...",
|
||||
"价税合计": "2900.00",
|
||||
"销售方名称": "...",
|
||||
"人员姓名": "",
|
||||
"刷卡日期": "2026/04/28",
|
||||
"公务卡号": "",
|
||||
"刷卡金额": "2850.00",
|
||||
"备注": "",
|
||||
"工号": "200000001"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应(成功):**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"doc_ok": true,
|
||||
"doc_url": "/api/download/<session_id>/%E6%98%93%E8%80%97%E5%93%81%E3%80%81%E5%87%BA%E5%BA%93%E5%8D%95.doc"
|
||||
}
|
||||
```
|
||||
|
||||
保存后会根据最新 CSV **重新生成** 出库单 Word(与会话 `config.json` 中的 `consumable_storage` 等配置一致)。
|
||||
|
||||
**纯差旅发票跳过出库单:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"doc_ok": null,
|
||||
"doc_skipped": true
|
||||
}
|
||||
```
|
||||
|
||||
**出库单生成失败:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"doc_ok": false,
|
||||
"doc_error": "出库单模板不存在,请将模板放在项目根目录"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. 启动管道处理(仅发票提取)
|
||||
|
||||
```
|
||||
POST /api/process/<session_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
> 此接口仅执行发票提取和 LLM 识别,**不会**触发 Agent 多轮校验,也**不会**自动提交到财务系统。如需完整的 Agent 校验流程,请使用 `/api/agent/process`。
|
||||
|
||||
**请求体:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| username | string | 否 | 财务系统工号 |
|
||||
| password | string | 否 | 登录密码 |
|
||||
| default_name | string | 否 | 默认报销人姓名 |
|
||||
| default_card_no | string | 否 | 默认公务卡号 |
|
||||
| default_person_id | string | 否 | 默认人员编号 |
|
||||
| consumable_storage | string | 否 | 出库单存放地点 |
|
||||
|
||||
**处理内容:**
|
||||
|
||||
1. 从会话目录 PDF 提取发票信息 → `invoice_summary.csv`
|
||||
2. 对支付截图多模态 LLM 识别,回填刷卡字段
|
||||
3. 根据发票类型自动分类:差旅发票(高铁票/酒店住宿)不生成出库单;普通发票从模板复制并自动填写
|
||||
|
||||
配置会写入 `src/web/uploads/<session_id>/config.json`。
|
||||
|
||||
**响应(立即):**
|
||||
|
||||
```json
|
||||
{ "status": "started" }
|
||||
```
|
||||
|
||||
处理在后台线程执行,进度与结果通过 `GET /api/logs/<session_id>`(SSE)获取。
|
||||
|
||||
**SSE 完成时 `result` 示例(成功):**
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"elapsed": "45.2s",
|
||||
"invoice_count": 4,
|
||||
"csv_url": "/api/download/<session_id>/invoice_summary.csv",
|
||||
"travel_count": 2,
|
||||
"general_count": 2,
|
||||
"doc_url": "/api/download/<session_id>/%E6%98%93%E8%80%97%E5%93%81%E3%80%81%E5%87%BA%E5%BA%93%E5%8D%95.doc",
|
||||
"doc_ok": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11. SSE 日志流
|
||||
|
||||
```
|
||||
GET /api/logs/<session_id>
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
每 0.5 秒轮询 4 个日志文件,通过文件 size 增量检测新内容:
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `session.log` | 普通日志(extractor、llm_extractor、matcher、pipeline、bot、agent、validator 等模块) |
|
||||
| `file_events.log` | 文件处理进度事件 |
|
||||
| `llm_stream.log` | LLM 流式输出 |
|
||||
| `agent_events.log` | Agent 调度事件 |
|
||||
|
||||
检测到 `result.json` 存在时,读取后发送 `done` 事件并断开连接。
|
||||
|
||||
**SSE 超时:** 900 秒。
|
||||
|
||||
---
|
||||
|
||||
### 12. 提交到财务系统
|
||||
|
||||
```
|
||||
POST /api/submit-financial/<session_id>
|
||||
```
|
||||
|
||||
**前置条件:**
|
||||
|
||||
- 会话目录存在 `config.json`,否则返回 `400`
|
||||
- 存在可用的发票 CSV(通常为 `invoice_summary.csv` 或 `payment_records.csv`)
|
||||
|
||||
**说明:**
|
||||
|
||||
- 前端一般在提交前调用 `/api/save` 保存表格修改
|
||||
- 根据发票类型选择填报模式:纯差旅发票走差旅报销流程,含普通发票走普通报销流程
|
||||
|
||||
**响应(立即):**
|
||||
|
||||
```json
|
||||
{ "status": "started" }
|
||||
```
|
||||
|
||||
**SSE 完成示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": {
|
||||
"ok": true,
|
||||
"submit_ok": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
失败时 `submit_ok: false`,`submit_error` 为错误描述。
|
||||
|
||||
---
|
||||
|
||||
## Agent 多轮校验流程
|
||||
|
||||
Agent 是系统的调度中枢,负责编排信息提取、规则校验、补充材料请求的完整流程。推荐使用 `/api/agent/process` 作为主入口。
|
||||
|
||||
### Agent 状态机
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IDLE
|
||||
IDLE --> EXTRACTING: 启动处理
|
||||
EXTRACTING --> READY: can_submit == true
|
||||
EXTRACTING --> AWAITING_SUPPLEMENT: can_submit == false
|
||||
EXTRACTING --> ERROR: 异常 / 轮次超限
|
||||
READY --> SUBMITTING: _emit_ready_and_submit()
|
||||
SUBMITTING --> DONE: 财务提交完成
|
||||
AWAITING_SUPPLEMENT --> EXTRACTING: 用户补充文件/文字
|
||||
AWAITING_SUPPLEMENT --> READY: 用户强制提交
|
||||
|
||||
note right of EXTRACTING
|
||||
LLM 提取 + validator 校验\n最多 3 次重试
|
||||
end note
|
||||
```
|
||||
|
||||
### 13. 获取 Agent 会话状态
|
||||
|
||||
```
|
||||
GET /api/agent/state/<session_id>
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "a1b2c3d4e5f6",
|
||||
"state": "extracting",
|
||||
"rounds": 1,
|
||||
"max_rounds": 5,
|
||||
"invoice_type": "travel",
|
||||
"extracted_info": { ... },
|
||||
"validation_reports": [ ... ],
|
||||
"user_supplements": [ ... ],
|
||||
"error_message": ""
|
||||
}
|
||||
```
|
||||
|
||||
**状态值:**
|
||||
|
||||
| 状态 | 含义 |
|
||||
|------|------|
|
||||
| `idle` | 初始状态 |
|
||||
| `extracting` | LLM 正在分析文件 |
|
||||
| `awaiting_supplement` | 信息不完整,等待用户补充 |
|
||||
| `ready` | 信息完整,可以提交 |
|
||||
| `submitting` | 正在提交到财务系统 |
|
||||
| `done` | 流程结束 |
|
||||
| `error` | 出错 |
|
||||
|
||||
---
|
||||
|
||||
### 14. 启动 Agent 多轮处理(主入口)
|
||||
|
||||
```
|
||||
POST /api/agent/process/<session_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| username | string | 否 | 财务系统工号 |
|
||||
| password | string | 否 | 登录密码 |
|
||||
| default_name | string | 否 | 默认报销人姓名 |
|
||||
| default_card_no | string | 否 | 默认公务卡号 |
|
||||
| default_person_id | string | 否 | 默认人员编号 |
|
||||
| consumable_storage | string | 否 | 出库单存放地点 |
|
||||
|
||||
**处理流程:**
|
||||
|
||||
1. 发票提取(同 `/api/process`)
|
||||
2. Agent 调度 LLM 分析提取结果
|
||||
3. validator 规则校验(最多 3 次校验-修正循环)
|
||||
4. LLM 语义判断信息完整性(`can_submit` 字段)
|
||||
5. 校验通过 → 自动提交到财务系统
|
||||
6. 校验未通过 → 等待用户补充材料
|
||||
|
||||
**响应(立即):**
|
||||
|
||||
```json
|
||||
{ "status": "started" }
|
||||
```
|
||||
|
||||
**SSE done 事件 - 信息完整(成功提交):**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": {
|
||||
"ok": true,
|
||||
"agent_ready": true,
|
||||
"submit_ok": true,
|
||||
"round": 1,
|
||||
"message": "信息完整,已自动提交到财务系统"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SSE done 事件 - 信息完整但提交失败:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": {
|
||||
"ok": true,
|
||||
"agent_ready": true,
|
||||
"submit_ok": false,
|
||||
"submit_error": "提交失败原因",
|
||||
"round": 1,
|
||||
"message": "校验通过但提交失败"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SSE done 事件 - 需补充材料:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": {
|
||||
"ok": true,
|
||||
"agent_ready": false,
|
||||
"agent_state": "awaiting_supplement",
|
||||
"round": 1,
|
||||
"waiting_for_supplement": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SSE done 事件 - 处理失败:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": {
|
||||
"ok": false,
|
||||
"error": "未提取到任何发票数据"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 15. 补充文件后重新分析
|
||||
|
||||
```
|
||||
POST /api/agent/supplement/<session_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
在 Agent 请求补充材料后,用户上传新文件并调用此接口触发重新分析。
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"files": ["补充材料1.pdf", "补充材料2.jpg"]
|
||||
}
|
||||
```
|
||||
|
||||
**处理流程:**
|
||||
|
||||
1. 记录补充文件
|
||||
2. 重新提取所有发票(包含新文件)
|
||||
3. 加载上一轮分析结果作为历史上下文
|
||||
4. 重新执行 Agent 校验
|
||||
|
||||
**响应(立即):**
|
||||
|
||||
```json
|
||||
{ "status": "started" }
|
||||
```
|
||||
|
||||
**SSE done 事件:** 同上(可能仍需补充或校验通过自动提交)。
|
||||
|
||||
**错误:**
|
||||
|
||||
```json
|
||||
{ "error": "未找到 Agent 状态" }
|
||||
```
|
||||
|
||||
HTTP `404`(未先调用 `/api/agent/process` 或 Agent 状态已丢失)。
|
||||
|
||||
---
|
||||
|
||||
### 16. 通过文字补充信息
|
||||
|
||||
```
|
||||
POST /api/agent/user-supplement/<session_id>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
用户通过对话方式提供补充信息,LLM 解析后更新已提取的信息并重新校验。
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "报销人是张三,公务卡号是 6228480402564890001"
|
||||
}
|
||||
```
|
||||
|
||||
**处理流程:**
|
||||
|
||||
1. LLM 分析用户文字,提取需要更新的字段
|
||||
2. 合并到已提取的信息中
|
||||
3. 保存到缓存
|
||||
4. 重新执行 Agent 校验
|
||||
|
||||
**响应(立即):**
|
||||
|
||||
```json
|
||||
{ "status": "started" }
|
||||
```
|
||||
|
||||
**SSE done 事件:** 同上(可能仍需补充或校验通过自动提交)。
|
||||
|
||||
**错误:**
|
||||
|
||||
```json
|
||||
{ "error": "请输入补充信息" }
|
||||
```
|
||||
|
||||
HTTP `400`(文本为空)。
|
||||
|
||||
---
|
||||
|
||||
### 17. 强制提交,跳过校验
|
||||
|
||||
```
|
||||
POST /api/agent/force-submit/<session_id>
|
||||
```
|
||||
|
||||
当 Agent 校验未通过或出错时,用户可选择强制提交,跳过所有校验直接提交到财务系统。
|
||||
|
||||
**处理流程:**
|
||||
|
||||
1. 将 Agent 状态设为 `READY`
|
||||
2. 执行财务提交
|
||||
|
||||
**响应(立即):**
|
||||
|
||||
```json
|
||||
{ "status": "started" }
|
||||
```
|
||||
|
||||
**SSE done 事件:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": {
|
||||
"ok": true,
|
||||
"submit_ok": true,
|
||||
"submit_error": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSE 事件类型
|
||||
|
||||
### Agent 事件
|
||||
|
||||
通过 `agent_events.log` 轮询推送:
|
||||
|
||||
| 事件类型 | 数据结构 | 触发条件 |
|
||||
|---|---|---|
|
||||
| `agent_state_change` | `{type, state, round, attempt, message}` | 状态切换 |
|
||||
| `agent_ready` | `{type, round, message}` | 双重校验通过 |
|
||||
| `agent_request_supplement` | `{type, round, missing_fields, missing_materials, semantic_issues, suggestion}` | 校验未通过 |
|
||||
| `agent_supplement_received` | `{type, files}` | 收到用户补充 |
|
||||
| `agent_force_submit` | `{type, message}` | 用户强制提交 |
|
||||
| `agent_extract_status` | `{type, state, round, attempt, message}` | 校验-修正循环中的每次尝试结果 |
|
||||
| `agent_error` | `{type, message}` | 提取失败 |
|
||||
| `agent_max_rounds` | `{type, message}` | 达到最大轮次 |
|
||||
|
||||
### 文件进度事件
|
||||
|
||||
通过 `file_events.log` 轮询推送:
|
||||
|
||||
| 事件类型 | 数据结构 | 触发条件 |
|
||||
|---|---|---|
|
||||
| `file_progress` | `{type, file, status, summary?, error?}` | 文件处理状态变更 |
|
||||
|
||||
`status` 取值: `processing` / `done` / `cached` / `error`
|
||||
|
||||
### LLM 流式事件
|
||||
|
||||
通过 `llm_stream.log` 轮询推送:
|
||||
|
||||
| 事件类型 | 数据结构 | 触发条件 |
|
||||
|---|---|---|
|
||||
| `llm_stream` | `{type, phase, text?}` | LLM 输出流 |
|
||||
|
||||
`phase` 取值: `start` / `reasoning` / `chunk` / `end` / `error`
|
||||
|
||||
### 完成事件
|
||||
|
||||
SSE 检测到 `result.json` 后直接发送:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "done",
|
||||
"result": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误码汇总
|
||||
|
||||
| HTTP | 场景 |
|
||||
|------|------|
|
||||
| 400 | 参数缺失、未找到配置、文本为空等 |
|
||||
| 404 | `session_id` 不存在、文件不存在、Agent 状态丢失 |
|
||||
| 500 | CSV 读取失败、服务未初始化等内部错误 |
|
||||
|
||||
统一错误体:
|
||||
|
||||
```json
|
||||
{ "error": "错误描述" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 发票类型分类
|
||||
|
||||
系统自动将发票分为两类,影响出库单生成和后续报销流程:
|
||||
|
||||
| 类型 | 判断依据 | 出库单 | 报销流程 |
|
||||
|------|----------|--------|----------|
|
||||
| 差旅发票 | 高铁票、酒店住宿等 | 不生成 | 差旅报销 |
|
||||
| 普通发票 | 其他(办公用品、耗材等) | 自动生成 | 普通报销 |
|
||||
|
||||
`/api/process` 和 `/api/save` 的响应中 `travel_count` / `general_count` 即为分类统计。
|
||||
|
||||
---
|
||||
|
||||
## 端到端流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant S as SSE连接
|
||||
participant B as 后端线程
|
||||
participant A as Agent调度器
|
||||
|
||||
F->>B: POST /api/session
|
||||
B-->>F: session_id
|
||||
|
||||
F->>B: POST /api/upload/{sid} (多次)
|
||||
F->>B: POST /api/agent/process/{sid}
|
||||
B-->>F: {status: "started"}
|
||||
|
||||
F->>S: GET /api/logs/{sid}
|
||||
|
||||
Note over B,A: 后台线程启动
|
||||
B->>A: extract_invoices()
|
||||
S-->>F: file_progress (processing/done)
|
||||
S-->>F: llm_stream (start/chunk/end)
|
||||
S-->>F: agent_state_change (extracting)
|
||||
|
||||
Note over A: LLM 提取 + validator 校验<br/>最多 3 次重试
|
||||
|
||||
alt 信息完整
|
||||
A->>A: state → READY
|
||||
A->>A: _emit_ready_and_submit()
|
||||
S-->>F: agent_ready
|
||||
S-->>F: done (submit_ok=true)
|
||||
F->>F: es.close()
|
||||
else 信息不完整
|
||||
A->>A: state → AWAITING_SUPPLEMENT
|
||||
S-->>F: agent_request_supplement
|
||||
S-->>F: done (waiting_for_supplement=true)
|
||||
F->>F: es.close()
|
||||
|
||||
F->>B: 上传补充文件或输入文字
|
||||
alt 文件补充
|
||||
F->>B: POST /api/agent/supplement/{sid}
|
||||
else 文字补充
|
||||
F->>B: POST /api/agent/user-supplement/{sid}
|
||||
end
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/{sid}
|
||||
|
||||
Note over A: 重新分析 + 校验
|
||||
|
||||
alt 仍不完整
|
||||
S-->>F: agent_request_supplement
|
||||
S-->>F: done (waiting=true)
|
||||
F->>F: es.close()
|
||||
Note over F: 可继续补充或强制提交
|
||||
else 完整
|
||||
A->>A: state → READY
|
||||
A->>A: _emit_ready_and_submit()
|
||||
S-->>F: agent_ready
|
||||
S-->>F: done (submit_ok=true)
|
||||
F->>F: es.close()
|
||||
end
|
||||
|
||||
alt 强制提交
|
||||
F->>B: POST /api/agent/force-submit/{sid}
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/{sid}
|
||||
S-->>F: agent_force_submit
|
||||
S-->>F: done
|
||||
F->>F: es.close()
|
||||
end
|
||||
end
|
||||
|
||||
F->>B: GET /api/data/{sid}
|
||||
B-->>F: fields + data
|
||||
F->>B: POST /api/save/{sid}
|
||||
Note over B: 更新 CSV,重新生成出库单
|
||||
B-->>F: doc_url
|
||||
|
||||
F->>B: GET /api/download/{sid}/易耗品、出库单.doc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关 CLI
|
||||
|
||||
不经过 Web、在本地直接填写出库单:
|
||||
|
||||
```bash
|
||||
uv run python -m src.infra.documents.consumable --csv invoice_summary.csv --doc "易耗品、出库单.doc"
|
||||
```
|
||||
|
||||
详见 [README.md](./README.md)。
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
---
|
||||
last_reviewed: 2026-10-19
|
||||
---
|
||||
|
||||
# MCP 接口文档
|
||||
|
||||
本项目的**唯一业务入口**是 MCP 服务器:`python -m src.mcp.server`(stdio 传输),工具名在宿主里表现为 `mcp__arb__<tool>`。
|
||||
没有 Web 界面,没有 CLI 产品入口,本项目也不调用任何大模型——**读图与综合判断由宿主 Agent 完成**,
|
||||
本项目只提供确定性工具(渲染、金额匹配、规则校验、文档生成、浏览器填报)。
|
||||
|
||||
## 注册到宿主
|
||||
|
||||
DSH(profile patch,`~/.dsh/profiles/<profile>/cordis.patch.yml`):
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
- id: mcp-arb
|
||||
name: '@deepseek-ai/dsh-mcp-client'
|
||||
config:
|
||||
serverName: arb
|
||||
transport: stdio
|
||||
command: 'D:/Code/AI Agent/自动报销系统/.venv/Scripts/python.exe'
|
||||
args: ['-m', 'src.mcp.server']
|
||||
cwd: 'D:/Code/AI Agent/自动报销系统'
|
||||
```
|
||||
|
||||
> 注意:DSH 对 stdio 子进程做了环境变量清洗(抹掉 `*KEY|PASSWORD|SECRET|TOKEN` 与全部 `DSH_*`),
|
||||
> 所以财务系统凭据只能来自 `scripts/data/config.json`,不能靠环境变量注入。
|
||||
|
||||
通用 MCP 客户端等价配置:
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "arb": { "command": "<项目根>/.venv/Scripts/python.exe", "args": ["-m", "src.mcp.server"], "cwd": "<项目根>" } } }
|
||||
```
|
||||
|
||||
改完 Python 代码需要让宿主重连(DSH 会热插拔新工具条目,但**不会**重载已运行子进程里的代码;
|
||||
杀掉 stdio 子进程即可自动重启并重同步工具)。
|
||||
|
||||
## 返回信封
|
||||
|
||||
所有工具返回统一信封,宿主判类型看 `ok`:
|
||||
|
||||
```json
|
||||
{ "ok": true, "case_id": "...", "next_action": "..." }
|
||||
{ "ok": false, "reason": "缺少 payment_records.csv", "next_action": "先 mcp__arb__match_payments 生成汇总" }
|
||||
```
|
||||
|
||||
- **不抛异常穿透协议**:文件缺失、参数不合法、COM 失败都转成 `ok: false` + `reason` + `next_action`。
|
||||
- `next_action` 是给 Agent 的下一步指令,通常是具体工具名,照做即可。
|
||||
- 部分客户端会把结果包在 `content[0].text`(JSON 字符串)里,同时提供 `structuredContent`;两者内容一致。
|
||||
|
||||
## 会话目录
|
||||
|
||||
一个报销任务 = 一个 `cases/<case_id>/` 目录(`case_id` 形如 `20261019-6月毕业采集`)。
|
||||
**阶段完全由产物推导**,没有状态机文件,可随时断电重来、可人工改文件后继续。
|
||||
|
||||
```
|
||||
cases/<case_id>/
|
||||
├── case.json 名称、类型、上传令牌(切勿提交)
|
||||
├── config.json 可选,会话级凭据覆盖(白名单字段)
|
||||
├── <材料>.pdf|jpg 发票、支付截图、申请单原件
|
||||
├── inbox/ 手机扫码上传落地点,case_add_files(source="inbox") 收取
|
||||
├── pages/<stem>/p01.png 渲染页面 + text.txt(PDF 文本层)+ meta.json(dpi/转正角/字符数)
|
||||
├── .invoice_cache/*.json 单张票据抽取结果(宿主产出,本服务规范化后落盘)
|
||||
├── match_result.json 匹配结果
|
||||
├── invoice_groups.json 差旅/普通分组与计数
|
||||
├── invoice_summary.csv 发票级汇总
|
||||
├── payment_records.csv 支付记录汇总(申购单取数)
|
||||
├── travel_applications.json 出差申请单
|
||||
├── travel_info.json 差旅报销结构化数据
|
||||
├── normal_info.json 普通报销结构化数据
|
||||
├── artifacts/ 申购单等文档产物
|
||||
├── screenshots/ (P3 落地后出现)填报过程截图
|
||||
└── submit_preview.json (P4 落地后出现)提交前快照与摘要
|
||||
```
|
||||
|
||||
`cases/` 整个目录都在 `.gitignore` 里:发票含身份证号与卡号。
|
||||
|
||||
## 标准流程
|
||||
|
||||
1. `case_create` → 记下 `case_id` 与返回的 `upload_page`(手机扫码上传页)
|
||||
2. `case_add_files`(本地路径用**正斜杠**)或 `case_add_files(source="inbox")` 收手机上传
|
||||
3. `case_render_pages` → `text_chars>0` 先 `case_read_text`,否则读 `pages/**.png`
|
||||
4. 逐张按 `extraction_spec` 出 JSON → `case_save_extraction`;核对后小改用 `case_patch_extraction`
|
||||
5. `match_payments` → **逐条处理 `review_required`**(未匹配、发票总额小于支付总额、一对多凑额)
|
||||
6. `info_skeleton` 取骨架 → 填值 → `save_info`(自动跑校验);`validate_info` 可反复试算不落盘
|
||||
7. 普通发票再 `doc_requisition` 生成申购单
|
||||
8. 每步之后 `case_status` 复查;`case_reset(scope)` 清派生产物重跑
|
||||
|
||||
## 硬约束
|
||||
|
||||
- **不得回显密码**,不得把凭据写进产物或日志
|
||||
- **不得臆造财务数据**:拿不准的字段标出来问用户,禁止自己宣布"可以提交"
|
||||
- 产物只写 `cases/<case_id>/`
|
||||
- 文本层与读图结论冲突时(尤其编号、卡号、金额位数)必须复核,不要默认信任某一路
|
||||
|
||||
## 尚未实现(不要调用,也不要在回答里承诺)
|
||||
|
||||
浏览器填报(`browser_*`)、提交预览与执行(`submit_*`)。需要这些步骤时告知用户该环节还在建设中,
|
||||
可临时用旧的 Web 流程(代码在提交 `5f57400` 里)。
|
||||
|
||||
---
|
||||
|
||||
# 工具参考
|
||||
|
||||
以下内容由运行中的服务器 schema 自动生成,与代码同源(`scripts/mcp_smoke.py` 会打印同一份清单)。
|
||||
|
||||
## 会话与材料
|
||||
|
||||
每个报销任务是一个会话目录 `cases/<case_id>/`,阶段由产物推导。
|
||||
|
||||
### `mcp__arb__case_create`
|
||||
|
||||
新建报销会话目录,返回 case_id 与目录路径。
|
||||
|
||||
name 是本次报销的人类可读名称(如「6月毕业采集」);
|
||||
invoice_type 可选 travel|normal,未定时留空,抽取后由发票分类决定。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `name` | string | 是 | — | — |
|
||||
| `invoice_type` | null/string | 否 | — | — |
|
||||
|
||||
### `mcp__arb__case_list`
|
||||
|
||||
列出所有报销会话及其推导阶段、计数与待收文件数。
|
||||
|
||||
无参数。
|
||||
|
||||
### `mcp__arb__case_status`
|
||||
|
||||
单个会话的完整态势:材料数、抽取完成/缺失/陈旧、匹配、信息、产物、收件箱、下一步建议。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
|
||||
### `mcp__arb__case_add_files`
|
||||
|
||||
把材料拷入会话目录。
|
||||
|
||||
paths 传绝对路径列表(从手机下载目录、微信文件目录等任意位置取文件);
|
||||
source='inbox' 表示收取手机扫码上传落在 inbox 的文件。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `paths` | array/null | 否 | — | — |
|
||||
| `source` | null/string | 否 | — | — |
|
||||
|
||||
### `mcp__arb__case_inbox`
|
||||
|
||||
列出手机/PC 上传进来但尚未进入流程的文件。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
|
||||
### `mcp__arb__case_reset`
|
||||
|
||||
清理派生产物以便重跑。scope=extractions|match|info|pages|artifacts|all。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `scope` | string | 否 | `"extractions"` | — |
|
||||
|
||||
## 抽取交接
|
||||
|
||||
渲染页面与读文本层由宿主 Agent 自己判断,本服务不调用任何模型。
|
||||
|
||||
### `mcp__arb__case_render_pages`
|
||||
|
||||
为材料准备可读取的页面图片与 PDF 文本层。
|
||||
|
||||
返回每项材料的页 PNG 路径、文本层字符数与自动转正角度。
|
||||
有文本层(text_chars > 0)时应优先读文本层,比读图更准且省 token。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `files` | array/null | 否 | — | — |
|
||||
| `dpi` | integer | 否 | `150` | — |
|
||||
| `force` | boolean | 否 | `false` | — |
|
||||
|
||||
### `mcp__arb__case_read_text`
|
||||
|
||||
读取某材料渲染时提取的 PDF 文本层;无文本层时返回空。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `file` | string | 是 | — | — |
|
||||
|
||||
### `mcp__arb__extraction_spec`
|
||||
|
||||
返回抽取字段定义。kind 省略给全部文档类型;'info' 给 travel_info/normal_info 骨架。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `kind` | null/string | 否 | — | — |
|
||||
|
||||
### `mcp__arb__case_save_extraction`
|
||||
|
||||
把单张票据的抽取结果写入会话缓存(按 <stem><suffix>.json 约定)。
|
||||
|
||||
日期会规范成 YYYY-MM-DD、金额规范成两位小数字符串;
|
||||
返回 warnings 提示必填缺失,但不阻断写入(完整性由 arb_validate_info 判定)。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `source_file` | string | 是 | — | — |
|
||||
| `data` | object | 是 | — | — |
|
||||
|
||||
### `mcp__arb__case_patch_extraction`
|
||||
|
||||
局部修正已落盘的抽取结果(人工核对后改字段用)。
|
||||
|
||||
只合并传入的键,返回每个被改字段的 from/to,避免静默覆盖;
|
||||
未在 schema 中出现的键会告警但仍写入(有些字段是给下游自定义用的)。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `source_file` | string | 是 | — | — |
|
||||
| `patch` | object | 是 | — | — |
|
||||
|
||||
## 匹配与校验
|
||||
|
||||
匹配与校验都是确定性代码;结论以 `validate_info` 为准,不要自己宣布可提交。
|
||||
|
||||
### `mcp__arb__match_payments`
|
||||
|
||||
按缓存做发票与支付记录的金额匹配,并写出 CSV 与 invoice_groups.json。
|
||||
|
||||
返回 matched / unmatched_invoices / unmatched_payments / review_required。
|
||||
未匹配与一对多凑额项必须复核,不要盲信贪心结果。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `tolerance` | null/number | 否 | — | — |
|
||||
|
||||
### `mcp__arb__info_skeleton`
|
||||
|
||||
按校验规则生成「形状完整、值为空」的可填骨架。
|
||||
|
||||
规则里 required 为 true 的字段(含 remark 这类备注)要求键必须存在,
|
||||
所以别凭记忆拼 JSON:先取骨架再逐个填值。骨架与 validation_rules.json 同源,不会漂移。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `info_type` | string | 是 | — | — |
|
||||
|
||||
### `mcp__arb__save_info`
|
||||
|
||||
写 travel_info 或 normal_info 到缓存,并立刻跑一次规则校验。
|
||||
|
||||
info_type 取 travel|normal。完整性以返回的 validation 为准,
|
||||
不要自己在 data 里塞 can_submit。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `info_type` | string | 是 | — | — |
|
||||
| `data` | object | 是 | — | — |
|
||||
|
||||
### `mcp__arb__validate_info`
|
||||
|
||||
校验报销信息完整性。省略 draft 时校验缓存里已存的 info。
|
||||
|
||||
也可传 draft 先试校验不落盘,适合改字段时反复迭代。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `info_type` | null/string | 否 | — | — |
|
||||
| `draft` | null/object | 否 | — | — |
|
||||
|
||||
### `mcp__arb__rules_get`
|
||||
|
||||
返回声明式校验规则(config/validation_rules.json),供补字段前对照。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `info_type` | null/string | 否 | — | — |
|
||||
|
||||
## 文档产物
|
||||
|
||||
Excel COM,需本机装有 Microsoft Excel;仅普通发票需要。
|
||||
|
||||
### `mcp__arb__doc_requisition`
|
||||
|
||||
生成低值易耗品申购单(Excel,仅普通发票)。
|
||||
|
||||
exclude 传物品名关键词可剔除不参与申购的发票;project 覆盖「对应实验实训项目」,
|
||||
默认取 normal_info 的报销事由。需要 Windows + 已安装 Microsoft Excel。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `exclude` | array/null | 否 | — | — |
|
||||
| `project` | null/string | 否 | — | — |
|
||||
+8
-5
@@ -1,18 +1,21 @@
|
||||
---
|
||||
last_reviewed: 2026-06-11
|
||||
last_reviewed: 2026-10-19
|
||||
---
|
||||
|
||||
# docs — 公开文档目录
|
||||
|
||||
本目录存放面向项目用户和外部贡献者的公开文档及说明文件。
|
||||
本目录存放面向项目用户和外部贡献者的公开文档。
|
||||
|
||||
## 文档清单
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `API.md` | Web API 完整接口文档:路由、请求/响应格式、SSE 日志流、错误码、端到端流程 |
|
||||
| `报销操作指南.md` | 面向最终用户的操作步骤说明 |
|
||||
| `MCP.md` | MCP 工具完整契约:注册方法、返回信封、会话目录、标准流程、17 个工具的参数表 |
|
||||
| `报销操作指南.md` | 面向最终用户的报销口径与材料准备说明 |
|
||||
|
||||
> 旧的 `API.md`(Flask 路由/SSE 文档)已随 Web 界面删除;需要历史接口形态时看 git 提交 `5f57400`。
|
||||
|
||||
## 文档边界
|
||||
|
||||
维护规范、实施方案、经验总结等内部资料统一放置在 `.agents/` 目录下,不混入本目录。
|
||||
维护规范、实施方案、经验总结等内部资料统一放在 `.agents/`;宿主 Agent 的报销操作纪律在
|
||||
`.agents/skills/reimbursement/SKILL.md`。
|
||||
|
||||
+142
-313
@@ -1,211 +1,101 @@
|
||||
---
|
||||
last_reviewed: 2026-06-09
|
||||
last_reviewed: 2026-10-19
|
||||
---
|
||||
|
||||
# 阜阳师范大学财务报销系统 - 自动化操作指南
|
||||
|
||||
> 适用场景:日常报销录入(支持 Web 界面和 CLI 两种方式)
|
||||
> 最后更新:2026-06-09
|
||||
> 适用场景:日常报销录入。入口是 MCP 服务器,编排由宿主 Agent(如 DSH)完成;工具参数表见 [MCP.md](./MCP.md)。
|
||||
> 本文讲财务侧口径、页面要素与材料要求。文中账号、卡号、姓名一律是占位示例——**真实值不要写进仓库任何位置**。
|
||||
|
||||
---
|
||||
|
||||
## 一、系统概览
|
||||
|
||||
```
|
||||
整体流程:
|
||||
|
||||
信息门户(SSO登录) → 财务系统入口 → 单点登录页 → 网络报销 → 日常报销录入
|
||||
(tyrz.fynu.edu.cn) (点击"财务系统") (新标签页) (a:has(img)) (/expen/common/common)
|
||||
|
||||
目标系统: http://210.45.32.214:8081
|
||||
用户: 张三 (工号: xxxxxxx)
|
||||
(tyrz.fynu.edu.cn) (点击「财务系统」) (新标签页) (a:has(img)) (/expen/common/common)
|
||||
```
|
||||
|
||||
### 关键 URL
|
||||
|
||||
| 系统 | URL | 说明 |
|
||||
|------|-----|------|
|
||||
| 系统 | 地址 | 说明 |
|
||||
|------|------|------|
|
||||
| SSO 登录 | `https://tyrz.fynu.edu.cn/sso/login` | 统一认证入口 |
|
||||
| 信息门户 | `https://tyrz.fynu.edu.cn/oshall` | 登录后跳转目标 |
|
||||
| 报销系统 | `http://210.45.32.214:8081` | 网络报销主系统 |
|
||||
| 日常报销录入 | `/expen/common/common?v=4.0` | 目标录入页面 |
|
||||
| 报销系统 | `http://<财务系统内网地址>:8081` | 网络报销主系统 |
|
||||
| 日常报销录入 | `/expen/common/common?v=4.0` | 普通报销目标页 |
|
||||
| 差旅报销 | `/expen/travel/travel?v=4.0` | 差旅报销目标页 |
|
||||
|
||||
地址来自 `scripts/data/config.json`,也可用环境变量覆盖(`SSO_LOGIN_URL` 等,见 `.env.example`;
|
||||
项目**不会**自动读取 `.env` 文件,需要自己导出或写进宿主的 env 配置)。
|
||||
|
||||
---
|
||||
|
||||
## 二、两种使用方式
|
||||
## 二、使用方式
|
||||
|
||||
### 方式一:Web 界面(推荐)
|
||||
没有 Web 界面,也没有 CLI 产品入口。准备三步,之后全部在 Agent 对话里完成:
|
||||
|
||||
适合日常使用,可视化操作,支持手机端拍照上传。
|
||||
1. **注册 MCP 服务器**到宿主:`python -m src.mcp.server`(stdio),DSH 的 patch 写法见 `docs/MCP.md`
|
||||
2. **配好** `scripts/data/config.json`(参考根目录 `config.example.json`)
|
||||
3. **要手机拍照上传时**启动收件器:`make upload`(默认 `0.0.0.0:5055`)
|
||||
|
||||
```bash
|
||||
uv run python src/web/app.py
|
||||
# 访问: http://localhost:5000
|
||||
```
|
||||
|
||||
### 方式二:CLI 命令行
|
||||
|
||||
适合脚本化、批量处理。
|
||||
|
||||
```bash
|
||||
# 全流程(发票提取 → LLM 识别 → 浏览器填报)
|
||||
uv run python src/main.py
|
||||
|
||||
# 分步执行
|
||||
uv run python src/main.py --step invoice # 仅发票提取
|
||||
uv run python src/main.py --step submit # 仅浏览器填报
|
||||
|
||||
# 覆盖登录凭据
|
||||
uv run python src/main.py -u 200000001 -p "your_password"
|
||||
```
|
||||
自检:`make mcp-smoke` 会真起 stdio 子进程握手并列出 17 个工具。
|
||||
|
||||
---
|
||||
|
||||
## 三、数据准备
|
||||
## 三、材料准备
|
||||
|
||||
### 3.1 发票数据 CSV
|
||||
| 材料 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| 发票 PDF / 图片 | 是 | 电子发票尽量给 PDF:有文本层,准确率明显高于读图 |
|
||||
| 支付截图 | 是 | 公务卡刷卡记录;要能看清**商户**、日期、金额、卡号后四位 |
|
||||
| 出差事前申请单 | 差旅必需 | 缺它财务不认,Agent 会主动索要 |
|
||||
| 其他附件 | 视情况 | 会议通知、合同等 |
|
||||
|
||||
上传 PDF 发票文件和支付截图,系统自动完成:
|
||||
1. 从 PDF 提取发票信息
|
||||
2. 多模态 LLM 识别支付截图中的刷卡信息
|
||||
3. 生成 `invoice_summary.csv`
|
||||
### 发票类型分流
|
||||
|
||||
### 3.2 发票类型分类
|
||||
| 类型 | 判断依据 | 产物 | 报销流程 |
|
||||
|------|----------|------|----------|
|
||||
| 差旅发票 | 高铁票、机票、酒店住宿等 | 不生成申购单 | 差旅报销(含按人按天补助) |
|
||||
| 普通发票 | 其他(办公用品、耗材、元件等) | 低值易耗品**申购单** | 普通报销(一条总明细 + 逐笔支付) |
|
||||
|
||||
系统自动将发票分为两类,影响后续处理流程:
|
||||
|
||||
| 类型 | 判断依据 | 出库单 | 报销流程 |
|
||||
|------|----------|--------|----------|
|
||||
| 差旅发票 | 高铁票、酒店住宿等 | 不生成 | 差旅报销 |
|
||||
| 普通发票 | 其他(办公用品、耗材等) | 自动生成 | 普通报销 |
|
||||
|
||||
### 3.3 附件文件
|
||||
|
||||
PDF 发票文件需与支付截图配对上传。系统通过金额匹配自动关联发票和支付记录。
|
||||
> Word 版「易耗品出库单」已下线:模板自 2026-08-31 起不在仓库,那条路径此前一直静默失败。
|
||||
|
||||
---
|
||||
|
||||
## 四、Web 界面操作流程
|
||||
## 四、Agent 操作流程
|
||||
|
||||
### 4.1 启动服务
|
||||
| 步 | 做什么 | 用什么 |
|
||||
|----|--------|--------|
|
||||
| 1 | 建会话,拿到手机上传页地址 | `case_create` |
|
||||
| 2 | 材料入库(本地路径用正斜杠;手机上传后用 `source="inbox"` 收取) | `case_add_files` |
|
||||
| 3 | 渲染页面与文本层 | `case_render_pages` → `case_read_text` 或直接读 PNG |
|
||||
| 4 | 逐张抽取落盘;核对后局部修正 | `case_save_extraction` / `case_patch_extraction` |
|
||||
| 5 | 金额匹配,**逐条处理 `review_required`** | `match_payments` |
|
||||
| 6 | 取骨架 → 填值 → 落盘校验(试算用 `validate_info` 不落盘) | `info_skeleton` → `save_info` |
|
||||
| 7 | 普通发票生成申购单(`exclude` 剔除不参与申购的物品) | `doc_requisition` |
|
||||
| 8 | 随时复查态势;要重跑就清派生产物 | `case_status` / `case_reset` |
|
||||
|
||||
```bash
|
||||
uv run python src/web/app.py
|
||||
# 访问: http://localhost:5000
|
||||
```
|
||||
**`ok: true` 的含义有限**:`match_payments` 成功只代表贪心跑完。一对多凑额、发票总额小于支付总额、
|
||||
未匹配项都会进 `review_required`,必须逐条确认——等额发票尤其容易配错。
|
||||
|
||||
### 4.2 上传文件
|
||||
**什么时候算完成**:`validate_info` 报告 `valid: true` 且无业务约束违反。缺料就向用户要材料;
|
||||
Agent 不许自己宣布「可以提交了」。
|
||||
|
||||
1. **发票 PDF**:点击或拖拽上传 PDF 文件(支持多选)
|
||||
2. **支付截图**:点击或拖拽上传图片文件(支持多选)
|
||||
3. **手机扫码上传**:扫描页面二维码,通过手机拍照上传支付截图
|
||||
### 产物位置
|
||||
|
||||
### 4.3 配置信息
|
||||
全部在 `cases/<case_id>/`(整个目录已 gitignore,含身份证号与卡号):
|
||||
|
||||
填写以下配置项(也可通过上传 `config.json` 快速填充):
|
||||
|
||||
| 字段 | 说明 |
|
||||
| 产物 | 说明 |
|
||||
|------|------|
|
||||
| 账号 | 财务系统工号 |
|
||||
| 密码 | 登录密码 |
|
||||
| 默认姓名 | 报销人姓名 |
|
||||
| 公务卡号 | 公务卡卡号 |
|
||||
| 人员编号 | 人员编号 |
|
||||
| 存放地点 | 出库单存放地点 |
|
||||
|
||||
### 4.4 开始处理
|
||||
|
||||
点击"开始处理"按钮,系统自动执行:
|
||||
|
||||
1. **发票提取**:从 PDF 提取发票信息
|
||||
2. **LLM 信息提取**:多模态 LLM 识别支付截图中的刷卡信息
|
||||
3. **发票分类**:自动区分差旅发票和普通发票
|
||||
4. **出库单生成**:普通发票自动生成易耗品出库单(差旅发票跳过)
|
||||
|
||||
处理过程中可在日志面板实时查看进度。
|
||||
|
||||
### 4.5 编辑发票数据
|
||||
|
||||
处理完成后,发票数据以可编辑表格形式展示:
|
||||
|
||||
- 直接点击单元格即可编辑
|
||||
- 修改后点击"提交到财务系统"时自动保存
|
||||
- 保存后会自动重新生成出库单
|
||||
|
||||
### 4.6 下载文件
|
||||
|
||||
处理完成后下载:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `invoice_summary.csv` | 发票汇总数据 |
|
||||
| `易耗品、出库单.doc` | 自动填写的出库单(仅普通发票) |
|
||||
|
||||
### 4.7 提交到财务系统
|
||||
|
||||
确认数据无误后,点击"提交到财务系统"按钮,系统自动:
|
||||
|
||||
1. 登录信息门户
|
||||
2. 进入财务系统
|
||||
3. 创建报销单
|
||||
4. 填写基本信息
|
||||
5. 录入报销明细
|
||||
6. 录入支付方式
|
||||
7. 上传附件
|
||||
| `pages/<票据>/p01.png` | 渲染页面(已按文本方向自动转正) |
|
||||
| `.invoice_cache/*.json` | 单张票据结构化数据 |
|
||||
| `match_result.json` / `invoice_groups.json` | 匹配与分组结果 |
|
||||
| `invoice_summary.csv` / `payment_records.csv` | 发票级与支付级汇总 |
|
||||
| `travel_info.json` / `normal_info.json` | 填报所需的完整结构化数据 |
|
||||
| `artifacts/低值易耗品申购_<case>.xls` | 申购单,拿去资产/财务口径核对 |
|
||||
|
||||
---
|
||||
|
||||
## 五、CLI 执行流程
|
||||
|
||||
### 5.1 全流程执行
|
||||
|
||||
```bash
|
||||
uv run python src/main.py
|
||||
```
|
||||
|
||||
执行步骤:
|
||||
|
||||
```
|
||||
Step 1: 发票提取
|
||||
├── 扫描 PDF 发票文件
|
||||
├── 提取发票信息
|
||||
├── 生成 invoice_summary.csv
|
||||
└── 自动分类(差旅/普通)
|
||||
|
||||
Step 2: LLM 信息提取
|
||||
├── 扫描支付截图
|
||||
├── 多模态 LLM 识别刷卡信息
|
||||
├── 回填 CSV 刷卡字段
|
||||
└── 更新 invoice_summary.csv
|
||||
|
||||
Step 3: 报销提交
|
||||
├── 登录信息门户
|
||||
├── 进入财务系统
|
||||
├── 创建报销单
|
||||
├── 填写基本信息
|
||||
├── 录入报销明细
|
||||
├── 录入支付方式
|
||||
├── 上传附件
|
||||
└── 提交(需手动确认)
|
||||
```
|
||||
|
||||
### 5.2 分步执行
|
||||
|
||||
```bash
|
||||
# 仅发票提取
|
||||
uv run python src/main.py --step invoice
|
||||
|
||||
# 仅浏览器填报
|
||||
uv run python src/main.py --step submit
|
||||
```
|
||||
|
||||
### 5.3 上传目录
|
||||
|
||||
CLI 模式自动发现 `src/web/uploads/` 下最新的会话文件夹,该文件夹包含上传的 PDF 和图片。
|
||||
|
||||
---
|
||||
|
||||
## 六、页面元素速查
|
||||
## 五、页面元素速查
|
||||
|
||||
### 基本信息页
|
||||
|
||||
@@ -247,7 +137,7 @@ CLI 模式自动发现 `src/web/uploads/` 下最新的会话文件夹,该文
|
||||
| 字段 | 选择器 | 操作 |
|
||||
|------|--------|------|
|
||||
| 增加按钮 | `#insertAcc` | click |
|
||||
| 附件类型 | `#fjlx` | select_option → '1'(发票) |
|
||||
| 附件类型 | `#fjlx` | select_option → 1(发票) |
|
||||
| 附件说明 | `#fpsmxx` | fill |
|
||||
| 文件上传 | `#file` | set_input_files |
|
||||
| 确定按钮 | `#cjtj` | click |
|
||||
@@ -257,198 +147,137 @@ CLI 模式自动发现 `src/web/uploads/` 下最新的会话文件夹,该文
|
||||
| 操作 | 选择器 |
|
||||
|------|--------|
|
||||
| 提交按钮 | `#submit` |
|
||||
| 提交按钮(备用) | `#submit2` |
|
||||
| 提交按钮(备用) | `#submit2` |
|
||||
|
||||
---
|
||||
|
||||
## 七、数据流向
|
||||
## 六、数据流向
|
||||
|
||||
```
|
||||
PDF 发票 + 支付截图
|
||||
│
|
||||
▼ extract_invoices()
|
||||
│
|
||||
├── 读取 PDF 发票
|
||||
├── 提取发票信息
|
||||
├── 自动分类(差旅/普通)
|
||||
└── 输出: invoice_summary.csv
|
||||
│
|
||||
▼ enrich_with_llm()
|
||||
├── 多模态 LLM 识别支付截图
|
||||
├── 回填刷卡字段
|
||||
└── 更新 invoice_summary.csv
|
||||
│
|
||||
▼ fill_consumable_from_template()
|
||||
├── 仅普通发票
|
||||
├── 从模板复制出库单
|
||||
└── 自动填写出库单
|
||||
│
|
||||
▼ run_bot()
|
||||
├── 登录信息门户
|
||||
├── 进入财务系统
|
||||
├── 创建报销单
|
||||
├── 填写基本信息
|
||||
├── 录入报销明细
|
||||
├── 录入支付方式
|
||||
├── 上传附件
|
||||
└── 提交
|
||||
材料(PDF / 图片)
|
||||
│ case_render_pages:页面 PNG + 文本层 + 自动转正
|
||||
▼
|
||||
宿主 Agent 读图与判断 ← 本项目不调用任何模型
|
||||
│ case_save_extraction(schemas.py 规范化日期与金额)
|
||||
▼
|
||||
.invoice_cache/*.json
|
||||
│ match_payments(分组 + 一对一/一对多贪心 + 业务约束)
|
||||
▼
|
||||
match_result.json / invoice_groups.json / 两份 CSV
|
||||
│ info_skeleton → 填值 → save_info(声明式规则校验)
|
||||
▼
|
||||
travel_info.json 或 normal_info.json
|
||||
│ doc_requisition(Excel COM,仅普通发票)
|
||||
▼
|
||||
artifacts/ 申购单
|
||||
│ 浏览器填报与提交:P3/P4 建设中,当前没有工具
|
||||
▼
|
||||
财务系统报销单
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、关键设计说明
|
||||
## 七、关键设计说明
|
||||
|
||||
### 8.1 浏览器启动
|
||||
### 7.1 浏览器启动
|
||||
|
||||
系统使用 Playwright 的 `sync_playwright()` 启动 Chromium 浏览器,支持 `headless` 模式。每次运行均启动新浏览器实例,需重新登录。可通过 `headless` 参数控制是否显示浏览器窗口。
|
||||
Playwright `sync_playwright()` 启动 Chromium,**默认有头**(`headless=False`)——提交不可逆,得看清楚在点什么。
|
||||
SSO 需要扫码或验证码时人工完成即可,流程会继续。每次运行都要重新登录。
|
||||
|
||||
### 8.2 明细录入策略
|
||||
### 7.2 明细录入策略
|
||||
|
||||
系统采用"一条总明细"策略:将所有发票合并为一条报销明细,报销总金额为所有发票刷卡金额之和,单据数为发票总张数。支付方式则逐张发票分别录入,每张发票对应一条支付记录。
|
||||
普通报销用「一条总明细」:所有发票合并成一条,报销总金额 = 各笔支付之和,单据数 = 发票张数;
|
||||
支付方式逐笔录入,一笔一条。差旅按人去程/返程分条录交通费,补助按人按天算。
|
||||
|
||||
### 8.3 经济科目选择
|
||||
### 7.3 经济科目与项目
|
||||
|
||||
系统在经济科目弹窗中固定选择第 3 行。如需更改科目,修改 `rows[2]` 的索引即可。
|
||||
经济科目弹窗固定选第 3 行(`rows[2]`),项目弹窗固定选第 1 行。要改就改这两个索引,
|
||||
位置在 `src/infra/browser/travel.py` 与 `normal.py`。
|
||||
|
||||
### 8.4 项目选择
|
||||
### 7.4 网络报销链接动态获取
|
||||
|
||||
系统在项目选择弹窗中固定选择第 1 行。如需更改项目,修改 `first_row` 的选择逻辑即可。
|
||||
单点登录页的「网络报销」链接参数每次不同,因此用 `a:has(img[src*="wlbx"])` 定位并读 `href`,不硬编码 URL。
|
||||
|
||||
### 8.5 网络报销链接动态获取
|
||||
### 7.5 手机上传
|
||||
|
||||
单点登录页的"网络报销"链接参数每次不同,系统通过 `a:has(img[src*="wlbx"])` 精确定位链接,动态提取 `href` 属性后导航,不硬编码 URL。
|
||||
|
||||
### 8.6 发票类型分流
|
||||
|
||||
- **差旅发票**(高铁票/酒店住宿):不生成易耗品出库单,走差旅报销流程
|
||||
- **普通发票**:生成易耗品出库单,走普通报销流程
|
||||
|
||||
### 8.7 移动端同步
|
||||
|
||||
PC 端生成二维码指向移动端上传页面,手机端上传的图片通过轮询同步到 PC 端,实现跨设备协作。
|
||||
收件器是**独立进程**:`case_create` 返回带令牌的手机页地址,上传落 `cases/<id>/inbox/`,
|
||||
再由 Agent 调 `case_add_files(source="inbox")` 收取。它只写不读(没有列表也没有下载路由)、令牌强制、
|
||||
扩展名白名单、25MB/文件、200 文件/会话。旧 Web 的「PC 端轮询同步」已删除。
|
||||
|
||||
---
|
||||
|
||||
## 九、配置说明
|
||||
## 八、配置说明
|
||||
|
||||
### config.json
|
||||
|
||||
项目根目录 `config.json` 包含默认配置:
|
||||
`scripts/data/config.json`(已在 `.gitignore`,**永远不要提交**):
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "200000001",
|
||||
"password": "your_password",
|
||||
"sso_login_url": "https://tyrz.fynu.edu.cn/sso/login",
|
||||
"portal_url": "https://tyrz.fynu.edu.cn/oshall",
|
||||
"reimburse_url": "http://210.45.32.214:8081",
|
||||
"reimburse_page": "/expen/common/common?v=4.0",
|
||||
"default_name": "示例报销人",
|
||||
"default_card_no": "6282880000000000",
|
||||
"default_person_id": "200000001",
|
||||
"consumable_storage": "躬行楼 C205",
|
||||
"llm": {
|
||||
"model": "qwen3.5-9b",
|
||||
"api_base": "http://10.0.0.10:1234/v1",
|
||||
"api_key": "123456"
|
||||
}
|
||||
"username": "<工号>",
|
||||
"password": "<密码>",
|
||||
"default_name": "<报销人姓名>",
|
||||
"default_card_no": "<公务卡号>",
|
||||
"default_person_id": "<人员编号>",
|
||||
"upload_pin": "<手机上传访问码,可留空>"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `username` | 财务系统工号 |
|
||||
| `password` | 登录密码 |
|
||||
| `sso_login_url` | SSO 登录地址 |
|
||||
| `portal_url` | 信息门户地址 |
|
||||
| `reimburse_url` | 报销系统地址 |
|
||||
| `reimburse_page` | 报销录入页面路径 |
|
||||
| `default_name` | 默认报销人姓名 |
|
||||
| `default_card_no` | 默认公务卡号 |
|
||||
| `default_person_id` | 默认人员编号 |
|
||||
| `consumable_storage` | 出库单存放地点 |
|
||||
| `llm.model` | LLM 模型名称 |
|
||||
| `llm.api_base` | LLM API 地址 |
|
||||
| `llm.api_key` | LLM API 密钥 |
|
||||
| `username` / `password` | 信息门户登录 |
|
||||
| `default_name` | 支付方式的「人员姓名」 |
|
||||
| `default_person_id` | 支付方式的「人员编号」 |
|
||||
| `default_card_no` | 支付方式的「公务卡号」 |
|
||||
| `upload_pin` | 上传收件器访问码(等价 `--pin` 或环境变量 `UPLOAD_PIN`) |
|
||||
|
||||
URL 类字段(`sso_login_url`、`portal_url`、`reimburse_url`、`reimburse_page`、`travel_page`)有内置默认值,需要才配。
|
||||
凭据不放环境变量:DSH 会清洗 stdio 子进程里含 KEY/PASSWORD/SECRET/TOKEN 的变量。
|
||||
|
||||
校验规则在 `config/validation_rules.json`;改规则要同步跑测试(骨架与规则有一致性用例)。
|
||||
|
||||
---
|
||||
|
||||
## 十、日志与调试
|
||||
## 九、日志与排查
|
||||
|
||||
### 日志输出
|
||||
|
||||
- 控制台实时输出(INFO 级别)
|
||||
- 文件日志:`logs/<日期>.log`(UTF-8 编码)
|
||||
- Web 界面:实时 SSE 日志流
|
||||
|
||||
### 截图保存
|
||||
|
||||
每个关键步骤自动截图到 `images/` 目录:
|
||||
|
||||
| 截图文件 | 对应步骤 |
|
||||
|----------|----------|
|
||||
| `debug_portal_loaded.png` | 登录成功 |
|
||||
| `debug_step3_project_modal.png` | 项目弹窗打开 |
|
||||
| `debug_step3_project_selected.png` | 项目选择完成 |
|
||||
| `debug_step3_done.png` | 基本信息完成 |
|
||||
| `debug_after_add_click.png` | 点击新增后 |
|
||||
| `debug_item_total.png` | 总明细录入完成 |
|
||||
| `debug_step5_done.png` | 支付方式完成 |
|
||||
| `debug_step6_done.png` | 附件上传完成 |
|
||||
| `debug_error.png` | 异常状态 |
|
||||
|
||||
### 超时设置
|
||||
|
||||
- 页面默认超时:30 秒
|
||||
- 登录门户等待:最多 30 秒
|
||||
- 单点登录页等待:最多 15 秒
|
||||
- SSE 日志流超时:10 分钟(600 秒)
|
||||
- 控制台 + `logs/<日期>.log`(UTF-8)。**没有 SSE 或任何长连接事件流**,进度靠 `case_status` 查
|
||||
- 浏览器调试截图在 `images/`(已 gitignore);P3 之后过程截图进 `cases/<id>/screenshots/NNN-<step>.png`
|
||||
- 超时:页面默认 30 秒、门户登录 30 秒、单点登录页 15 秒
|
||||
- 排查顺序:`case_status` → `match_result.json` → 某张 `.invoice_cache/*.json` → `logs/<日期>.log`
|
||||
|
||||
---
|
||||
|
||||
## 十一、常见问题
|
||||
## 十、常见问题
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|------|------|----------|
|
||||
| 登录超时 | SSO 需要手动验证码/微信扫码 | 手动完成验证后脚本继续 |
|
||||
| 未找到财务系统入口 | 门户页面结构变化 | 检查 `images/debug_*` 截图定位 |
|
||||
| 经济科目选择失败 | 弹窗加载延迟 | 检查超时设置,增加等待时间 |
|
||||
| 附件上传失败 | PDF 文件不存在或路径错误 | 确认 PDF 在当前工作目录 |
|
||||
| 金额不匹配 | 明细合计 ≠ 支付合计 | 检查 CSV 数据中刷卡金额 |
|
||||
| 提交被拦截 | 必填项为空 | 检查 `logs/<日期>.log` 定位失败步骤 |
|
||||
| LLM 多模态提取失败 | llama-index 版本不兼容 | 确保使用 llama-index-core >= 0.14.x,多模态消息使用 `blocks` 格式 |
|
||||
| 出库单生成失败 | 缺少 pywin32 或模板文件 | 安装 `pywin32`,确保项目根目录有 `易耗品、出库单.doc` 模板 |
|
||||
| 差旅发票生成了出库单 | 分类不准确 | 检查发票内容是否包含"高铁票""酒店住宿"等关键词 |
|
||||
| 问题 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| 登录超时 | SSO 要扫码或验证码 | 有头模式下人工完成,流程会继续 |
|
||||
| 未找到财务系统入口 | 门户页面结构变了 | 看 `images/debug_*` 截图定位选择器 |
|
||||
| 经济科目选择失败 | 弹窗加载延迟 | 增大等待,或调整点第几行的索引 |
|
||||
| 金额不匹配 | 明细合计 ≠ 支付合计 | 复核 `match_result.json` 与 `review_required` |
|
||||
| 附件上传失败 | PDF 路径不对 | 材料要在 `cases/<id>/` 下,路径用正斜杠 |
|
||||
| 宿主看不到新增工具 | 旧 stdio 子进程还在跑旧代码 | 杀掉子进程,DSH 会自动重启并重同步工具 |
|
||||
| 报「binding arguments must be lossless JSON」 | Windows 路径用了反斜杠 | 改成 `D:/Code/...` |
|
||||
| 手机上传 403 | URL 缺令牌或令牌不对 | 用 `case_create` 返回的完整地址 |
|
||||
| 抽取字段读错(编号/卡号/金额位数) | 文本层与读图本来就会冲突 | 放大该区域重看,两路都不许默认采信 |
|
||||
| 申购单生成失败 | 未装 Excel 或 pywin32;或有弹窗未关 | 需要 Windows + Excel,人工关掉残留 Excel 窗口再试 |
|
||||
|
||||
---
|
||||
|
||||
## 十二、相关 CLI 命令
|
||||
|
||||
### 单独填写出库单
|
||||
## 十一、相关命令
|
||||
|
||||
```bash
|
||||
uv run python -m src.infra.documents.consumable --csv invoice_summary.csv --doc "易耗品、出库单.doc"
|
||||
```
|
||||
make check # ruff + format + mypy src + deptry
|
||||
make test # pytest + 覆盖率
|
||||
make mcp-smoke # MCP stdio 自检
|
||||
make upload # 启动扫码上传收件器
|
||||
|
||||
### 分步执行管道
|
||||
|
||||
```bash
|
||||
# 仅发票提取
|
||||
uv run python src/main.py --step invoice
|
||||
|
||||
# 仅浏览器填报
|
||||
uv run python src/main.py --step submit
|
||||
```
|
||||
|
||||
### 启动 Web 服务
|
||||
|
||||
```bash
|
||||
uv run python src/web/app.py
|
||||
# 访问: http://localhost:5000
|
||||
# 不经 MCP 单独重生成申购单(调试用)
|
||||
uv run python -m src.infra.documents.requisition --session cases/<case_id> --exclude 千斤顶
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:API 文档
|
||||
## 相关文档
|
||||
|
||||
详细的 API 接口文档见 [API.md](./API.md)。
|
||||
- [docs/MCP.md](./MCP.md) — 17 个工具的参数与返回约定
|
||||
- [.agents/skills/reimbursement/SKILL.md](../.agents/skills/reimbursement/SKILL.md) — 宿主 Agent 的操作纪律
|
||||
- [AGENTS.md](../AGENTS.md) — 开发约束与踩坑记录
|
||||
|
||||
Reference in New Issue
Block a user