303 lines
14 KiB
Python
Executable File
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)
|