feat: 优化PDF解析筛选逻辑,移除MIMIC关键词依赖并增加AI医学相关性判断

- 移除第一层MIMIC-IV关键词筛选,简化筛选流程
- 增强AI分析功能,同时判断医学相关性和任务类型
- 修改_analyze_research_task函数返回包含医学相关性和任务类型的完整结果
- 更新筛选条件:只有同时满足"医学相关"和"指定任务类型"的论文才通过筛选
- 优化相关注释和日志输出,提高代码可维护性
This commit is contained in:
iomgaa 2025-08-26 23:06:48 +08:00
parent 76c04eae4a
commit 22c90728e5

View File

@ -18,7 +18,11 @@ from typing import List, Dict, Optional, Tuple
class PDFParser: class PDFParser:
"""PDF解析类 - 用于将PDF文件转换为Markdown格式并按任务类型筛选 """PDF解析类 - 用于将PDF文件转换为Markdown格式并筛选医学相关论文
筛选机制
1. 医学相关性使用AI判断论文是否属于医学临床生物医学等领域
2. 任务类型在医学相关的基础上进一步筛选指定的研究任务类型
支持的任务类型 支持的任务类型
- prediction: 预测任务 (PRED_) - prediction: 预测任务 (PRED_)
@ -43,16 +47,11 @@ class PDFParser:
# OCR API配置 # OCR API配置
self.ocr_api_url = "http://100.106.4.14:7861/parse" self.ocr_api_url = "http://100.106.4.14:7861/parse"
# AI模型API配置用于四类任务识别prediction/classification/time_series/correlation # AI模型API配置用于医学相关性和四类任务识别)
self.ai_api_url = "http://100.82.33.121:11001/v1/chat/completions" self.ai_api_url = "http://100.82.33.121:11001/v1/chat/completions"
self.ai_model = "gpt-oss-20b" self.ai_model = "gpt-oss-20b"
# MIMIC-IV关键词配置用于内容筛选 # 注意原来的MIMIC关键词配置已移除现在使用AI判断医学相关性
self.mimic_keywords = [
"MIMIC-IV", "MIMIC 4", "MIMIC IV", "MIMIC-4",
"Medical Information Mart Intensive Care IV",
"MIMIC-IV dataset", "MIMIC-IV database"
]
# 任务类型到前缀的映射配置 # 任务类型到前缀的映射配置
self.task_type_prefixes = { self.task_type_prefixes = {
@ -105,44 +104,9 @@ class PDFParser:
logging.info(f"发现 {len(pdf_files)} 个PDF文件待处理") logging.info(f"发现 {len(pdf_files)} 个PDF文件待处理")
return pdf_files return pdf_files
def _check_mimic_keywords(self, output_subdir: Path) -> bool: # 注意_check_mimic_keywords函数已移除
"""检查Markdown文件是否包含MIMIC-IV关键词 # 原功能检查Markdown文件是否包含MIMIC-IV关键词
# 移除原因改用AI分析医学相关性不再依赖特定关键词筛选
Args:
output_subdir (Path): 包含Markdown文件的输出子目录
Returns:
bool: 是否包含MIMIC-IV关键词
"""
try:
# 查找所有.md文件
md_files = list(output_subdir.glob("*.md"))
if not md_files:
logging.warning(f"未找到Markdown文件进行MIMIC关键词检查: {output_subdir}")
return False
# 检查每个Markdown文件的内容
for md_file in md_files:
try:
with open(md_file, 'r', encoding='utf-8') as f:
content = f.read().lower() # 转换为小写进行不区分大小写匹配
# 检查是否包含任何MIMIC-IV关键词
for keyword in self.mimic_keywords:
if keyword.lower() in content:
logging.info(f"发现MIMIC-IV关键词 '{keyword}' 在文件 {md_file.name}")
return True
except Exception as e:
logging.error(f"读取Markdown文件时发生错误: {md_file.name} - {e}")
continue
logging.info(f"未发现MIMIC-IV关键词: {output_subdir.name}")
return False
except Exception as e:
logging.error(f"检查MIMIC关键词时发生错误: {output_subdir} - {e}")
return False
def _extract_introduction(self, output_subdir: Path) -> Optional[str]: def _extract_introduction(self, output_subdir: Path) -> Optional[str]:
"""从Markdown文件中提取Introduction部分 """从Markdown文件中提取Introduction部分
@ -210,33 +174,46 @@ class PDFParser:
logging.error(f"提取Introduction时发生错误: {output_subdir} - {e}") logging.error(f"提取Introduction时发生错误: {output_subdir} - {e}")
return None return None
def _analyze_research_task(self, introduction: str) -> str: def _analyze_research_task(self, introduction: str) -> Dict[str, any]:
"""使用AI模型分析论文的研究任务类型 """使用AI模型分析论文的医学相关性和研究任务类型
Args: Args:
introduction (str): 论文的Introduction内容 introduction (str): 论文的Introduction内容
Returns: Returns:
str: 任务类型 ('prediction', 'classification', 'time_series', 'correlation', 'none') Dict[str, any]: 包含医学相关性和任务类型的分析结果
- is_medical: bool是否为医学相关论文
- task_type: str任务类型 ('prediction', 'classification', 'time_series', 'correlation', 'none')
- medical_confidence: float医学相关性置信度
- task_confidence: float任务类型置信度
""" """
try: try:
# 构造AI分析的提示词 # 构造AI分析的提示词
system_prompt = """你是一个医学研究专家。请分析给定的论文Introduction部分判断该研究属于以下哪种任务类型 system_prompt = """你是一个医学研究专家。请分析给定的论文Introduction部分判断两个维度
1. prediction - 预测任务预测未来事件结局或数值如死亡率预测住院时长预测疾病进展预测 1. 医学相关性判断该论文是否属于医学临床医学生物医学公共卫生护理学等医学相关领域
2. classification - 分类任务将患者或病例分类到不同类别如疾病诊断分类风险等级分类药物反应分类 - 医学相关涉及疾病患者临床数据医疗干预生物医学指标等
3. time_series - 时间序列分析分析随时间变化的医疗数据如生命体征趋势分析病情演进分析纵向队列研究 - 非医学相关纯计算机科学工程学物理学经济学等非医学领域
4. correlation - 关联性分析研究变量间的关系或关联如痾病与人口特征关系药物与副作用关联风险因素识别
5. none - 不属于以上任何类型
请以JSON格式回答包含任务类型和置信度 2. 任务类型如果是医学相关论文进一步判断属于以下哪种任务类型
{\"task_type\": \"prediction\", \"confidence\": 0.85} - prediction: 预测任务预测未来事件结局或数值如死亡率预测住院时长预测疾病进展预测
- classification: 分类任务将患者或病例分类到不同类别如疾病诊断分类风险等级分类药物反应分类
- time_series: 时间序列分析分析随时间变化的医疗数据如生命体征趋势分析病情演进分析纵向队列研究
- correlation: 关联性分析研究变量间的关系或关联如疾病与人口特征关系药物与副作用关联风险因素识别
- none: 不属于以上任何类型
请以JSON格式回答包含所有字段
{\"is_medical\": true, \"task_type\": \"prediction\", \"medical_confidence\": 0.90, \"task_confidence\": 0.85}
字段说明
- is_medical: 布尔值是否为医学相关论文
- task_type: 任务类型prediction/classification/time_series/correlation/none
- medical_confidence: 医学相关性置信度0-1之间
- task_confidence: 任务类型置信度0-1之间
task_type必须是以下选项之一predictionclassificationtime_seriescorrelationnone
confidence为0-1之间的数值表示判断的置信度
只返回JSON不要添加其他文字""" 只返回JSON不要添加其他文字"""
user_prompt = f"请分析以下论文Introduction判断属于哪种任务类型\n\n{introduction[:2000]}" # 限制长度避免token过多 user_prompt = f"请分析以下论文Introduction判断医学相关性和任务类型:\n\n{introduction[:2000]}" # 限制长度避免token过多
# 构造API请求数据 # 构造API请求数据
api_data = { api_data = {
@ -245,7 +222,7 @@ confidence为0-1之间的数值表示判断的置信度。
{"role": "system", "content": system_prompt}, {"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt} {"role": "user", "content": user_prompt}
], ],
"max_tokens": 50, # 需要返回JSON格式 "max_tokens": 100, # 需要返回更复杂的JSON格式
"temperature": 0.1 # 降低随机性 "temperature": 0.1 # 降低随机性
} }
@ -264,35 +241,50 @@ confidence为0-1之间的数值表示判断的置信度。
try: try:
# 解析JSON响应 # 解析JSON响应
parsed_response = json.loads(ai_response) parsed_response = json.loads(ai_response)
is_medical = parsed_response.get('is_medical', False)
task_type = parsed_response.get('task_type', 'none').lower() task_type = parsed_response.get('task_type', 'none').lower()
confidence = parsed_response.get('confidence', 0.0) medical_confidence = parsed_response.get('medical_confidence', 0.0)
task_confidence = parsed_response.get('task_confidence', 0.0)
# 验证任务类型是否有效 # 验证任务类型是否有效
valid_types = ['prediction', 'classification', 'time_series', 'correlation', 'none'] valid_types = ['prediction', 'classification', 'time_series', 'correlation', 'none']
if task_type not in valid_types: if task_type not in valid_types:
logging.warning(f"AI返回了无效的任务类型: {task_type},使用默认值 'none'") logging.warning(f"AI返回了无效的任务类型: {task_type},使用默认值 'none'")
task_type = "none" task_type = "none"
confidence = 0.0 task_confidence = 0.0
# 只接受高置信度的结果 # 检查医学相关性置信度(要求至少 0.7
if confidence < 0.7: if medical_confidence < 0.7:
logging.info(f"AI分析置信度过低 ({confidence:.2f}),归类为 'none'") logging.info(f"医学相关性置信度过低 ({medical_confidence:.2f}),标记为非医学论文")
is_medical = False
# 检查任务类型置信度(要求至少 0.7
if task_confidence < 0.7:
logging.info(f"任务类型置信度过低 ({task_confidence:.2f}),标记为 'none'")
task_type = "none" task_type = "none"
logging.info(f"AI分析结果: 任务类型={task_type}, 置信度={confidence:.2f}") # 构建返回结果
return task_type result = {
'is_medical': is_medical,
'task_type': task_type,
'medical_confidence': medical_confidence,
'task_confidence': task_confidence
}
logging.info(f"AI分析结果: 医学相关={is_medical}({medical_confidence:.2f}), 任务类型={task_type}({task_confidence:.2f})")
return result
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logging.error(f"解析AI JSON响应失败: {ai_response} - 错误: {e}") logging.error(f"解析AI JSON响应失败: {ai_response} - 错误: {e}")
return "none" return {'is_medical': False, 'task_type': 'none', 'medical_confidence': 0.0, 'task_confidence': 0.0}
else: else:
logging.error(f"AI API调用失败状态码: {response.status_code}") logging.error(f"AI API调用失败状态码: {response.status_code}")
return "none" return {'is_medical': False, 'task_type': 'none', 'medical_confidence': 0.0, 'task_confidence': 0.0}
except Exception as e: except Exception as e:
logging.error(f"AI分析研究任务时发生错误: {e}") logging.error(f"AI分析研究任务时发生错误: {e}")
return "none" return {'is_medical': False, 'task_type': 'none', 'medical_confidence': 0.0, 'task_confidence': 0.0}
def _mark_valid_folder(self, output_subdir: Path, task_type: str) -> bool: def _mark_valid_folder(self, output_subdir: Path, task_type: str) -> bool:
"""为通过筛选的文件夹添加任务类型前缀标记 """为通过筛选的文件夹添加任务类型前缀标记
@ -541,26 +533,28 @@ confidence为0-1之间的数值表示判断的置信度。
# 获取解压后的文件夹路径 # 获取解压后的文件夹路径
output_subdir = self.markdown_dir / pdf_file.stem output_subdir = self.markdown_dir / pdf_file.stem
# 第一层筛选检查MIMIC-IV关键词 # AI分析研究任务医学相关性 + 任务类型)
logging.info(f"开始MIMIC-IV关键词筛选: {pdf_file.stem}")
if not self._check_mimic_keywords(output_subdir):
logging.info(f"未通过MIMIC-IV关键词筛选跳过: {pdf_file.stem}")
return True # 处理成功但未通过筛选
# 第二层筛选AI分析研究任务
logging.info(f"开始AI研究任务分析: {pdf_file.stem}") logging.info(f"开始AI研究任务分析: {pdf_file.stem}")
introduction = self._extract_introduction(output_subdir) introduction = self._extract_introduction(output_subdir)
if not introduction: if not introduction:
logging.warning(f"无法提取Introduction跳过AI分析: {pdf_file.stem}") logging.warning(f"无法提取Introduction跳过AI分析: {pdf_file.stem}")
return True # 处理成功但无法进行任务分析 return True # 处理成功但无法进行任务分析
task_type = self._analyze_research_task(introduction) analysis_result = self._analyze_research_task(introduction)
if task_type == "none": is_medical = analysis_result['is_medical']
logging.info(f"未通过研究任务筛选 (task_type=none),跳过: {pdf_file.stem}") task_type = analysis_result['task_type']
# 检查是否通过筛选(必须是医学相关且属于指定任务类型)
if not is_medical:
logging.info(f"未通过医学相关性筛选,跳过: {pdf_file.stem}")
return True # 处理成功但未通过筛选 return True # 处理成功但未通过筛选
# 两层筛选都通过,根据任务类型标记文件夹 if task_type == "none":
logging.info(f"通过所有筛选,标记为{task_type}任务论文: {pdf_file.stem}") logging.info(f"未通过任务类型筛选 (task_type=none),跳过: {pdf_file.stem}")
return True # 处理成功但未通过筛选
# 通过所有筛选,根据任务类型标记文件夹
logging.info(f"通过所有筛选,标记为{task_type}任务医学论文: {pdf_file.stem}")
if self._mark_valid_folder(output_subdir, task_type): if self._mark_valid_folder(output_subdir, task_type):
logging.info(f"论文筛选完成,已标记为{task_type}任务: {pdf_file.stem}") logging.info(f"论文筛选完成,已标记为{task_type}任务: {pdf_file.stem}")
else: else: