Files
X-Financial/document/development/attachment-association-background-job/CONCEPT.md
caoxiaozhu f9553a6a1a docs: 清理过期计划文档并补全 work-log 与开发/用户文档
- 删除已落地的 improvement-roadmap、superpowers 计划与 ui-mockups 参考稿,删除早期 work-log(2026-05-06~08)
- 新增 2026-05-23 起的 work-log 与 attachment-association-background-job、reimbursement-draft-action-branching 等开发文档及用户文档
- docker-compose(.full).yml 微调服务配置
2026-06-24 10:42:57 +08:00

145 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 附件自动关联后台任务方案
## 背景
小财管家 AI 模式里,用户上传票据后会先做 OCR。当前附件自动关联报销草稿的后半段仍依赖前端会话内存
- 前端保留浏览器里的 `File` 对象。
- 前端在当前会话里完成草稿匹配。
- 用户点击“确认自动关联”后,再用这些 `File` 上传到报销单明细。
这会带来一个核心问题:用户发送消息后,如果刷新页面、切换会话或退出当前会话,浏览器内存里的附件原件会丢失,历史消息中的关联动作就无法继续执行。
票据夹已经能持久化 OCR 结果和源文件,并且 OCR 返回里已经带有 `receipt_id`。所以正式方案应该把“附件关联”从前端内存任务调整为后端可查询任务。
## 目标
1. 用户一上传附件即触发 OCR附件卡片显示“识别中”识别完成后显示“当前附件已识别”。
2. 用户点击发送后,前端不再依赖 `File` 原件做后续归集,而是把 OCR 产生的 `receipt_id` 提交给后端。
3. 后端创建附件关联任务,并在后台继续匹配、复制票据夹源文件、关联报销单明细。
4. 用户退出或刷新当前会话后,前端可通过保存下来的 `job_id` 查询任务状态并更新消息。
5. 如果匹配失败、置信度不足或找不到可编辑草稿,系统给出清晰提示,引导用户补充说明、上传附件或新建草稿。
## 非目标
第一版不承诺后端服务进程重启后任务状态仍可恢复。
本次实现使用轻量内存任务池,解决“用户离开前端会话导致连接断开”的问题。后续如果要做到服务重启、横向扩容、任务审计都可恢复,再新增数据库任务表。
## 核心流程
```text
用户上传票据
→ 前端立即调用 OCR
→ OCR 写入票据夹并返回 receipt_id
→ 附件卡片显示“当前附件已识别”
→ 用户点击发送
→ 前端 POST 创建附件关联任务
→ 后端后台任务继续运行
→ 前端轮询 job_id
→ 成功:消息显示已关联,并刷新报销单详情
→ 失败:消息显示失败原因和下一步动作
```
## 后端设计
### API
新增接口:
- `POST /api/v1/reimbursements/attachment-association-jobs`
- `GET /api/v1/reimbursements/attachment-association-jobs/{job_id}`
创建任务请求只传持久化引用,不传浏览器文件:
```json
{
"receipt_ids": ["receipt-1", "receipt-2"],
"prompt": "请帮我处理已上传的附件。",
"conversation_id": "inline-xxx"
}
```
状态返回:
```json
{
"job_id": "job-xxx",
"status": "running",
"message": "正在匹配可关联的报销草稿...",
"receipt_ids": ["receipt-1", "receipt-2"],
"claim_id": "",
"claim_no": "",
"uploaded_count": 0,
"skipped_count": 0,
"error": ""
}
```
状态枚举:
- `queued`:任务已创建,等待后台执行。
- `running`:正在匹配和归集。
- `succeeded`:已完成自动归集。
- `failed`:无法自动完成,返回可读错误。
### 任务执行
后端任务执行步骤:
1.`receipt_ids` 读取票据夹明细和源文件。
2. 从票据 OCR 文本、结构化字段、日期、城市提取匹配信号。
3. 查询当前用户可见且可编辑的报销草稿,排除申请单和已归档单据。
4. 对候选草稿按日期、城市、事由、草稿状态评分。
5. 置信度足够时选择目标草稿。
6. 为每份票据找到可用费用明细,没有合适明细时创建空明细。
7. 从票据夹源文件复制到报销单附件目录,并沿用 `source_receipt_id` 回填 OCR 信息。
8. 更新票据夹状态为 `linked`
9. 写入任务最终状态。
## 前端设计
### 会话消息
AI 消息需要持久化 `attachmentAssociationJob`
```json
{
"jobId": "job-xxx",
"status": "running",
"receiptIds": ["receipt-1", "receipt-2"]
}
```
这样历史会话恢复后,不再需要浏览器里的 `File` 对象,只要消息里仍有 `jobId`,就可以继续查询后端状态。
### 发送行为
当用户上传附件并点击发送:
1. 前端确认 OCR 已完成。
2. 从 OCR 结果里提取 `receipt_id`
3. 创建后端任务。
4. 立即持久化带 `job_id` 的 pending 消息。
5. 页面仍打开时轮询任务状态。
6. 页面恢复或打开历史会话时,对未完成任务继续轮询。
## 异常处理
- 未完成 OCR禁止发送提示“附件 OCR 识别中,请稍等,识别完成后再继续对话。”
- OCR 失败:禁止发送,提示“请先移除识别失败的附件或重新上传。”
- 没有 `receipt_id`:提示“当前附件没有持久化票据记录,请重新上传后再试。”
- 没有可编辑草稿:任务失败,提示用户先新建或选择草稿。
- 多个候选置信度接近:第一版不自动归集,提示补充说明或手动选择。
- 服务重启导致内存任务丢失:返回任务不存在,前端提示“后台任务状态已失效,请重新发送附件关联请求。”
## 验收标准
1. 上传附件后必须先进入 OCR 识别中状态,识别完成前不能发送对话。
2. 发送附件关联请求后,前端能收到并保存 `job_id`
3. 用户离开当前会话后,后端任务仍会继续执行。
4. 用户回到历史会话后,前端可以根据 `job_id` 查询并更新最终状态。
5. 成功后报销单明细出现附件,票据夹状态变为已关联。
6. 找不到草稿、低置信度、源文件缺失时,消息能给出明确原因。