evb-test-new-UI / app.py
mmrech's picture
Update app.py
fcfbe6c verified
"""
EVB Prognosis Calculator - Production Gradio Application
Integrates trained Random Forest model from research paper
Research Paper: Rech MM, et al. Development and prospective validation of a machine learning
model to predict mortality in cirrhosis with esophageal variceal bleeding.
World J Hepatol 2025; In press
Author: Matheus Machado Rech, MD
Institution: Universidade de Caxias do Sul, Brazil
"""
import gradio as gr
import numpy as np
import pandas as pd
from datetime import datetime
import json
import math
import joblib
import warnings
warnings.filterwarnings('ignore')
# ============================================================================
# LOAD TRAINED MODEL
# ============================================================================
import os
# Try multiple locations for model files
model_paths = [
'calibrated_random_forest_model_updated_v1_1.joblib', # Same directory
'./calibrated_random_forest_model_updated_v1_1.joblib',
'/mnt/user-data/outputs/calibrated_random_forest_model_updated_v1_1.joblib', # Claude outputs
os.path.join(os.path.dirname(__file__), 'calibrated_random_forest_model_updated_v1_1.joblib'),
]
preprocessor_paths = [
'preprocessor_v1_0.joblib',
'./preprocessor_v1_0.joblib',
'/mnt/user-data/outputs/preprocessor_v1_0.joblib', # Claude outputs
os.path.join(os.path.dirname(__file__), 'preprocessor_v1_0.joblib'),
]
MODEL = None
PREPROCESSOR = None
MODEL_LOADED = False
for model_path in model_paths:
if os.path.exists(model_path):
try:
MODEL = joblib.load(model_path)
print(f"✓ Model loaded from: {model_path}")
break
except Exception as e:
print(f"✗ Failed to load model from {model_path}: {e}")
continue
for prep_path in preprocessor_paths:
if os.path.exists(prep_path):
try:
PREPROCESSOR = joblib.load(prep_path)
print(f"✓ Preprocessor loaded from: {prep_path}")
break
except Exception as e:
print(f"✗ Failed to load preprocessor from {prep_path}: {e}")
continue
if MODEL is not None and PREPROCESSOR is not None:
MODEL_LOADED = True
print("✓✓ Trained Random Forest model loaded successfully")
else:
print("⚠ Warning: Could not load trained model files")
print(" Make sure calibrated_random_forest_model_updated_v1_1.joblib and preprocessor_v1_0.joblib")
print(" are in the same directory as this script")
print(" Falling back to heuristic model for demonstration")
MODEL_LOADED = False
# ============================================================================
# FEATURE PREPARATION FOR ML MODEL
# ============================================================================
def prepare_features_for_model(
age, sex, etiology, omeprazole, hrs, spironolactone, furosemide,
beta_blocker, dialysis, pvt, ascites, hcc, encephalopathy,
albumin, total_bili, inr, creatinine, platelet, ast, alt,
hemoglobin, sodium, potassium, time_to_endo, varix_size,
red_wall_marks, rupture_points, active_bleeding, terlipressin_dose
):
"""
Prepare features matching the 36-variable training dataset
Features from paper (in order):
Age, sex, cause, omeprazole, HRS, spironolactone, furosemide, beta-blocker,
hemodialysis, PVT, ascites, HCC, encephalopathy, albumin, total bilirubin,
direct bilirubin, PT, INR, creatinine, platelet, AST, ALT, hemoglobin,
hematocrit, leucocytes, sodium, potassium, time-to-endoscopy, varix size,
red wall marks, rupture points, active bleeding, therapy, terlipressin doses
"""
# Create feature dictionary
features = {
'age': age,
'sex': 1 if sex == "Male" else 0,
# Etiology (one-hot encoded)
'etiology_alcohol': 1 if etiology == "Alcohol" else 0,
'etiology_hcv': 1 if etiology == "HCV" else 0,
'etiology_hbv': 1 if etiology == "HBV" else 0,
'etiology_nash': 1 if etiology == "NASH" else 0,
'etiology_mixed': 1 if etiology == "Alcohol + HCV" else 0,
'etiology_other': 1 if etiology == "Other" else 0,
# Medications
'omeprazole': 1 if omeprazole == "Yes" else 0,
'spironolactone': 1 if spironolactone == "Yes" else 0,
'furosemide': 1 if furosemide == "Yes" else 0,
'beta_blocker': 1 if beta_blocker == "Yes" else 0,
# Clinical conditions
'hepatorenal_syndrome': 1 if hrs == "Yes" else 0,
'hemodialysis': 1 if dialysis == "Yes" else 0,
'portal_vein_thrombosis': 1 if pvt == "Yes" else 0,
'hepatocellular_carcinoma': 1 if hcc == "Yes" else 0,
# Ascites (ordinal: 0=none, 1=mild, 2=severe)
'ascites': 0 if ascites == "None" else (1 if ascites == "Mild" else 2),
# Encephalopathy (ordinal: 0=none, 1=mild, 2=severe)
'encephalopathy': 0 if encephalopathy == "None" else (1 if encephalopathy == "Mild" else 2),
# Laboratory values
'albumin': albumin,
'total_bilirubin': total_bili,
'inr': inr,
'creatinine': creatinine,
'platelet': platelet,
'ast': ast,
'alt': alt,
'hemoglobin': hemoglobin,
'sodium': sodium,
'potassium': potassium,
# Bleeding characteristics
'time_to_endoscopy': time_to_endo,
'varix_size': varix_size, # 1=small, 2=medium, 3=large
'red_wall_marks': 1 if red_wall_marks == "Yes" else 0,
'rupture_points': 1 if rupture_points == "Yes" else 0,
'active_bleeding': 1 if active_bleeding == "Yes" else 0,
# Treatment
'terlipressin_dose': terlipressin_dose
}
return pd.DataFrame([features])
# ============================================================================
# ML MODEL PREDICTION
# ============================================================================
def predict_with_ml_model(features_df):
"""
Use trained Random Forest model for prediction
Returns:
mortality_probability (float): 1-year mortality probability
"""
try:
# Preprocess features
X_processed = PREPROCESSOR.transform(features_df)
# Get probability prediction
mortality_prob = MODEL.predict_proba(X_processed)[0, 1]
return float(mortality_prob)
except Exception as e:
print(f"Error in ML prediction: {e}")
return None
# ============================================================================
# CLINICAL SCORE CALCULATIONS
# ============================================================================
def calculate_meld(bilirubin: float, inr: float, creatinine: float, dialysis: bool) -> int:
"""Calculate MELD score"""
b = max(bilirubin, 1.0)
i = max(inr, 1.0)
c = 4.0 if dialysis else min(max(creatinine, 1.0), 4.0)
meld = 3.78 * math.log(b) + 11.2 * math.log(i) + 9.57 * math.log(c) + 6.43
return max(min(round(meld), 40), 6)
def calculate_meld_na(meld: int, sodium: float) -> int:
"""Calculate MELD-Na"""
na = max(min(sodium, 137), 125)
delta = 137 - na
meld_na = meld + 1.32 * delta - (0.033 * meld * delta)
return max(min(round(meld_na), 40), 6)
def calculate_child_pugh(bilirubin: float, albumin: float, inr: float,
ascites: str, encephalopathy: str) -> tuple:
"""Calculate Child-Pugh"""
points = 0
points += 1 if bilirubin < 2 else (2 if bilirubin <= 3 else 3)
points += 1 if albumin > 3.5 else (2 if albumin >= 2.8 else 3)
points += 1 if inr < 1.7 else (2 if inr <= 2.3 else 3)
ascites_map = {'none': 1, 'mild': 2, 'severe': 3}
points += ascites_map.get(ascites, 1)
enc_map = {'none': 1, 'mild': 2, 'severe': 3}
points += enc_map.get(encephalopathy, 1)
cp_class = 'A' if points <= 6 else ('B' if points <= 9 else 'C')
return points, cp_class
def calculate_albi(albumin: float, bilirubin: float) -> tuple:
"""Calculate ALBI"""
score = (math.log10(bilirubin) * 0.66) + (albumin * -0.085)
grade = '1' if score <= -2.60 else ('2' if score <= -1.39 else '3')
return round(score, 2), grade
# ============================================================================
# HEURISTIC FALLBACK (if ML model fails)
# ============================================================================
def estimate_mortality_heuristic(meld_na, age, ascites, encephalopathy,
active_bleeding, time_to_endo, hrs, cp_class):
"""Heuristic model as fallback"""
base_risk = (meld_na - 6) / 34 * 0.65
age_weight = 0.08 if age > 65 else (0.12 if age > 75 else 0)
ascites_weights = {'none': 0, 'mild': 0.06, 'severe': 0.12}
enc_weights = {'none': 0, 'mild': 0.07, 'severe': 0.14}
total_risk = (
base_risk + age_weight +
ascites_weights.get(ascites, 0) +
enc_weights.get(encephalopathy, 0) +
(0.08 if active_bleeding == "Yes" else 0) +
(min((time_to_endo - 12) / 36, 1) * 0.05 if time_to_endo > 12 else 0) +
(0.12 if hrs == "Yes" else 0) +
{'A': 0, 'B': 0.04, 'C': 0.08}.get(cp_class, 0)
)
return max(min(total_risk, 0.98), 0.02)
# ============================================================================
# MAIN CALCULATION FUNCTION
# ============================================================================
def calculate_evb_risk(
# Demographics
age, sex, etiology,
# Clinical
ascites, encephalopathy, hrs, dialysis, pvt, hcc,
# Medications
omeprazole, spironolactone, furosemide, beta_blocker,
# Lab values
total_bili, albumin, inr, creatinine, sodium, potassium,
platelet, ast, alt, hemoglobin,
# Bleeding details
active_bleeding, time_to_endo, varix_size, red_wall_marks,
rupture_points, terlipressin_dose
):
"""Main calculation with ML model integration"""
# Calculate clinical scores
meld = calculate_meld(total_bili, inr, creatinine, dialysis == "Yes")
meld_na = calculate_meld_na(meld, sodium)
cp_score, cp_class = calculate_child_pugh(total_bili, albumin, inr,
ascites.lower(), encephalopathy.lower())
albi_score, albi_grade = calculate_albi(albumin, total_bili)
# Prepare features for ML model
features_df = prepare_features_for_model(
age, sex, etiology, omeprazole, hrs, spironolactone, furosemide,
beta_blocker, dialysis, pvt, ascites, hcc, encephalopathy,
albumin, total_bili, inr, creatinine, platelet, ast, alt,
hemoglobin, sodium, potassium, time_to_endo, varix_size,
red_wall_marks, rupture_points, active_bleeding, terlipressin_dose
)
# Get mortality prediction
if MODEL_LOADED:
mortality_prob = predict_with_ml_model(features_df)
model_type = "Trained Random Forest (AUC=0.91)"
if mortality_prob is None:
# Fallback if prediction fails
mortality_prob = estimate_mortality_heuristic(
meld_na, age, ascites.lower(), encephalopathy.lower(),
active_bleeding, time_to_endo, hrs, cp_class
)
model_type = "Heuristic Fallback"
else:
mortality_prob = estimate_mortality_heuristic(
meld_na, age, ascites.lower(), encephalopathy.lower(),
active_bleeding, time_to_endo, hrs, cp_class
)
model_type = "Heuristic Model (Demo)"
# Risk categorization
if mortality_prob < 0.30:
risk_category = "Low Risk"
risk_color = "#00f2fe"
bg_gradient = "rgba(0, 242, 254, 0.1)"
elif mortality_prob < 0.60:
risk_category = "Moderate Risk"
risk_color = "#f9d423"
bg_gradient = "rgba(249, 212, 35, 0.1)"
else:
risk_category = "High Risk"
risk_color = "#ff4b2b"
bg_gradient = "rgba(255, 75, 43, 0.1)"
# Create result display
results_html = f"""
<div style='position: relative; padding: 40px; background: linear-gradient(135deg, {bg_gradient}, transparent);
border: 2px solid rgba(255,255,255,0.1); border-radius: 24px; backdrop-filter: blur(20px);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);'>
<div style='text-align: center; margin-bottom: 32px;'>
<svg width="200" height="200" style='transform: rotate(-90deg);'>
<circle cx="100" cy="100" r="90" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="12"/>
<circle cx="100" cy="100" r="90" fill="none" stroke="{risk_color}" stroke-width="12"
stroke-dasharray="{mortality_prob * 565} 565" stroke-linecap="round"/>
</svg>
<div style='margin-top: -140px;'>
<div style='font-size: 3.5rem; font-weight: 300; color: {risk_color};
text-shadow: 0 0 20px {risk_color}50;'>
{mortality_prob*100:.1f}%
</div>
<div style='font-size: 0.9rem; color: #999; margin-top: 8px; letter-spacing: 1px;'>
1-YEAR MORTALITY
</div>
<div style='font-size: 0.75rem; color: #666; margin-top: 4px;'>
{model_type}
</div>
</div>
</div>
<div style='text-align: center;'>
<span style='display: inline-block; padding: 12px 32px; background: {risk_color};
color: {"#000" if risk_category != "High Risk" else "#fff"};
border-radius: 24px; font-weight: 700; font-size: 1.1rem;
letter-spacing: 1px; text-transform: uppercase;
box-shadow: 0 4px 16px {risk_color}40;'>
{risk_category}
</span>
</div>
</div>
"""
# Scores table
meld_mort = "<10%" if meld < 10 else ("10-19%" if meld < 20 else ("20-50%" if meld < 30 else ">50%"))
scores_html = f"""
<div style='margin-top: 24px; padding: 24px; background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.1); border-radius: 16px;'>
<h3 style='color: #00d2ff; font-size: 0.85rem; text-transform: uppercase;
letter-spacing: 2px; margin-bottom: 16px;'>
📊 Clinical Scores
</h3>
<table style='width: 100%; color: #fff; font-size: 0.9rem;'>
<tr style='border-bottom: 1px solid rgba(255,255,255,0.05);'>
<td style='padding: 12px 0; font-weight: 600;'>MELD</td>
<td style='padding: 12px 0; color: #00d2ff; font-weight: 700; font-size: 1.2rem;'>{meld}</td>
<td style='padding: 12px 0; color: #999; font-size: 0.85rem;'>3-month: {meld_mort}</td>
</tr>
<tr style='border-bottom: 1px solid rgba(255,255,255,0.05);'>
<td style='padding: 12px 0; font-weight: 600;'>MELD-Na</td>
<td style='padding: 12px 0; color: #00d2ff; font-weight: 700; font-size: 1.2rem;'>{meld_na}</td>
<td style='padding: 12px 0; color: #999; font-size: 0.85rem;'>Sodium-adjusted</td>
</tr>
<tr style='border-bottom: 1px solid rgba(255,255,255,0.05);'>
<td style='padding: 12px 0; font-weight: 600;'>Child-Pugh</td>
<td style='padding: 12px 0; color: #92fe9d; font-weight: 700; font-size: 1.2rem;'>{cp_score} ({cp_class})</td>
<td style='padding: 12px 0; color: #999; font-size: 0.85rem;'>{"Compensated" if cp_class == "A" else "Decompensated"}</td>
</tr>
<tr>
<td style='padding: 12px 0; font-weight: 600;'>ALBI</td>
<td style='padding: 12px 0; color: #f9d423; font-weight: 700; font-size: 1.2rem;'>{albi_score} (Grade {albi_grade})</td>
<td style='padding: 12px 0; color: #999; font-size: 0.85rem;'>Liver function</td>
</tr>
</table>
</div>
"""
# Export data
export_data = {
"timestamp": datetime.now().isoformat(),
"model_used": model_type,
"results": {
"estimated_1yr_mortality": f"{mortality_prob*100:.1f}%",
"risk_category": risk_category,
"scores": {"MELD": meld, "MELD_Na": meld_na, "Child_Pugh": f"{cp_score} ({cp_class})", "ALBI": f"{albi_score} (Grade {albi_grade})"}
}
}
return results_html, scores_html, json.dumps(export_data, indent=2)
# ============================================================================
# PRESET SCENARIOS
# ============================================================================
PRESETS = {
"Compensated Cirrhosis (Child A)": {
"age": 55, "total_bili": 1.2, "inr": 1.1, "creatinine": 0.9,
"albumin": 3.8, "sodium": 140, "ascites": "None", "encephalopathy": "None",
"active_bleeding": "No", "hrs": "No", "dialysis": "No",
"time_to_endo": 8, "terlipressin_dose": 2,
"platelet": 150, "ast": 45, "alt": 38, "hemoglobin": 13.5, "potassium": 4.2
},
"Decompensated Cirrhosis (Child B)": {
"age": 62, "total_bili": 2.8, "inr": 1.6, "creatinine": 1.3,
"albumin": 3.0, "sodium": 134, "ascites": "Mild", "encephalopathy": "None",
"active_bleeding": "Yes", "hrs": "No", "dialysis": "No",
"time_to_endo": 14, "terlipressin_dose": 2,
"platelet": 95, "ast": 78, "alt": 62, "hemoglobin": 10.2, "potassium": 4.0
},
"Advanced Disease (Child C)": {
"age": 58, "total_bili": 5.2, "inr": 2.4, "creatinine": 2.1,
"albumin": 2.4, "sodium": 128, "ascites": "Severe", "encephalopathy": "Severe",
"active_bleeding": "Yes", "hrs": "No", "dialysis": "No",
"time_to_endo": 18, "terlipressin_dose": 4,
"platelet": 62, "ast": 125, "alt": 95, "hemoglobin": 8.1, "potassium": 3.6
}
}
# ============================================================================
# GRADIO INTERFACE
# ============================================================================
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;400;600;700&display=swap');
* { font-family: 'Rajdhani', sans-serif !important; }
body {
background: #0a0e14;
margin: 0;
padding: 0;
}
.gradio-container {
max-width: 1400px !important;
background: transparent !important;
}
/* Vapor field animation */
@keyframes vaporMove {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(-100px, -50px) scale(1.1); }
66% { transform: translate(50px, 100px) scale(0.9); }
}
body::before {
content: '';
position: fixed;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background:
radial-gradient(circle at 20% 50%, rgba(0, 210, 255, 0.15), transparent 25%),
radial-gradient(circle at 80% 80%, rgba(146, 254, 157, 0.15), transparent 25%);
animation: vaporMove 20s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
.gradio-container { position: relative; z-index: 1; }
.gr-box, .gr-form, .gr-input {
background: rgba(255, 255, 255, 0.03) !important;
backdrop-filter: blur(20px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
border-radius: 12px !important;
}
.gr-button-primary {
background: linear-gradient(135deg, #00d2ff, #92fe9d) !important;
border: none !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: 2px !important;
color: #000 !important;
font-size: 1.1rem !important;
padding: 16px 32px !important;
box-shadow: 0 4px 20px rgba(0, 210, 255, 0.4) !important;
}
label {
color: #fff !important;
font-weight: 600 !important;
}
"""
def create_app():
with gr.Blocks(css=custom_css, title="EVB Prognosis - ML Model", theme=gr.themes.Glass()) as app:
# Header
model_status = "✓ ML Model Active" if MODEL_LOADED else "⚠ Demo Mode"
model_color = "#92fe9d" if MODEL_LOADED else "#f9d423"
gr.HTML(f"""
<div style='text-align: center; padding: 48px 0 32px 0;'>
<div style='font-family: "Orbitron", sans-serif; font-size: 3.5rem; font-weight: 700;
background: linear-gradient(135deg, #00d2ff 0%, #92fe9d 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
letter-spacing: 4px; margin-bottom: 16px;'>
EVB PROGNOSIS
</div>
<div style='font-size: 0.85rem; color: {model_color}; letter-spacing: 2px;'>
{model_status} • Random Forest Classifier (AUC=0.91)
</div>
</div>
""")
# Disclaimer
gr.HTML("""
<div style='margin: 0 auto 32px; max-width: 900px; padding: 20px;
background: linear-gradient(135deg, rgba(255, 75, 43, 0.2), rgba(127, 29, 29, 0.3));
border: 2px solid #ff4b2b; border-radius: 16px; text-align: center;'>
<div style='font-size: 1.2rem; color: #ff4b2b; font-weight: 700; margin-bottom: 8px;'>
⚠️ FOR EDUCATIONAL AND RESEARCH PURPOSES ONLY
</div>
<div style='font-size: 0.9rem; color: #ffb3b3;'>
Not validated for clinical decision-making without physician supervision.
</div>
</div>
""")
with gr.Row():
with gr.Column(scale=2):
preset_dropdown = gr.Dropdown(
choices=[""] + list(PRESETS.keys()),
label="⚡ Load Clinical Scenario",
value=""
)
gr.Markdown("### 👤 Demographics")
with gr.Row():
age = gr.Slider(18, 100, value=52, label="Age")
sex = gr.Dropdown(["Male", "Female"], value="Male", label="Sex")
etiology = gr.Dropdown(
["Alcohol", "HCV", "HBV", "NASH", "Alcohol + HCV", "Other"],
value="Alcohol", label="Etiology"
)
gr.Markdown("### 🏥 Clinical Status")
with gr.Row():
ascites = gr.Dropdown(["None", "Mild", "Severe"], value="None", label="Ascites")
encephalopathy = gr.Dropdown(["None", "Mild", "Severe"], value="None", label="Encephalopathy")
with gr.Row():
hrs = gr.Dropdown(["No", "Yes"], value="No", label="Hepatorenal Syndrome")
dialysis = gr.Dropdown(["No", "Yes"], value="No", label="Dialysis (≥2x/wk)")
with gr.Row():
pvt = gr.Dropdown(["No", "Yes"], value="No", label="Portal Vein Thrombosis")
hcc = gr.Dropdown(["No", "Yes"], value="No", label="Hepatocellular Carcinoma")
gr.Markdown("### 💊 Medications")
with gr.Row():
omeprazole = gr.Dropdown(["No", "Yes"], value="No", label="Omeprazole")
spironolactone = gr.Dropdown(["No", "Yes"], value="No", label="Spironolactone")
with gr.Row():
furosemide = gr.Dropdown(["No", "Yes"], value="No", label="Furosemide")
beta_blocker = gr.Dropdown(["No", "Yes"], value="No", label="Beta-Blocker")
gr.Markdown("### 🧪 Laboratory Values")
with gr.Row():
total_bili = gr.Slider(0.1, 30, value=2.1, step=0.1, label="Total Bilirubin (mg/dL)")
albumin = gr.Slider(1, 5, value=3.4, step=0.1, label="Albumin (g/dL)")
with gr.Row():
inr = gr.Slider(0.5, 6, value=1.3, step=0.1, label="INR")
creatinine = gr.Slider(0.1, 10, value=1.0, step=0.1, label="Creatinine (mg/dL)")
with gr.Row():
sodium = gr.Slider(120, 150, value=138, label="Sodium (mEq/L)")
potassium = gr.Slider(2.5, 6.5, value=4.0, step=0.1, label="Potassium (mEq/L)")
with gr.Row():
platelet = gr.Slider(10, 500, value=150, label="Platelet (×10³/μL)")
hemoglobin = gr.Slider(4, 20, value=12, step=0.1, label="Hemoglobin (g/dL)")
with gr.Row():
ast = gr.Slider(10, 500, value=45, label="AST (U/L)")
alt = gr.Slider(10, 500, value=38, label="ALT (U/L)")
gr.Markdown("### 🩸 Bleeding Episode")
with gr.Row():
active_bleeding = gr.Dropdown(["No", "Yes"], value="No", label="Active Bleeding")
time_to_endo = gr.Number(value=12, label="Time to Endoscopy (hrs)")
with gr.Row():
varix_size = gr.Slider(1, 3, value=2, step=1, label="Varix Size (1=small, 3=large)")
terlipressin_dose = gr.Number(value=2, label="Terlipressin (mg)")
with gr.Row():
red_wall_marks = gr.Dropdown(["No", "Yes"], value="No", label="Red Wall Marks")
rupture_points = gr.Dropdown(["No", "Yes"], value="No", label="Rupture Points")
with gr.Column(scale=1):
calculate_btn = gr.Button("🔮 CALCULATE RISK", variant="primary", size="lg")
results_display = gr.HTML()
scores_display = gr.HTML()
with gr.Accordion("📊 Export JSON", open=False):
export_code = gr.Code(language="json")
# Citation
gr.HTML("""
<div style='text-align: center; margin-top: 48px; padding: 24px; border-top: 1px solid rgba(255,255,255,0.05);'>
<div style='font-size: 0.85rem; color: #666;'>
<strong style='color: #00d2ff;'>Research:</strong> Rech MM, et al.
Development and prospective validation of a machine learning model to predict mortality
in cirrhosis with esophageal variceal bleeding. <em>World J Hepatol</em> 2025; In press
</div>
</div>
""")
# Event handlers
def load_preset(preset_name):
if not preset_name or preset_name not in PRESETS:
return [gr.update()] * 26
p = PRESETS[preset_name]
return [
p.get("age", 52), p.get("sex", "Male"), p.get("etiology", "Alcohol"),
p.get("ascites", "None"), p.get("encephalopathy", "None"),
p.get("hrs", "No"), p.get("dialysis", "No"), p.get("pvt", "No"), p.get("hcc", "No"),
p.get("omeprazole", "No"), p.get("spironolactone", "No"),
p.get("furosemide", "No"), p.get("beta_blocker", "No"),
p.get("total_bili", 2.1), p.get("albumin", 3.4), p.get("inr", 1.3),
p.get("creatinine", 1.0), p.get("sodium", 138), p.get("potassium", 4.0),
p.get("platelet", 150), p.get("hemoglobin", 12), p.get("ast", 45), p.get("alt", 38),
p.get("active_bleeding", "No"), p.get("time_to_endo", 12),
p.get("varix_size", 2), p.get("terlipressin_dose", 2),
p.get("red_wall_marks", "No"), p.get("rupture_points", "No")
]
preset_dropdown.change(
load_preset,
inputs=[preset_dropdown],
outputs=[age, sex, etiology, ascites, encephalopathy, hrs, dialysis, pvt, hcc,
omeprazole, spironolactone, furosemide, beta_blocker,
total_bili, albumin, inr, creatinine, sodium, potassium,
platelet, hemoglobin, ast, alt, active_bleeding, time_to_endo,
varix_size, terlipressin_dose, red_wall_marks, rupture_points]
)
calculate_btn.click(
calculate_evb_risk,
inputs=[age, sex, etiology, ascites, encephalopathy, hrs, dialysis, pvt, hcc,
omeprazole, spironolactone, furosemide, beta_blocker,
total_bili, albumin, inr, creatinine, sodium, potassium,
platelet, hemoglobin, ast, alt, active_bleeding, time_to_endo,
varix_size, terlipressin_dose, red_wall_marks, rupture_points],
outputs=[results_display, scores_display, export_code]
)
return app
if __name__ == "__main__":
app = create_app()
app.launch()