README.md CHANGED
@@ -1,14 +1,13 @@
1
  ---
2
  title: SleepGuardAI
3
- emoji: 😴
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: streamlit
7
- sdk_version: 1.57.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- # SleepGuardAI
13
-
14
- AI-powered sleep risk screening system.
 
1
  ---
2
  title: SleepGuardAI
3
+ emoji: 👁
4
+ colorFrom: gray
5
+ colorTo: yellow
6
+ sdk: gradio
7
+ sdk_version: 6.4.0
8
  app_file: app.py
9
  pinned: false
10
+ short_description: 'AI-based sleep risk screening system '
11
  ---
12
 
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
app.py CHANGED
@@ -1,184 +1,106 @@
1
  import joblib
2
- import numpy as np
3
  import pandas as pd
4
- import streamlit as st
5
-
6
- # =========================
7
- # CONFIG / CONSTANTS
8
- # =========================
9
-
10
- # These thresholds decide the UI label.
11
- # You can change them anytime.
12
- TH_LOW = 0.20 # below this => Low Risk
13
- TH_HIGH = 0.40 # above this => High Risk
14
- # between TH_LOW and TH_HIGH => Moderate Risk
15
-
16
- MODEL_PATH = "best_model_v2_calibrated.joblib"
17
-
18
- FEATURE_ORDER = [
19
- "Age",
20
- "Gender",
21
- "BMI",
22
- "BP_SYS",
23
- "BP_DIA",
24
- "Phys_Activity_Days",
25
- "DPQ_Score",
26
- "Smoking_Indicator",
27
- "Alcohol_Feature",
28
- "Diabetes_Indicator",
29
- "Cycle",
30
- ]
31
-
32
- # =========================
33
- # HELPERS
34
- # =========================
35
-
36
- def label_from_probability(prob: float) -> str:
37
- """3-level risk label based on thresholds."""
38
- if prob < TH_LOW:
39
- return "Low Risk"
40
- elif prob < TH_HIGH:
41
- return "Moderate Risk"
42
- else:
43
- return "High Risk"
44
 
45
- def get_texts(lang: str):
46
- """Simple bilingual text dictionary."""
47
  if lang == "বাংলা":
48
- return {
49
- "title": "SleepGuardAI – Sleep Risk Screening (NHANES-based)",
50
- "subtitle": "র্ম পূরণ করুন → ঝুঁকির স্কোর দেখুন। (এটি মেডিকেয়াগনসিস নয়)",
51
- "predict_btn": "Predict / ফলফল দেখুন",
52
- "result": "ফলাফল",
53
- "prob": "ঝকি সম্বনা",
54
- "thresholds": "থ্রেশহোল্ড",
55
- "warning": "⚠️ এটি একটি স্ক্রিনিং টুল, মেডিকেল ডায়াগনসিস নয়। লক্ষণ থাকলে ডাক্তার দেখান।",
56
- "low": "কম ঝুঁকি",
57
- "mod": "মাঝারি ঝুঁকি",
58
- "high": "উচ্চ ঝুঁকি",
59
- }
60
  else:
61
- return {
62
- "title": "SleepGuardAI – Sleep Risk Screening (NHANES-based)",
63
- "subtitle": "Fill the form → get a risk score. (This is NOT a medical diagnosis)",
64
- "predict_btn": "Predict / ফলাফল দেখুন",
65
- "result": "Result",
66
- "prob": "Risk probability",
67
- "thresholds": "Thresholds",
68
- "warning": "⚠️ This is a screening tool, not a medical diagnosis. If you have symptoms, consult a clinician.",
69
- "low": "Low Risk",
70
- "mod": "Moderate Risk",
71
- "high": "High Risk",
72
- }
73
-
74
- @st.cache_resource
75
- def load_model():
76
- return joblib.load(MODEL_PATH)
77
-
78
- def build_feature_row(
79
- age, gender, bmi, bp_sys, bp_dia, phys_days, dpq, smoking, alcohol, diabetes, cycle
80
- ) -> pd.DataFrame:
81
- """Build one-row DataFrame in the exact feature order used by training."""
82
- row = {
 
 
 
 
 
83
  "Age": float(age),
84
- "Gender": str(gender), # keep as string (your pipeline encodes it)
85
  "BMI": float(bmi),
86
  "BP_SYS": float(bp_sys),
87
  "BP_DIA": float(bp_dia),
88
  "Phys_Activity_Days": float(phys_days),
89
  "DPQ_Score": float(dpq),
90
- "Smoking_Indicator": int(smoking),
91
  "Alcohol_Feature": float(alcohol),
92
- "Diabetes_Indicator": int(diabetes),
93
- "Cycle": str(cycle),
94
- }
95
- df = pd.DataFrame([row])[FEATURE_ORDER]
96
- return df
97
-
98
- # =========================
99
- # UI
100
- # =========================
101
-
102
- st.set_page_config(page_title="SleepGuardAI", layout="wide")
103
-
104
- lang = st.radio("Language / ভাষা", ["English", "বাংলা"], horizontal=True)
105
- T = get_texts(lang)
106
-
107
- st.title(T["title"])
108
- st.caption(T["subtitle"])
109
-
110
- # Load model
111
- try:
112
- bundle = load_model()
113
- except FileNotFoundError:
114
- st.error(
115
- f"Model file not found: '{MODEL_PATH}'. Put it in the same folder as app.py."
116
- )
117
- st.stop()
118
-
119
- # We support two possible save formats:
120
- # 1) bundle is a dict with {"model":..., "calibrator":..., ...}
121
- # 2) bundle is directly a sklearn Pipeline/Calibrated model with predict_proba
122
- model = bundle.get("model") if isinstance(bundle, dict) and "model" in bundle else bundle
123
- calibrator = bundle.get("calibrator") if isinstance(bundle, dict) and "calibrator" in bundle else None
124
-
125
- colA, colB = st.columns(2)
126
-
127
- with colA:
128
- age = st.slider("Age / বয়স", 10, 90, 30)
129
- gender = st.selectbox("Gender", ["Male", "Female"])
130
-
131
- bmi = st.slider("BMI", 10.0, 50.0, 25.0, 0.1)
132
- bp_sys = st.slider("Systolic BP", 80, 220, 120)
133
- bp_dia = st.slider("Diastolic BP", 40, 140, 80)
134
-
135
- phys_days = st.slider("Physical Activity Days/Week", 0, 7, 3)
136
-
137
- with colB:
138
- st.markdown("### Optional (improves accuracy if known)")
139
- dpq = st.slider("DPQ Depression Score (0–27)", 0, 27, 5)
140
- smoking = st.selectbox("Smoking (1=Yes, 2=No)", [2, 1], index=0)
141
- diabetes = st.selectbox("Diabetes (1=Yes, 2=No)", [2, 1], index=0)
142
- alcohol = st.slider("Alcohol feature (proxy)", 0.0, 30.0, 2.0, 0.5)
143
- cycle = st.selectbox("NHANES Cycle", ["G", "H", "I", "J"])
144
-
145
- st.divider()
146
-
147
- if st.button(T["predict_btn"], use_container_width=True):
148
- X = build_feature_row(age, gender, bmi, bp_sys, bp_dia, phys_days, dpq, smoking, alcohol, diabetes, cycle)
149
-
150
- # Get probability
151
- # If you saved a separate calibrator, run model -> probs -> calibrator
152
- # Otherwise assume model already outputs calibrated proba.
153
- try:
154
- raw_proba = model.predict_proba(X)[:, 1]
155
- except Exception as e:
156
- st.error(f"Model could not compute predict_proba. Error: {e}")
157
- st.stop()
158
-
159
- if calibrator is not None:
160
- # calibrator usually expects 2D array
161
- prob = float(calibrator.predict(raw_proba.reshape(-1, 1))[0])
162
- else:
163
- prob = float(raw_proba[0])
164
 
165
- label = label_from_probability(prob)
 
 
166
 
167
- # Display with bilingual mapping
168
- if label == "Low Risk":
169
- pretty_label = T["low"]
170
- st.success(f"{T['result']}: {pretty_label}")
171
- elif label == "Moderate Risk":
172
- pretty_label = T["mod"]
173
- st.warning(f"{T['result']}: {pretty_label}")
174
- else:
175
- pretty_label = T["high"]
176
- st.error(f"{T['result']}: {pretty_label}")
177
 
178
- st.write(f"**{T['prob']}:** {prob:.3f}")
179
- st.write(f"**{T['thresholds']}:** Low<{TH_LOW:.2f} | Moderate {TH_LOW:.2f}–{TH_HIGH:.2f} | High≥{TH_HIGH:.2f}")
180
- st.caption(T["warning"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
- # Debug view if you want to show professor:
183
- with st.expander("Show input feature row (for debugging/paper)"):
184
- st.dataframe(X)
 
1
  import joblib
 
2
  import pandas as pd
3
+ import gradio as gr
4
+
5
+ # ---------------- Load model bundle ----------------
6
+ bundle = joblib.load("best_model_v2_calibrated.joblib")
7
+ model = bundle["model"]
8
+ threshold = float(bundle["threshold"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # ---------------- Output formatting ----------------
11
+ def format_output(lang, label, proba):
12
  if lang == "বাংলা":
13
+ risk_text = "উচ্চ ঝুঁকি" if label == "High Risk" else "কম ঝুঁকি"
14
+ return (
15
+ f"### ফলাফল: **{risk_text}**\n"
16
+ f"- ঝুঁকির সম্ভা (Probability): **{proba:.3f}**\n"
17
+ f"- Threshold: **{threshold:.2f}**\n\n"
18
+ "⚠️ এটি একটি **স্ক্রিনিং টল**, চিকিৎসা নির্ণয় নয়। সমকলে ডাক্তারের পরামর্শ নিন।"
19
+ )
 
 
 
 
 
20
  else:
21
+ return (
22
+ f"### Result: **{label}**\n"
23
+ f"- Risk probability: **{proba:.3f}**\n"
24
+ f"- Threshold: **{threshold:.2f}**\n\n"
25
+ "⚠️ This is a **screening tool**, not a medical diagnosis. If you have symptoms, consult a clinician."
26
+ )
27
+
28
+ def web_speak_html(text, lang):
29
+ voice_lang = "bn-BD" if lang == "বাংলা" else "en-US"
30
+ safe = text.replace("`", "").replace("\\", "\\\\").replace("'", "\\'")
31
+ return f"""
32
+ <div style="display:flex;gap:10px;align-items:center;">
33
+ <button onclick="(function(){{
34
+ const msg = new SpeechSynthesisUtterance('{safe}');
35
+ msg.lang = '{voice_lang}';
36
+ window.speechSynthesis.cancel();
37
+ window.speechSynthesis.speak(msg);
38
+ }})()" style="padding:10px 14px;border-radius:10px;border:1px solid #ccc;cursor:pointer;">
39
+ 🔊 Speak / শোনান
40
+ </button>
41
+ <span style="opacity:0.7;">(Voice depends on browser installed voices)</span>
42
+ </div>
43
+ """
44
+
45
+ # ---------------- Prediction ----------------
46
+ def predict(lang, age, gender, bmi, bp_sys, bp_dia, phys_days, dpq, smoking, alcohol, diabetes, cycle):
47
+ sample = pd.DataFrame([{
48
  "Age": float(age),
49
+ "Gender": gender,
50
  "BMI": float(bmi),
51
  "BP_SYS": float(bp_sys),
52
  "BP_DIA": float(bp_dia),
53
  "Phys_Activity_Days": float(phys_days),
54
  "DPQ_Score": float(dpq),
55
+ "Smoking_Indicator": float(smoking),
56
  "Alcohol_Feature": float(alcohol),
57
+ "Diabetes_Indicator": float(diabetes),
58
+ "Cycle": cycle
59
+ }])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ proba = float(model.predict_proba(sample)[:, 1][0])
62
+ pred = int(proba >= threshold)
63
+ label = "High Risk" if pred == 1 else "Low Risk"
64
 
65
+ md = format_output(lang, label, proba)
66
+ speak = web_speak_html(md, lang)
67
+ return md, speak
68
+
69
+ # ---------------- UI ----------------
70
+ with gr.Blocks(title="SleepGuardAI – Sleep Risk Screening") as demo:
71
+ gr.Markdown("# SleepGuardAI – Sleep Risk Screening (NHANES-based)")
72
+ gr.Markdown("Fill the form → get risk score. Bilingual output + Speak button included.")
73
+
74
+ lang = gr.Radio(["English", "বাংলা"], value="English", label="Language / ভাষা")
75
 
76
+ with gr.Row():
77
+ age = gr.Slider(10, 90, value=30, label="Age / বয়স")
78
+ gender = gr.Dropdown(["Male", "Female"], value="Male", label="Gender")
79
+
80
+ with gr.Row():
81
+ bmi = gr.Slider(10, 50, value=25, label="BMI")
82
+ bp_sys = gr.Slider(80, 220, value=120, label="Systolic BP")
83
+ bp_dia = gr.Slider(40, 140, value=80, label="Diastolic BP")
84
+
85
+ phys = gr.Slider(0, 7, value=3, step=1, label="Physical Activity Days/Week")
86
+
87
+ gr.Markdown("### Optional (improves accuracy if known)")
88
+ with gr.Row():
89
+ dpq = gr.Slider(0, 27, value=0, step=1, label="DPQ Depression Score (0–27)")
90
+ smoking = gr.Dropdown([1, 2], value=2, label="Smoking (1=Yes, 2=No)")
91
+ diabetes = gr.Dropdown([1, 2], value=2, label="Diabetes (1=Yes, 2=No)")
92
+ alcohol = gr.Slider(0, 30, value=0, step=1, label="Alcohol feature (proxy)")
93
+
94
+ cycle = gr.Dropdown(["G", "H", "I", "J"], value="J", label="NHANES Cycle")
95
+
96
+ btn = gr.Button("Predict / ফলাফল দেখুন")
97
+ out_md = gr.Markdown()
98
+ out_speak = gr.HTML()
99
+
100
+ btn.click(
101
+ predict,
102
+ inputs=[lang, age, gender, bmi, bp_sys, bp_dia, phys, dpq, smoking, alcohol, diabetes, cycle],
103
+ outputs=[out_md, out_speak]
104
+ )
105
 
106
+ demo.launch()
 
 
find_threshold_recall.py DELETED
@@ -1,79 +0,0 @@
1
- import joblib
2
- import numpy as np
3
- import pandas as pd
4
- from sklearn.model_selection import train_test_split
5
- from sklearn.metrics import recall_score, fbeta_score, roc_auc_score
6
-
7
- MODEL_PATH = "best_model_v2_calibrated.joblib"
8
- CSV_PATH = "nhanes_sleep_extended_v2.csv"
9
-
10
- TARGET = "Sleep_Risk"
11
-
12
- FEATURES = [
13
- "Age",
14
- "Gender",
15
- "BMI",
16
- "BP_SYS",
17
- "BP_DIA",
18
- "Phys_Activity_Days",
19
- "DPQ_Score",
20
- "Smoking_Indicator",
21
- "Alcohol_Feature",
22
- "Diabetes_Indicator",
23
- "Cycle",
24
- ]
25
-
26
- def main():
27
- df = pd.read_csv(CSV_PATH)
28
-
29
- # drop rows with missing values in required columns
30
- df = df.dropna(subset=FEATURES + [TARGET]).copy()
31
-
32
- X = df[FEATURES]
33
- y = df[TARGET].astype(int)
34
-
35
- # Train/test split (keep same logic as training if possible)
36
- X_train, X_test, y_train, y_test = train_test_split(
37
- X, y, test_size=0.20, random_state=42, stratify=y
38
- )
39
-
40
- bundle = joblib.load(MODEL_PATH)
41
- model = bundle.get("model") if isinstance(bundle, dict) and "model" in bundle else bundle
42
- calibrator = bundle.get("calibrator") if isinstance(bundle, dict) and "calibrator" in bundle else None
43
-
44
- # Get probabilities
45
- p_test_raw = model.predict_proba(X_test)[:, 1]
46
- if calibrator is not None:
47
- p_test = calibrator.predict(p_test_raw.reshape(-1, 1))
48
- else:
49
- p_test = p_test_raw
50
-
51
- # AUC (threshold-free)
52
- auc = roc_auc_score(y_test, p_test)
53
- print(f"TEST AUC: {auc:.4f}")
54
-
55
- # Sweep thresholds
56
- thresholds = np.round(np.arange(0.05, 0.90, 0.01), 2)
57
-
58
- best_f2 = (-1, None)
59
- th_recall_085 = None
60
-
61
- for th in thresholds:
62
- pred = (p_test >= th).astype(int)
63
- rec = recall_score(y_test, pred)
64
- f2 = fbeta_score(y_test, pred, beta=2)
65
-
66
- if f2 > best_f2[0]:
67
- best_f2 = (f2, th)
68
-
69
- if th_recall_085 is None and rec >= 0.85:
70
- th_recall_085 = th
71
-
72
- print(f"Best threshold by F2: {best_f2[1]} (F2={best_f2[0]:.4f})")
73
- if th_recall_085 is not None:
74
- print(f"Threshold achieving recall ≥ 0.85 : {th_recall_085}")
75
- else:
76
- print("No threshold achieved recall ≥ 0.85 in tested range.")
77
-
78
- if __name__ == "__main__":
79
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
nhanes_sleep_extended_v2.csv DELETED
The diff for this file is too large to render. See raw diff
 
requirements.txt CHANGED
@@ -1,7 +1,6 @@
1
- pandas
2
- numpy
3
- scikit-learn
4
- joblib
5
- xgboost
6
- matplotlib
7
- protobuf<4
 
1
+ gradio
2
+ pandas
3
+ numpy
4
+ scikit-learn
5
+ joblib
6
+ xgboost