""" 增强分析模块 本地优先 + API 增强的混合模式 设计原则: 1. 本地工具优先执行核心分析 2. API 用于补充最新信息和验证 3. API 失败不影响本地结果 4. 可配置是否启用 API 增强 """ import json from pathlib import Path from typing import Dict, List, Optional, Any from dataclasses import dataclass, asdict from .api_clients import ( MultiAPIQuery, PfamClient, NCBIClient, EnsemblPlantsClient, ) @dataclass class DomainResult: """Domain 分析结果(本地 + API 增强)""" # 本地分析结果 pfam_acc: str pfam_name: str description: str score: float evalue: float start: int end: int # API 增强信息(可选) latest_description: Optional[str] = None protein_count: Optional[int] = None clan: Optional[str] = None clan_name: Optional[str] = None go_terms: Optional[List[str]] = None interpro: Optional[Dict] = None # 数据来源标记 data_source: str = "local" # "local" | "api" | "merged" @dataclass class GeneInfo: """基因信息(API 查询)""" gene_id: str symbol: str description: str organism: str chromosome: Optional[str] = None location: Optional[str] = None synonyms: Optional[List[str]] = None # 表达数据 expression_data: Optional[List[Dict]] = None # 同源基因 orthologs: Optional[List[Dict]] = None class EnhancedDomainAnalyzer: """ 增强版 Domain 分析器 本地 Pfam HMMER 扫描 + API 补充最新注释 """ def __init__(self, local_pfam_path: str, enable_api: bool = True): """ 初始化分析器 Args: local_pfam_path: 本地 Pfam HMM 文件路径 enable_api: 是否启用 API 增强 """ self.local_pfam_path = Path(local_pfam_path) self.enable_api = enable_api if enable_api: self.pfam_client = PfamClient() self.query_manager = MultiAPIQuery() def analyze(self, protein_fasta: str, enhance: bool = True) -> List[DomainResult]: """ 分析蛋白质结构域 Args: protein_fasta: 蛋白质 FASTA 文件路径 enhance: 是否用 API 增强结果 Returns: DomainResult 列表 """ # 1. 本地 HMMER 扫描(核心功能,必须成功) local_results = self._run_local_hmmscan(protein_fasta) if not enhance or not self.enable_api: return local_results # 2. API 增强(可选,失败不影响结果) enhanced_results = [] for domain in local_results: try: enhanced = self._enhance_with_api(domain) enhanced_results.append(enhanced) except Exception as e: # API 失败,保留本地结果 print(f"[WARN] API enhancement failed for {domain.pfam_acc}: {e}") domain.data_source = "local" enhanced_results.append(domain) return enhanced_results def _run_local_hmmscan(self, fasta_file: str) -> List[DomainResult]: """ 运行本地 HMMER hmmscan 实际实现应调用 subprocess 运行 hmmscan 这里返回模拟结果 """ import subprocess output_file = Path(fasta_file).with_suffix('.domtblout') cmd = [ 'hmmscan', '--domtblout', str(output_file), '--noali', '-E', '1e-5', str(self.local_pfam_path), fasta_file ] try: subprocess.run(cmd, check=True, capture_output=True) return self._parse_domtblout(output_file) except subprocess.CalledProcessError as e: print(f"[ERROR] HMMER failed: {e}") return [] except FileNotFoundError: print("[WARN] hmmscan not found, returning empty results") return [] def _parse_domtblout(self, domtblout_file: Path) -> List[DomainResult]: """解析 HMMER domtblout 文件""" results = [] if not domtblout_file.exists(): return results with open(domtblout_file) as f: for line in f: if line.startswith('#'): continue parts = line.split() if len(parts) < 22: continue # domtblout 格式解析 try: result = DomainResult( pfam_acc=parts[1], pfam_name=parts[0], description=' '.join(parts[22:]) if len(parts) > 22 else '', score=float(parts[13]), evalue=float(parts[12]), start=int(parts[19]), end=int(parts[20]), data_source="local" ) results.append(result) except (ValueError, IndexError): continue return results def _enhance_with_api(self, domain: DomainResult) -> DomainResult: """ 用 API 增强 Domain 结果 Args: domain: 本地分析结果 Returns: 增强后的结果 """ # 查询 Pfam API api_info = self.pfam_client.get_family_info(domain.pfam_acc) if api_info: domain.latest_description = api_info.get('description') domain.protein_count = api_info.get('protein_count') domain.clan = api_info.get('clan') domain.clan_name = api_info.get('clan_name') domain.go_terms = api_info.get('go_terms', []) domain.interpro = api_info.get('interpro') domain.data_source = "merged" return domain def generate_report(self, results: List[DomainResult], output_file: str): """ 生成增强报告 Args: results: DomainResult 列表 output_file: 输出文件路径 """ report = { 'summary': { 'total_domains': len(results), 'local_only': sum(1 for r in results if r.data_source == 'local'), 'api_enhanced': sum(1 for r in results if r.data_source == 'merged'), }, 'domains': [asdict(r) for r in results], } with open(output_file, 'w') as f: json.dump(report, f, indent=2) print(f"[INFO] Report saved to {output_file}") class EnhancedReportGenerator: """ 增强报告生成器 本地分析结果 + API 补充基因信息 """ def __init__(self, enable_api: bool = True): self.enable_api = enable_api if enable_api: self.ncbi_client = NCBIClient() self.ensembl_client = EnsemblPlantsClient() self.query_manager = MultiAPIQuery() def enhance_report(self, local_report: Dict, gene_symbols: List[str]) -> Dict: """ 增强报告内容 Args: local_report: 本地分析结果 gene_symbols: 基因符号列表 Returns: 增强后的报告 """ if not self.enable_api: return local_report enhanced = local_report.copy() enhanced['gene_annotations'] = {} for symbol in gene_symbols: try: # 查询基因信息 gene_info = self._query_gene_info(symbol) if gene_info: enhanced['gene_annotations'][symbol] = gene_info except Exception as e: print(f"[WARN] Failed to query gene {symbol}: {e}") return enhanced def _query_gene_info(self, symbol: str) -> Optional[Dict]: """查询基因详细信息""" results = self.query_manager.query_all('gene', { 'gene_symbol': symbol, 'organism': 'Arabidopsis' }) # 合并结果 gene_info = { 'symbol': symbol, 'ncbi': None, 'ensembl': None, } # 从 NCBI 获取信息 if 'ncbi' in results and results['ncbi'].status == 'success': data = results['ncbi'].data if isinstance(data, list) and len(data) > 0: gene = data[0] gene_info['ncbi'] = { 'gene_id': gene.gene_id, 'description': gene.description, 'chromosome': gene.chromosome, 'location': gene.location, } return gene_info if gene_info['ncbi'] or gene_info['ensembl'] else None # 使用示例 if __name__ == "__main__": # 示例 1: Domain 分析(本地 + API 增强) print("=" * 60) print("示例 1: Domain 分析(本地 + API 增强)") print("=" * 60) analyzer = EnhancedDomainAnalyzer( local_pfam_path=str(Path(__file__).resolve().parents[1] / "data" / "hmm" / "Pfam-A.hmm"), enable_api=True ) # 分析蛋白质 # results = analyzer.analyze("/path/to/protein.fa", enhance=True) # analyzer.generate_report(results, "domain_report.json") print("\n配置:") print(f" 本地 Pfam: {analyzer.local_pfam_path}") print(f" API 增强: {'启用' if analyzer.enable_api else '禁用'}") # 示例 2: 报告增强 print("\n" + "=" * 60) print("示例 2: 报告增强") print("=" * 60) generator = EnhancedReportGenerator(enable_api=True) # 假设的本地报告 local_report = { 'job_id': 'test_001', 'stages': ['identify', 'phylogeny', 'domain'], 'results': {} } # 增强报告 # enhanced = generator.enhance_report(local_report, ['SPL1', 'SPL2', 'SPL3']) print("\n配置:") print(f" API 增强: {'启用' if generator.enable_api else '禁用'}")