Darkweb007's picture
Initial commit: Genomic Variant Clinical Significance Classifier
6cc7ae7
Raw
History Blame Contribute Delete
30 kB
import gradio as gr
import json
# ── helpers ──────────────────────────────────────────────────────────────────
def chip(text: str, color: str = "#94a3b8") -> str:
return (
f'<span style="display:inline-block;padding:2px 10px;border-radius:12px;'
f'background:rgba(255,255,255,0.1);color:{color};font-size:0.75rem;'
f'font-weight:600;letter-spacing:0.04em;margin:2px 3px;">{text}</span>'
)
def badge(label: str, bg: str, color: str) -> str:
return (
f'<span style="display:inline-block;padding:4px 14px;border-radius:20px;'
f'background:{bg};color:{color};font-size:0.82rem;font-weight:700;'
f'letter-spacing:0.05em;">{label}</span>'
)
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'<div style="margin-bottom:6px;color:#94a3b8;font-size:0.82rem;">'
f'Patient: <span style="color:#e2e8f0;font-weight:600;">{patient_name}</span></div>'
if patient_name else ""
)
reasoning_section = (
f'<div style="margin-top:10px;padding:10px 14px;border-radius:8px;'
f'background:rgba(255,255,255,0.05);color:#94a3b8;font-size:0.82rem;line-height:1.6;">'
f'<span style="color:#a5f3fc;font-weight:600;">Pathogenicity Reasoning:</span> {pathogenicity_reasoning}</div>'
if pathogenicity_reasoning else ""
)
pop_section = (
f'<div style="margin-top:6px;color:#94a3b8;font-size:0.82rem;">'
f'<span style="color:#fcd34d;font-weight:600;">Population Assessment:</span> {population_assessment}</div>'
if population_assessment else ""
)
return f"""
<div style="background:{bg};border:1px solid {border};border-radius:14px;
padding:20px 24px;margin-bottom:20px;font-family:'Inter',sans-serif;">
{pat_section}
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:14px;">
<div>
<span style="font-size:1.25rem;font-weight:700;color:#f1f5f9;">{gene}</span>
<span style="color:#94a3b8;font-size:0.9rem;margin-left:10px;">{variant_notation}</span>
</div>
{acmg_badge(acmg_class)}
</div>
<div style="display:flex;flex-wrap:wrap;gap:18px;margin-bottom:14px;">
<div style="color:#94a3b8;font-size:0.82rem;">
Chromosome: <span style="color:#c4b5fd;font-weight:600;">{chromosome}</span>
</div>
<div style="color:#94a3b8;font-size:0.82rem;">
Position: <span style="color:#c4b5fd;font-weight:600;">{position}</span>
</div>
<div style="color:#94a3b8;font-size:0.82rem;">
Zygosity: <span style="color:#c4b5fd;font-weight:600;">{zygosity}</span>
</div>
<div style="color:#94a3b8;font-size:0.82rem;">
Population AF: <span style="color:#fcd34d;font-weight:600;">{population_af}</span>
</div>
</div>
<div style="margin-bottom:10px;">
<span style="color:#94a3b8;font-size:0.8rem;font-weight:600;text-transform:uppercase;
letter-spacing:0.06em;">ACMG Criteria Met</span>
<div style="margin-top:6px;">{criteria_chips}</div>
</div>
<div style="padding:12px 16px;border-radius:10px;background:rgba(0,0,0,0.25);margin-bottom:12px;">
<div style="color:#7dd3fc;font-size:0.8rem;font-weight:600;text-transform:uppercase;
letter-spacing:0.06em;margin-bottom:6px;">Clinical Significance</div>
<div style="color:#e2e8f0;font-size:0.9rem;line-height:1.6;">{clinical_significance}</div>
</div>
<div style="padding:12px 16px;border-radius:10px;background:rgba(0,0,0,0.25);">
<div style="color:#86efac;font-size:0.8rem;font-weight:600;text-transform:uppercase;
letter-spacing:0.06em;margin-bottom:6px;">Recommended Action</div>
<div style="color:#e2e8f0;font-size:0.9rem;line-height:1.6;">{recommended_action}</div>
</div>
{reasoning_section}
{pop_section}
</div>
"""
# ── 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 = """
<div style="font-family:'Inter',sans-serif;padding:4px 0 18px;">
<div style="color:#a5f3fc;font-size:0.78rem;font-weight:600;text-transform:uppercase;
letter-spacing:0.08em;margin-bottom:4px;">Pre-Computed Analysis Β· No API Key Required</div>
<div style="color:#94a3b8;font-size:0.85rem;">
Four representative genomic variants illustrating the full ACMG classification spectrum.
</div>
</div>
"""
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 "<div style='color:#fca5a5;padding:20px;font-family:Inter,sans-serif;'>Please enter your OpenAI API key in the field above.</div>"
if not gene or not gene.strip():
return "<div style='color:#fcd34d;padding:20px;font-family:Inter,sans-serif;'>Please enter a gene name (e.g. BRCA1, TP53, CFTR).</div>"
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"<div style='color:#fca5a5;padding:20px 24px;border-radius:12px;"
f"background:rgba(239,68,68,0.15);font-family:Inter,sans-serif;'>"
f"<strong>Error:</strong> {err}</div>"
)
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 = """
<div style="font-family:'Inter',sans-serif;color:#e2e8f0;max-width:860px;margin:0 auto;">
<!-- Workflow link -->
<div style="background:rgba(99,102,241,0.18);border:1px solid rgba(99,102,241,0.45);
border-radius:12px;padding:16px 22px;margin-bottom:28px;display:flex;
align-items:center;gap:14px;flex-wrap:wrap;">
<div style="font-size:1.5rem;">πŸ”—</div>
<div>
<div style="color:#a5b4fc;font-weight:700;font-size:0.95rem;margin-bottom:3px;">
n8n Automation Workflow
</div>
<a href="https://aravind5.app.n8n.cloud/workflow/PLACEHOLDER_GENOMIC"
target="_blank"
style="color:#818cf8;font-size:0.85rem;word-break:break-all;">
https://aravind5.app.n8n.cloud/workflow/PLACEHOLDER_GENOMIC
</a>
</div>
</div>
<!-- Architecture -->
<div style="margin-bottom:28px;">
<div style="color:#a5f3fc;font-size:0.78rem;font-weight:700;text-transform:uppercase;
letter-spacing:0.08em;margin-bottom:14px;">n8n Workflow Architecture</div>
<div style="display:flex;flex-direction:column;gap:10px;">
<!-- steps -->
<div style="background:rgba(30,30,50,0.55);border:1px solid rgba(148,163,184,0.15);
border-radius:10px;padding:14px 18px;display:flex;gap:14px;align-items:flex-start;">
<div style="background:rgba(99,102,241,0.35);color:#a5b4fc;font-weight:700;
border-radius:8px;padding:4px 10px;font-size:0.8rem;white-space:nowrap;">Step 1</div>
<div>
<div style="color:#f1f5f9;font-weight:600;margin-bottom:3px;">Webhook Trigger</div>
<div style="color:#94a3b8;font-size:0.85rem;">Receives variant data payload (gene, position, zygosity, clinical indication) from external LIMS or EHR system via HTTP POST.</div>
</div>
</div>
<div style="background:rgba(30,30,50,0.55);border:1px solid rgba(148,163,184,0.15);
border-radius:10px;padding:14px 18px;display:flex;gap:14px;align-items:flex-start;">
<div style="background:rgba(34,197,94,0.25);color:#86efac;font-weight:700;
border-radius:8px;padding:4px 10px;font-size:0.8rem;white-space:nowrap;">Step 2</div>
<div>
<div style="color:#f1f5f9;font-weight:600;margin-bottom:3px;">Database Enrichment</div>
<div style="color:#94a3b8;font-size:0.85rem;">Queries ClinVar, gnomAD, and ClinGen APIs for prior classification, population frequency data, and functional evidence annotations.</div>
</div>
</div>
<div style="background:rgba(30,30,50,0.55);border:1px solid rgba(148,163,184,0.15);
border-radius:10px;padding:14px 18px;display:flex;gap:14px;align-items:flex-start;">
<div style="background:rgba(249,115,22,0.25);color:#fdba74;font-weight:700;
border-radius:8px;padding:4px 10px;font-size:0.8rem;white-space:nowrap;">Step 3</div>
<div>
<div style="color:#f1f5f9;font-weight:600;margin-bottom:3px;">GPT-4o-mini ACMG Classifier</div>
<div style="color:#94a3b8;font-size:0.85rem;">Sends enriched variant context to GPT-4o-mini with structured ACMG/AMP prompt. Returns JSON classification with criteria evidence codes and clinical narrative.</div>
</div>
</div>
<div style="background:rgba(30,30,50,0.55);border:1px solid rgba(148,163,184,0.15);
border-radius:10px;padding:14px 18px;display:flex;gap:14px;align-items:flex-start;">
<div style="background:rgba(239,68,68,0.25);color:#fca5a5;font-weight:700;
border-radius:8px;padding:4px 10px;font-size:0.8rem;white-space:nowrap;">Step 4</div>
<div>
<div style="color:#f1f5f9;font-weight:600;margin-bottom:3px;">Report Generation & Delivery</div>
<div style="color:#94a3b8;font-size:0.85rem;">Generates structured clinical report, logs to Google Sheets for audit trail, and routes high-severity Pathogenic findings to clinical team via email/Slack alert.</div>
</div>
</div>
</div>
</div>
<!-- ACMG Criteria Table -->
<div>
<div style="color:#a5f3fc;font-size:0.78rem;font-weight:700;text-transform:uppercase;
letter-spacing:0.08em;margin-bottom:14px;">ACMG/AMP Classification Criteria Reference</div>
<div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:0.85rem;">
<thead>
<tr style="background:rgba(99,102,241,0.20);">
<th style="padding:10px 14px;text-align:left;color:#a5b4fc;font-weight:700;
border-bottom:1px solid rgba(148,163,184,0.2);">Code</th>
<th style="padding:10px 14px;text-align:left;color:#a5b4fc;font-weight:700;
border-bottom:1px solid rgba(148,163,184,0.2);">Strength</th>
<th style="padding:10px 14px;text-align:left;color:#a5b4fc;font-weight:700;
border-bottom:1px solid rgba(148,163,184,0.2);">Direction</th>
<th style="padding:10px 14px;text-align:left;color:#a5b4fc;font-weight:700;
border-bottom:1px solid rgba(148,163,184,0.2);">Description</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#fca5a5;font-weight:700;">PVS1</td>
<td style="padding:9px 14px;color:#94a3b8;">Very Strong</td>
<td style="padding:9px 14px;color:#fca5a5;">Pathogenic</td>
<td style="padding:9px 14px;color:#cbd5e1;">Null variant (frameshift, nonsense, splice Β±1/2) in gene where LOF is disease mechanism</td>
</tr>
<tr style="background:rgba(255,255,255,0.03);border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#fca5a5;font-weight:700;">PS1–PS4</td>
<td style="padding:9px 14px;color:#94a3b8;">Strong</td>
<td style="padding:9px 14px;color:#fca5a5;">Pathogenic</td>
<td style="padding:9px 14px;color:#cbd5e1;">Same AA change as established pathogenic; functional studies; de novo; prevalence increase in affected</td>
</tr>
<tr style="border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#fdba74;font-weight:700;">PM1–PM6</td>
<td style="padding:9px 14px;color:#94a3b8;">Moderate</td>
<td style="padding:9px 14px;color:#fdba74;">Pathogenic</td>
<td style="padding:9px 14px;color:#cbd5e1;">Critical domain; absent in population; cosegregation; in trans with pathogenic; de novo (unconfirmed)</td>
</tr>
<tr style="background:rgba(255,255,255,0.03);border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#fde047;font-weight:700;">PP1–PP5</td>
<td style="padding:9px 14px;color:#94a3b8;">Supporting</td>
<td style="padding:9px 14px;color:#fde047;">Pathogenic</td>
<td style="padding:9px 14px;color:#cbd5e1;">Cosegregation; functional evidence; reputable source (ClinVar); in trans with pathogenic; in cis pathogenic</td>
</tr>
<tr style="border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#6ee7b7;font-weight:700;">BA1</td>
<td style="padding:9px 14px;color:#94a3b8;">Stand-Alone</td>
<td style="padding:9px 14px;color:#6ee7b7;">Benign</td>
<td style="padding:9px 14px;color:#cbd5e1;">Allele frequency &gt;5% in population databases (gnomAD, 1000G, ESP)</td>
</tr>
<tr style="background:rgba(255,255,255,0.03);border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#86efac;font-weight:700;">BS1–BS4</td>
<td style="padding:9px 14px;color:#94a3b8;">Strong</td>
<td style="padding:9px 14px;color:#86efac;">Benign</td>
<td style="padding:9px 14px;color:#cbd5e1;">Allele frequency greater than expected; benign functional studies; nonsegregation; in trans VUS</td>
</tr>
<tr style="border-bottom:1px solid rgba(148,163,184,0.1);">
<td style="padding:9px 14px;color:#86efac;font-weight:700;">BP1–BP7</td>
<td style="padding:9px 14px;color:#94a3b8;">Supporting</td>
<td style="padding:9px 14px;color:#86efac;">Benign</td>
<td style="padding:9px 14px;color:#cbd5e1;">Missense in gene with only truncating pathogenic; silent with no splice impact; in trans pathogenic; multiple lines benign computational</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Disclaimer -->
<div style="margin-top:28px;padding:14px 18px;border-radius:10px;
background:rgba(234,179,8,0.12);border:1px solid rgba(234,179,8,0.30);">
<div style="color:#fde047;font-weight:700;font-size:0.8rem;margin-bottom:4px;">
CLINICAL DISCLAIMER
</div>
<div style="color:#94a3b8;font-size:0.82rem;line-height:1.6;">
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.
</div>
</div>
</div>
"""
# ── 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 = """
<div style="font-family:'Inter',sans-serif;padding:20px 0 8px;text-align:center;">
<div style="font-size:2rem;font-weight:800;
background:linear-gradient(135deg,#a5f3fc,#818cf8,#c084fc);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;margin-bottom:8px;">
Genomic Variant Clinical Significance Classifier
</div>
<div style="color:#94a3b8;font-size:0.9rem;max-width:600px;margin:0 auto;">
ACMG/AMP 2015 guideline-based variant classification powered by GPT-4o-mini
</div>
</div>
"""
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(
'<div style="font-family:Inter,sans-serif;color:#94a3b8;font-size:0.85rem;'
'padding:6px 0 16px;">Enter variant details below and provide your OpenAI API key to run live ACMG classification.</div>'
)
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=(
'<div style="font-family:Inter,sans-serif;color:#475569;'
'padding:40px 20px;text-align:center;border:1px dashed rgba(148,163,184,0.2);'
'border-radius:14px;background:rgba(30,30,50,0.3);">'
'Classification result will appear here</div>'
)
)
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()