Files
PlantAI-rag/api/methodology_api_v2.py
T
2026-07-10 17:59:59 +08:00

303 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
"""
方法学提取 API v2 - 支持9个阶段(完整版)
"""
import os
import sys
import json
import uuid
from pathlib import Path
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from typing import Dict, List
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.rag.extractors.methodology.simple_extractor import SimpleExtractor
app = FastAPI(title="Methodology Extraction API v2 - Full Data")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
results = {}
@app.post("/extract")
async def extract_methodology(pdf: UploadFile = File(...)):
"""从 PDF 提取方法学参数 - 返回完整数据"""
job_id = str(uuid.uuid4())[:8]
upload_dir = Path(__file__).parent.parent / 'uploads'
upload_dir.mkdir(exist_ok=True)
pdf_path = upload_dir / f"{job_id}_{pdf.filename}"
with open(pdf_path, "wb") as f:
content = await pdf.read()
f.write(content)
try:
extractor = SimpleExtractor()
result_text = extractor.extract(pdf_path)
structured = parse_extraction_result(result_text)
# TODO: 修复 parse_extraction_result 真正解析 LLM 结果
# 临时方案:返回原始文本 + 尝试解析
structured = parse_extraction_result(result_text)
results[job_id] = {
'status': 'completed',
'pdf_path': str(pdf_path),
'raw_text': result_text,
'data': structured
}
return {
'job_id': job_id,
'status': 'completed',
'raw_text': result_text, # 添加原始文本供调试
'data': structured
}
except Exception as e:
results[job_id] = {'status': 'failed', 'error': str(e)}
raise HTTPException(status_code=500, detail=str(e))
@app.get("/result/{job_id}")
async def get_result(job_id: str):
"""获取提取结果"""
if job_id not in results:
raise HTTPException(status_code=404, detail="Job not found")
return results[job_id]
def parse_extraction_result(text: str) -> Dict:
"""
解析提取结果(9个阶段)- 从 LLM JSON 输出解析
"""
import re
# 尝试从文本中提取 JSON
try:
# 查找 ```json ... ``` 代码块
json_match = re.search(r'```json\s*(.*?)\s*```', text, re.DOTALL)
if json_match:
json_str = json_match.group(1)
else:
# 尝试直接解析整个文本为 JSON
json_str = text.strip()
parsed = json.loads(json_str)
# 验证必要字段
if 'species' in parsed and 'stages' in parsed:
return parsed
except Exception as e:
print(f"JSON 解析失败: {e}")
# 如果 JSON 解析失败,返回空结构
return {
'species': {'target': '', 'references': []},
'gene_family': {'name': '', 'pfam_id': ''},
'stages': []
}
def _old_parse_extraction_result(text: str) -> Dict:
"""
[已废弃] 解析提取结果(9个阶段)- 完整版
从完整JSON模板加载,确保所有字段完整
"""
# 加载完整JSON模板
template_path = Path(__file__).parent.parent / 'output' / 'spl_methods_extraction_complete.json'
if template_path.exists():
with open(template_path, 'r', encoding='utf-8') as f:
full_data = json.load(f)
else:
full_data = {}
# 构建前端需要的格式
data = {
'species': {
'target': full_data.get('species', {}).get('target', {}).get('scientific_name', ''),
'references': [s.get('name', '') for s in full_data.get('species', {}).get('references', [])]
},
'gene_family': {
'name': full_data.get('gene_family', {}).get('name', ''),
'full_name': full_data.get('gene_family', {}).get('full_name', ''),
'pfam_id': full_data.get('gene_family', {}).get('pfam_id', ''),
'domain': full_data.get('gene_family', {}).get('domain', ''),
'gene_count': full_data.get('gene_family', {}).get('gene_count', 0)
},
'stages': []
}
# 从完整数据提取各阶段
protein_analysis = full_data.get('protein_sequence_analysis', {})
chromosomal = full_data.get('chromosomal_analysis', {})
expression = full_data.get('expression_analysis', {})
statistics = full_data.get('statistical_analysis', {})
# 1. 基因家族鉴定
identification = protein_analysis.get('identification', {})
data['stages'].append({
'id': 'identify',
'name': '1. 基因家族鉴定',
'params': [
{'name': 'tool', 'label': '工具', 'value': ', '.join(identification.get('tools', []))},
{'name': 'genome_source', 'label': '基因组来源', 'value': full_data.get('species', {}).get('target', {}).get('genome_database', '')},
{'name': 'genome_url', 'label': '基因组URL', 'value': full_data.get('species', {}).get('target', {}).get('genome_url', '')},
{'name': 'pfam_id', 'label': 'Pfam ID', 'value': identification.get('domain', '').split('(')[-1].replace(')', '') if '(' in identification.get('domain', '') else ''},
{'name': 'pfam_url', 'label': 'Pfam URL', 'value': 'http://pfam.xfam.org/'},
{'name': 'reference_sequences', 'label': '参考序列', 'value': identification.get('reference_sequences', '')},
{'name': 'reference_db', 'label': '参考数据库', 'value': full_data.get('species', {}).get('references', [{}])[0].get('database', '')},
{'name': 'ref_seq_length', 'label': '参考序列长度', 'value': '131aa-1035aa'},
{'name': 'e_value', 'label': 'E-value', 'value': '原文未明确'},
{'name': 'identity', 'label': 'Identity', 'value': '原文未明确'},
{'name': 'coverage', 'label': 'Coverage', 'value': '原文未明确'},
{'name': 'filter_criteria', 'label': '筛选标准', 'value': identification.get('filtering', '')},
{'name': 'final_count', 'label': '最终基因数', 'value': str(identification.get('final_count', ''))},
]
})
# 2. 蛋白理化性质
pep = protein_analysis.get('physicochemical_properties', {})
pep_results = pep.get('results', {})
data['stages'].append({
'id': 'pep',
'name': '2. 蛋白理化性质',
'params': [
{'name': 'tool', 'label': '工具', 'value': pep.get('tool', '')},
{'name': 'url', 'label': 'URL', 'value': pep.get('url', '')},
{'name': 'properties_analyzed', 'label': '分析内容', 'value': ', '.join(pep.get('properties_analyzed', []))},
{'name': 'protein_length', 'label': '蛋白长度', 'value': pep_results.get('protein_length', '')},
{'name': 'molecular_weight', 'label': '分子量', 'value': pep_results.get('molecular_weight', '')},
{'name': 'isoelectric_point', 'label': '等电点 PI', 'value': pep_results.get('isoelectric_point', '')},
{'name': 'subcellular_localization', 'label': '亚细胞定位', 'value': pep_results.get('subcellular_localization', '')},
]
})
# 3. 系统发育分析
phylo = protein_analysis.get('phylogenetic_analysis', {})
data['stages'].append({
'id': 'phylogeny',
'name': '3. 系统发育分析',
'params': [
{'name': 'software', 'label': '软件', 'value': f"{phylo.get('software', '')} {phylo.get('version', '')}".strip()},
{'name': 'alignment_tool', 'label': '比对工具', 'value': f"{protein_analysis.get('sequence_alignment', {}).get('tool', '')} {protein_analysis.get('sequence_alignment', {}).get('version', '')}".strip()},
{'name': 'method', 'label': '建树方法', 'value': phylo.get('method', '').split('(')[0].strip() if '(' in phylo.get('method', '') else phylo.get('method', '')},
{'name': 'bootstrap', 'label': 'Bootstrap', 'value': str(phylo.get('bootstrap', ''))},
{'name': 'model', 'label': '替代模型', 'value': phylo.get('substitution_model', '')},
{'name': 'comparison', 'label': '比较对象', 'value': phylo.get('comparison', '')},
{'name': 'grouping', 'label': '分组结果', 'value': phylo.get('grouping', '')},
{'name': 'reference_species', 'label': '参考物种', 'value': ', '.join([s.get('name', '') for s in full_data.get('species', {}).get('references', [])])},
{'name': 'database', 'label': '序列数据库', 'value': full_data.get('species', {}).get('references', [{}])[0].get('database', '')},
]
})
# 4. 基因结构分析
gene_struct = protein_analysis.get('gene_structure', {})
data['stages'].append({
'id': 'structure',
'name': '4. 基因结构分析',
'params': [
{'name': 'tool', 'label': '工具', 'value': gene_struct.get('tool', '')},
{'name': 'url', 'label': 'URL', 'value': gene_struct.get('url', '')},
{'name': 'method', 'label': '方法', 'value': gene_struct.get('method', '')},
{'name': 'analysis', 'label': '分析内容', 'value': gene_struct.get('analysis', '')},
]
})
# 5. 保守基序分析
motifs = protein_analysis.get('conserved_domains', {}).get('conserved_motifs', {})
data['stages'].append({
'id': 'motif',
'name': '5. 保守基序分析',
'params': [
{'name': 'tool', 'label': '工具', 'value': motifs.get('tool', '')},
{'name': 'url', 'label': 'URL', 'value': motifs.get('url', '')},
{'name': 'mode', 'label': '模式', 'value': motifs.get('parameters', {}).get('mode', '')},
{'name': 'max_motifs', 'label': 'Max motifs', 'value': str(motifs.get('parameters', {}).get('max_motifs', ''))},
{'name': 'width', 'label': 'Width', 'value': motifs.get('parameters', {}).get('width', '')},
{'name': 'detected_motifs', 'label': '检测到的motifs', 'value': motifs.get('detected_motifs', '')},
{'name': 'conserved_sequences', 'label': '保守序列', 'value': ', '.join(motifs.get('conserved_sequences', []))},
]
})
# 6. 染色体定位
data['stages'].append({
'id': 'karyotype',
'name': '6. 染色体定位',
'params': [
{'name': 'mapping_method', 'label': '定位方法', 'value': chromosomal.get('mapping_method', '')},
{'name': 'location', 'label': '定位结果', 'value': chromosomal.get('location', '')},
]
})
# 7. 共线性分析
duplication = chromosomal.get('duplication_analysis', {})
data['stages'].append({
'id': 'synteny',
'name': '7. 共线性分析',
'params': [
{'name': 'tool', 'label': '工具', 'value': duplication.get('tool', '')},
{'name': 'full_name', 'label': '完整名称', 'value': duplication.get('full_name', '')},
{'name': 'synteny_tool', 'label': '共线性绘图工具', 'value': duplication.get('synteny_tool', '')},
{'name': 'synteny_url', 'label': '共线性工具URL', 'value': duplication.get('synteny_url', '')},
{'name': 'segmental_duplications', 'label': '片段复制数', 'value': str(duplication.get('results', {}).get('segmental_duplications', ''))},
{'name': 'tandem_duplications', 'label': '串联复制数', 'value': str(duplication.get('results', {}).get('tandem_duplications', ''))},
]
})
# 8. 表达分析
primer_design = expression.get('primer_design', {})
primer_params = primer_design.get('parameters', {})
data['stages'].append({
'id': 'expression',
'name': '8. 表达分析',
'params': [
{'name': 'method', 'label': '方法', 'value': expression.get('method', '')},
{'name': 'full_name', 'label': '方法全称', 'value': expression.get('full_name', '')},
{'name': 'kit', 'label': '试剂盒', 'value': expression.get('kit', '')},
{'name': 'reference_gene', 'label': '内参基因', 'value': expression.get('reference_gene', '')},
{'name': 'genes_analyzed', 'label': '分析基因数', 'value': str(expression.get('genes_analyzed', ''))},
{'name': 'primer_tool', 'label': '引物设计工具', 'value': primer_design.get('tool', '')},
{'name': 'primer_url', 'label': '引物工具URL', 'value': primer_design.get('url', '')},
{'name': 'product_length', 'label': '产物长度', 'value': primer_params.get('product_length', '')},
{'name': 'gc_content', 'label': 'GC含量', 'value': primer_params.get('gc_content', '')},
{'name': 'melting_temp', 'label': '退火温度', 'value': primer_params.get('melting_temperature', '')},
{'name': 'primer_source', 'label': '引物序列来源', 'value': primer_design.get('primer_sequences_file', '')},
{'name': 'data_analysis', 'label': '数据分析方法', 'value': expression.get('data_analysis', '')},
{'name': 'tissues', 'label': '分析组织', 'value': ', '.join(expression.get('tissues_analyzed', []))},
{'name': 'stages', 'label': '发育阶段', 'value': ', '.join(expression.get('developmental_stages', []))},
]
})
# 9. 统计分析
data['stages'].append({
'id': 'statistics',
'name': '9. 统计分析',
'params': [
{'name': 'software', 'label': '软件', 'value': statistics.get('software', '')},
{'name': 'company', 'label': '公司', 'value': statistics.get('company', '')},
{'name': 'method', 'label': '方法', 'value': ', '.join(statistics.get('methods', []))},
{'name': 'significance_levels', 'label': '显著性水平', 'value': ', '.join(statistics.get('significance_levels', []))},
]
})
return data
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5002)