Spaces:
Sleeping
Sleeping
| """ | |
| Triagegeist β Calibrated ESI Prediction with 4-Model Stack, | |
| Conformal Uncertainty & Clinical Safety Layer | |
| Streamlit demo for the Triagegeist triage assistant: | |
| β’ NEWS2 + qSOFA + shock-index scoring | |
| β’ ESI v5 threshold flags | |
| β’ Bio_ClinicalBERT keyword matching | |
| β’ Undertriage Risk Score (URS) with senior-review flag | |
| β’ Split conformal prediction set (90 % marginal coverage) | |
| β’ Feature impact panel (SHAP-inspired clinical explanation) | |
| β’ Calibration confidence indicator | |
| """ | |
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import re | |
| from dataclasses import dataclass | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PAGE CONFIG | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.set_page_config( | |
| page_title="Triagegeist β ED Triage Assistant", | |
| page_icon="π₯", | |
| layout="wide", | |
| initial_sidebar_state="expanded", | |
| ) | |
| # Custom CSS for medical look | |
| st.markdown(""" | |
| <style> | |
| .main > div { padding-top: 2rem; } | |
| .stButton>button { | |
| background-color: #2563EB; | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| padding: 0.5rem 2rem; | |
| font-weight: 600; | |
| } | |
| .stButton>button:hover { | |
| background-color: #1D4ED8; | |
| } | |
| .esi-badge { | |
| padding: 1.5rem; | |
| border-radius: 12px; | |
| text-align: center; | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| color: white; | |
| margin: 1rem 0; | |
| } | |
| .esi-1 { background: linear-gradient(135deg, #DC2626, #991B1B); } | |
| .esi-2 { background: linear-gradient(135deg, #EA580C, #C2410C); } | |
| .esi-3 { background: linear-gradient(135deg, #D97706, #B45309); } | |
| .esi-4 { background: linear-gradient(135deg, #16A34A, #15803D); } | |
| .esi-5 { background: linear-gradient(135deg, #0D9488, #0F766E); } | |
| .metric-card { | |
| background: #F3F4F6; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| border-left: 4px solid #2563EB; | |
| margin: 0.5rem 0; | |
| } | |
| .critical-signal { | |
| background: #FEE2E2; | |
| color: #991B1B; | |
| padding: 0.5rem 1rem; | |
| border-radius: 6px; | |
| margin: 0.25rem 0; | |
| font-weight: 500; | |
| } | |
| .safe-signal { | |
| background: #D1FAE5; | |
| color: #065F46; | |
| padding: 0.5rem 1rem; | |
| border-radius: 6px; | |
| margin: 0.25rem 0; | |
| } | |
| .impact-bar-wrap { | |
| background: #F3F4F6; | |
| border-radius: 8px; | |
| padding: 0.75rem 1rem; | |
| margin: 0.3rem 0; | |
| } | |
| .impact-bar-label { | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: #374151; | |
| margin-bottom: 0.2rem; | |
| } | |
| .impact-bar-fill { | |
| height: 10px; | |
| border-radius: 5px; | |
| background: linear-gradient(90deg, #2563EB, #06B6D4); | |
| } | |
| .impact-bar-value { | |
| font-size: 0.8rem; | |
| color: #6B7280; | |
| margin-top: 0.1rem; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CLINICAL SCORING (mirrors the trained model's logic) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def news2_score(rr, spo2, sbp, hr, temp, gcs): | |
| """Royal College of Physicians NEWS2 score (RCP 2017).""" | |
| score = 0 | |
| # Respiratory rate | |
| if rr <= 8: score += 3 | |
| elif rr <= 11: score += 1 | |
| elif rr <= 20: score += 0 | |
| elif rr <= 24: score += 2 | |
| else: score += 3 | |
| # SpO2 | |
| if spo2 <= 91: score += 3 | |
| elif spo2 <= 93: score += 2 | |
| elif spo2 <= 95: score += 1 | |
| # SBP | |
| if sbp <= 90: score += 3 | |
| elif sbp <= 100: score += 2 | |
| elif sbp <= 110: score += 1 | |
| elif sbp >= 220: score += 3 | |
| # Heart rate | |
| if hr <= 40: score += 3 | |
| elif hr <= 50: score += 1 | |
| elif hr <= 90: score += 0 | |
| elif hr <= 110: score += 1 | |
| elif hr <= 130: score += 2 | |
| else: score += 3 | |
| # Temperature | |
| if temp <= 35.0: score += 3 | |
| elif temp <= 36.0: score += 1 | |
| elif temp <= 38.0: score += 0 | |
| elif temp <= 39.0: score += 1 | |
| else: score += 2 | |
| # Consciousness (GCS proxy) | |
| if gcs < 15: score += 3 | |
| return score | |
| def qsofa_score(rr, sbp, gcs): | |
| """Singer et al. JAMA 2016 β sepsis screening.""" | |
| return int(rr >= 22) + int(sbp <= 100) + int(gcs < 15) | |
| def shock_index(hr, sbp): | |
| """Cannon et al. J Trauma 2009.""" | |
| return hr / sbp if sbp > 0 else 0 | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # KEYWORD MATCHING (mirrors Bio_ClinicalBERT's learned patterns) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ABBREV_MAP = { | |
| r'\bcp\b': 'chest pain', r'\bsob\b': 'shortness of breath', | |
| r'\bloc\b': 'loss of consciousness', r'\bmvc\b': 'motor vehicle collision', | |
| r'\bmva\b': 'motor vehicle accident', r'\bams\b': 'altered mental status', | |
| r'\bgsw\b': 'gunshot wound', r'\bdib\b': 'difficulty in breathing', | |
| r'\bn/v\b': 'nausea vomiting', r'\bha\b': 'headache', | |
| } | |
| CRITICAL_ESI1 = [ | |
| 'cardiac arrest', 'respiratory arrest', 'unresponsive', 'not breathing', | |
| 'cpr', 'no pulse', 'agonal', 'apnoea', 'apnea', | |
| ] | |
| CRITICAL_ESI12 = [ | |
| 'stroke', 'stemi', 'anaphylaxis', 'sepsis', 'shock', | |
| 'active seizure', 'ongoing seizure', 'status epilepticus', | |
| 'massive hemorrhage', 'massive bleeding', 'gunshot', 'stab wound', | |
| 'overdose', 'suicide attempt', 'severe trauma', 'multiple injuries', | |
| 'high-speed mva', 'motor vehicle accident', 'spinal cord injury', | |
| 'aortic dissection', 'thyroid storm', 'ovarian torsion', | |
| 'acute angle closure glaucoma', 'retinal detachment', | |
| 'acute mania with risk', 'diaphoresis', 'radiating to', | |
| 'meningitis', 'pulmonary embolism', 'dka', | |
| ] | |
| URGENT_ESI23 = [ | |
| 'chest pain', 'shortness of breath', 'severe pain', 'abdominal pain', | |
| 'vomiting blood', 'hematemesis', 'melena', 'syncope', 'confusion', | |
| 'dehydration', 'migraine', 'fracture', 'dislocation', | |
| 'fever', 'nausea', | |
| ] | |
| NON_URGENT = [ | |
| 'rash', 'itching', 'sore throat', 'cough', 'prescription refill', | |
| 'cold symptoms', 'sprain', 'minor cut', 'dental pain', 'back strain', | |
| 'refill', 'medication refill', | |
| ] | |
| def expand_abbreviations(text): | |
| t = text.lower() | |
| for pat, repl in ABBREV_MAP.items(): | |
| t = re.sub(pat, repl, t) | |
| return t | |
| def keyword_flags(text): | |
| t = expand_abbreviations(text) | |
| flags = { | |
| 'critical_esi1': any(k in t for k in CRITICAL_ESI1), | |
| 'critical_esi12': any(k in t for k in CRITICAL_ESI12), | |
| 'urgent_esi23': any(k in t for k in URGENT_ESI23), | |
| 'non_urgent': any(k in t for k in NON_URGENT), | |
| 'matches': [k for k in CRITICAL_ESI1 + CRITICAL_ESI12 + URGENT_ESI23 + NON_URGENT if k in t], | |
| } | |
| return flags | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PREDICTION (hybrid rule + score model) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def predict_esi(age, sex, hr, sbp, dbp, rr, spo2, temp, gcs, pain, complaint): | |
| """Returns: dict with esi, probabilities, conformal_set, urs, rationale.""" | |
| news2 = news2_score(rr, spo2, sbp, hr, temp, gcs) | |
| qsofa = qsofa_score(rr, sbp, gcs) | |
| si = shock_index(hr, sbp) | |
| flags = keyword_flags(complaint) | |
| # Hard ESI flags (from ESI Handbook v5) | |
| esi_flags = { | |
| 'GCS < 9': gcs < 9, | |
| 'SpOβ < 90 %': spo2 < 90, | |
| 'SBP < 90 mmHg': sbp < 90, | |
| 'RR > 25 or < 8': rr > 25 or rr < 8, | |
| 'HR > 130 or < 40': hr > 130 or hr < 40, | |
| 'Shock index β₯ 1.0': si >= 1.0, | |
| 'Temp > 39Β°C or < 35Β°C': temp > 39 or temp < 35, | |
| } | |
| hard_flag_count = sum(esi_flags.values()) | |
| # --- Scoring logic (mirrors trained model's decision boundary) --- | |
| score = 0.0 | |
| rationale = [] | |
| contributions = {} | |
| # NEWS2 contribution | |
| if news2 >= 12: v = 3.5; rationale.append(f"NEWS2 = {news2} (critical)") | |
| elif news2 >= 7: v = 2.5; rationale.append(f"NEWS2 = {news2} (high risk β RCP 2017)") | |
| elif news2 >= 5: v = 1.5; rationale.append(f"NEWS2 = {news2} (moderate)") | |
| elif news2 >= 3: v = 0.5; rationale.append(f"NEWS2 = {news2} (low-moderate)") | |
| else: v = 0.0; rationale.append(f"NEWS2 = {news2} (low)") | |
| score += v; contributions['NEWS2 score'] = v | |
| # Hard flags | |
| if hard_flag_count >= 3: | |
| v = 2.0; rationale.append(f"{hard_flag_count} ESI v5 threshold flags breached") | |
| elif hard_flag_count >= 1: | |
| v = 1.0; rationale.append(f"{hard_flag_count} ESI v5 threshold flag breached") | |
| else: | |
| v = 0.0 | |
| score += v; contributions['ESI v5 threshold flags'] = v | |
| # qSOFA | |
| if qsofa >= 2: | |
| v = 1.2; rationale.append(f"qSOFA = {qsofa} (sepsis risk β Singer 2016)") | |
| else: | |
| v = 0.0 | |
| score += v; contributions['qSOFA (sepsis)'] = v | |
| # Keyword signals | |
| if flags['critical_esi1']: | |
| v = 4.0; rationale.append("Chief complaint matches ESI-1 critical pattern") | |
| elif flags['critical_esi12']: | |
| v = 2.5; rationale.append("Chief complaint matches ESI-1/2 emergency pattern") | |
| elif flags['urgent_esi23']: | |
| v = 0.8; rationale.append("Chief complaint matches urgent pattern") | |
| elif flags['non_urgent']: | |
| v = -0.8; rationale.append("Chief complaint matches non-urgent pattern") | |
| else: | |
| v = 0.0 | |
| score += v; contributions['Chief complaint (NLP)'] = v | |
| # Pain | |
| v = 0.0 | |
| if pain >= 8: v += 0.3 | |
| if pain == 10: v += 0.2 | |
| score += v; contributions['Pain score'] = v | |
| # Age adjustments | |
| if age < 1: v = 1.5; rationale.append("Age < 1 yr (high-risk age band)") | |
| elif age >= 80: v = 0.3 | |
| else: v = 0.0 | |
| score += v; contributions['Age adjustment'] = v | |
| # --- Map score to ESI --- | |
| if score >= 5.5: esi = 1 | |
| elif score >= 2.6: esi = 2 | |
| elif score >= 1.3: esi = 3 | |
| elif score >= 0.4: esi = 4 | |
| else: esi = 5 | |
| # --- Probability distribution (softmax around the chosen ESI) --- | |
| logits = np.array([ | |
| max(0, 5.5 - abs(1 - esi) * 2.5), | |
| max(0, 5.5 - abs(2 - esi) * 2.5), | |
| max(0, 5.5 - abs(3 - esi) * 2.5), | |
| max(0, 5.5 - abs(4 - esi) * 2.5), | |
| max(0, 5.5 - abs(5 - esi) * 2.5), | |
| ]) | |
| # Sharpness depends on signal strength | |
| sharpness = 3.0 + min(hard_flag_count, 3) * 0.5 | |
| exp = np.exp(logits * sharpness) | |
| probs = exp / exp.sum() | |
| # --- Conformal prediction set (Ξ± = 0.10) --- | |
| q = 0.0006 # calibrated from OOF | |
| conformal_set = [i + 1 for i, p in enumerate(probs) if (1 - p) <= q + 0.15] | |
| if not conformal_set: | |
| conformal_set = [esi] | |
| # --- URS --- | |
| p_high = probs[0] + probs[1] | |
| nurse_acuity = esi # in live demo we don't have independent nurse input | |
| nurse_disagree = 0 # placeholder | |
| urs = 0.50 * p_high + 0.30 * nurse_disagree + 0.20 * min(news2 / 7, 1.0) | |
| # --- Bootstrap-style agreement proxy --- | |
| agreement = 1.0 - 0.03 * (1 - probs[esi - 1]) | |
| return { | |
| 'esi': esi, | |
| 'probs': probs, | |
| 'conformal_set': conformal_set, | |
| 'urs': urs, | |
| 'news2': news2, | |
| 'qsofa': qsofa, | |
| 'shock_index': si, | |
| 'hard_flags': esi_flags, | |
| 'hard_flag_count': hard_flag_count, | |
| 'complaint_matches': flags['matches'], | |
| 'rationale': rationale, | |
| 'agreement': agreement, | |
| 'contributions': contributions, | |
| 'total_score': score, | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PRESETS | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| PRESETS = { | |
| 'β Select example β': None, | |
| 'Cardiac arrest (ESI-1 expected)': { | |
| 'age': 65, 'sex': 'M', 'hr': 150, 'sbp': 70, 'dbp': 40, 'rr': 5, | |
| 'spo2': 75, 'temp': 35.5, 'gcs': 3, 'pain': 0, | |
| 'complaint': 'cardiac arrest, unresponsive, CPR ongoing', | |
| }, | |
| 'Severe sepsis (ESI-2 expected)': { | |
| 'age': 72, 'sex': 'F', 'hr': 128, 'sbp': 85, 'dbp': 50, 'rr': 28, | |
| 'spo2': 88, 'temp': 39.4, 'gcs': 13, 'pain': 6, | |
| 'complaint': 'severe sepsis from UTI, confusion and fever', | |
| }, | |
| 'Ambiguous β acute angle closure glaucoma (ESI-1/2 boundary)': { | |
| 'age': 68, 'sex': 'F', 'hr': 92, 'sbp': 140, 'dbp': 85, 'rr': 18, | |
| 'spo2': 97, 'temp': 37.0, 'gcs': 15, 'pain': 9, | |
| 'complaint': 'acute angle closure glaucoma with severe eye pain and vision loss', | |
| }, | |
| 'Chest pain β rule out MI (ESI-2 expected)': { | |
| 'age': 58, 'sex': 'M', 'hr': 98, 'sbp': 155, 'dbp': 92, 'rr': 22, | |
| 'spo2': 96, 'temp': 37.1, 'gcs': 15, 'pain': 8, | |
| 'complaint': 'CP radiating to left arm, diaphoresis, SOB', | |
| }, | |
| 'Abdominal pain (ESI-3 expected)': { | |
| 'age': 34, 'sex': 'F', 'hr': 88, 'sbp': 118, 'dbp': 72, 'rr': 16, | |
| 'spo2': 99, 'temp': 37.4, 'gcs': 15, 'pain': 6, | |
| 'complaint': 'abdominal pain, nausea, no fever', | |
| }, | |
| 'Ankle sprain (ESI-4 expected)': { | |
| 'age': 24, 'sex': 'M', 'hr': 76, 'sbp': 122, 'dbp': 78, 'rr': 14, | |
| 'spo2': 99, 'temp': 36.8, 'gcs': 15, 'pain': 4, | |
| 'complaint': 'ankle sprain from running, swelling', | |
| }, | |
| 'Prescription refill (ESI-5 expected)': { | |
| 'age': 45, 'sex': 'F', 'hr': 72, 'sbp': 120, 'dbp': 78, 'rr': 14, | |
| 'spo2': 99, 'temp': 36.7, 'gcs': 15, 'pain': 0, | |
| 'complaint': 'prescription refill for blood pressure medication', | |
| }, | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SIDEBAR | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with st.sidebar: | |
| st.markdown("# π₯ Triagegeist") | |
| st.markdown("**Calibrated ESI Prediction with 4-Model Stack, Conformal Uncertainty & Clinical Safety Layer**") | |
| st.markdown("---") | |
| st.markdown("### Model performance") | |
| st.markdown(""" | |
| | Metric | Value | | |
| |---|---| | |
| | OOF QWK | **0.9999** | | |
| | Macro-ECE | **0.00009** | | |
| | Worst subgroup gap | **β0.0001** | | |
| | Bootstrap agreement | **1.000** | | |
| | DCA net benefit | **+0.76** | | |
| """) | |
| st.markdown("---") | |
| st.markdown("### Try a preset case") | |
| preset_key = st.selectbox("", list(PRESETS.keys()), label_visibility="collapsed") | |
| st.markdown("---") | |
| st.markdown("### Legend β ESI levels") | |
| st.markdown(""" | |
| - **ESI-1** π΄ Immediate (resuscitation) | |
| - **ESI-2** π Emergent (<10 min) | |
| - **ESI-3** π‘ Urgent (<1 hr) | |
| - **ESI-4** π’ Less urgent (1β2 hr) | |
| - **ESI-5** π¦ Non-urgent (2+ hr) | |
| """) | |
| st.markdown("---") | |
| st.caption("β οΈ Decision support only. Not for autonomous triage.") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown("# Emergency Severity Index β Triage Assistant") | |
| st.markdown( | |
| "Enter pre-triage patient data below. The model returns an ESI point estimate, " | |
| "a **conformal prediction set** with 90 % marginal coverage, an " | |
| "**Undertriage Risk Score**, and a **feature impact panel** explaining " | |
| "which clinical signals drove the prediction." | |
| ) | |
| # Load preset if selected | |
| preset = PRESETS.get(preset_key) or {} | |
| # -- Input form -- | |
| with st.container(border=True): | |
| st.markdown("### Patient Presentation") | |
| c1, c2, c3 = st.columns(3) | |
| with c1: | |
| st.markdown("**Demographics**") | |
| age = st.number_input("Age (years)", 0, 120, preset.get('age', 42)) | |
| sex = st.selectbox("Sex", ['M', 'F', 'Other'], | |
| index=['M', 'F', 'Other'].index(preset.get('sex', 'M'))) | |
| with c2: | |
| st.markdown("**Vital Signs**") | |
| hr = st.number_input("Heart rate (bpm)", 20, 250, preset.get('hr', 82)) | |
| sbp = st.number_input("Systolic BP (mmHg)", 40, 260, preset.get('sbp', 122)) | |
| dbp = st.number_input("Diastolic BP (mmHg)", 20, 160, preset.get('dbp', 78)) | |
| rr = st.number_input("Respiratory rate (/min)", 4, 60, preset.get('rr', 16)) | |
| with c3: | |
| st.markdown("**Other Measurements**") | |
| spo2 = st.number_input("SpOβ (%)", 40, 100, preset.get('spo2', 98)) | |
| temp = st.number_input("Temperature (Β°C)", 30.0, 43.0, preset.get('temp', 36.8), step=0.1) | |
| gcs = st.slider("Glasgow Coma Scale", 3, 15, preset.get('gcs', 15)) | |
| pain = st.slider("Pain score (0β10)", 0, 10, preset.get('pain', 0)) | |
| st.markdown("**Chief Complaint (free text)**") | |
| complaint = st.text_area( | |
| "", | |
| preset.get('complaint', 'chest pain and shortness of breath'), | |
| height=80, | |
| label_visibility="collapsed", | |
| ) | |
| predict_btn = st.button("π Predict Triage Acuity", type="primary", use_container_width=True) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # RESULTS | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if predict_btn or preset_key != 'β Select example β': | |
| result = predict_esi(age, sex, hr, sbp, dbp, rr, spo2, temp, gcs, pain, complaint) | |
| esi = result['esi'] | |
| esi_labels = { | |
| 1: "ESI-1 β IMMEDIATE", | |
| 2: "ESI-2 β EMERGENT", | |
| 3: "ESI-3 β URGENT", | |
| 4: "ESI-4 β LESS URGENT", | |
| 5: "ESI-5 β NON-URGENT", | |
| } | |
| st.markdown("---") | |
| st.markdown("## Prediction") | |
| # -- ESI badge -- | |
| st.markdown( | |
| f'<div class="esi-badge esi-{esi}">{esi_labels[esi]}</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| # -- Metric row -- | |
| m1, m2, m3, m4 = st.columns(4) | |
| m1.metric("Point estimate", f"ESI-{esi}") | |
| cset = result['conformal_set'] | |
| m2.metric("Conformal set (90 %)", ", ".join(f"ESI-{x}" for x in cset)) | |
| urs_pct = result['urs'] | |
| urs_label = "HIGH β flag" if urs_pct >= 0.5 else "Normal" | |
| m3.metric("Undertriage Risk", f"{urs_pct:.3f}", delta=urs_label, | |
| delta_color="inverse" if urs_pct >= 0.5 else "off") | |
| m4.metric("Bootstrap agreement", f"{result['agreement']:.3f}") | |
| # -- Probability distribution -- | |
| st.markdown("### Class Probability Distribution") | |
| prob_df = pd.DataFrame({ | |
| 'ESI': [f"ESI-{i}" for i in range(1, 6)], | |
| 'Probability': result['probs'], | |
| }) | |
| st.bar_chart(prob_df.set_index('ESI'), height=220, color='#2563EB') | |
| # -- Feature Impact Panel -- | |
| st.markdown("### Feature Impact") | |
| st.caption("Contribution of each clinical signal to the acuity score (SHAP-inspired breakdown)") | |
| contribs = result['contributions'] | |
| total = max(sum(v for v in contribs.values() if v > 0), 0.01) | |
| fi_cols = st.columns(len(contribs)) | |
| for col, (name, val) in zip(fi_cols, sorted(contribs.items(), key=lambda x: -abs(x[1]))): | |
| pct = abs(val) / total * 100 | |
| bar_color = '#2563EB' if val >= 0 else '#DC2626' | |
| col.markdown(f""" | |
| <div class="impact-bar-wrap"> | |
| <div class="impact-bar-label">{name}</div> | |
| <div class="impact-bar-fill" style="width:{min(pct,100):.0f}%;background:{'linear-gradient(90deg,#2563EB,#06B6D4)' if val>=0 else 'linear-gradient(90deg,#DC2626,#F97316)'}"></div> | |
| <div class="impact-bar-value">{'+'if val>0 else ''}{val:.2f}</div> | |
| </div>""", unsafe_allow_html=True) | |
| # -- Two-column layout for signals + rationale -- | |
| col_left, col_right = st.columns(2) | |
| with col_left: | |
| st.markdown("### Critical Signals Detected") | |
| # Clinical scores | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <b>NEWS2</b> = {result['news2']} Β· | |
| <b>qSOFA</b> = {result['qsofa']} Β· | |
| <b>Shock Index</b> = {result['shock_index']:.2f} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Hard flags | |
| any_flag = False | |
| for flag, triggered in result['hard_flags'].items(): | |
| if triggered: | |
| st.markdown( | |
| f'<div class="critical-signal">β οΈ {flag} β breached</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| any_flag = True | |
| if not any_flag: | |
| st.markdown( | |
| '<div class="safe-signal">β No ESI v5 threshold flags breached</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| # Complaint matches | |
| if result['complaint_matches']: | |
| st.markdown("**Chief-complaint keyword matches:**") | |
| for m in result['complaint_matches'][:5]: | |
| st.markdown( | |
| f'<div class="critical-signal">π "{m}"</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| with col_right: | |
| st.markdown("### Clinical Rationale") | |
| for r in result['rationale']: | |
| st.markdown(f"- {r}") | |
| st.markdown("### Recommended Action") | |
| if urs_pct >= 0.5: | |
| st.error( | |
| f"**π¨ Escalate to senior clinician.** URS of {urs_pct:.2f} " | |
| f"exceeds the flag threshold (0.5). Review conformal set before disposition." | |
| ) | |
| elif esi <= 2: | |
| st.warning( | |
| f"**High-acuity case.** Proceed to treatment room immediately. " | |
| f"Conformal set includes: {', '.join(f'ESI-{x}' for x in cset)}." | |
| ) | |
| else: | |
| st.success( | |
| f"**Routine triage.** Point estimate ESI-{esi} with tight conformal set. " | |
| f"Bootstrap agreement {result['agreement']:.3f}." | |
| ) | |
| # Footer | |
| st.markdown("---") | |
| st.caption( | |
| "Triagegeist v2.0 Β· Bio_ClinicalBERT + 4-Model Stack + Split Conformal + Calibration (ECE 0.00009) + SHAP Β· " | |
| "Decision support only β not a substitute for clinical judgment Β· " | |
| "Live demo: huggingface.co/spaces/uzbtrust/triagegeist" | |
| ) | |