Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/02-技术设计/07-文献处理技术选型.md
HaHafeng a79abf88db docs(platform): Complete platform infrastructure planning
- Create platform infrastructure planning core document (766 lines)
- Update architecture design to support cloud-native deployment
- Update development specs and operations documentation
- Simplify ASL module docs by removing duplicate implementations

New Documents:
- Platform Infrastructure Planning (04-骞冲彴鍩虹璁炬柦瑙勫垝.md)
- Cloud-Native Development Standards (08-浜戝師鐢熷紑鍙戣鑼?md)
- Git Commit Standards (06-Git鎻愪氦瑙勮寖.md)
- Cloud-Native Deployment Guide (03-浜戝師鐢熼儴缃叉灦鏋勬寚鍗?md)
- Daily Summary (2025-11-16 work summary)

Updated Documents (11 files):
- System architecture design docs (3 files)
- Implementation and standards docs (4 files)
- Operations documentation (1 file)
- ASL module planning docs (3 files)

Key Achievements:
- Platform-level infrastructure architecture established
- Zero-code switching between local/cloud environments
- 100% support for 4 PRD deployment modes
- Support for modular product combinations
- 99% efficiency improvement for module development
- Net +1426 lines of quality documentation

Implementation: 2.5 days (20 hours) for 8 infrastructure modules
2025-11-16 21:36:57 +08:00

1027 lines
30 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.
# ASL 文献处理技术选型
> **文档版本:** V1.0
> **创建日期:** 2025-11-15
> **适用模块:** AI 智能文献ASL
> **目标:** 定义初筛、全文复筛、全文提取的技术栈和实现路径
---
## 📋 文档概述
ASL 模块涉及三种不同的文献处理场景,每种场景有不同的技术特点和实现方案:
| 场景 | 输入格式 | 核心技术 | 主要挑战 |
|------|---------|---------|---------|
| **标题摘要初筛** | Excel 文件 | Excel 解析 + LLM 筛选 | 批量处理效率 |
| **全文复筛** | PDF 全文 | PDF 提取 + LLM 筛选 | PDF 解析准确率 |
| **全文数据提取** | PDF 全文 | PDF 提取 + LLM 结构化提取 | 表格、公式准确提取 |
---
## 🎯 技术架构总览
```
┌─────────────────────────────────────────────────────────┐
│ ASL 文献处理流程 │
└─────────────────────────────────────────────────────────┘
├─ 场景 1: 标题摘要初筛
│ └─ 用户上传 Excel → 解析 → LLM 批量筛选 → 导出结果
├─ 场景 2: 全文复筛
│ └─ 用户上传 PDF → PDF 提取 → LLM 筛选 → 复核
└─ 场景 3: 全文数据提取
└─ PDF → 提取 + 结构化 → LLM 提取数据 → 人工复核
┌─────────────────────────────────────────────────────────┐
│ 技术栈分层架构(共享) │
├─────────────────────────────────────────────────────────┤
│ 前端层: React 19 + Ant Design 5 + xlsx/exceljs │
├─────────────────────────────────────────────────────────┤
│ 后端层: Node.js (Fastify) + TypeScript │
├─────────────────────────────────────────────────────────┤
│ 文档处理层: Python 微服务 (extraction_service) │
│ ├─ PyMuPDF: 快速 PDF 提取 │
│ ├─ Nougat: 英文科学文献高质量提取 ⭐ │
│ └─ Language Detector: 自动语言检测 │
├─────────────────────────────────────────────────────────┤
│ LLM 层: DeepSeek-V3 + Qwen3 / GPT-5 + Claude-4.5 │
├─────────────────────────────────────────────────────────┤
│ 数据库: PostgreSQL 15 (asl_schema) │
└─────────────────────────────────────────────────────────┘
```
---
## 📌 场景 1: 标题摘要初筛
### 1.1 技术特点
- **输入格式**: Excel 文件 (`.xlsx` / `.xls`)
- **数据规模**: 50-500 篇文献/批次
- **主要字段**: 标题、摘要、DOI、作者、发表年份、期刊
- **处理重点**: 批量高效处理,无需 PDF 解析
### 1.2 技术选型
#### 前端Excel 上传与解析
| 技术 | 库 | 用途 | 优势 |
|------|-----|------|------|
| **Excel 上传** | `antd Upload` | 文件上传组件 | 拖拽上传、进度条 |
| **Excel 解析** | `xlsx` / `exceljs` | 前端解析 Excel | 纯前端处理,快速预览 |
| **模板验证** | 自定义逻辑 | 校验列名和数据格式 | 提前发现格式错误 |
**推荐方案:`xlsx` 库SheetJS**
- ✅ 支持 `.xlsx``.xls` 格式
- ✅ 纯 JavaScript前端直接解析
- ✅ 体积小(~600KB性能好
- ✅ 支持大文件1000+ 行)
**代码示例:**
```typescript
import * as XLSX from 'xlsx';
function parseExcel(file: File): Promise<Literature[]> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result as ArrayBuffer);
const workbook = XLSX.read(data, { type: 'array' });
// 读取第一个工作表
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
// 转换为 JSON
const jsonData = XLSX.utils.sheet_to_json(worksheet);
// 映射为标准格式
const literatures = jsonData.map((row: any) => ({
title: row['Title'] || row['标题'],
abstract: row['Abstract'] || row['摘要'],
doi: row['DOI'],
authors: row['Authors'] || row['作者'],
year: row['Year'] || row['年份'],
journal: row['Journal'] || row['期刊'],
}));
resolve(literatures);
} catch (error) {
reject(new Error('Excel 解析失败'));
}
};
reader.onerror = () => reject(new Error('文件读取失败'));
reader.readAsArrayBuffer(file);
});
}
```
#### 后端:批量筛选处理
**处理流程:**
```
Excel 数据 → 批量分组10-20 篇/组)→ 并行调用 LLM → 汇总结果
```
**关键技术点:**
1. **批量分组**避免单次请求过大10-20 篇/组最优
2. **并行处理**:使用 `Promise.all` 并行调用 LLM
3. **进度推送**WebSocket 实时推送处理进度
4. **断点续传**:支持任务中断后继续
**代码示例:**
```typescript
async function batchScreening(
literatures: Literature[],
protocol: Protocol,
progressCallback: (progress: number) => void
) {
const batchSize = 15;
const batches = chunk(literatures, batchSize);
const results = [];
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
// 并行处理当前批次
const batchResults = await Promise.all(
batch.map(lit => dualModelScreening(lit, protocol))
);
results.push(...batchResults);
// 推送进度
const progress = Math.round(((i + 1) / batches.length) * 100);
progressCallback(progress);
}
return results;
}
```
### 1.3 数据流
```
用户操作 前端处理 后端处理 LLM 处理
│ │ │ │
├─ 上传 Excel │ │ │
│ └──────────────→│ │ │
│ ├─ 解析 Excel │ │
│ ├─ 验证格式 │ │
│ ├─ 显示预览 │ │
│ │ │ │
│ ├─ 提交筛选任务 │ │
│ │ └───────────────→│ │
│ │ ├─ 保存任务 │
│ │ ├─ 分组15 篇/组) │
│ │ │ │
│ │ ├─ 批次 1 │
│ │ │ └──────────────→│
│ │ │ ├─ DeepSeek 筛选
│ │ │ ├─ Qwen3 筛选
│ │ │ ├─ 对比结果
│ │ │ ←──────────────┘
│ │ ├─ 保存结果 │
│ │ │ │
│ │ ├─ 批次 2... │
│ │ │ │
│ │ ←───────────────┤ 返回完整结果 │
│ ←──────────────┤ 显示结果 │ │
└─ 人工复核 │ │ │
```
---
## 📌 场景 2 & 3: 全文复筛与数据提取
### 2.1 技术特点
- **输入格式**: PDF 文件(英文医学文献)
- **文件特点**:
- 科学论文格式(标题、摘要、引言、方法、结果、讨论、参考文献)
- 包含复杂表格、公式、图表
- 通常 10-30 页
- **处理重点**: 高准确率提取,保留结构和格式
### 2.2 技术选型PDF 提取
#### 核心方案Nougat + PyMuPDF 顺序降级策略 ⭐
**现有架构**(已实现,位于 `extraction_service/`
```python
# 顺序降级策略
def extract_pdf(file_path: str):
# Step 1: 检测语言
language = detect_language(file_path)
# Step 2: 中文 PDF → PyMuPDF快速
if language == 'chinese':
return extract_pdf_pymupdf(file_path)
# Step 3: 英文 PDF → 尝试 Nougat
if check_nougat_available():
result = extract_pdf_nougat(file_path)
# 质量检查(阈值 0.7
if result['quality_score'] >= 0.7:
return result # ✅ Nougat 成功
# Step 4: 降级到 PyMuPDF
return extract_pdf_pymupdf(file_path)
```
#### 技术对比
| 方案 | 优势 | 劣势 | 适用场景 |
|------|------|------|---------|
| **Nougat** ⭐ | • 专为科学文献设计<br>• 公式、表格准确率高<br>• 输出 Markdown 格式<br>• 保留文档结构 | • 速度慢1-2 分钟/20 页)<br>• 需要 GPU 加速<br>• 内存占用大(~4GB | 英文医学文献全文提取 |
| **PyMuPDF** | • 速度快(秒级)<br>• 内存占用低<br>• 部署简单 | • 公式、表格易丢失<br>• 纯文本输出<br>• 布局易混乱 | 中文文献、快速预览 |
| **Adobe API** | • 商业级准确率<br>• 云端处理 | • 需付费<br>• 网络依赖<br>• 隐私风险 | 不推荐(成本高) |
| **Tesseract OCR** | • 开源免费<br>• 支持多语言 | • 需要图像预处理<br>• 准确率不稳定 | 扫描版 PDF备选 |
**推荐方案Nougat + PyMuPDF降级**
#### Nougat 核心优势(医学文献场景)
```
✅ 专为科学文献设计
├─ 训练数据arXiv 论文 + 科学期刊
├─ 公式识别LaTeX 格式输出
├─ 表格保留Markdown 表格格式
└─ 结构化输出:章节、段落清晰
✅ 输出格式Markdown
├─ 标题层级:# ## ###
├─ 表格:| Header | Data |
├─ 公式:$$ formula $$
└─ 引用:[1] [2] [3]
✅ 质量评估机制
├─ 自动质量评分0-1
├─ 低质量自动降级 PyMuPDF
└─ 保证提取成功率
```
#### 实现细节
**服务架构:**
```
Node.js Backend (Port 3001)
├─ 调用 ExtractionClient.ts
│ └─ HTTP 请求 → Python 微服务
Python Extraction Service (Port 8000)
├─ /api/extract/pdf
│ ├─ detect_language()
│ ├─ extract_pdf_nougat() → Nougat Model
│ └─ extract_pdf_pymupdf() → PyMuPDF
└─ /api/health
└─ 检查 Nougat 可用性
```
**Node.js 调用代码:**
```typescript
import { extractionClient } from '@common/document/ExtractionClient';
async function extractLiteraturePDF(file: Buffer, filename: string) {
try {
// 方法 1: 自动选择(推荐)
const result = await extractionClient.extractPdf(
file,
filename,
'auto'
);
// 方法 2: 强制使用 Nougat
// const result = await extractionClient.extractPdf(file, filename, 'nougat');
return {
text: result.text,
method: result.method, // "nougat" | "pymupdf"
quality: result.metadata.quality_score,
pageCount: result.metadata.page_count,
hasTables: result.metadata.has_tables,
hasFormulas: result.metadata.has_formulas
};
} catch (error) {
console.error('PDF extraction failed:', error);
throw error;
}
}
```
**Python 提取代码:**
```python
# extraction_service/services/nougat_extractor.py
def extract_pdf_nougat(file_path: str) -> Dict[str, Any]:
"""
使用 Nougat 提取 PDF 文本
命令行调用:
nougat <pdf_path> -o <output_dir> --markdown --no-skipping
"""
cmd = [
'nougat',
file_path,
'-o', output_dir,
'--markdown', # 输出 Markdown 格式
'--no-skipping' # 不跳过任何页面
]
# 执行 Nougat超时 5 分钟)
process = subprocess.Popen(cmd, ...)
stdout, stderr = process.communicate(timeout=300)
# 读取输出文件(.mmd
markdown_text = read_output_file()
# 质量评估
quality_score = evaluate_nougat_quality(markdown_text)
return {
"success": True,
"method": "nougat",
"text": markdown_text,
"format": "markdown",
"metadata": {
"quality_score": quality_score,
"has_tables": detect_tables(markdown_text),
"has_formulas": detect_formulas(markdown_text)
}
}
```
### 2.3 文本后处理
**Nougat 输出优化:**
```typescript
function postProcessNougatOutput(markdown: string): ProcessedText {
return {
// 原始 Markdown
raw: markdown,
// 章节分割
sections: extractSections(markdown), // {abstract, methods, results, ...}
// 表格提取
tables: extractTables(markdown),
// 公式提取
formulas: extractFormulas(markdown),
// 纯文本(去除格式)
plainText: markdownToPlainText(markdown),
// 结构化数据(用于 LLM
structured: {
title: extractTitle(markdown),
abstract: extractAbstract(markdown),
methodology: extractMethodology(markdown),
results: extractResults(markdown),
}
};
}
```
---
## 📌 场景 4: 文献下载Unpaywall API
### 3.1 技术背景
**Unpaywall** 是一个免费的开放获取Open Access文献 API可以
- ✅ 通过 DOI 查询文献是否有免费全文
- ✅ 获取合法的 PDF 下载链接
- ✅ 完全免费,无需付费
- ✅ 数据库覆盖 3000+ 万篇文献
**官网**: https://unpaywall.org/products/api
### 3.2 技术选型
#### API 调用方式
**基础信息:**
- **API 端点**: `https://api.unpaywall.org/v2/{doi}?email={your_email}`
- **请求方法**: GET
- **认证方式**: 无需 API Key仅需提供邮箱
- **速率限制**: 100,000 次/天(免费)
**示例请求:**
```bash
curl "https://api.unpaywall.org/v2/10.1038/nature12373?email=YOUR_EMAIL"
```
**响应示例:**
```json
{
"doi": "10.1038/nature12373",
"title": "The genome of the woodland strawberry",
"is_oa": true,
"oa_status": "gold",
"best_oa_location": {
"url": "https://www.nature.com/articles/nature12373.pdf",
"url_for_pdf": "https://www.nature.com/articles/nature12373.pdf",
"url_for_landing_page": "https://www.nature.com/articles/nature12373",
"license": "cc-by",
"version": "publishedVersion"
},
"oa_locations": [...]
}
```
#### Node.js 实现
**服务封装:**
```typescript
// backend/src/common/literature/UnpaywallClient.ts
import axios from 'axios';
import { config } from '../../config/env';
export interface UnpaywallResult {
doi: string;
title: string;
isOA: boolean; // 是否开放获取
oaStatus: string; // "gold" | "green" | "hybrid" | "bronze" | "closed"
pdfUrl: string | null; // PDF 下载链接
landingPageUrl: string; // 文献页面链接
license: string | null; // 许可协议
version: string | null; // "publishedVersion" | "acceptedVersion"
}
class UnpaywallClient {
private baseUrl = 'https://api.unpaywall.org/v2';
private email: string;
constructor(email: string = config.unpaywallEmail) {
this.email = email;
}
/**
* 通过 DOI 查询文献信息
*/
async getByDoi(doi: string): Promise<UnpaywallResult> {
try {
const url = `${this.baseUrl}/${doi}?email=${this.email}`;
const response = await axios.get(url, {
timeout: 10000, // 10 秒超时
});
const data = response.data;
// 获取最佳下载位置
const bestOA = data.best_oa_location;
return {
doi: data.doi,
title: data.title,
isOA: data.is_oa,
oaStatus: data.oa_status,
pdfUrl: bestOA?.url_for_pdf || null,
landingPageUrl: bestOA?.url_for_landing_page || data.doi_url,
license: bestOA?.license || null,
version: bestOA?.version || null,
};
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
throw new Error(`DOI not found: ${doi}`);
}
}
throw new Error(`Unpaywall API error: ${error.message}`);
}
}
/**
* 批量查询(带速率限制)
*/
async getBatch(dois: string[]): Promise<UnpaywallResult[]> {
const results = [];
for (const doi of dois) {
try {
const result = await this.getByDoi(doi);
results.push(result);
// 速率限制100ms/请求
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`Failed to fetch ${doi}:`, error.message);
results.push(null); // 失败项标记为 null
}
}
return results.filter(r => r !== null);
}
/**
* 下载 PDF 文件
*/
async downloadPdf(pdfUrl: string, outputPath: string): Promise<void> {
try {
const response = await axios.get(pdfUrl, {
responseType: 'arraybuffer',
timeout: 60000, // 1 分钟超时
});
const fs = require('fs');
fs.writeFileSync(outputPath, response.data);
} catch (error) {
throw new Error(`PDF download failed: ${error.message}`);
}
}
}
export const unpaywallClient = new UnpaywallClient();
```
**环境变量配置:**
```env
# .env
UNPAYWALL_EMAIL=your-email@example.com
```
#### 业务集成
**场景 1批量检查文献是否可下载**
```typescript
async function checkLiteratureAvailability(literatures: Literature[]) {
const dois = literatures
.map(lit => lit.doi)
.filter(doi => doi); // 过滤空 DOI
const results = await unpaywallClient.getBatch(dois);
return literatures.map(lit => ({
...lit,
downloadable: results.find(r => r.doi === lit.doi)?.isOA || false,
pdfUrl: results.find(r => r.doi === lit.doi)?.pdfUrl || null,
}));
}
```
**场景 2用户点击下载全文**
```typescript
async function downloadLiteratureFullText(doi: string) {
// Step 1: 查询 Unpaywall
const unpaywallResult = await unpaywallClient.getByDoi(doi);
if (!unpaywallResult.pdfUrl) {
throw new Error('该文献无免费全文');
}
// Step 2: 下载 PDF
const filename = `${doi.replace(/\//g, '_')}.pdf`;
const outputPath = `./downloads/${filename}`;
await unpaywallClient.downloadPdf(unpaywallResult.pdfUrl, outputPath);
// Step 3: 提取文本(调用 extraction_service
const extractionResult = await extractionClient.extractPdf(
fs.readFileSync(outputPath),
filename,
'auto'
);
return {
pdfPath: outputPath,
text: extractionResult.text,
method: extractionResult.method,
};
}
```
### 3.3 前端集成
**批量下载按钮:**
```typescript
// 批量检查可下载性
async function checkDownloadable(selectedRows: Literature[]) {
setLoading(true);
const results = await api.checkLiteratureAvailability(selectedRows);
const downloadableCount = results.filter(r => r.downloadable).length;
message.success(`发现 ${downloadableCount} 篇可下载全文`);
setLiteratures(results);
setLoading(false);
}
// 下载全文
async function downloadFullText(literature: Literature) {
if (!literature.downloadable) {
message.warning('该文献无免费全文');
return;
}
try {
const result = await api.downloadLiteratureFullText(literature.doi);
message.success('下载成功');
// 打开 PDF 查看器
openPdfViewer(result.pdfPath);
} catch (error) {
message.error(`下载失败: ${error.message}`);
}
}
```
---
## 🔍 补充技术点
### 4.1 您提到的技术点总结
| 技术点 | 状态 | 说明 |
|--------|------|------|
| ✅ Nougat 模型 | 已实现 | `extraction_service/services/nougat_extractor.py` |
| ✅ PyMuPDF | 已实现 | `extraction_service/services/pdf_extractor.py` |
| ✅ 顺序降级策略 | 已实现 | 英文→Nougat中文→PyMuPDF |
| 🆕 Unpaywall API | 需新增 | 本文档提供实现方案 |
| ✅ Excel 解析 | 需新增 | 使用 `xlsx` 库(前端) |
### 4.2 可能遗漏的技术点 ⭐
#### 1表格提取增强
**问题**Nougat 虽然保留表格结构,但 LLM 直接处理 Markdown 表格可能不准确。
**解决方案Table Transformer**
```python
# 使用微软的 Table Transformer 模型
# https://github.com/microsoft/table-transformer
from transformers import TableTransformerForObjectDetection
import torch
def extract_tables_enhanced(pdf_path: str):
"""
使用 Table Transformer 精确定位表格
"""
model = TableTransformerForObjectDetection.from_pretrained(
"microsoft/table-transformer-detection"
)
# 检测表格位置
tables = model.detect_tables(pdf_path)
# 提取每个表格
for table in tables:
table_image = crop_table(pdf_path, table.bbox)
table_data = ocr_table(table_image)
return structured_tables
```
**优先级V2.0**MVP 阶段 Nougat 足够)
#### 2引用解析与链接
**问题**:科学文献包含大量引用 `[1] [2] [3]`,需要解析并链接到参考文献。
**解决方案GROBID**
```python
# GROBID: 开源科学文献解析工具
# https://github.com/kermitt2/grobid
import requests
def parse_references(pdf_path: str):
"""
使用 GROBID 解析参考文献
"""
with open(pdf_path, 'rb') as f:
files = {'input': f}
response = requests.post(
'http://localhost:8070/api/processFulltextDocument',
files=files
)
# 返回结构化的引用列表
return response.json()['references']
```
**优先级V2.0**(非核心功能)
#### 3公式识别与渲染
**问题**Nougat 输出 LaTeX 公式,前端需要渲染。
**解决方案KaTeX / MathJax**
```typescript
// 前端渲染 LaTeX 公式
import katex from 'katex';
import 'katex/dist/katex.min.css';
function renderFormula(latex: string) {
return katex.renderToString(latex, {
throwOnError: false,
displayMode: true,
});
}
```
**优先级MVP**(提升用户体验)
#### 4PDF 预览与标注
**问题**:人工复核时需要查看原文,并高亮标注。
**解决方案PDF.js + Annotator.js**
```typescript
// React 组件
import { Viewer } from '@react-pdf-viewer/core';
import '@react-pdf-viewer/core/lib/styles/index.css';
function PdfViewer({ pdfUrl, annotations }) {
return (
<Viewer
fileUrl={pdfUrl}
plugins={[
highlightPlugin({
highlights: annotations // 高亮位置
})
]}
/>
);
}
```
**优先级MVP**(核心功能)
#### 5文献去重
**问题**Excel 上传可能包含重复文献(同一篇文献不同版本)。
**解决方案:基于 DOI 和标题的去重**
```typescript
function deduplicateLiteratures(literatures: Literature[]) {
const seen = new Set();
return literatures.filter(lit => {
// 优先使用 DOI
if (lit.doi) {
if (seen.has(lit.doi)) return false;
seen.add(lit.doi);
return true;
}
// 否则使用标题(标准化后)
const normalizedTitle = normalizeTitle(lit.title);
if (seen.has(normalizedTitle)) return false;
seen.add(normalizedTitle);
return true;
});
}
function normalizeTitle(title: string): string {
return title
.toLowerCase()
.replace(/[^\w\s]/g, '') // 去除标点
.replace(/\s+/g, ' ') // 规范化空格
.trim();
}
```
**优先级MVP**(必须功能)
#### 6文献元数据补全
**问题**Excel 上传的数据可能不完整(缺 DOI、年份等
**解决方案Crossref API**
```typescript
// 通过标题查询 DOI
async function enrichMetadata(literature: Literature) {
if (literature.doi) return literature; // 已有 DOI
// 调用 Crossref API
const response = await axios.get(
`https://api.crossref.org/works?query.title=${literature.title}`
);
const match = response.data.message.items[0];
return {
...literature,
doi: match.DOI,
year: match['published-print']?.['date-parts'][0][0],
journal: match['container-title'][0],
};
}
```
**优先级V1.0**(增强功能)
#### 7批处理进度持久化
**问题**批量筛选耗时长1000 篇 > 10 分钟),需支持断点续传。
**解决方案Redis + 任务队列**
```typescript
// 使用 Bull 队列
import Queue from 'bull';
const screeningQueue = new Queue('literature-screening', {
redis: { host: 'localhost', port: 6379 }
});
// 添加任务
screeningQueue.add({
projectId: 'xxx',
literatures: [...],
protocol: {...}
});
// 处理任务
screeningQueue.process(async (job) => {
const { projectId, literatures, protocol } = job.data;
for (let i = 0; i < literatures.length; i++) {
// 处理单篇文献
await screenLiterature(literatures[i], protocol);
// 更新进度
job.progress((i + 1) / literatures.length * 100);
}
});
```
**优先级V1.0**(体验优化)
#### 8错误处理与重试
**问题**LLM 调用可能失败(网络、超时、限流)。
**解决方案:指数退避重试**
```typescript
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// 指数退避1s, 2s, 4s
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
```
**优先级MVP**(必须功能)
---
## 📊 技术选型总结
### MVP 阶段必选技术
| 层级 | 技术 | 用途 |
|------|------|------|
| **前端** | `xlsx` | Excel 解析 |
| **前端** | `PDF.js` | PDF 预览 |
| **前端** | `KaTeX` | 公式渲染 |
| **后端** | `ExtractionClient` | 调用 Python 微服务 |
| **后端** | `UnpaywallClient` | 文献下载 |
| **Python** | `Nougat` | 英文 PDF 提取 |
| **Python** | `PyMuPDF` | 快速 PDF 提取 |
| **数据库** | `asl_schema` | 数据存储 |
### V1.0 增强技术
| 技术 | 用途 |
|------|------|
| Crossref API | 元数据补全 |
| Bull Queue | 任务队列 |
| Redis | 进度持久化 |
### V2.0 高级技术
| 技术 | 用途 |
|------|------|
| Table Transformer | 表格精确提取 |
| GROBID | 引用解析 |
| Semantic Scholar API | 学术图谱 |
---
## 📁 测试数据存放建议
根据 ASL 模块的文件夹结构,测试数据应该放在:
```
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/
└── 05-测试文档/
├── 01-测试计划.md
├── 02-标题摘要初筛测试用例.md
└── 03-测试数据/ ← 新建文件夹
├── README.md ← 说明文档
├── screening-test-data/
│ ├── literature-list-199.xlsx ← 199 篇文献列表
│ ├── picos-criteria.txt ← PICOS 标准
│ └── expected-results.json ← 预期结果(金标准)
├── pdf-samples/
│ ├── sample-rct-01.pdf
│ ├── sample-cohort-01.pdf
│ └── README.md
└── extraction-test-data/
└── README.md
```
**推荐结构:**
```
05-测试文档/
├── 01-测试计划.md
├── 02-标题摘要初筛测试用例.md
└── 03-测试数据/
├── README.md ← 重要!说明测试数据来源、版权、使用方法
├── screening/
│ ├── literature-list-199.xlsx
│ ├── picos-criteria.txt
│ ├── inclusion-criteria.txt
│ ├── exclusion-criteria.txt
│ └── gold-standard.json ← 人工标注的正确答案
└── pdf-extraction/
├── sample-01-high-quality.pdf
├── sample-02-with-tables.pdf
└── sample-03-chinese.pdf
```
**README.md 示例:**
```markdown
# ASL 测试数据集
## 📋 数据说明
### 1. 标题摘要初筛测试数据
- **文件**: `literature-list-199.xlsx`
- **数量**: 199 篇英文医学文献
- **字段**: 标题、摘要、DOI、作者、年份、期刊
- **来源**: [描述数据来源]
- **版权**: [说明版权信息]
### 2. PICOS 标准
- **文件**: `picos-criteria.txt`
- **内容**: Population, Intervention, Comparison, Outcome, Study Design
- **纳入标准**: 5 条
- **排除标准**: 8 条
### 3. 金标准(人工标注结果)
- **文件**: `gold-standard.json`
- **标注人**: [标注专家信息]
- **标注时间**: [时间]
- **预期准确率**: ≥ 90%
## 🎯 使用方法
### 运行测试
```bash
npm run test:asl:screening
```
### 评估准确率
```bash
npm run test:asl:evaluate -- --gold-standard gold-standard.json
```
## 📊 预期结果
- 纳入: 45 篇
- 排除: 132 篇
- 不确定: 22 篇
```
---
## 📚 相关文档
- [质量保障与可追溯策略](./06-质量保障与可追溯策略.md)
- [数据库设计](./01-数据库设计.md)
- [API 设计规范](./02-API设计规范.md)
- [文档提取微服务](../../../../extraction_service/README.md)
---
**更新日志**
- 2025-11-15: 创建文档,定义初筛、全文处理、文献下载技术选型