Files
prop-ai-hr/docs/superpowers/specs/2026-07-12-personal-scanned-pdf-ocr-design.md

163 lines
6.9 KiB
Markdown
Raw Permalink 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 助理扫描 PDF OCR 设计
## 1. 背景与目标
个人 AI 助理已经支持 PDF 收藏、私有 OSS、异步解析和个人知识检索,但当前解析器只提取 PDF 文本层。扫描件没有文本层时,资料会进入 `FAILED / PERSONAL_PARSE_EMPTY`。
本次目标是在不改变普通 PDF 快速路径、不引入 Tesseract 等新 OCR 服务的前提下,复用现有视觉模型,为扫描 PDF 提供异步、分批、可观测、可重试的完整 OCR。
## 2. 范围
### 包含
- 仅在 PDF 文本提取结果为空时触发视觉 OCR。
- 按每批 20 页处理,持续处理到整份文件完成。
- 单文件最多 200 页;超过时明确失败并提示拆分文件。
- 记录总页数、已处理页数、成功页数、失败页数和失败页码。
- 全部批次完成后再将资料置为 `READY` 并写入检索片段。
- 单页失败不终止整份文件;完成后允许只重试失败页。
- 删除资料时清理 OCR 中间结果、最终片段和私有 OSS 对象。
### 不包含
- 不对已有文本层的 PDF 再做 OCR。
- 不新增第三方 OCR 供应商或 Tesseract 依赖。
- 不在同步上传请求中执行 PDF 渲染或视觉模型调用。
- 不允许部分 OCR 内容在资料 `READY` 前进入检索。
## 3. 方案选择
采用“现有异步解析 Worker + PDF OCR 批次状态表”的方案。
未采用的方案:
- 单次 Worker 内完整 OCR:实现简单,但长文件会长期占用 worker,进程重启后难以从页级进度恢复。
- 只识别前 20 页:成本可控,但会永久丢失用户资料内容,不符合个人知识库完整性要求。
- 本地 Tesseract:增加部署依赖,且与项目现有视觉 OCR 技术边界不一致。
## 4. 架构与组件
### 4.1 `PersonalIngestionWorker`
保持普通文档现有行为。PDF 文本层解析为空时:
1. 获取 PDF 页数。
2. 页数超过 200 时,将资料置为 `FAILED / PERSONAL_PDF_PAGE_LIMIT`。
3. 创建或恢复 OCR 任务,将资料保持为 `PARSING`。
4. 每次 worker 调度领取一个最多 20 页的批次。
5. 批次完成后继续排队下一批,直到所有页处理结束。
6. 聚合成功页面文本,统一切片、写入 `aihr_personal_fragment`,最后置为 `READY`。
### 4.2 PDF 页面渲染器
新增单一职责组件 `PersonalPdfPageRenderer`:
- 输入 PDF 字节和页码范围。
- 使用项目现有 PDFBox 依赖将页面渲染成受限分辨率 JPEG/PNG。
- 对单页像素尺寸和输出字节设置上限,防止压缩炸弹和内存失控。
- 不负责模型调用、数据库写入或状态流转。
### 4.3 视觉 OCR 适配器
新增 `PersonalVisionOcr` 接口,生产实现复用现有 `category=vision`,缺失时按项目规则回退启用的 `category=chat` OpenAI-compatible 模型。
- 输入:渲染后的单页图片、页码。
- 输出:规范化文本;空文本视为该页失败。
- Prompt 只要求忠实提取文字、保留标题与段落,不总结、不补写。
- 模型未配置时,资料置为 `FAILED / PERSONAL_OCR_MODEL_UNAVAILABLE`,不返回假内容。
### 4.4 OCR 状态表
新增 `aihr_personal_ocr_job`,按 `tenant_id + owner_user_id + item_id` 隔离:
- `total_pages`
- `processed_pages`
- `success_pages`
- `failed_pages`
- `failed_page_numbers_json`
- `next_page`
- `status`: `PENDING/RUNNING/RETRY/COMPLETED/FAILED`
- `attempt_count`
- `last_error_code`
- 时间字段
新增 `aihr_personal_ocr_page` 保存页级中间结果:
- 所属 item、页码、状态、OCR 文本、尝试次数、脱敏错误码。
- 唯一键为 `tenant_id + owner_user_id + item_id + page_number`。
- 中间文本只用于最终聚合,不进入检索接口。
## 5. 数据流
1. H5 上传 PDF,接口仍立即返回 `QUEUED`。
2. Worker 下载私有 OSS 对象并执行普通 PDF 文本解析。
3. 有文本:沿用当前解析、切片和 `READY` 流程。
4. 无文本:创建 OCR job,资料进入扫描 PDF OCR 流程。
5. Worker 领取 20 页批次,逐页渲染、调用视觉模型、保存页级结果并更新进度。
6. 批次结束后释放 worker;后续调度继续领取下一批。
7. 全部页面处理结束:
- 至少一页成功:按页码聚合文本,写最终片段;若有失败页,资料仍为 `READY`,同时保留 `PERSONAL_OCR_PARTIAL` 提示信息。
- 全部页面失败:资料置为 `FAILED / PERSONAL_OCR_EMPTY`。
8. 用户在资料详情查看进度和失败页,可触发“重试失败页”。
## 6. 状态与前端表现
资料详情响应增加可选 OCR 字段,旧客户端可忽略:
- `ocrRequired`
- `ocrTotalPages`
- `ocrProcessedPages`
- `ocrSuccessPages`
- `ocrFailedPages`
- `ocrFailedPageNumbers`
- `ocrStatus`
H5 展示:
- `PARSING`:`正在识别扫描 PDF:20/86 页`。
- 部分成功:`已收录,2 页识别失败`,提供“重试失败页”。
- 超过 200 页:`文件超过 200 页,请拆分后重新上传`。
- 无视觉模型:`扫描 PDF 识别服务未配置`。
## 7. 错误与恢复
- 单页模型超时或空结果:记录页级失败,继续下一页。
- 批次进程中断:通过 job 的 `next_page` 和页级唯一键幂等恢复。
- 重复调度:领取 job 时使用状态条件更新,避免两个 worker 同时处理同一批次。
- 重试只处理失败页,不重复调用已成功页面。
- 错误信息只保存受控错误码,不落模型原始响应、密钥或完整堆栈。
- 删除资料后,未开始的 worker 通过 owner/item/status 条件失去领取资格;清理任务删除 OCR job/page。
## 8. 成本与资源边界
- 每批 20 页。
- 单文件最多 200 页。
- 同一资料同一页默认最多 3 次 OCR 尝试。
- 页面渲染分辨率和图片字节设置固定上限。
- 继续遵守 `AIHR_AI_RUNTIME_ENABLED` 与 `AIHR_AI_CHAT_ENABLED` 成本闸门;关闭时不外发 OCR 请求。
## 9. 测试策略
按 TDD 实现:
1. 普通文本 PDF 不调用 OCR。
2. 扫描 PDF 文本为空时创建 OCR job。
3. 201 页 PDF 返回明确页数上限错误。
4. 每次只领取最多 20 页。
5. 批次中断后从未完成页继续,成功页不重复调用。
6. 单页失败不阻断其他页;最终状态与成功/失败计数正确。
7. 全部失败时资料为 `FAILED`,部分成功时资料为 `READY` 并带失败页提示。
8. 重试只处理失败页。
9. 删除资料清理 OCR 中间结果。
10. 双用户、双租户不能读取或重试对方 OCR 任务。
11. 用 `关于修订证书管理办法的通知.pdf` 做本地真实烟测:8 页全部处理,最终 `READY`,可检索并带个人引用。
## 10. 验收标准
- 现有普通 PDF、文本、网页收藏回归不受影响。
- 8 页扫描 PDF 可以异步进入 `READY`,页面显示真实进度。
- 21 页以上文件能跨批次继续,不截断剩余页面。
- 超过 200 页明确拒绝,不静默截断。
- OCR 中间文本在完成前不可检索。
- 失败页可单独重试,删除后无 DB、向量或 OSS 残留。