|
|
@@ -0,0 +1,120 @@
|
|
|
+# 报告解析迁移设计:Java → LangGraph(Python 算法 + LLM 兜底)
|
|
|
+
|
|
|
+## 背景
|
|
|
+
|
|
|
+当前健康报告(菌群检测 PDF)解析在 Java 侧通过 `PdfParseService`(PDFBox + 规则解析)实现,但存在以下问题:
|
|
|
+
|
|
|
+- 格式兼容性差:inline 格式频繁抛出 `StringIndexOutOfBoundsException`
|
|
|
+- 采集不全:Python 版 `extract_full_report_v5.py` 覆盖 13 个分组,Java 版只有 8 个
|
|
|
+- 维护成本高:同一套解析逻辑在 Java 和 Python 各维护一份
|
|
|
+
|
|
|
+## 目标架构
|
|
|
+
|
|
|
+```
|
|
|
+用户上传 PDF
|
|
|
+ ↓
|
|
|
+Java HealthReportController
|
|
|
+ ├─ 保存文件到磁盘
|
|
|
+ ├─ 调 LangGraph /api/v1/report/parse
|
|
|
+ └─ 返回结构化数据给前端
|
|
|
+ ↓
|
|
|
+LangGraph report_parse_agent
|
|
|
+ ├─ Python 算法解析(复用 extract_full_report_v5.py)
|
|
|
+ │ ├─ 成功 → 返回结构化 JSON
|
|
|
+ │ └─ 失败 → LLM 兜底解析
|
|
|
+ └─ 返回结构化数据给 Java
|
|
|
+```
|
|
|
+
|
|
|
+## 组件职责
|
|
|
+
|
|
|
+### Java 侧(`HealthReportController`)
|
|
|
+
|
|
|
+- 接收文件上传(MultipartFile)
|
|
|
+- 保存文件到磁盘(`/tmp/uploads/` 或配置路径)
|
|
|
+- 调用 LangGraph 解析接口(POST `/api/v1/report/parse`)
|
|
|
+- 将 LangGraph 返回的结构化数据转换为 `ParsedReportPayload.Payload`
|
|
|
+- 后续流程不变(草稿 → 确认 → 入库)
|
|
|
+
|
|
|
+### LangGraph 侧(新增 `report_parse_agent`)
|
|
|
+
|
|
|
+- 接收文件路径
|
|
|
+- 调用 Python 解析脚本(从 `extract_full_report_v5.py` 提取核心逻辑)
|
|
|
+- 算法解析失败时,调用 LLM 进行结构化提取
|
|
|
+- 返回统一格式的 JSON
|
|
|
+
|
|
|
+## API 接口设计
|
|
|
+
|
|
|
+### LangGraph 解析接口
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/v1/report/parse
|
|
|
+Content-Type: application/json
|
|
|
+
|
|
|
+Request:
|
|
|
+{
|
|
|
+ "file_path": "/tmp/uploads/xxx.pdf",
|
|
|
+ "family_id": 123,
|
|
|
+ "user_id": 456
|
|
|
+}
|
|
|
+
|
|
|
+Response:
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "data": {
|
|
|
+ "summary": { ... },
|
|
|
+ "disease_risks": [ ... ],
|
|
|
+ "indicators": [ ... ],
|
|
|
+ "gut_flora": [ ... ],
|
|
|
+ "probiotic_species": [ ... ],
|
|
|
+ "food_suitability": [ ... ],
|
|
|
+ "extracted_name": "某人",
|
|
|
+ "extracted_gender": "male",
|
|
|
+ "extracted_age": 53
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 数据流
|
|
|
+
|
|
|
+1. 用户上传 PDF → `Java HealthReportController.parsePreview()`
|
|
|
+2. Java 保存文件 → `POST /api/v1/report/parse?file_path=...`
|
|
|
+3. LangGraph 收到请求:
|
|
|
+ a. 调用 Python 算法解析模块(复用 `extract_full_report_v5.py` 核心逻辑)
|
|
|
+ b. 算法成功 → 返回结构化数据
|
|
|
+ c. 算法失败 → 调用 LLM(GPT-4o 等)提取结构化数据
|
|
|
+4. LangGraph 返回结构化 JSON 给 Java
|
|
|
+5. Java 将 JSON 转为 `ParsedReportPayload.Payload`,后续流程不变
|
|
|
+
|
|
|
+## 算法解析模块(Python)
|
|
|
+
|
|
|
+从 `extract_full_report_v5.py` 提取核心逻辑,封装为可调用的 Python 函数:
|
|
|
+
|
|
|
+```python
|
|
|
+def parse_report_pdf(file_path: str) -> dict:
|
|
|
+ """解析菌群报告 PDF,返回结构化数据"""
|
|
|
+ # 1. 读取 PDF 文本(PyPDF2)
|
|
|
+ # 2. 检测格式(triplet / inline)
|
|
|
+ # 3. 提取各分组数据
|
|
|
+ # 4. 返回统一格式字典
|
|
|
+```
|
|
|
+
|
|
|
+## LLM 兜底
|
|
|
+
|
|
|
+当算法解析失败时(返回空数据或关键字段缺失),调用 LLM:
|
|
|
+
|
|
|
+```python
|
|
|
+async def parse_with_llm(file_path: str) -> dict:
|
|
|
+ """LLM 兜底解析"""
|
|
|
+ # 1. 提取 PDF 文本
|
|
|
+ # 2. 构造 prompt,要求 LLM 按指定 JSON Schema 输出
|
|
|
+ # 3. 解析 LLM 输出
|
|
|
+ # 4. 返回结构化数据
|
|
|
+```
|
|
|
+
|
|
|
+## 迁移步骤
|
|
|
+
|
|
|
+1. 在 LangGraph 中创建 `report_parse_agent`(算法 + LLM 兜底)
|
|
|
+2. 创建 `POST /api/v1/report/parse` 接口
|
|
|
+3. 修改 Java `HealthReportController.parsePreview()`,先调 LangGraph,失败时回退到本地 Java 解析
|
|
|
+4. 验证通过后,移除 Java 端的 `PdfParseService`
|
|
|
+5. 编写测试用例覆盖 triplet/inline 两种格式
|