""" ChEMBL Compound Mining Script Queries ChEMBL API for compounds targeting HDT genes. Author: Dr. Siddalingaiah H S """ import pandas as pd import time from pathlib import Path # Check if chembl client is installed try: from chembl_webresource_client.new_client import new_client CHEMBL_AVAILABLE = True except ImportError: CHEMBL_AVAILABLE = False print("WARNING: chembl_webresource_client not installed.") print("Install with: pip install chembl_webresource_client") BASE_DIR = Path(__file__).parent.parent def query_target_by_gene(gene_symbol: str) -> dict: """ Query ChEMBL for a human protein target by gene symbol. Returns: dict with target_chembl_id and metadata, or None """ if not CHEMBL_AVAILABLE: return None target = new_client.target try: results = target.filter( target_synonym__iexact=gene_symbol, target_type='SINGLE PROTEIN', organism='Homo sapiens' ).only(['target_chembl_id', 'pref_name', 'target_type']) results_list = list(results) if results_list: return results_list[0] # Try alternative search by target synonyms results = target.filter( target_synonym__icontains=gene_symbol, target_type='SINGLE PROTEIN', organism='Homo sapiens' ).only(['target_chembl_id', 'pref_name', 'target_type']) results_list = list(results) return results_list[0] if results_list else None except Exception as e: print(f" Error querying {gene_symbol}: {e}") return None def query_activities_for_target(target_chembl_id: str, min_pchembl: float = 6.0) -> list: """ Query ChEMBL for bioactivity data for a target. Args: target_chembl_id: ChEMBL target ID (e.g., 'CHEMBL203') min_pchembl: Minimum pChEMBL value (default 6.0 = 1 μM) Returns: List of activity records with compound info """ if not CHEMBL_AVAILABLE: return [] activity = new_client.activity try: results = activity.filter( target_chembl_id=target_chembl_id, pchembl_value__gte=min_pchembl, assay_type__in=['B', 'F'] # Binding and Functional assays ).only([ 'molecule_chembl_id', 'molecule_pref_name', 'pchembl_value', 'standard_type', 'standard_value', 'standard_units', ]) return list(results)[:40] # Limit to top 40 for faster POC training except Exception as e: print(f" Error querying activities: {e}") return [] def get_molecule_info(molecule_chembl_id: str) -> dict: """Get detailed molecule info including clinical phase and SMILES.""" if not CHEMBL_AVAILABLE: return {} molecule = new_client.molecule try: result = molecule.filter( molecule_chembl_id=molecule_chembl_id ).only([ 'molecule_chembl_id', 'pref_name', 'max_phase', 'molecule_type', 'first_approval', 'molecule_structures', # For SMILES ]) results_list = list(result) if not results_list: return {} res = results_list[0] # Extract SMILES if available structures = res.get('molecule_structures') if structures: res['canonical_smiles'] = structures.get('canonical_smiles') return res except Exception as e: return {} def mine_compounds_for_gene_list(gene_list: list, output_path: Path = None): """ Mine ChEMBL for compounds targeting a list of genes. Args: gene_list: List of gene symbols output_path: Path to save results CSV Returns: DataFrame with compound bioactivity data """ if not CHEMBL_AVAILABLE: print("ERROR: ChEMBL client not available. Cannot mine compounds.") return pd.DataFrame() print("=" * 70) print("CHEMBL COMPOUND MINING") print("=" * 70) all_compounds = [] for i, gene in enumerate(gene_list): print(f"\n[{i+1}/{len(gene_list)}] Processing {gene}...") # Find target target_info = query_target_by_gene(gene) if not target_info: print(f" No ChEMBL target found for {gene}") continue target_id = target_info['target_chembl_id'] print(f" Found target: {target_id} ({target_info.get('pref_name', 'N/A')})") # Query activities activities = query_activities_for_target(target_id) print(f" Found {len(activities)} activities with pChEMBL >= 6.0") # Process unique compounds seen_molecules = set() for act in activities: mol_id = act.get('molecule_chembl_id') if mol_id and mol_id not in seen_molecules: seen_molecules.add(mol_id) # Get molecule details mol_info = get_molecule_info(mol_id) compound_entry = { 'Gene': gene, 'Target_ChEMBL_ID': target_id, 'Target_Name': target_info.get('pref_name', ''), 'Molecule_ChEMBL_ID': mol_id, 'Drug_Name': act.get('molecule_pref_name') or mol_info.get('pref_name', ''), 'SMILES': mol_info.get('canonical_smiles', ''), 'pChEMBL': act.get('pchembl_value'), 'Assay_Type': act.get('standard_type', ''), 'Max_Phase': mol_info.get('max_phase', 0), 'First_Approval': mol_info.get('first_approval'), 'Source': 'ChEMBL_API', } all_compounds.append(compound_entry) # Rate limiting time.sleep(0.2) # Create DataFrame df = pd.DataFrame(all_compounds) if len(df) > 0: # Sort by pChEMBL df = df.sort_values('pChEMBL', ascending=False) # Summary print("\n" + "=" * 70) print("SUMMARY") print("=" * 70) print(f"Total compounds found: {len(df)}") print(f"Unique drugs: {df['Drug_Name'].nunique()}") print(f"FDA approved (Phase 4): {len(df[df['Max_Phase'] == 4])}") print(f"Phase 3: {len(df[df['Max_Phase'] == 3])}") print(f"Phase 2: {len(df[df['Max_Phase'] == 2])}") if output_path: df.to_csv(output_path, index=False) print(f"\nSaved to: {output_path}") return df def main(): """Main entry point for compound mining.""" # Load prioritized targets gene_sig_path = BASE_DIR / 'outputs' / 'tables' / 'targets_ranked_authentic.csv' if not gene_sig_path.exists(): # Fall back to verified signature if ranked not yet created gene_sig_path = BASE_DIR / 'data' / 'gene_signature_verified.csv' genes_df = pd.read_csv(gene_sig_path, comment='#') else: genes_df = pd.read_csv(gene_sig_path) # Get top 10 targets for compound mining (larger training set for AI) if 'Druggability' in genes_df.columns: high_drug = genes_df[genes_df['Druggability'].isin(['High', 'Moderate'])] gene_list = high_drug['Symbol'].head(6).tolist() else: gene_list = genes_df['Symbol'].head(6).tolist() print(f"Mining compounds for {len(gene_list)} genes: {gene_list}") output_path = BASE_DIR / 'outputs' / 'tables' / 'compounds_from_chembl.csv' compounds_df = mine_compounds_for_gene_list(gene_list, output_path) return compounds_df if __name__ == '__main__': main()