File size: 6,650 Bytes
03289af
b703a33
03289af
b703a33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03289af
b703a33
 
03289af
b703a33
 
 
 
 
 
 
 
 
 
 
 
03289af
b703a33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03289af
b703a33
03289af
 
 
 
 
b703a33
03289af
b703a33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03289af
b703a33
03289af
b703a33
 
 
 
 
 
 
 
 
 
03289af
b703a33
 
 
03289af
b703a33
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import joblib
import numpy as np
import pandas as pd
import streamlit as st

# =========================
# CONFIG / CONSTANTS
# =========================

# These thresholds decide the UI label.
# You can change them anytime.
TH_LOW = 0.20   # below this => Low Risk
TH_HIGH = 0.40  # above this => High Risk
# between TH_LOW and TH_HIGH => Moderate Risk

MODEL_PATH = "best_model_v2_calibrated.joblib"

FEATURE_ORDER = [
    "Age",
    "Gender",
    "BMI",
    "BP_SYS",
    "BP_DIA",
    "Phys_Activity_Days",
    "DPQ_Score",
    "Smoking_Indicator",
    "Alcohol_Feature",
    "Diabetes_Indicator",
    "Cycle",
]

# =========================
# HELPERS
# =========================

def label_from_probability(prob: float) -> str:
    """3-level risk label based on thresholds."""
    if prob < TH_LOW:
        return "Low Risk"
    elif prob < TH_HIGH:
        return "Moderate Risk"
    else:
        return "High Risk"

def get_texts(lang: str):
    """Simple bilingual text dictionary."""
    if lang == "বাংলা":
        return {
            "title": "SleepGuardAI – Sleep Risk Screening (NHANES-based)",
            "subtitle": "ফর্ম পূরণ করুন → ঝুঁকির স্কোর দেখুন। (এটি মেডিকেল ডায়াগনসিস নয়)",
            "predict_btn": "Predict / ফলাফল দেখুন",
            "result": "ফলাফল",
            "prob": "ঝুঁকি সম্ভাবনা",
            "thresholds": "থ্রেশহোল্ড",
            "warning": "⚠️ এটি একটি স্ক্রিনিং টুল, মেডিকেল ডায়াগনসিস নয়। লক্ষণ থাকলে ডাক্তার দেখান।",
            "low": "কম ঝুঁকি",
            "mod": "মাঝারি ঝুঁকি",
            "high": "উচ্চ ঝুঁকি",
        }
    else:
        return {
            "title": "SleepGuardAI – Sleep Risk Screening (NHANES-based)",
            "subtitle": "Fill the form → get a risk score. (This is NOT a medical diagnosis)",
            "predict_btn": "Predict / ফলাফল দেখুন",
            "result": "Result",
            "prob": "Risk probability",
            "thresholds": "Thresholds",
            "warning": "⚠️ This is a screening tool, not a medical diagnosis. If you have symptoms, consult a clinician.",
            "low": "Low Risk",
            "mod": "Moderate Risk",
            "high": "High Risk",
        }

@st.cache_resource
def load_model():
    return joblib.load(MODEL_PATH)

def build_feature_row(

    age, gender, bmi, bp_sys, bp_dia, phys_days, dpq, smoking, alcohol, diabetes, cycle

) -> pd.DataFrame:
    """Build one-row DataFrame in the exact feature order used by training."""
    row = {
        "Age": float(age),
        "Gender": str(gender),  # keep as string (your pipeline encodes it)
        "BMI": float(bmi),
        "BP_SYS": float(bp_sys),
        "BP_DIA": float(bp_dia),
        "Phys_Activity_Days": float(phys_days),
        "DPQ_Score": float(dpq),
        "Smoking_Indicator": int(smoking),
        "Alcohol_Feature": float(alcohol),
        "Diabetes_Indicator": int(diabetes),
        "Cycle": str(cycle),
    }
    df = pd.DataFrame([row])[FEATURE_ORDER]
    return df

# =========================
# UI
# =========================

st.set_page_config(page_title="SleepGuardAI", layout="wide")

lang = st.radio("Language / ভাষা", ["English", "বাংলা"], horizontal=True)
T = get_texts(lang)

st.title(T["title"])
st.caption(T["subtitle"])

# Load model
try:
    bundle = load_model()
except FileNotFoundError:
    st.error(
        f"Model file not found: '{MODEL_PATH}'. Put it in the same folder as app.py."
    )
    st.stop()

# We support two possible save formats:
# 1) bundle is a dict with {"model":..., "calibrator":..., ...}
# 2) bundle is directly a sklearn Pipeline/Calibrated model with predict_proba
model = bundle.get("model") if isinstance(bundle, dict) and "model" in bundle else bundle
calibrator = bundle.get("calibrator") if isinstance(bundle, dict) and "calibrator" in bundle else None

colA, colB = st.columns(2)

with colA:
    age = st.slider("Age / বয়স", 10, 90, 30)
    gender = st.selectbox("Gender", ["Male", "Female"])

    bmi = st.slider("BMI", 10.0, 50.0, 25.0, 0.1)
    bp_sys = st.slider("Systolic BP", 80, 220, 120)
    bp_dia = st.slider("Diastolic BP", 40, 140, 80)

    phys_days = st.slider("Physical Activity Days/Week", 0, 7, 3)

with colB:
    st.markdown("### Optional (improves accuracy if known)")
    dpq = st.slider("DPQ Depression Score (0–27)", 0, 27, 5)
    smoking = st.selectbox("Smoking (1=Yes, 2=No)", [2, 1], index=0)
    diabetes = st.selectbox("Diabetes (1=Yes, 2=No)", [2, 1], index=0)
    alcohol = st.slider("Alcohol feature (proxy)", 0.0, 30.0, 2.0, 0.5)
    cycle = st.selectbox("NHANES Cycle", ["G", "H", "I", "J"])

st.divider()

if st.button(T["predict_btn"], use_container_width=True):
    X = build_feature_row(age, gender, bmi, bp_sys, bp_dia, phys_days, dpq, smoking, alcohol, diabetes, cycle)

    # Get probability
    # If you saved a separate calibrator, run model -> probs -> calibrator
    # Otherwise assume model already outputs calibrated proba.
    try:
        raw_proba = model.predict_proba(X)[:, 1]
    except Exception as e:
        st.error(f"Model could not compute predict_proba. Error: {e}")
        st.stop()

    if calibrator is not None:
        # calibrator usually expects 2D array
        prob = float(calibrator.predict(raw_proba.reshape(-1, 1))[0])
    else:
        prob = float(raw_proba[0])

    label = label_from_probability(prob)

    # Display with bilingual mapping
    if label == "Low Risk":
        pretty_label = T["low"]
        st.success(f"{T['result']}: {pretty_label}")
    elif label == "Moderate Risk":
        pretty_label = T["mod"]
        st.warning(f"{T['result']}: {pretty_label}")
    else:
        pretty_label = T["high"]
        st.error(f"{T['result']}: {pretty_label}")

    st.write(f"**{T['prob']}:** {prob:.3f}")
    st.write(f"**{T['thresholds']}:** Low<{TH_LOW:.2f} | Moderate {TH_LOW:.2f}{TH_HIGH:.2f} | High≥{TH_HIGH:.2f}")
    st.caption(T["warning"])

    # Debug view if you want to show professor:
    with st.expander("Show input feature row (for debugging/paper)"):
        st.dataframe(X)