import os os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" """Sentinel — causal sepsis early-warning demo (Gradio).""" import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np import pandas as pd import gradio as gr from sentinel_core import SepsisPredictor, derive_alerts, LABEL THRESHOLD, PERSISTENCE, REFRACTORY = 0.70, 2, 6 predictor = SepsisPredictor("sentinel_causal.pt", "scaler.csv", threshold=THRESHOLD) EXAMPLES = { "Septic patient (deteriorates to sepsis)": "examples/septic_patient.psv", "Non-septic patient (stable)": "examples/nonseptic_patient.psv", } def _run(df): onset = None if LABEL in df.columns and (df[LABEL] == 1).any(): onset = int((df[LABEL] == 1).argmax()) + 6 # label shifted 6h pre-onset feats = df.drop(columns=[LABEL]) if LABEL in df.columns else df risk = predictor.predict(feats) alert_hours, _ = derive_alerts(risk, THRESHOLD, PERSISTENCE, REFRACTORY) return risk, alert_hours, onset def _plot(risk, alert_hours, onset): hours = list(range(len(risk))) fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(hours, risk, color="#BD3F36", lw=2, label="Sepsis risk") ax.axhline(THRESHOLD, color="grey", ls="--", lw=1, label=f"Threshold ({THRESHOLD})") if onset is not None and onset <= len(risk): ax.axvline(onset, color="black", ls=":", lw=1.5, label=f"Onset (h{onset})") if alert_hours: ax.scatter(alert_hours, [risk[h] for h in alert_hours], color="#BD3F36", s=90, marker="v", zorder=5, label="Alert fired") ax.fill_between(hours, 0, risk, color="#BD3F36", alpha=0.08) ax.set(xlabel="ICU hour", ylabel="Predicted sepsis risk", ylim=(0, 1), title="Streaming sepsis risk (causal, real-time simulation)") ax.legend(loc="upper left", fontsize=9) fig.tight_layout() return fig def analyze(example_choice, uploaded): if uploaded is not None: df = pd.read_csv(uploaded.name, sep="|") source = "uploaded patient" else: df = pd.read_csv(EXAMPLES[example_choice], sep="|") source = example_choice risk, alert_hours, onset = _run(df) fig = _plot(risk, alert_hours, onset) peak = float(np.max(risk)) first = alert_hours[0] if alert_hours else None lines = [f"**{source}** — {len(risk)} ICU hours"] lines.append(f"Peak risk: {peak:.2f}") if onset is not None: lines.append(f"Documented clinical onset: ~hour {onset}") if first is not None: lead = f", ~{onset - first}h before onset" if onset else "" lines.append(f"First alert: hour {first}{lead}") lines.append(f"Total alerts: {len(alert_hours)} (at hours {alert_hours})") else: lines.append("No alerts fired (risk stayed below the sustained-alert policy).") return fig, "\n\n".join(lines) with gr.Blocks(title="Sentinel — Sepsis Early Warning") as demo: gr.Markdown( "# Sentinel — Causal Sepsis Early Warning\n" "Predicts hour-by-hour sepsis risk from streaming ICU vitals (PhysioNet 2019), " "with a fatigue-aware alerting policy. Risk at each hour uses only data up to " "that hour (a true causal simulation).\n\n" "*Research demonstration on public data. **Not for clinical use.***" ) with gr.Row(): with gr.Column(scale=1): choice = gr.Dropdown(list(EXAMPLES.keys()), value=list(EXAMPLES.keys())[0], label="Example patient") upload = gr.File(label="…or upload a PhysioNet .psv", file_types=[".psv"]) btn = gr.Button("Analyze", variant="primary") with gr.Column(scale=2): plot = gr.Plot(label="Risk trajectory") summary = gr.Markdown() btn.click(analyze, [choice, upload], [plot, summary]) demo.load(analyze, [choice, upload], [plot, summary]) if __name__ == "__main__": demo.launch()