Sazzz02's picture
Update app.py
e72328f verified
"""
Cross-Medical-System Drug Recommender v4.0
4 Tabs: Search | Cross-Compare | FDA Live | Dashboard
Symptoms + Medicine dropdown, Plotly dashboard, OpenFDA API
"""
import gradio as gr
import pandas as pd
import numpy as np
import joblib, json, os, re, warnings, requests
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
warnings.filterwarnings("ignore")
# ── Constants ────────────────────────────────────────────────────
OPENFDA_BASE = "https://api.fda.gov/drug"
MODEL_DIR = os.path.join(os.path.dirname(__file__), "models")
SYSTEM_COLORS = {
"Allopathic": "#3b82f6",
"Unani": "#f97316",
"Ayurvedic": "#22c55e",
"Homeopathic": "#a855f7",
"Herbal": "#ef4444",
}
# ── Dropdown options: Symptoms AND Medicines ─────────────────────
SEARCH_OPTIONS = {
# ══ SYMPTOMS / CONDITIONS ════════════════════════════════════
"── SYMPTOMS & CONDITIONS ──": None, # separator (disabled)
"🤒 Fever": "fever paracetamol tablet",
"🩹 Body Pain / General Pain": "pain relief tablet",
"🤕 Headache": "headache pain tablet",
"😤 Cold & Runny Nose": "cold antihistamine tablet",
"😮‍💨 Cough": "cough syrup liquid",
"🫁 Asthma / Breathing Difficulty": "asthma bronchodilator tablet",
"🤮 Nausea & Vomiting": "nausea vomiting tablet",
"🫃 Acidity / Heartburn": "acidity antacid capsule",
"💩 Diarrhea": "diarrhea tablet",
"🤢 Stomach Pain": "stomach pain tablet",
"😴 Anxiety / Sleeplessness": "anxiety sleep tablet",
"🫀 High Blood Pressure": "hypertension blood pressure tablet",
"🩸 High Blood Sugar (Diabetes)": "diabetes blood sugar tablet",
"🦴 Joint Pain / Arthritis": "joint pain inflammation tablet",
"🦷 Tooth / Ear Infection": "antibiotic infection capsule",
"👁️ Eye Infection": "eye drops infection",
"🩺 Urinary Tract Infection (UTI)": "urinary tract infection antibiotic tablet",
"🌿 Worm / Parasite Infection": "deworming tablet",
"🫧 Skin Fungal Infection": "antifungal cream tablet",
"😵 Dizziness / Vertigo": "vertigo dizziness tablet",
# ══ MEDICINES BY NAME ════════════════════════════════════════
"── MEDICINES BY NAME ──": None, # separator (disabled)
# Antibiotics
"🦠 Azithromycin — Antibiotic": "Azithromycin 500mg tablet",
"🦠 Amoxicillin — Antibiotic": "Amoxicillin 500mg capsule",
"🦠 Ciprofloxacin — Antibiotic": "Ciprofloxacin 500mg tablet",
"🦠 Metronidazole — Antibiotic": "Metronidazole 400mg tablet",
"🦠 Ceftriaxone — Injection": "Ceftriaxone 1gm injection",
"🦠 Levofloxacin — Antibiotic": "Levofloxacin 500mg tablet",
# Pain & Fever
"🤒 Paracetamol — Fever/Pain": "Paracetamol 500mg tablet",
"🤒 Diclofenac — Anti-inflammatory": "Diclofenac Sodium 50mg tablet",
"🤒 Naproxen — Pain (Joints)": "Naproxen 250mg tablet",
"🤒 Ibuprofen — Pain/Fever": "Ibuprofen 400mg tablet",
# Heart & BP
"💓 Amlodipine — Blood Pressure": "Amlodipine 5mg tablet",
"💓 Atorvastatin — Cholesterol": "Atorvastatin 20mg tablet",
"💓 Losartan — Hypertension": "Losartan Potassium 50mg tablet",
"💓 Metoprolol — Beta Blocker": "Metoprolol 50mg tablet",
# Diabetes
"🩺 Metformin — Diabetes": "Metformin Hydrochloride 500mg tablet",
"🩺 Glibenclamide — Blood Sugar": "Glibenclamide 5mg tablet",
# Respiratory & Allergy
"🫁 Salbutamol — Asthma": "Salbutamol 2mg tablet syrup",
"🫁 Montelukast — Allergy/Asthma": "Montelukast 10mg tablet",
"🫁 Fexofenadine — Antihistamine": "Fexofenadine 120mg tablet",
"🫁 Cetirizine — Antihistamine": "Cetirizine 10mg tablet",
# Neuro
"🧠 Pregabalin — Nerve Pain": "Pregabalin 75mg capsule",
"🧠 Clonazepam — Anxiety/Seizure": "Clonazepam 0.5mg tablet",
# GI / Stomach
"🫃 Omeprazole — Acid Reflux": "Omeprazole 20mg capsule",
"🫃 Esomeprazole — GERD": "Esomeprazole 40mg capsule",
"🫃 Domperidone — Nausea": "Domperidone 10mg tablet",
"🫃 Ondansetron — Nausea": "Ondansetron 4mg tablet",
# Antifungal / Deworming
"🌿 Albendazole — Deworming": "Albendazole 400mg tablet",
"🌿 Fluconazole — Antifungal": "Fluconazole 150mg capsule",
# Vitamins
"💊 Vitamin D3 — Bone/Immunity": "Cholecalciferol Vitamin D3 tablet",
"💊 Zinc + Multivitamin": "Zinc Nicotinamide vitamin tablet",
}
# Remove separator entries to get valid choices
ALL_LABELS = list(SEARCH_OPTIONS.keys())
# ── Load PKL Models ───────────────────────────────────────────────
def load_models():
vec = joblib.load(os.path.join(MODEL_DIR, "tfidf_vectorizer.pkl"))
mat = joblib.load(os.path.join(MODEL_DIR, "tfidf_matrix.pkl"))
db = pd.read_csv(os.path.join(MODEL_DIR, "drug_database.csv"))
with open(os.path.join(MODEL_DIR, "model_metadata.json")) as f:
meta = json.load(f)
return vec, mat, db, meta
try:
from sklearn.metrics.pairwise import cosine_similarity
vectorizer, tfidf_matrix, drug_db, metadata = load_models()
MEDICAL_SYSTEMS = ["All Systems"] + sorted(drug_db["medical_system"].unique().tolist())
MODEL_LOADED = True
print(f"✅ Loaded {len(drug_db):,} drugs")
except Exception as e:
print(f"Model load failed: {e}")
MODEL_LOADED = False
drug_db = pd.DataFrame()
metadata = {}
MEDICAL_SYSTEMS = ["All Systems"]
# ── Helpers ───────────────────────────────────────────────────────
def _clean(t):
if pd.isna(t): return ""
t = re.sub(r"[^a-z0-9\s\+\-\.]", " ", str(t).lower())
return re.sub(r"\s+", " ", t).strip()
def _get_query(label):
return SEARCH_OPTIONS.get(label) or label
def _extract_generic(label):
"""Extract the best single search term from a dropdown label for OpenFDA queries.
Examples
--------
'🦠 Azithromycin — Antibiotic' → 'Azithromycin'
'💊 Vitamin D3 — Bone/Immunity' → 'Cholecalciferol' (mapped)
'🤒 Paracetamol — Fever/Pain' → 'Paracetamol'
'🩺 Metformin — Diabetes' → 'Metformin'
'🤒 Fever' (symptom) → uses query word
"""
# Hardcoded map for labels whose first real word is ambiguous for FDA search
FDA_MAP = {
"Vitamin D3": "Cholecalciferol",
"Zinc": "Zinc",
"Salbutamol": "Albuterol", # US FDA name
"Paracetamol": "Acetaminophen", # US FDA name
"Fexofenadine": "Fexofenadine",
"Cetirizine": "Cetirizine",
"Amlodipine": "Amlodipine",
"Atorvastatin": "Atorvastatin",
"Metformin": "Metformin",
"Omeprazole": "Omeprazole",
"Esomeprazole": "Esomeprazole",
"Domperidone": "Domperidone",
"Ondansetron": "Ondansetron",
"Albendazole": "Albendazole",
"Fluconazole": "Fluconazole",
"Pregabalin": "Pregabalin",
"Clonazepam": "Clonazepam",
"Montelukast": "Montelukast",
"Losartan": "Losartan",
"Metoprolol": "Metoprolol",
}
# Strip everything after —
raw = label.split("—")[0]
cleaned = re.sub(r"[^\w\s]", "", raw).strip()
words = [w for w in cleaned.split() if len(w) > 2] # skip short emoji fragments
if not words:
return cleaned
# Try each word against the map
for w in words:
if w in FDA_MAP:
return FDA_MAP[w]
# Heuristic: return the longest word (most likely to be a pharmaceutical term)
return max(words, key=len)
def _openfda(endpoint, params, timeout=10):
try:
r = requests.get(f"{OPENFDA_BASE}/{endpoint}.json", params=params,
timeout=timeout, headers={"User-Agent": "DrugRecommender/4.0"})
return r.json() if r.status_code == 200 else {"error": f"HTTP {r.status_code}", "message": r.text[:200]}
except requests.exceptions.Timeout:
return {"error": "timeout", "message": "OpenFDA timed out — try again."}
except Exception as e:
return {"error": "connection", "message": str(e)}
# ── Core Recommender ──────────────────────────────────────────────
def recommend(label, system_filter, top_n, min_score):
if not MODEL_LOADED:
return None, "❌ Models not loaded."
query = _get_query(label)
if not query:
return None, "⚠️ Please select a valid option (not a section header)."
q_vec = vectorizer.transform([_clean(query)])
sims = cosine_similarity(q_vec, tfidf_matrix).flatten()
if system_filter != "All Systems":
mask = drug_db["medical_system"] == system_filter
work = sims.copy(); work[~mask] = 0
else:
work = sims
idx = [i for i in work.argsort()[-(top_n*4):][::-1] if sims[i] >= min_score][:top_n]
if not idx:
return None, f"⚠️ No results above score {min_score}. Lower the threshold."
out = drug_db.iloc[idx][["brand_name","generic_name","dosage_form","strength","medical_system","manufacturer"]].copy()
out["score"] = [round(float(sims[i]),4) for i in idx]
out = out.sort_values("score", ascending=False).reset_index(drop=True)
out.index = range(1, len(out)+1); out.index.name = "Rank"
out.columns = ["Brand Name","Generic Name","Dosage Form","Strength","Medical System","Manufacturer","Score"]
sys_str = " · ".join(f"**{k}** {v}" for k,v in out["Medical System"].value_counts().items())
label_short = re.sub(r"[^\w\s\-/]","",label).strip()[:40]
summary = f"### ✅ {len(out)} results for **{label_short}**\n\n{sys_str}\n\n*Query: `{query}`*"
return out, summary
def cross_compare(label, top_per):
if not MODEL_LOADED: return None, "❌ Models not loaded."
query = _get_query(label)
if not query: return None, "⚠️ Select a valid option."
q_vec = vectorizer.transform([_clean(query)])
sims = cosine_similarity(q_vec, tfidf_matrix).flatten()
rows = []
for sys in sorted(drug_db["medical_system"].unique()):
mask = drug_db["medical_system"] == sys
s = sims.copy(); s[~mask] = 0
for i in [x for x in s.argsort()[-top_per:][::-1] if sims[x] > 0.01]:
r = drug_db.iloc[i]
rows.append({"System": r["medical_system"], "Brand": r["brand_name"],
"Generic": r["generic_name"], "Form": r["dosage_form"],
"Strength": r["strength"], "Score": round(float(sims[i]),4)})
if not rows: return None, "No results found."
df = pd.DataFrame(rows).sort_values(["System","Score"],ascending=[True,False]).reset_index(drop=True)
df.index = range(1, len(df)+1); df.index.name = "Rank"
label_short = re.sub(r"[^\w\s\-/]","",label).strip()[:40]
return df, f"### 🌐 **{label_short}** — {len(df)} drugs across {df['System'].nunique()} systems"
# ── OpenFDA ───────────────────────────────────────────────────────
def fda_label(label):
g = _extract_generic(label)
d = _openfda("label", {"search": f"openfda.generic_name:{g}", "limit": 1})
if "error" in d:
return f"### ⚠️ {d['message']}\n\n*`{g}` may not be in US FDA records.*"
res = d.get("results", [])
if not res: return f"ℹ️ No FDA label for **{g}**."
r = res[0]; ofd = r.get("openfda", {})
lines = [f"## 💊 {g.title()} — FDA Label", "_U.S. Food & Drug Administration · OpenFDA_\n"]
for k,t in [("brand_name","Brand Names"),("manufacturer_name","Manufacturer"),("route","Route")]:
v = ofd.get(k,[])
if v: lines.append(f"**{t}:** {', '.join(v[:5])}")
lines.append("")
for field, heading, lim in [
("indications_and_usage","📋 Indications & Usage",700),
("warnings","⚠️ Warnings",500),
("dosage_and_administration","💉 Dosage",500),
("adverse_reactions","🔴 Adverse Reactions",400),
("drug_interactions","🔗 Drug Interactions",400),
]:
v = r.get(field,[])
if v: lines += [f"### {heading}", v[0][:lim]+"…\n"]
lines.append("---\n*[OpenFDA](https://open.fda.gov) · Research only · Not clinical advice*")
return "\n".join(lines)
def fda_adverse(label):
g = _extract_generic(label)
d = _openfda("event",{"search":f"patient.drug.medicinalproduct:{g}","count":"patient.reaction.reactionmeddrapt.exact","limit":15})
if "error" in d: return None, f"### ⚠️ {d['message']}"
res = d.get("results",[])
if not res: return None, f"ℹ️ No FAERS data for **{g}**."
df = pd.DataFrame(res, columns=["Adverse Reaction","Report Count"])
df = df.sort_values("Report Count",ascending=False).reset_index(drop=True)
df.index = range(1,len(df)+1); df.index.name = "Rank"
return df, f"### 📊 FAERS: **{g.title()}** · {df['Report Count'].sum():,} total reports"
def fda_ndc(label):
g = _extract_generic(label)
d = _openfda("ndc",{"search":f"generic_name:{g}","limit":10})
if "error" in d: return None, f"### ⚠️ {d['message']}"
res = d.get("results",[])
if not res: return None, f"ℹ️ No NDC data for **{g}**."
df = pd.DataFrame([{"Brand":r.get("brand_name","—"),"Generic":r.get("generic_name","—"),
"Form":r.get("dosage_form","—"),"Route":", ".join(r.get("route",[])),"Manufacturer":r.get("labeler_name","—"),
"NDC":r.get("product_ndc","—")} for r in res])
df.index = range(1,len(df)+1); df.index.name = "#"
return df, f"### 🏷️ NDC Registry: **{g.title()}** · {len(df)} products"
# ── Dashboard Charts ──────────────────────────────────────────────
if MODEL_LOADED and not drug_db.empty:
_sys = drug_db["medical_system"].value_counts()
_dos = drug_db["dosage_form"].value_counts().head(10)
_mfr = drug_db["manufacturer"].value_counts().head(15)
_cross = pd.crosstab(drug_db["medical_system"], drug_db["dosage_form"])
_cross = _cross[[c for c in _dos.index[:8] if c in _cross.columns]]
else:
_sys = pd.Series({"No data":1}); _dos = _sys.copy(); _mfr = _sys.copy(); _cross = pd.DataFrame()
def _sc(labels): return [SYSTEM_COLORS.get(l,"#64748b") for l in labels]
def make_dashboard():
# ── Row 1: KPI cards via annotations ──────────────────────────
fig_kpi = go.Figure()
kpis = [
("53,581", "Total Drugs", "#3b82f6"),
("725", "Manufacturers", "#22c55e"),
("5", "Medical Systems", "#a855f7"),
("1,702", "Unique Compounds", "#f97316"),
]
for i,(val,lbl,col) in enumerate(kpis):
x = 0.13 + i*0.25
r2,g2,b2 = int(col[1:3],16), int(col[3:5],16), int(col[5:7],16)
fill_rgba = f"rgba({r2},{g2},{b2},0.12)"
fig_kpi.add_shape(type="rect", x0=x-0.11, x1=x+0.11, y0=0.05, y1=0.95,
fillcolor=fill_rgba, line=dict(color=col, width=2), xref="paper", yref="paper")
fig_kpi.add_annotation(x=x, y=0.62, text=f"<b>{val}</b>", showarrow=False,
font=dict(size=28, color=col), xref="paper", yref="paper")
fig_kpi.add_annotation(x=x, y=0.28, text=lbl, showarrow=False,
font=dict(size=13, color="#475569"), xref="paper", yref="paper")
fig_kpi.update_layout(height=130, margin=dict(t=10,b=10,l=10,r=10),
paper_bgcolor="white", plot_bgcolor="white",
xaxis=dict(visible=False), yaxis=dict(visible=False))
# ── Donut ──────────────────────────────────────────────────────
fig_donut = go.Figure(go.Pie(
labels=_sys.index.tolist(), values=_sys.values.tolist(), hole=0.58,
marker=dict(colors=_sc(_sys.index), line=dict(color="#fff",width=2.5)),
textinfo="label+percent", textfont=dict(size=12),
hovertemplate="<b>%{label}</b><br>%{value:,} drugs · %{percent}<extra></extra>",
))
fig_donut.update_layout(
title=dict(text="<b>Drug Share by Medical System</b>", x=0.5, font=dict(size=15)),
annotations=[dict(text=f"<b>53,581</b><br>Drugs", x=0.5, y=0.5, font=dict(size=13), showarrow=False)],
legend=dict(orientation="h", y=-0.05, x=0.5, xanchor="center", font=dict(size=11)),
height=340, margin=dict(t=50,b=30,l=10,r=10), paper_bgcolor="white",
)
# ── H-Bar dosage ───────────────────────────────────────────────
labs = _dos.index.tolist()[::-1]; vals = _dos.values.tolist()[::-1]
fig_bar = go.Figure(go.Bar(
y=labs, x=vals, orientation="h",
marker=dict(color=px.colors.sequential.Blues_r[:len(labs)]),
text=[f" {v:,}" for v in vals], textposition="outside",
hovertemplate="<b>%{y}</b>: %{x:,}<extra></extra>",
))
fig_bar.update_layout(
title=dict(text="<b>Top 10 Dosage Forms</b>", x=0.5, font=dict(size=15)),
xaxis=dict(showgrid=True, gridcolor="#f0f0f0", title="Count"),
yaxis=dict(title=""), height=340,
margin=dict(t=50,b=30,l=160,r=60), paper_bgcolor="white", plot_bgcolor="white",
)
# ── Grouped bar system × dosage ────────────────────────────────
fig_grp = go.Figure()
palette = px.colors.qualitative.Pastel
for j,col in enumerate([c for c in _cross.columns if not _cross.empty]):
fig_grp.add_trace(go.Bar(
name=col, x=_cross.index.tolist(), y=_cross[col].tolist(),
marker_color=palette[j % len(palette)],
hovertemplate=f"<b>{col}</b><br>%{{x}}: %{{y:,}}<extra></extra>",
))
fig_grp.update_layout(
barmode="group",
title=dict(text="<b>Dosage Form per Medical System</b>", x=0.5, font=dict(size=15)),
xaxis=dict(title=""), yaxis=dict(title="Drug Count", showgrid=True, gridcolor="#f0f0f0"),
legend=dict(title="Form", orientation="h", y=-0.22, x=0.5, xanchor="center", font=dict(size=10)),
height=380, margin=dict(t=50,b=100,l=60,r=20), paper_bgcolor="white", plot_bgcolor="white",
)
# ── Treemap ────────────────────────────────────────────────────
tmdf = drug_db.groupby(["medical_system","manufacturer"]).size().reset_index(name="count")
tmdf = tmdf.sort_values("count",ascending=False).groupby("medical_system").head(6).reset_index(drop=True)
fig_tree = px.treemap(tmdf, path=["medical_system","manufacturer"], values="count",
color="medical_system", color_discrete_map=SYSTEM_COLORS,
custom_data=["count"])
fig_tree.update_traces(hovertemplate="<b>%{label}</b><br>Products: %{customdata[0]:,}<extra></extra>",
textfont=dict(size=11))
fig_tree.update_layout(
title=dict(text="<b>Top Manufacturers by Medical System</b>", x=0.5, font=dict(size=15)),
height=420, margin=dict(t=50,b=10,l=10,r=10), paper_bgcolor="white",
)
# ── Radar ──────────────────────────────────────────────────────
cats = [c for c in ["Tablet","Capsule","Liquid","Injection","Syrup"] if not _cross.empty and c in _cross.columns]
fig_radar = go.Figure()
if cats:
sub = _cross[cats]
sub_n = sub.div(sub.max(axis=0),axis=1).fillna(0)*100
for sys in sub_n.index:
vals = sub_n.loc[sys].tolist()
col = SYSTEM_COLORS.get(sys,"#64748b")
r2,g2,b2 = int(col[1:3],16), int(col[3:5],16), int(col[5:7],16)
fill_rgba = f"rgba({r2},{g2},{b2},0.15)"
fig_radar.add_trace(go.Scatterpolar(
r=vals+[vals[0]], theta=cats+[cats[0]],
fill="toself", fillcolor=fill_rgba,
line=dict(color=col,width=2.5), name=sys,
hovertemplate=f"<b>{sys}</b><br>%{{theta}}: %{{r:.0f}}%<extra></extra>",
))
fig_radar.update_layout(
polar=dict(radialaxis=dict(visible=True,range=[0,115],tickfont=dict(size=9),gridcolor="#e5e7eb"),
angularaxis=dict(tickfont=dict(size=12)), bgcolor="white"),
title=dict(text="<b>System Profile — Dosage Radar</b>", x=0.5, font=dict(size=15)),
legend=dict(orientation="h", y=-0.1, x=0.5, xanchor="center"),
height=400, margin=dict(t=50,b=60,l=50,r=50), paper_bgcolor="white",
)
# ── Top manufacturers bar ──────────────────────────────────────
fig_mfr = go.Figure(go.Bar(
y=_mfr.index.tolist()[::-1], x=_mfr.values.tolist()[::-1],
orientation="h",
marker=dict(color=px.colors.sequential.Teal_r[:15]),
text=[f" {v:,}" for v in _mfr.values.tolist()[::-1]],
textposition="outside",
hovertemplate="<b>%{y}</b>: %{x:,} products<extra></extra>",
))
fig_mfr.update_layout(
title=dict(text="<b>Top 15 Manufacturers by Product Count</b>", x=0.5, font=dict(size=15)),
xaxis=dict(showgrid=True, gridcolor="#f0f0f0", title="Products"),
yaxis=dict(title=""), height=450,
margin=dict(t=50,b=30,l=250,r=80), paper_bgcolor="white", plot_bgcolor="white",
)
return fig_kpi, fig_donut, fig_bar, fig_grp, fig_tree, fig_radar, fig_mfr
# ═══════════════════════════════════════════════════════════════════
# GRADIO UI
# ═══════════════════════════════════════════════════════════════════
CSS = """
.gradio-container { max-width:1080px !important; margin:auto !important;
font-family:'Segoe UI',system-ui,sans-serif !important; }
.hero { background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 55%,#0f172a 100%);
border:1px solid rgba(99,102,241,.35); border-radius:16px;
padding:28px 32px 22px; margin-bottom:18px; text-align:center; }
.sbadge { display:inline-block; border-radius:999px; padding:4px 13px;
font-size:12px; margin:3px; }
.fix-note { background:rgba(34,197,94,.08); border:1px solid rgba(34,197,94,.25);
border-radius:10px; padding:11px 16px; font-size:13px; margin:8px 0 12px; }
footer { display:none !important; }
"""
HEADER = """
<div class="hero">
<h1 style="color:white;font-size:2em;margin:0 0 8px;font-weight:800;">
💊 Cross-Medical-System Drug Recommender
</h1>
<p style="color:#94a3b8;margin:0 0 14px;font-size:1rem;">
53,581 drugs · Search by Symptom or Medicine · NLP-Powered · Master's Thesis
</p>
<div>
<span class="sbadge" style="background:rgba(59,130,246,.15);border:1px solid rgba(59,130,246,.3);color:#93c5fd;">🔵 Allopathic 36,251</span>
<span class="sbadge" style="background:rgba(249,115,22,.12);border:1px solid rgba(249,115,22,.3);color:#fdba74;">🟠 Unani 8,460</span>
<span class="sbadge" style="background:rgba(34,197,94,.12);border:1px solid rgba(34,197,94,.3);color:#86efac;">🟢 Ayurvedic 5,262</span>
<span class="sbadge" style="background:rgba(168,85,247,.12);border:1px solid rgba(168,85,247,.3);color:#d8b4fe;">🟣 Homeopathic 2,580</span>
<span class="sbadge" style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5;">🔴 Herbal 1,028</span>
<span class="sbadge" style="background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);color:#6ee7b7;">🇺🇸 OpenFDA API</span>
</div>
</div>
"""
FIX_NOTE = """
<div class="fix-note">
<strong>✅ Bug fix v3:</strong> Non-allopathic drugs no longer appear in Allopathic compound searches.
Brand names like <em>"Feverfit"</em> are excluded from TF-IDF — only Generic Name drives Allopathic matching.
</div>
"""
with gr.Blocks(css=CSS, title="💊 Drug Recommender", theme=gr.themes.Soft()) as demo:
gr.HTML(HEADER)
# ── Shared selector ──────────────────────────────────────────
gr.Markdown("### 👇 Pick a symptom or medicine — then explore any tab")
with gr.Row():
with gr.Column(scale=5):
selector = gr.Dropdown(
choices=ALL_LABELS,
value="🤒 Fever",
label="🔍 Search by Symptom or Medicine Name",
info="20 symptoms + 30 medicines = 50 options total",
interactive=True,
)
with gr.Column(scale=2):
sys_filter = gr.Dropdown(
choices=MEDICAL_SYSTEMS, value="All Systems",
label="🏥 Filter by Medical System",
)
# ── 4 Tabs ───────────────────────────────────────────────────
with gr.Tabs():
# ══════════════════════════════════════════════════════════
# TAB 1 — Recommendations
# ══════════════════════════════════════════════════════════
with gr.TabItem("🔍 Find Drugs"):
gr.HTML(FIX_NOTE)
gr.Markdown("Finds similar drugs using **TF-IDF cosine similarity** across all 53,581 records.")
with gr.Row():
top_n = gr.Slider(3, 25, value=10, step=1, label="📋 Results")
min_score = gr.Slider(0.01, 0.50, value=0.05, step=0.01, label="🎯 Min Score")
rec_btn = gr.Button("🚀 Get Recommendations", variant="primary", size="lg")
rec_summary = gr.Markdown()
rec_table = gr.DataFrame(wrap=True, interactive=False,
label="📋 Recommended Drugs")
rec_btn.click(fn=recommend,
inputs=[selector, sys_filter, top_n, min_score],
outputs=[rec_table, rec_summary])
gr.Markdown("""
---
**Matching logic:**
- **Symptoms** (e.g. Fever) → maps to compound query like `"fever paracetamol tablet"`
- **Allopathic** → matched by Generic Name compound ✅
- **Ayurvedic / Unani / Homeopathic / Herbal** → matched by dosage form + system
""")
# ══════════════════════════════════════════════════════════
# TAB 2 — Cross-System Compare
# ══════════════════════════════════════════════════════════
with gr.TabItem("🌐 Cross-System Compare"):
gr.Markdown("""
### 🏆 Core Thesis Feature
Best results from **every medical tradition** shown side by side for the same query.
""")
top_per = gr.Slider(1, 5, value=3, step=1, label="Results per System")
cmp_btn = gr.Button("🔄 Compare All 5 Systems", variant="primary", size="lg")
cmp_summary = gr.Markdown()
cmp_table = gr.DataFrame(wrap=True, interactive=False,
label="🌐 All 5 Systems Side by Side")
cmp_btn.click(fn=cross_compare, inputs=[selector, top_per],
outputs=[cmp_table, cmp_summary])
# ══════════════════════════════════════════════════════════
# TAB 3 — FDA Live API
# ══════════════════════════════════════════════════════════
with gr.TabItem("🇺🇸 FDA Live Data"):
gr.Markdown("""
### Live data from the U.S. Food & Drug Administration
> 🔌 **OpenFDA APIs** — Free · No key required ·
[open.fda.gov](https://open.fda.gov)
""")
with gr.Row():
fda_lbl_btn = gr.Button("📋 Drug Label", variant="primary")
fda_ae_btn = gr.Button("⚠️ Adverse Events (FAERS)", variant="secondary")
fda_ndc_btn = gr.Button("🏷️ NDC Registry", variant="secondary")
fda_lbl_out = gr.Markdown()
with gr.Row():
ae_summary = gr.Markdown()
ndc_summary = gr.Markdown()
with gr.Row():
ae_table = gr.DataFrame(label="⚠️ Adverse Reactions", wrap=True)
ndc_table = gr.DataFrame(label="🏷️ NDC Products", wrap=True)
fda_lbl_btn.click(fn=fda_label, inputs=[selector], outputs=[fda_lbl_out])
fda_ae_btn .click(fn=fda_adverse, inputs=[selector], outputs=[ae_table, ae_summary])
fda_ndc_btn.click(fn=fda_ndc, inputs=[selector], outputs=[ndc_table, ndc_summary])
gr.Markdown("⚠️ *FDA data covers US-approved drugs. Not all Bangladesh registry drugs appear here.*")
# ══════════════════════════════════════════════════════════
# TAB 4 — Dataset Dashboard
# ══════════════════════════════════════════════════════════
with gr.TabItem("📊 Dataset Dashboard"):
gr.Markdown("""
### 📊 Visual overview of the 53,581-drug Bangladesh registry
Seven charts covering distribution, dosage forms, manufacturers, and system profiles.
""")
dash_btn = gr.Button("🎨 Load Dashboard", variant="primary", size="lg")
kpi_plot = gr.Plot(label="📌 Key Metrics")
with gr.Row():
donut_plot = gr.Plot(label="① Drug Share by System")
bar_plot = gr.Plot(label="② Top 10 Dosage Forms")
with gr.Row():
grp_plot = gr.Plot(label="③ Dosage per System")
tree_plot = gr.Plot(label="④ Manufacturer Treemap")
with gr.Row():
radar_plot = gr.Plot(label="⑤ System Profile Radar")
mfr_plot = gr.Plot(label="⑥ Top 15 Manufacturers")
dash_btn.click(fn=make_dashboard, inputs=[],
outputs=[kpi_plot, donut_plot, bar_plot,
grp_plot, tree_plot, radar_plot, mfr_plot])
gr.HTML("""
<div style="text-align:center;padding:14px;color:#94a3b8;font-size:12px;
border-top:1px solid #e2e8f0;margin-top:12px;">
💊 Drug Recommender v4.0 · Master's Thesis ·
53,581 drugs · Symptoms + Medicines · TF-IDF · OpenFDA · Plotly Dashboard
</div>
""")
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)