import gradio as gr import json # ── helpers ────────────────────────────────────────────────────────────────── def chip(text: str, color: str = "#94a3b8") -> str: return ( f'{text}' ) def badge(label: str, bg: str, color: str) -> str: return ( f'{label}' ) def acmg_badge(acmg_class: str) -> str: mapping = { "Pathogenic": ("rgba(239,68,68,0.35)", "#fca5a5"), "Likely Pathogenic":("rgba(249,115,22,0.35)", "#fdba74"), "VUS": ("rgba(234,179,8,0.35)", "#fde047"), "Likely Benign": ("rgba(34,197,94,0.35)", "#86efac"), "Benign": ("rgba(16,185,129,0.35)", "#6ee7b7"), } bg, color = mapping.get(acmg_class, ("rgba(100,100,120,0.35)", "#cbd5e1")) return badge(acmg_class, bg, color) def card_bg(acmg_class: str) -> str: mapping = { "Pathogenic": "rgba(239,68,68,0.12)", "Likely Pathogenic": "rgba(249,115,22,0.12)", "VUS": "rgba(234,179,8,0.10)", "Likely Benign": "rgba(34,197,94,0.10)", "Benign": "rgba(16,185,129,0.10)", } return mapping.get(acmg_class, "rgba(30,30,50,0.50)") def border_color(acmg_class: str) -> str: mapping = { "Pathogenic": "rgba(239,68,68,0.55)", "Likely Pathogenic": "rgba(249,115,22,0.55)", "VUS": "rgba(234,179,8,0.55)", "Likely Benign": "rgba(34,197,94,0.55)", "Benign": "rgba(16,185,129,0.55)", } return mapping.get(acmg_class, "rgba(148,163,184,0.30)") def render_variant_card( patient_name: str, gene: str, variant_notation: str, chromosome: str, position: str, zygosity: str, acmg_class: str, criteria_met: list, population_af: str, clinical_significance: str, recommended_action: str, pathogenicity_reasoning: str = "", population_assessment: str = "", ) -> str: criteria_chips = "".join(chip(c, "#a5f3fc") for c in criteria_met) bg = card_bg(acmg_class) border = border_color(acmg_class) pat_section = ( f'
' f'Patient: {patient_name}
' if patient_name else "" ) reasoning_section = ( f'
' f'Pathogenicity Reasoning: {pathogenicity_reasoning}
' if pathogenicity_reasoning else "" ) pop_section = ( f'
' f'Population Assessment: {population_assessment}
' if population_assessment else "" ) return f"""
{pat_section}
{gene} {variant_notation}
{acmg_badge(acmg_class)}
Chromosome: {chromosome}
Position: {position}
Zygosity: {zygosity}
Population AF: {population_af}
ACMG Criteria Met
{criteria_chips}
Clinical Significance
{clinical_significance}
Recommended Action
{recommended_action}
{reasoning_section} {pop_section}
""" # ── pre-computed demo cases ─────────────────────────────────────────────────── DEMO_CASES = [ dict( patient_name="", gene="BRCA1", variant_notation="c.5266dupC (p.Gln1756ProfsTer74)", chromosome="chr17", position="41,245,466", zygosity="Heterozygous", acmg_class="Pathogenic", criteria_met=["PVS1", "PS3", "PM2", "PP3"], population_af="0.0001 (gnomAD)", clinical_significance=( "Frameshift causing premature stop codon at position 1756. " "Pathogenic for Hereditary Breast and Ovarian Cancer (HBOC) syndrome. " "Loss of BRCA1 function disrupts homologous recombination DNA repair." ), recommended_action=( "Refer to certified genetic counselor. Discuss prophylactic risk-reduction " "surgery options (bilateral mastectomy / salpingo-oophorectomy). " "Cascade testing of first-degree relatives strongly recommended." ), ), dict( patient_name="", gene="CFTR", variant_notation="c.1521_1523delCTT (p.Phe508del)", chromosome="chr7", position="117,548,628", zygosity="Homozygous", acmg_class="Pathogenic", criteria_met=["PVS1", "PS1", "PM3", "PP5"], population_af="0.0139 (carrier frequency)", clinical_significance=( "Most common Cystic Fibrosis-causing variant (≈70% of CF alleles). " "Homozygous state is consistent with classic Cystic Fibrosis. " "Causes misfolding and premature degradation of CFTR protein." ), recommended_action=( "Diagnose Cystic Fibrosis. Refer to accredited CF Center. " "Initiate evaluation for CFTR modulator therapy (e.g., Elexacaftor/Tezacaftor/Ivacaftor). " "Baseline pulmonary function tests, sweat chloride, and pancreatic assessment." ), ), dict( patient_name="", gene="ATM", variant_notation="c.7271T>G (p.Val2424Gly)", chromosome="chr11", position="108,123,551", zygosity="Heterozygous", acmg_class="VUS", criteria_met=["PM1", "PM2", "PP3"], population_af="0.000089", clinical_significance=( "Missense variant located in the kinase domain of ATM. " "Functional studies remain inconclusive. PM1 supported by critical domain location; " "however, evidence is insufficient for pathogenic or benign classification at this time." ), recommended_action=( "Variant of Uncertain Significance — cannot be used for independent clinical decision-making. " "Recommend periodic reclassification as new functional data and population studies emerge. " "Consider segregation studies in affected family members." ), ), dict( patient_name="", gene="MLH1", variant_notation="c.116+5G>A", chromosome="chr3", position="37,034,801", zygosity="Heterozygous", acmg_class="Likely Benign", criteria_met=["BP4", "BP7", "BA1 (partial)"], population_af="0.0042", clinical_significance=( "Intronic splice-region variant. Multiple in silico computational tools " "(SpliceSiteFinder, MaxEntScan, NNSPLICE) predict no significant impact on splicing. " "Observed at appreciable frequency in the general population." ), recommended_action=( "Likely benign variant. No clinical action required based on this variant alone. " "Document in medical record. Re-evaluate if new evidence emerges or in context of " "strong family history of Lynch Syndrome." ), ), ] def build_demo_html() -> str: header = """
Pre-Computed Analysis · No API Key Required
Four representative genomic variants illustrating the full ACMG classification spectrum.
""" cards = "".join(render_variant_card(**c) for c in DEMO_CASES) return header + cards # ── GPT-4o-mini classification ──────────────────────────────────────────────── SYSTEM_PROMPT = """You are an expert clinical genomics scientist specializing in ACMG/AMP variant classification guidelines (2015 and 2019 ClinGen updates). Given a genomic variant and clinical context, perform a structured classification and return ONLY valid JSON with these keys: - acmg_class: one of "Pathogenic", "Likely Pathogenic", "VUS", "Likely Benign", "Benign" - criteria_met: array of ACMG criteria strings (e.g. ["PVS1","PM2","PP3"]) - clinical_significance: 2-4 sentence clinical interpretation - recommended_action: 2-4 sentence clinical recommendation - population_assessment: 1-2 sentence assessment of population frequency significance - pathogenicity_reasoning: 2-3 sentence detailed molecular reasoning Be precise, evidence-based, and medically accurate.""" def classify_variant( patient_name, gene, chromosome, position, ref_allele, alt_allele, zygosity, population_af, indication, panel_type, api_key ): if not api_key or not api_key.strip(): return "
Please enter your OpenAI API key in the field above.
" if not gene or not gene.strip(): return "
Please enter a gene name (e.g. BRCA1, TP53, CFTR).
" user_message = f"""Classify the following genomic variant: Gene: {gene.strip()} Chromosome: {chromosome} Position: {int(position) if position else 'Unknown'} Reference Allele: {ref_allele or 'N/A'} Alt Allele: {alt_allele or 'N/A'} Zygosity: {zygosity} Population AF: {population_af:.6f} Clinical Indication / Phenotype: {indication or 'Not specified'} Genetic Panel: {panel_type or 'Not specified'} Apply ACMG/AMP 2015 classification criteria. Return JSON only.""" try: from openai import OpenAI client = OpenAI(api_key=api_key.strip()) response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_message}, ], temperature=0.1, response_format={"type": "json_object"}, ) raw = response.choices[0].message.content data = json.loads(raw) except Exception as e: err = str(e) return ( f"
" f"Error: {err}
" ) variant_notation = f"{ref_allele or '?'}>{alt_allele or '?'}" return render_variant_card( patient_name=patient_name or "", gene=gene.strip(), variant_notation=variant_notation, chromosome=chromosome, position=str(int(position)) if position else "N/A", zygosity=zygosity, acmg_class=data.get("acmg_class", "VUS"), criteria_met=data.get("criteria_met", []), population_af=f"{population_af:.6f}", clinical_significance=data.get("clinical_significance", ""), recommended_action=data.get("recommended_action", ""), pathogenicity_reasoning=data.get("pathogenicity_reasoning", ""), population_assessment=data.get("population_assessment", ""), ) # ── How It Works content ────────────────────────────────────────────────────── HOW_IT_WORKS_HTML = """
🔗
n8n Automation Workflow
https://aravind5.app.n8n.cloud/workflow/PLACEHOLDER_GENOMIC
n8n Workflow Architecture
Step 1
Webhook Trigger
Receives variant data payload (gene, position, zygosity, clinical indication) from external LIMS or EHR system via HTTP POST.
Step 2
Database Enrichment
Queries ClinVar, gnomAD, and ClinGen APIs for prior classification, population frequency data, and functional evidence annotations.
Step 3
GPT-4o-mini ACMG Classifier
Sends enriched variant context to GPT-4o-mini with structured ACMG/AMP prompt. Returns JSON classification with criteria evidence codes and clinical narrative.
Step 4
Report Generation & Delivery
Generates structured clinical report, logs to Google Sheets for audit trail, and routes high-severity Pathogenic findings to clinical team via email/Slack alert.
ACMG/AMP Classification Criteria Reference
Code Strength Direction Description
PVS1 Very Strong Pathogenic Null variant (frameshift, nonsense, splice ±1/2) in gene where LOF is disease mechanism
PS1–PS4 Strong Pathogenic Same AA change as established pathogenic; functional studies; de novo; prevalence increase in affected
PM1–PM6 Moderate Pathogenic Critical domain; absent in population; cosegregation; in trans with pathogenic; de novo (unconfirmed)
PP1–PP5 Supporting Pathogenic Cosegregation; functional evidence; reputable source (ClinVar); in trans with pathogenic; in cis pathogenic
BA1 Stand-Alone Benign Allele frequency >5% in population databases (gnomAD, 1000G, ESP)
BS1–BS4 Strong Benign Allele frequency greater than expected; benign functional studies; nonsegregation; in trans VUS
BP1–BP7 Supporting Benign Missense in gene with only truncating pathogenic; silent with no splice impact; in trans pathogenic; multiple lines benign computational
CLINICAL DISCLAIMER
This tool is intended for research and educational purposes only. Classifications generated by AI should not be used as the sole basis for clinical decision-making. All variant classifications must be reviewed and confirmed by a board-certified clinical geneticist or molecular pathologist before clinical application.
""" # ── Gradio UI ───────────────────────────────────────────────────────────────── CUSTOM_CSS = """ body, .gradio-container { background: #0f0f1a !important; font-family: 'Inter', sans-serif !important; } .gr-panel, .panel, .block { background: transparent !important; border: none !important; } .gr-box { background: rgba(30,30,50,0.5) !important; border: 1px solid rgba(148,163,184,0.2) !important; border-radius: 10px !important; } label, .gr-input-label { color: #94a3b8 !important; font-size: 0.82rem !important; font-weight: 600 !important; } input, textarea, select { background: rgba(15,15,30,0.8) !important; color: #e2e8f0 !important; border: 1px solid rgba(148,163,184,0.25) !important; border-radius: 8px !important; } button.primary { background: linear-gradient(135deg, rgba(99,102,241,0.8), rgba(139,92,246,0.8)) !important; color: #f1f5f9 !important; border: 1px solid rgba(139,92,246,0.5) !important; border-radius: 10px !important; font-weight: 700 !important; } .tab-nav button { color: #94a3b8 !important; background: transparent !important; } .tab-nav button.selected { color: #a5f3fc !important; border-bottom: 2px solid #a5f3fc !important; } """ HEADER_HTML = """
Genomic Variant Clinical Significance Classifier
ACMG/AMP 2015 guideline-based variant classification powered by GPT-4o-mini
""" with gr.Blocks(theme=gr.themes.Soft(), css=CUSTOM_CSS, title="Genomic Variant Classifier") as demo: gr.HTML(HEADER_HTML) with gr.Tabs(): # ── TAB 1: Live Demo ───────────────────────────────────────────────── with gr.Tab("Live Demo"): gr.HTML(build_demo_html()) # ── TAB 2: Classify Variant ────────────────────────────────────────── with gr.Tab("Classify Variant"): gr.HTML( '
Enter variant details below and provide your OpenAI API key to run live ACMG classification.
' ) with gr.Row(): with gr.Column(scale=1): api_key = gr.Textbox( type="password", label="OpenAI API Key", placeholder="sk-...", ) patient_name = gr.Textbox( label="Patient Name / ID (optional)", placeholder="e.g. Patient_001", ) gene = gr.Textbox( label="Gene Symbol", placeholder="e.g. BRCA1, TP53, CFTR", ) chromosome = gr.Dropdown( label="Chromosome", choices=[ "chr1","chr2","chr3","chr4","chr5","chr6", "chr7","chr8","chr9","chr10","chr11","chr12", "chr13","chr14","chr15","chr16","chr17","chr18", "chr19","chr20","chr21","chr22","chrX","chrY", ], value="chr17", ) position = gr.Number( label="Genomic Position (GRCh38)", value=41245466, precision=0, ) ref_allele = gr.Textbox( label="Reference Allele", placeholder="e.g. A, CTTT", ) alt_allele = gr.Textbox( label="Alternate Allele", placeholder="e.g. G, -", ) zygosity = gr.Radio( label="Zygosity", choices=["Heterozygous", "Homozygous"], value="Heterozygous", ) population_af = gr.Slider( minimum=0, maximum=0.05, step=0.0001, value=0.0001, label="Population Allele Frequency (gnomAD)", ) indication = gr.Textbox( label="Clinical Indication / Phenotype", placeholder="e.g. Hereditary breast/ovarian cancer, Lynch Syndrome", ) panel_type = gr.Textbox( label="Genetic Panel / Test Name", placeholder="e.g. BRCA1/2 Panel, Hereditary Cancer 47-gene", ) classify_btn = gr.Button("Classify Variant", variant="primary") with gr.Column(scale=1): result_html = gr.HTML( value=( '
' 'Classification result will appear here
' ) ) classify_btn.click( fn=classify_variant, inputs=[ patient_name, gene, chromosome, position, ref_allele, alt_allele, zygosity, population_af, indication, panel_type, api_key, ], outputs=result_html, ) # ── TAB 3: How It Works ────────────────────────────────────────────── with gr.Tab("How It Works"): gr.HTML(HOW_IT_WORKS_HTML) if __name__ == "__main__": demo.launch()