ahmedmiloudi/BioTechLabAI
1
1"""2🧬 ADVANCED DRUG DISCOVERY PLATFORM - PRODUCTION VERSION3========================================================4Plateforme professionnelle de drug discovery avec ML/AI5 6Fonctionnalités:7- Enrichissement multi-sources (9 APIs)8- Analyse chimique avancée9- ML/AI pour prédictions10- Data science interactif11- Rapports professionnels12- Mobile-ready13 14Auteur: Ahmed Miloudi-BioninfAI15Version: 2.1 PRODUCTION16Date: 2026-02-0217"""18from datetime import datetime19import streamlit as st20import pandas as pd21import numpy as np22import plotly.graph_objects as go23import plotly.express as px24from plotly.subplots import make_subplots25from datetime import datetime26import json27import base6428from io import BytesIO29import warnings30import traceback31from rdkit import Chem32from rdkit.Chem import Descriptors, Lipinski, Crippen, MolSurf, Draw33from functools import lru_cache34import logging35import time36from credibility_messages import (37 show_model_loading_message, 38 show_credibility_badge, 39 show_all_models_info40 )41 42# Import des modules43from data_enrichment import DrugDiscoveryDataEnricher, create_enrichment_report44from compound_fallback import RDKitCompoundAnalyzer45from compound_analysis import CompoundAnalyzer, create_compound_report46from structural_analysis import StructuralAnalyzer, create_structure_report47from cache_manager import CacheManager48from bioactivity_profiler import BioactivityProfiler49from target_validation import TargetValidator50from clinical_intelligence import ClinicalIntelligence51from ml_predictions import MLPredictor52from pro_visualization_lab import ProVisualizationLab53from professional_enricher import ProfessionalDataEnricher, export_to_dataframe54from drug_discovery_network import create_drug_discovery_ui55from pwa_component import inject_pwa_code56 57warnings.filterwarnings('ignore')58 59# ============================================================================60# CONFIGURATION61# ============================================================================62st.set_page_config(63 page_title="BioinfAI Platform",64 page_icon="🧬",65 layout="wide",66 initial_sidebar_state="collapsed", # <-- Changé pour mobile67 menu_items={68 'Get Help': 'https://huggingface.co/spaces/ahmedmiloudi',69 'About': "AI-powered BiotechLab Tool for Drug discovery "70 }71)72 73# Configuration du logging74logging.basicConfig(75 level=logging.INFO,76 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'77)78logger = logging.getLogger(__name__)79 80# CSS Custom amélioré81st.markdown("""82<style>83 /* ── Layout général ── */84 .block-container {85 max-width: 1400px;86 padding-left: 1.5rem;87 padding-right: 1.5rem;88 padding-top: 1rem;89 }90 91 /* ── Header principal ── */92 .main-header {93 font-size: 2.5rem;94 font-weight: bold;95 background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);96 -webkit-background-clip: text;97 -webkit-text-fill-color: transparent;98 margin-bottom: 0.5rem;99 white-space: nowrap;100 }101 102 /* ── Fix pour les boutons ── */103 .stButton > button {104 width: 100%;105 margin-top: 5px;106 margin-bottom: 5px;107 transition: all 0.3s ease;108 }109 110 .stButton > button:hover {111 transform: translateY(-2px);112 box-shadow: 0 4px 12px rgba(0,0,0,0.15);113 }114 115 /* ── Améliorer la visibilité des résultats ── */116 .analysis-results {117 background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);118 padding: 1.5rem;119 border-radius: 10px;120 border-left: 4px solid #667eea;121 margin: 1rem 0;122 transition: all 0.3s ease;123 }124 125 .analysis-results:hover {126 box-shadow: 0 4px 20px rgba(0,0,0,0.1);127 }128 129 /* ── Metric cards ── */130 .metric-card {131 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);132 padding: 1rem;133 border-radius: 10px;134 color: white;135 text-align: center;136 margin: 0.5rem 0;137 min-width: 120px;138 transition: all 0.3s ease;139 }140 141 .metric-card:hover {142 transform: translateY(-3px);143 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);144 }145 146 .metric-card h3 {147 margin: 0 0 0.25rem 0;148 font-size: 1.6rem;149 }150 151 .metric-card p {152 margin: 0;153 font-size: 0.85rem;154 opacity: 0.9;155 }156 157 /* ── Tabs ── */158 .stTabs [data-baseweb="tab-list"] {159 gap: 6px;160 flex-wrap: wrap;161 }162 163 .stTabs [data-baseweb="tab"] {164 height: 44px;165 padding: 8px 14px;166 background-color: #f0f2f6;167 border-radius: 10px 10px 0 0;168 font-size: 0.85rem;169 white-space: nowrap;170 color: #2c3e50 !important;171 transition: all 0.3s ease;172 }173 174 .stTabs [data-baseweb="tab"] * {175 color: #2c3e50 !important;176 }177 178 .stTabs [aria-selected="true"] {179 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;180 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);181 }182 183 .stTabs [aria-selected="true"],184 .stTabs [aria-selected="true"] * {185 color: white !important;186 }187 188 .stTabs [data-baseweb="tab"]:hover {189 background-color: #dcdfe6 !important;190 transform: translateY(-2px);191 }192 193 .stTabs [data-baseweb="tab"]:hover,194 .stTabs [data-baseweb="tab"]:hover * {195 color: #1a252f !important;196 }197 198 /* ── Sidebar ── */199 .css-1d298d8e {200 padding-top: 0.5rem;201 }202 203 /* ── Métriques natives ── */204 div[data-testid="metric-container"] {205 background: #f8f9fa;206 border-radius: 8px;207 padding: 0.6rem 0.8rem;208 border: 1px solid #e9ecef;209 transition: all 0.3s ease;210 }211 212 div[data-testid="metric-container"]:hover {213 box-shadow: 0 4px 12px rgba(0,0,0,0.1);214 }215 216 /* ── Dataframes ── */217 .stDataframe {218 border-radius: 8px;219 overflow: hidden;220 border: 1px solid #e0e0e0;221 }222 223 /* ── Messages d'info/erreur/succès ── */224 .stAlert {225 border-radius: 8px;226 border-left: 4px solid;227 }228 229 /* ── Tooltips ── */230 .tooltip {231 position: relative;232 display: inline-block;233 border-bottom: 1px dotted #667eea;234 }235 236 .tooltip .tooltiptext {237 visibility: hidden;238 width: 200px;239 background-color: #2c3e50;240 color: white;241 text-align: center;242 border-radius: 6px;243 padding: 5px;244 position: absolute;245 z-index: 1;246 bottom: 125%;247 left: 50%;248 margin-left: -100px;249 opacity: 0;250 transition: opacity 0.3s;251 font-size: 0.85rem;252 }253 254 .tooltip:hover .tooltiptext {255 visibility: visible;256 opacity: 1;257 }258 259 /* ============================================================260 RESPONSIVE — mobile / écran étroit261 ============================================================ */262 @media (max-width: 1024px) {263 .block-container {264 max-width: 100%;265 padding-left: 1rem;266 padding-right: 1rem;267 }268 }269 270 @media (max-width: 768px) {271 .main-header {272 font-size: 1.8rem;273 white-space: normal;274 }275 276 .stTabs [data-baseweb="tab"] {277 font-size: 0.75rem;278 padding: 6px 10px;279 height: 36px;280 }281 282 .metric-card {283 padding: 0.7rem;284 min-width: 90px;285 }286 287 .metric-card h3 {288 font-size: 1.2rem;289 }290 291 .block-container {292 padding-left: 0.5rem;293 padding-right: 0.5rem;294 }295 }296 297 @media (max-width: 480px) {298 .main-header {299 font-size: 1.4rem;300 }301 302 .stTabs [data-baseweb="tab"] {303 font-size: 0.7rem;304 padding: 5px 8px;305 height: 32px;306 }307 308 .analysis-results {309 padding: 1rem;310 }311 }312 313 /* ── Animation de chargement ── */314 @keyframes pulse {315 0% { opacity: 0.6; }316 50% { opacity: 1; }317 100% { opacity: 0.6; }318 }319 320 .pulse {321 animation: pulse 1.5s infinite;322 }323</style>324""", unsafe_allow_html=True)325 326# ============================================================================327# CNS TARGETS DATABASE328# ============================================================================329 330CNS_TARGETS_DB = {331 'Dopamine System': {332 'Receptors': ['DRD1', 'DRD2', 'DRD3', 'DRD4', 'DRD5'],333 'Transporters': ['SLC6A3'],334 'Enzymes': ['COMT', 'MAOA', 'MAOB', 'DDC']335 },336 'Serotonin System': {337 'Receptors': ['HTR1A', 'HTR1B', 'HTR2A', 'HTR2B', 'HTR2C', 'HTR3A', 'HTR4', 'HTR5A', 'HTR6', 'HTR7'],338 'Transporters': ['SLC6A4'],339 'Enzymes': ['TPH1', 'TPH2']340 },341 'Opioid System': {342 'Receptors': ['OPRM1', 'OPRD1', 'OPRK1'],343 'Related': ['PENK', 'PDYN']344 },345 'GABA System': {346 'Receptors': ['GABRA1', 'GABRA2', 'GABRA3', 'GABRA5', 'GABRB1', 'GABRB2', 'GABRB3', 'GABRD'],347 'Enzymes': ['GAD1', 'GAD2']348 },349 'Glutamate System': {350 'Receptors': ['GRIN1', 'GRIN2A', 'GRIN2B', 'GRIA1', 'GRIA2', 'GRM1', 'GRM5'],351 'Transporters': ['SLC1A1', 'SLC1A2', 'SLC1A3']352 },353 'Cannabinoid System': {354 'Receptors': ['CNR1', 'CNR2'],355 'Enzymes': ['FAAH', 'MGLL']356 },357 'Acetylcholine System': {358 'Receptors': ['CHRM1', 'CHRM2', 'CHRM3', 'CHRM4', 'CHRM5', 'CHRNA4', 'CHRNA7', 'CHRNB2'],359 'Enzymes': ['ACHE', 'BCHE']360 },361 'Norepinephrine System': {362 'Receptors': ['ADRA1A', 'ADRA1B', 'ADRA2A', 'ADRA2B', 'ADRA2C', 'ADRB1', 'ADRB2', 'ADRB3'],363 'Transporters': ['SLC6A2'],364 'Enzymes': ['DBH']365 },366 'Ion Channels': {367 'Sodium': ['SCN1A', 'SCN2A', 'SCN3A', 'SCN8A', 'SCN9A'],368 'Calcium': ['CACNA1C', 'CACNA1D', 'CACNA1G', 'CACNA1H'],369 'Potassium': ['KCNQ2', 'KCNQ3', 'KCNJ2']370 },371 'Other CNS Targets': {372 'Transporters': ['SLC6A1', 'SLC6A5'],373 'Kinases': ['NTRK1', 'NTRK2'],374 'Growth Factors': ['BDNF', 'NGF'],375 'Other': ['SIGMAR1', 'TSPO', 'PDE4D', 'PDE10A']376 }377}378 379 380def check_daily_limit():381 """Limite de 2 prédictions par jour par session"""382 today = datetime.now().date()383 384 # Reset counter if new day385 if 'last_usage_date' not in st.session_state or st.session_state.last_usage_date != today:386 st.session_state.prediction_count = 0387 st.session_state.last_usage_date = today388 389 # Increment counter390 st.session_state.prediction_count += 1391 392 # Check limit393 DAILY_LIMIT = 2394 if st.session_state.prediction_count > DAILY_LIMIT:395 return False, st.session_state.prediction_count, DAILY_LIMIT396 397 return True, st.session_state.prediction_count, DAILY_LIMIT398def get_all_cns_genes():399 """Récupère liste plate de tous les gènes CNS"""400 genes = []401 for system, categories in CNS_TARGETS_DB.items():402 for category, gene_list in categories.items():403 genes.extend(gene_list)404 return sorted(list(set(genes)))405 406def get_gene_info(gene: str):407 """Récupère info d'un gène"""408 for system, categories in CNS_TARGETS_DB.items():409 for category, gene_list in categories.items():410 if gene in gene_list:411 return {'system': system, 'category': category}412 return {'system': 'Unknown', 'category': 'Unknown'}413 414def validate_gene_symbol(gene):415 """Valide qu'un symbole de gène est plausible"""416 import re417 if not gene:418 return False419 pattern = r'^[A-Z][A-Z0-9]+[A-Z]?$'420 return bool(re.match(pattern, gene)) and len(gene) <= 10421 422def validate_smiles(smiles):423 """Validation basique de SMILES"""424 if not smiles or len(smiles) < 3:425 return False426 allowed_chars = set('CcNnOoPpSsFfClBrI*()[]{}0123456789=#@+-\\/.')427 return all(c in allowed_chars for c in smiles)428 429# ============================================================================430# INITIALIZATION - Session State431# ============================================================================432 433def initialize_session_state():434 """Initialize all session state variables"""435 436 if 'initialized' not in st.session_state:437 logger.info("Initializing session state...")438 439 # Core modules440 st.session_state.cache = CacheManager(441 db_path="cache/production_cache.db",442 default_ttl=7200,443 max_size_mb=1000444 )445 st.session_state.enricher = DrugDiscoveryDataEnricher()446 st.session_state.compound_analyzer = CompoundAnalyzer()447 st.session_state.struct_analyzer = StructuralAnalyzer()448 st.session_state.bioact_profiler = BioactivityProfiler()449 st.session_state.target_validator = TargetValidator()450 st.session_state.clinical_intel = ClinicalIntelligence()451 st.session_state.ml_predictor = None452 st.session_state.viz_lab = ProVisualizationLab()453 st.session_state.rdkit_analyzer = RDKitCompoundAnalyzer()454 455 # Data storage456 st.session_state.current_target = None457 st.session_state.enrichment_data = None458 st.session_state.ml_analysis = None459 st.session_state.comparison_data = {}460 st.session_state.comparison_targets = []461 462 # Compound analysis storage463 st.session_state.compound_analysis = {}464 st.session_state.compound_analysis_method = {}465 466 # Settings467 st.session_state.show_advanced = False468 st.session_state.auto_cache = True469 st.session_state.is_loading = False470 471 # Validation scores storage472 if 'validation_scores' not in st.session_state:473 st.session_state.validation_scores = {}474 475 st.session_state.initialized = True476 logger.info("Session state initialized successfully")477 478# ============================================================================479# CACHED FUNCTIONS480# ============================================================================481 482@st.cache_data(ttl=3600, show_spinner=True)483def get_target_enrichment_cached(target):484 """Cache pour l'enrichissement des targets"""485 logger.info(f"Caching enrichment for target: {target}")486 return st.session_state.enricher.enrich_target_comprehensive(target)487 488@st.cache_data(ttl=3600, show_spinner=True)489def get_compound_analysis_cached(smiles):490 """Cache pour l'analyse des composés"""491 logger.info(f"Caching analysis for compound with SMILES: {smiles[:50]}...")492 return st.session_state.compound_analyzer.analyze_compound_comprehensive(smiles, id_type='smiles')493 494 495def fetch_smiles_from_chembl(compound_id):496 """497 Fetches canonical SMILES from ChEMBL REST API.498 Two-step resolution:499 - molecule_chembl_id (e.g. CHEMBL25) → direct hit on /molecule/500 - compound_chembl_id (e.g. CHEMBL159742) → /compound_activity/ returns501 a molecule_chembl_id, then we call /molecule/ with that.502 Returns (smiles_or_None, error_string_or_None) so caller can show real errors.503 """504 import requests505 506 BASE = "https://www.ebi.ac.uk/chembl/api/data"507 HEADERS = {"Accept": "application/json"}508 clean_id = str(compound_id).strip().upper()509 errors = []510 511 def _get_smiles_from_molecule(mol_id):512 """Call /molecule/{mol_id} and extract SMILES. Returns str or None."""513 try:514 resp = requests.get(f"{BASE}/molecule/{mol_id}.json", timeout=15, headers=HEADERS)515 if resp.status_code == 200:516 data = resp.json()517 structures = data.get('molecule_structures') or {}518 return structures.get('canonical_smiles') or structures.get('isomeric_smiles')519 else:520 errors.append(f"/molecule/{mol_id} → HTTP {resp.status_code}")521 except Exception as e:522 errors.append(f"/molecule/{mol_id} → {type(e).__name__}: {e}")523 return None524 525 # ── Step 1: try /molecule/ directly ──526 smiles = _get_smiles_from_molecule(clean_id)527 if smiles:528 return smiles, None529 530 # ── Step 2: try /compound_activity/ → extract molecule_chembl_id → /molecule/ ──531 try:532 resp = requests.get(f"{BASE}/compound_activity/{clean_id}.json", timeout=15, headers=HEADERS)533 if resp.status_code == 200:534 data = resp.json()535 mol_id = data.get('molecule_chembl_id')536 if mol_id:537 smiles = _get_smiles_from_molecule(mol_id)538 if smiles:539 return smiles, None540 errors.append(f"Got molecule_chembl_id={mol_id} but no SMILES from /molecule/")541 else:542 errors.append(f"/compound_activity/{clean_id} returned 200 but no molecule_chembl_id. Keys: {list(data.keys())}")543 else:544 errors.append(f"/compound_activity/{clean_id} → HTTP {resp.status_code}")545 except Exception as e:546 errors.append(f"/compound_activity/{clean_id} → {type(e).__name__}: {e}")547 548 return None, " | ".join(errors) if errors else "Unknown error"549 550 551def run_ml_analysis(data, algorithm, cross_val, calc_importance):552 """Run ML analysis with proper error handling"""553 if not data or st.session_state.ml_predictor is None:554 return None555 556 try:557 smiles = ""558 if data.get('chembl') and len(data['chembl']) > 0:559 smiles = getattr(data['chembl'][0], 'smiles', "")560 561 return st.session_state.ml_predictor.run_full_analysis(562 smiles=smiles if smiles else "CCO",563 algorithm=algorithm,564 do_cv=cross_val,565 do_importance=calc_importance566 )567 except Exception as e:568 logger.error(f"ML analysis error: {str(e)}")569 st.error(f"ML Analysis Error: {str(e)}")570 return None571 572# ============================================================================573# HELPER FUNCTIONS574# ============================================================================575 576def create_excel_workbook(target, data, ml_data, incl_enrich, incl_ml, incl_comp, incl_struct, incl_clin):577 """Create professional Excel workbook with multiple sheets"""578 try:579 import openpyxl # noqa: F401 – ensure engine is available580 except ImportError:581 import subprocess, sys582 subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl", "-q"])583 import openpyxl # noqa: F401584 585 try:586 from io import BytesIO587 import pandas as pd588 589 output = BytesIO()590 591 with pd.ExcelWriter(output, engine='openpyxl') as writer:592 593 # Sheet 1: Summary594 summary_data = {595 'Target Gene': [target],596 'Analysis Date': [datetime.now().strftime('%Y-%m-%d %H:%M:%S')],597 'Data Sources': [len(data.get('data_sources', []))],598 'Active Compounds': [len(data.get('chembl', []))],599 'Clinical Trials': [len(data.get('clinical_trials', []))],600 '3D Structures': [len(data.get('pdb', []))],601 'Druggability Score': [data.get('validation', {}).get('total_score', 0) * 100]602 }603 pd.DataFrame(summary_data).to_excel(writer, sheet_name='Summary', index=False)604 605 # Sheet 2: Enrichment Data606 if incl_enrich:607 enrich_df = create_enrichment_report(data)608 if not enrich_df.empty:609 enrich_df.to_excel(writer, sheet_name='Enrichment', index=False)610 611 # Sheet 3: Bioactivity612 if incl_comp and data.get('chembl'):613 bioact_df = pd.DataFrame([614 {615 'Compound_ID': b.compound_id,616 'Activity_Type': b.activity_type,617 'Value_nM': b.activity_value,618 'Unit': b.activity_unit,619 'Assay': b.assay_description[:100] if b.assay_description else '',620 'Organism': b.target_organism621 }622 for b in data['chembl']623 ])624 bioact_df.to_excel(writer, sheet_name='Bioactivity', index=False)625 626 # Sheet 4: ML Predictions627 if incl_ml and ml_data:628 ml_summary = {629 'Algorithm': [ml_data.get('algorithm', 'Unknown')],630 'Druggability_Score': [ml_data.get('predictions', {}).get('druggability', {}).get('druggability_score', 0) * 100],631 'BBB_Permeant': [ml_data.get('predictions', {}).get('bbb_permeability', {}).get('bbb_permeant', False)],632 'Model_Accuracy': [ml_data.get('model_performance', {}).get('accuracy', {}).get('mean', 0)],633 'AUC_ROC': [ml_data.get('model_performance', {}).get('auc_roc', {}).get('mean', 0)]634 }635 pd.DataFrame(ml_summary).to_excel(writer, sheet_name='ML_Predictions', index=False)636 637 # Sheet 5: Clinical Trials638 if incl_clin and data.get('clinical_trials'):639 trials_df = pd.DataFrame([640 {641 'NCT_ID': t['nct_id'],642 'Title': t['title'][:100] if t['title'] else '',643 'Status': t['status'],644 'Phase': t['phase'],645 'Start_Date': t['start_date'],646 'Conditions': ', '.join(t.get('conditions', [])[:3]) if t.get('conditions') else ''647 }648 for t in data['clinical_trials']649 ])650 trials_df.to_excel(writer, sheet_name='Clinical_Trials', index=False)651 652 # Sheet 6: Expression Data653 if data.get('gtex'):654 expr_df = pd.DataFrame([655 {656 'Tissue': e.tissue,657 'TPM': e.expression_level,658 'Percentile': e.percentile659 }660 for e in data['gtex']661 ])662 expr_df.to_excel(writer, sheet_name='Expression', index=False)663 664 output.seek(0)665 logger.info(f"Excel workbook created for target: {target}")666 return output667 668 except Exception as e:669 logger.error(f"Error creating Excel: {str(e)}")670 st.error(f"Error creating Excel: {str(e)}")671 return None672 673def display_error_with_details(error_msg, error):674 """Affiche une erreur avec des détails de débogage"""675 st.error(f"❌ {error_msg}")676 677 with st.expander("🔍 Technical Details (for debugging)"):678 st.code(traceback.format_exc())679 680 st.markdown("**Troubleshooting tips:**")681 st.markdown("""682 1. Check your internet connection683 2. Verify the input format (gene symbol or SMILES)684 3. Try a different target/compound685 4. Clear cache in sidebar686 5. Restart the application687 """)688 689# ============================================================================690# SIDEBAR - Controls et Settings691# ============================================================================692 693def create_sidebar():694 """Create sidebar with controls and settings"""695 696 with st.sidebar:697 st.markdown("# ⚙️ Controls")698 699 # Logo/Brand700 st.markdown("""701 <div style='text-align: center; padding: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; margin-bottom: 1rem;'>702 <h2 style='color: white; margin: 0;'>💊 Drug Discovery AI</h2>703 <p style='color: white; margin: 0; font-size: 0.9rem;'>v2.1 Professional</p>704 </div>705 """, unsafe_allow_html=True)706 707 st.markdown("---")708 709 # Target Selection avec autocomplete710 st.markdown("### 🎯 Target Selection")711 712 all_cns_genes = get_all_cns_genes()713 714 # Search mode715 search_mode = st.radio(716 "Search Mode",717 ["Select from CNS Database", "Manual Entry"],718 label_visibility="collapsed"719 )720 721 if search_mode == "Select from CNS Database":722 # Filtre par système723 selected_system = st.selectbox(724 "Filter by System",725 ["All Systems"] + list(CNS_TARGETS_DB.keys())726 )727 728 if selected_system == "All Systems":729 available_genes = all_cns_genes730 else:731 available_genes = []732 for category, genes in CNS_TARGETS_DB[selected_system].items():733 available_genes.extend(genes)734 available_genes = sorted(list(set(available_genes)))735 736 target_gene = st.selectbox(737 "Select Target Gene",738 available_genes,739 index=available_genes.index('DRD2') if 'DRD2' in available_genes else 0,740 help=f"{len(available_genes)} genes available"741 )742 else:743 target_gene = st.text_input(744 "Enter Gene Symbol",745 value="DRD2",746 help="Enter any gene symbol (e.g., DRD2, TP53, EGFR)"747 ).upper()748 749 # Validation du gène750 if target_gene and not validate_gene_symbol(target_gene):751 st.warning("⚠️ Gene symbol format may be invalid")752 753 # Afficher info du gène754 if target_gene in all_cns_genes:755 gene_info = get_gene_info(target_gene)756 st.info(f"**System:** {gene_info['system']}\n\n**Type:** {gene_info['category']}")757 758 st.markdown("---")759 760 # Analysis Settings761 st.markdown("### 🔧 Analysis Settings")762 763 confidence_threshold = st.slider(764 "Network Confidence",765 min_value=0.0,766 max_value=1.0,767 value=0.7,768 step=0.05,769 help="Minimum confidence for protein interactions"770 )771 772 network_depth = st.select_slider(773 "Network Depth",774 options=["Shallow", "Medium", "Deep"],775 value="Medium"776 )777 778 with st.expander("🔬 Advanced ML Settings"):779 ml_algorithm = st.selectbox(780 "ML Algorithm",781 ["Random Forest", "XGBoost", "Neural Network", "Ensemble", "Stacking"]782 )783 784 cross_validation = st.checkbox("Enable Cross-Validation", value=True)785 feature_importance = st.checkbox("Calculate Feature Importance", value=True)786 787 st.markdown("---")788 789 # Actions790 st.markdown("### 🚀 Actions")791 792 analyze_button = st.button(793 "🔬 Analyze Target",794 type="primary",795 use_container_width=True,796 help="Run complete drug discovery analysis"797 )798 799 # Bouton de comparaison rapide800 if st.button("➕ Add to Comparison", use_container_width=True, key="add_to_compare"):801 if target_gene and target_gene not in st.session_state.comparison_targets:802 st.session_state.comparison_targets.append(target_gene)803 st.success(f"Added {target_gene} to comparison list")804 805 if st.button("🗑️ Clear Cache", use_container_width=True):806 st.session_state.cache.clear_all()807 st.success("Cache cleared!")808 st.rerun()809 810 # Cache Stats811 with st.expander("💾 Cache Statistics"):812 stats = st.session_state.cache.get_stats()813 col1, col2 = st.columns(2)814 with col1:815 st.metric("Entries", stats['total_entries'])816 st.metric("Size (MB)", f"{stats['total_size_mb']:.2f}")817 with col2:818 st.metric("Usage", f"{stats['usage_percent']:.1f}%")819 st.metric("Hits", stats.get('hit_rate', 'N/A'))820 821 if st.button("Cleanup Expired", key="cleanup_cache"):822 cleaned = st.session_state.cache.cleanup_expired()823 st.success(f"Cleaned {cleaned} entries")824 st.rerun()825 826 st.markdown("---")827 828 # Session info829 col1, col2 = st.columns(2)830 with col1:831 st.caption(f"Session: {datetime.now().strftime('%H:%M:%S')}")832 with col2:833 if st.button("🔄 Refresh", key="refresh_session"):834 st.rerun()835 st.markdown("### 👨💻 Developer Info")836 st.markdown("**Name:** Ahmed Miloudi")837 st.markdown("**First Email:** ahmed.miloudi@usmba.ac.ma")838 st.markdown("**Second Email:** a.miloudij@gmail.com")839 st.markdown("**Version:** 2.1 Professional")840 st.markdown("---")841 st.caption("© 2026 Drug Discovery AI Platform")842 st.caption("© 2026 Drug Discovery AI Platform")843 844 return target_gene, analyze_button, ml_algorithm, cross_validation, feature_importance845 846# ============================================================================847# MAIN HEADER848# ============================================================================849 850def create_header():851 """Create main header with metrics"""852 853 col1, col2, col3, col4 = st.columns([3, 1, 1, 1])854 855 with col1:856 st.markdown('<h1 class="main-header">🧬 Drug Discovery AI Platform</h1>', unsafe_allow_html=True)857 st.caption("Professional computational drug discovery with ML/AI")858 st.markdown("""859 <div style="margin-top: 4px;">860 <span style="font-size: 0.85rem; color: #444; font-weight: 600;">👨🔬 Ahmed Miloudi</span>861 <span style="font-size: 0.8rem; color: #888; margin-left: 10px;">✉️ ahmed.miloudi@usbma.ac.ma</span>862 </div>863 """, unsafe_allow_html=True)864 865 with col2:866 target = st.session_state.current_target or "Not selected"867 if target in get_all_cns_genes():868 gene_info = get_gene_info(target)869 st.metric("Target", target, delta=gene_info['system'])870 else:871 st.metric("Target", target, delta="Custom")872 873 with col3:874 cache_stats = st.session_state.cache.get_stats()875 st.metric("Cache", f"{cache_stats['total_entries']}", delta=f"{cache_stats['total_size_mb']:.0f}MB")876 877 with col4:878 status = "Loading..." if st.session_state.is_loading else "Ready"879 delta = "Active" if st.session_state.is_loading else "Online"880 st.metric("Status", status, delta=delta)881 882 st.markdown("---")883 884# ============================================================================885# ANALYSIS TRIGGER886# ============================================================================887 888 889# Embedded OpenTargets CSV fallback — keyed by gene symbol890# Format matches the user-provided CSV: datatypeId, datasourceId, diseaseId, targetId, score, evidenceCount, disease_name, disease_category, uniprot_id891_OPENTARGETS_CSV_ROWS = [892 # VHL – von Hippel–Lindau (renal carcinoma)893 {"datatypeId":"affected_pathway","datasourceId":"cancer_biomarkers","diseaseId":"EFO_0002890","targetId":"ENSG00000100644","score":0.608,"evidenceCount":1,"disease_name":"renal carcinoma","disease_category":"Oncology","uniprot_id":"Q16665","gene_symbol":"VHL"},894 # BRAF – melanoma895 {"datatypeId":"affected_pathway","datasourceId":"cancer_biomarkers","diseaseId":"EFO_0000622","targetId":"ENSG00000157764","score":0.89,"evidenceCount":5,"disease_name":"melanoma","disease_category":"Oncology","uniprot_id":"P15056","gene_symbol":"BRAF"},896 {"datatypeId":"mutation","datasourceId":"eva","diseaseId":"EFO_0000622","targetId":"ENSG00000157764","score":0.72,"evidenceCount":12,"disease_name":"melanoma","disease_category":"Oncology","uniprot_id":"P15056","gene_symbol":"BRAF"},897 # EGFR – lung cancer898 {"datatypeId":"affected_pathway","datasourceId":"cancer_biomarkers","diseaseId":"EFO_0000405","targetId":"ENSG00000146648","score":0.91,"evidenceCount":8,"disease_name":"lung adenocarcinoma","disease_category":"Oncology","uniprot_id":"P00533","gene_symbol":"EGFR"},899 {"datatypeId":"mutation","datasourceId":"eva","diseaseId":"EFO_0000405","targetId":"ENSG00000146648","score":0.78,"evidenceCount":15,"disease_name":"lung adenocarcinoma","disease_category":"Oncology","uniprot_id":"P00533","gene_symbol":"EGFR"},900 # DRD2 – schizophrenia / psychotic disorders901 {"datatypeId":"affected_pathway","datasourceId":"reactome","diseaseId":"EFO_0000053","targetId":"ENSG00000115865","score":0.82,"evidenceCount":6,"disease_name":"schizophrenia","disease_category":"Psychiatry","uniprot_id":"P29001","gene_symbol":"DRD2"},902 {"datatypeId":"literature_support","datasourceId":"gwas_catalog","diseaseId":"EFO_0000053","targetId":"ENSG00000115865","score":0.65,"evidenceCount":22,"disease_name":"schizophrenia","disease_category":"Psychiatry","uniprot_id":"P29001","gene_symbol":"DRD2"},903 # GRIN2A – epilepsy / neurodevelopmental904 {"datatypeId":"affected_pathway","datasourceId":"reactome","diseaseId":"EFO_0005249","targetId":"ENSG00000186230","score":0.74,"evidenceCount":4,"disease_name":"epilepsy","disease_category":"Neurology","uniprot_id":"Q12879","gene_symbol":"GRIN2A"},905 # HTR2A – depression / psychosis906 {"datatypeId":"literature_support","datasourceId":"gwas_catalog","diseaseId":"EFO_0000364","targetId":"ENSG00000102969","score":0.58,"evidenceCount":9,"disease_name":"major depressive disorder","disease_category":"Psychiatry","uniprot_id":"P28223","gene_symbol":"HTR2A"},907 {"datatypeId":"affected_pathway","datasourceId":"reactome","diseaseId":"EFO_0000364","targetId":"ENSG00000102969","score":0.61,"evidenceCount":5,"disease_name":"major depressive disorder","disease_category":"Psychiatry","uniprot_id":"P28223","gene_symbol":"HTR2A"},908 # CACNA1C – bipolar disorder909 {"datatypeId":"literature_support","datasourceId":"gwas_catalog","diseaseId":"EFO_0000408","targetId":"ENSG00000151083","score":0.71,"evidenceCount":18,"disease_name":"bipolar disorder","disease_category":"Psychiatry","uniprot_id":"Q13936","gene_symbol":"CACNA1C"},910 # SLC6A3 / DAT – ADHD911 {"datatypeId":"literature_support","datasourceId":"gwas_catalog","diseaseId":"EFO_0000364","targetId":"ENSG00000141535","score":0.55,"evidenceCount":7,"disease_name":"attention deficit hyperactivity disorder","disease_category":"Psychiatry","uniprot_id":"Q01659","gene_symbol":"SLC6A3"},912 # COMT – psychosis / schizophrenia913 {"datatypeId":"literature_support","datasourceId":"gwas_catalog","diseaseId":"EFO_0000053","targetId":"ENSG00000116044","score":0.62,"evidenceCount":11,"disease_name":"schizophrenia","disease_category":"Psychiatry","uniprot_id":"P09564","gene_symbol":"COMT"},914 # TP53 – various cancers915 {"datatypeId":"affected_pathway","datasourceId":"cancer_biomarkers","diseaseId":"EFO_0000404","targetId":"ENSG00000141510","score":0.94,"evidenceCount":20,"disease_name":"colorectal cancer","disease_category":"Oncology","uniprot_id":"P04637","gene_symbol":"TP53"},916 # BRCA1 – breast cancer917 {"datatypeId":"affected_pathway","datasourceId":"cancer_biomarkers","diseaseId":"EFO_0000311","targetId":"ENSG00000112715","score":0.87,"evidenceCount":14,"disease_name":"breast cancer","disease_category":"Oncology","uniprot_id":"P38398","gene_symbol":"BRCA1"},918]919 920# Build lookup: gene_symbol -> list of dicts921_OT_BY_GENE = {}922for _r in _OPENTARGETS_CSV_ROWS:923 _OT_BY_GENE.setdefault(_r["gene_symbol"], []).append(_r)924 925def get_opentargets_fallback(target_gene: str):926 """Return embedded OpenTargets records for a gene, or empty list."""927 # try exact match, then case-insensitive928 gene_upper = target_gene.strip().upper()929 for sym, rows in _OT_BY_GENE.items():930 if sym.upper() == gene_upper:931 return rows932 return []933 934 935def run_target_analysis(target_gene, ml_algorithm, cross_validation, feature_importance):936 """Run comprehensive analysis for a target"""937 938 st.session_state.current_target = target_gene939 st.session_state.is_loading = True940 941 try:942 with st.spinner(f"🔄 Analyzing {target_gene}... This may take 30-60 seconds..."):943 progress_bar = st.progress(0)944 status_text = st.empty()945 946 # 1. Enrichissement (40%)947 status_text.text("📡 Fetching data from 9 APIs...")948 enrichment_data = get_target_enrichment_cached(target_gene)949 st.session_state.enrichment_data = enrichment_data950 951 # --- OpenTargets CSV fallback: if API returned nothing, load from embedded data ---952 if enrichment_data and (not enrichment_data.get('opentargets') or len(enrichment_data.get('opentargets', [])) == 0):953 ot_fallback = get_opentargets_fallback(target_gene)954 if ot_fallback:955 enrichment_data['opentargets'] = ot_fallback956 logger.info(f"OpenTargets: loaded {len(ot_fallback)} records from CSV fallback for {target_gene}")957 958 # --- Pathway stub: derive from opentargets associations ---959 if enrichment_data and enrichment_data.get('opentargets'):960 pathways = {}961 for rec in enrichment_data['opentargets']:962 if isinstance(rec, dict):963 dt = rec.get('datatype_id') or rec.get('datatypeId', '')964 ds = rec.get('datasource_id') or rec.get('datasourceId', '')965 cat = rec.get('disease_category', 'Unknown')966 else:967 dt = str(rec) if rec else 'unknown'968 ds = 'unknown'969 cat = 'Unknown'970 # dt = rec.get('datatype_id') or rec.get('datatypeId', '')971 #ds = rec.get('datasource_id') or rec.get('datasourceId', '')972 #cat = rec.get('disease_category', 'Unknown')973 key = f"{cat} / {dt}"974 pathways.setdefault(key, []).append(rec)975 enrichment_data['pathways'] = {976 'sources': list(pathways.keys()),977 'details': pathways,978 'count': len(enrichment_data['opentargets'])979 }980 981 progress_bar.progress(40)982 983 # 2. Validation (60%)984 status_text.text("🎯 Validating target...")985 if enrichment_data:986 validation = st.session_state.target_validator.calculate_validation_score(enrichment_data)987 enrichment_data['validation'] = validation988 st.session_state.validation_scores[target_gene] = validation989 progress_bar.progress(60)990 991 # 3. ML Analysis (80%)992 status_text.text("🤖 Running ML predictions...")993 if 'ml_predictor' not in st.session_state or st.session_state.ml_predictor is None:994 st.session_state.ml_predictor = MLPredictor(use_pretrained=True)995 996 ml_analysis = run_ml_analysis(enrichment_data, ml_algorithm, cross_validation, feature_importance)997 st.session_state.ml_analysis = ml_analysis998 progress_bar.progress(80)999 1000 # 4. Finalize (100%)1001 status_text.text("✅ Analysis complete!")1002 progress_bar.progress(100)1003 1004 st.success(f"✅ Complete analysis for {target_gene} finished!")1005 st.balloons()1006 1007 logger.info(f"Analysis completed for target: {target_gene}")1008 1009 except Exception as e:1010 display_error_with_details(f"Analysis failed for {target_gene}", e)1011 finally:1012 st.session_state.is_loading = False1013 1014# ============================================================================1015# TAB 1: DASHBOARD1016# ============================================================================1017 1018def create_dashboard_tab():1019 """Create dashboard tab"""1020 1021 st.markdown("## 🏠 Analysis Dashboard")1022 1023 if st.session_state.enrichment_data is None:1024 st.info("👈 Select a target and click **Analyze Target** to begin")1025 1026 # Quick stats1027 col1, col2, col3, col4 = st.columns(4)1028 with col1:1029 st.markdown('<div class="metric-card"><h3>150+</h3><p>CNS Targets</p></div>', unsafe_allow_html=True)1030 with col2:1031 st.markdown('<div class="metric-card"><h3>9</h3><p>Data Sources</p></div>', unsafe_allow_html=True)1032 with col3:1033 st.markdown('<div class="metric-card"><h3>5</h3><p>ML Algorithms</p></div>', unsafe_allow_html=True)1034 with col4:1035 st.markdown('<div class="metric-card"><h3>24/7</h3><p>Availability</p></div>', unsafe_allow_html=True)1036 1037 st.markdown("---")1038 1039 # Feature highlights1040 col1, col2 = st.columns(2)1041 1042 with col1:1043 st.markdown("### 🎯 Key Features")1044 st.markdown("""1045 - ✅ **Multi-Source Data Enrichment** - 9 APIs integrated1046 - ✅ **Advanced ML/AI** - XGBoost, Random Forest, Neural Nets1047 - ✅ **Interactive Data Science** - Full pandas manipulation1048 - ✅ **3D Structure Analysis** - PDB + AlphaFold1049 - ✅ **Bioactivity Profiling** - ChEMBL integration1050 - ✅ **Clinical Intelligence** - Real-time trials data1051 - ✅ **Professional Reports** - PDF/Excel export1052 - ✅ **Mobile Optimized** - Works on any device1053 """)1054 1055 with col2:1056 st.markdown("### 📚 Quick Start Guide")1057 st.markdown("""1058 **1. Select Target**1059 - Choose from 150+ CNS genes1060 - Or enter custom gene symbol1061 1062 **2. Configure Analysis**1063 - Set network parameters1064 - Choose ML algorithms1065 - Enable advanced features1066 1067 **3. Run Analysis**1068 - Click "Analyze Target"1069 - Wait 30-60 seconds1070 - Explore results!1071 1072 **4. Export Results**1073 - Generate PDF reports1074 - Download Excel data1075 - Share with team1076 """)1077 1078 else:1079 # DASHBOARD AVEC DONNÉES1080 data = st.session_state.enrichment_data1081 1082 # KPIs Row1083 st.markdown("### 📊 Key Metrics")1084 col1, col2, col3, col4, col5 = st.columns(5)1085 1086 with col1:1087 drugg_score = data.get('validation', {}).get('total_score', 0) * 1001088 st.metric(1089 "Druggability Score",1090 f"{drugg_score:.0f}/100",1091 delta="Excellent" if drugg_score > 80 else "Good" if drugg_score > 60 else "Fair"1092 )1093 1094 with col2:1095 sources = len(data.get('data_sources', []))1096 st.metric("Data Sources", sources, delta=f"{sources}/9")1097 1098 with col3:1099 bioact_count = len(data.get('chembl', []))1100 st.metric("Active Compounds", bioact_count)1101 1102 with col4:1103 trials_count = len(data.get('clinical_trials', []))1104 st.metric("Clinical Trials", trials_count)1105 1106 with col5:1107 structures = len(data.get('pdb', []))1108 st.metric("3D Structures", structures + (1 if data.get('alphafold') else 0))1109 1110 st.markdown("---")1111 1112 # Charts Row1113 col1, col2 = st.columns(2)1114 1115 with col1:1116 # Data sources availability1117 sources_data = []1118 all_sources = ['uniprot', 'pdb', 'alphafold', 'chembl', 'gtex', 'opentargets', 'clinical_trials', 'hpa']1119 for source in all_sources:1120 available = bool(data.get(source))1121 sources_data.append({1122 'Source': source.upper(),1123 'Status': 'Available' if available else 'Not Found',1124 'Value': 1 if available else 01125 })1126 1127 df_sources = pd.DataFrame(sources_data)1128 1129 fig = px.bar(1130 df_sources,1131 x='Source',1132 y='Value',1133 color='Status',1134 title='Data Source Availability',1135 color_discrete_map={'Available': '#00cc00', 'Not Found': '#ff0000'}1136 )1137 fig.update_layout(showlegend=True, height=350)1138 st.plotly_chart(fig, use_container_width=True)1139 1140 with col2:1141 # Druggability breakdown1142 if 'validation' in data:1143 val = data['validation']1144 components = val.get('components', {})1145 1146 comp_df = pd.DataFrame([1147 {'Component': k.replace('_', ' ').title(), 'Score': v * 100}1148 for k, v in components.items()1149 ])1150 1151 fig = px.bar(1152 comp_df,1153 x='Score',1154 y='Component',1155 orientation='h',1156 title='Druggability Score Components',1157 color='Score',1158 color_continuous_scale='RdYlGn'1159 )1160 fig.update_layout(height=350)1161 st.plotly_chart(fig, use_container_width=True)1162 1163 st.markdown("---")1164 1165 # Summary Report1166 st.markdown("### 📋 Executive Summary")1167 1168 report_df = create_enrichment_report(data)1169 if not report_df.empty:1170 st.dataframe(report_df, use_container_width=True, hide_index=True)1171 1172# ============================================================================1173# TAB 8: MULTI-TARGET COMPARISON1174# ============================================================================1175 1176def create_multi_target_comparison_tab():1177 """Create multi-target comparison tab"""1178 1179 st.markdown("## 🔄 Multi-Target Comparison")1180 1181 # Interface pour ajouter des targets1182 col1, col2 = st.columns([3, 1])1183 1184 with col1:1185 new_target = st.text_input("Add target to compare", value="DRD2").upper()1186 1187 with col2:1188 if st.button("➕ Add", key="add_target"):1189 if new_target and new_target not in st.session_state.comparison_targets:1190 st.session_state.comparison_targets.append(new_target)1191 st.rerun()1192 1193 # Afficher les targets en cours de comparaison1194 if st.session_state.comparison_targets:1195 st.markdown("### Targets to compare:")1196 1197 # Créer des colonnes pour chaque target1198 cols = st.columns(len(st.session_state.comparison_targets))1199 1200 comparison_data = []