# OCR Engine Adapter Contract

中台通过 `OcrEngineAdapter` 隔离算法实现。PaddleOCR、PP-ChatOCR、第三方 OCR 或自研 OCR 都应被包成同一类适配器。中台不依赖任何历史 OCR 项目，也不内置算法服务实现。

## 适配器层目标

本阶段约定：

- Java 侧只关注任务生命周期、重试、入库与回放。
- 算法侧侧重返回标准化文本块/键值对与原始 JSON。
- 每次调用都必须可回放：同一任务的不同识别尝试使用 `runNo` 区分，支持横向对比效果。

## HTTP 调用方式

当前后端支持以下 OCR 引擎适配器：

- `paddle-layout`：直接调用外部 PaddleOCR `/layout-parsing` JSON API，可作为结构识别 provider。
- `pp-chatocr`：直接调用已部署 PP-ChatOCR `/chatocr-visual` + `/chatocr-chat` JSON API，作为可选语义键值抽取链路。

新增 OCR provider 时，必须遵守同一边界：

- provider 以外部 HTTP 服务、云 API 或独立进程形式部署，中台不内置算法服务代码。
- 新增一个实现 `OcrEngineAdapter` 的适配器，把 provider 原始响应转换为 `rawJson`、`textBlocks`、`keyValues`。
- 新增独立配置前缀和开关，默认禁用；不得把新 provider 设为平台默认值。
- 在 `/api/v1/engines` 暴露 provider 描述、能力标签和可用状态。
- 任务执行时保存 endpoint、开关、超时和字段清单等调用快照，保证可审计和可回放。
- provider 失败只能影响本次任务或本次 run，不能把平台主流程绑定到单一算法实现。

### `paddle-layout`

`paddle-layout` HTTP 适配器默认禁用，启用后会向配置的 endpoint 发送：

```http
POST {PADDLE_LAYOUT_ENDPOINT}
Content-Type: application/json
```

JSON 字段：

- `file`：原始图片或 PDF 文件的 Base64。
- `fileType`：`0` 表示 PDF，`1` 表示图片。

当前已验证的外部服务地址：

```properties
PADDLE_LAYOUT_ENDPOINT=http://192.168.1.13:18080/layout-parsing
```

后端会从返回的 `result.layoutParsingResults[].prunedResult.overall_ocr_res` 中抽取全文文本块，并从 `table_res_list[].pred_html` 中保存表格 HTML 块。

### `pp-chatocr`

`pp-chatocr` HTTP 适配器默认禁用，启用后先调用视觉接口：

```http
POST {PP_CHAT_OCR_VISUAL_ENDPOINT}
Content-Type: application/json
```

JSON 字段：

- `file`：原始图片或 PDF 文件的 Base64。
- `fileType`：`0` 表示 PDF，`1` 表示图片。
- `useDocOrientationClassify`、`useDocUnwarping`、`useTextlineOrientation`、`useSealRecognition`、`useTableRecognition`：视觉解析选项。

如果配置了 `PP_CHAT_OCR_EXTRACTION_KEYS`，后端会继续调用：

```http
POST {PP_CHAT_OCR_CHAT_ENDPOINT}
Content-Type: application/json
```

JSON 字段：

- `keyList`：需要抽取的字段名清单。
- `visualInfo`：来自 `/chatocr-visual` 返回的 `result.visualInfo`。
- `useVectorRetrieval`：当前默认 `false`。
- `minCharacters`：当前默认 `500`。

当前已验证的外部服务地址：

```properties
PP_CHAT_OCR_VISUAL_ENDPOINT=http://192.168.1.13:18083/chatocr-visual
PP_CHAT_OCR_CHAT_ENDPOINT=http://192.168.1.13:18083/chatocr-chat
```

注意：`/chatocr-chat` 依赖字段清单和大模型配置，服务 HTTP 200 不等于一定会返回非空 `chatResult`。验收时应同时检查 `rawJson`、`textBlocks` 和 `keyValues`。

## 推荐响应格式

算法服务推荐返回中台标准结构：

```json
{
  "engineVersion": "PaddleOCR or PP-ChatOCR",
  "rawJson": "{... original engine output ...}",
  "textBlocks": [
    {
      "pageNo": 1,
      "blockType": "TEXT",
      "textContent": "识别出的全文或文本块",
      "confidence": 0.98,
      "bbox": [10, 20, 300, 60],
      "readingOrder": 1
    }
  ],
  "keyValues": [
    {
      "fieldKey": "operator_name",
      "fieldName": "经营者名称",
      "valueText": "某某餐饮店",
      "normalizedValue": "某某餐饮店",
      "confidence": 0.92,
      "pageNo": 1,
      "evidence": {
        "source": "pp-chatocr",
        "bbox": [10, 20, 300, 60]
      }
    }
  ]
}
```

也可以用统一响应壳：

```json
{
  "code": "OK",
  "data": {
    "engineVersion": "PaddleOCR or PP-ChatOCR",
    "rawJson": "{...}",
    "textBlocks": [],
    "keyValues": []
  }
}
```

当算法服务支持调用参数回显时，推荐保留以下附加字段（可选）：

```json
{
  "runNo": 1,
  "engineTraceId": "uuid-xxx",
  "elapsedMs": 12345
}
```

中台会把运行上下文（包括 `runNo`、引擎配置、请求文件上下文）持久化到 `ocr_raw_result.request_payload_json` 以供复盘。

## 兼容字段

HTTP 适配器当前兼容以下别名：

- 文本块数组：`textBlocks`、`texts`、`ocrTextBlocks`
- 键值对数组：`keyValues`、`kvPairs`、`fields`
- 键值对象：`kv`
- 文本字段：`textContent` 或 `text`
- 键字段：`fieldKey` 或 `key`
- 值字段：`valueText` 或 `value`

如果算法服务只返回原始 JSON，中台仍会保存到 `ocr_raw_result`，但不会自动产生标准文本块和键值对。

## 多 run 回放与效果对比

每次执行会记录：

- `runNo`：`attemptCount + 1`，即同一任务的识别序号。
- `requestPayloadJson`：本次调用快照（包含当前实现可持久化的请求上下文字段）。
- `elapsedMs`：本次算法耗时（毫秒）。
- `errorCode` / `errorMessage`：失败时记录可回溯错误。

接口返回也会携带 `runNo`，前端可直接按 `runNo` 分组展示。

### 回放建议

- 比较 `runNo` 为 `latest` 和历史值的 `textBlocks`、`keyValues`、`rawJson`。
- 关注字段覆盖率与置信度（`extractionQuality`）趋势，验证是否有回归。
- 必要时使用 `runNo` 锁定回填基准，避免历史脏数据参与新任务提交。

## 联调真实能力建议（PaddleOCR + PP-ChatOCR + LLM）

阶段 2 的真实联调建议执行顺序：

1. 验证 PaddleOCR `/layout-parsing` 基本连通：上传样张成功返回 `errorCode=0`，并产生 `textBlocks`、表格 HTML 和 `rawJson`。
2. 验证 PP-ChatOCR `/chatocr-visual` 基本连通：上传同一样张成功返回 `visualInfo` 和视觉解析结果。
3. 将 `PP_CHAT_OCR_EXTRACTION_KEYS` 覆盖为目标模板字段清单，观察 `/chatocr-chat` 生成的 `keyValues` 命中率。
4. 若启用 LLM 辅助抽取，确认 PP-ChatOCR 容器侧大模型配置已生效，验证 `chatResult` 字段是否稳定返回业务字段，不允许只用 HTTP 200 作为成功标准。
5. 记录 1～3 次不同参数配置（`runNo`），保存原始响应与重试结果用于选择最优参数。

建议在算法服务侧同步检查的环境变量（示例）：

```properties
PADDLE_LAYOUT_ENDPOINT=http://192.168.1.13:18080/layout-parsing
PP_CHAT_OCR_CHAT_ENDPOINT=http://192.168.1.13:18083/chatocr-chat
PP_CHAT_OCR_VISUAL_ENDPOINT=http://192.168.1.13:18083/chatocr-visual
PADDLEOCR_CHAT_BOT_BASE_URL=https://api.openai.com/v1
PADDLEOCR_CHAT_BOT_API_TYPE=openai
PADDLEOCR_CHAT_BOT_API_KEY=sk-...
PADDLEOCR_CHAT_BOT_MODEL_NAME=gpt-4o-mini
```

## 配置

```properties
OCR_ENGINE_DEFAULT_CODE=local-placeholder
PADDLE_LAYOUT_ENABLED=false
PADDLE_LAYOUT_ENDPOINT=http://192.168.1.13:18080/layout-parsing
PADDLE_LAYOUT_TIMEOUT=60s
PP_CHAT_OCR_ENABLED=false
PP_CHAT_OCR_VISUAL_ENDPOINT=http://192.168.1.13:18083/chatocr-visual
PP_CHAT_OCR_CHAT_ENDPOINT=http://192.168.1.13:18083/chatocr-chat
PP_CHAT_OCR_TIMEOUT=120s
PP_CHAT_OCR_EXTRACTION_KEYS=经营者名称,申请日期,经营场所地址
```

示例：切换到已部署 PaddleOCR 结构识别 provider：

```properties
OCR_ENGINE_DEFAULT_CODE=paddle-layout
PADDLE_LAYOUT_ENABLED=true
PADDLE_LAYOUT_ENDPOINT=http://192.168.1.13:18080/layout-parsing
```

示例：切换到已部署 PP-ChatOCR 语义抽取 provider：

```properties
OCR_ENGINE_DEFAULT_CODE=pp-chatocr
PP_CHAT_OCR_ENABLED=true
PP_CHAT_OCR_VISUAL_ENDPOINT=http://192.168.1.13:18083/chatocr-visual
PP_CHAT_OCR_CHAT_ENDPOINT=http://192.168.1.13:18083/chatocr-chat
PP_CHAT_OCR_EXTRACTION_KEYS=经营者名称,申请日期,经营场所地址
```

如果算法链路使用 PP-ChatOCR + LLM，建议同步设置容器侧大模型参数：

```properties
PADDLEOCR_CHAT_BOT_BASE_URL=https://api.openai.com/v1
PADDLEOCR_CHAT_BOT_API_KEY=sk-***
PADDLEOCR_CHAT_BOT_API_TYPE=openai
PADDLEOCR_CHAT_BOT_MODEL_NAME=gpt-4o-mini
```
