george2cool36's picture
Initial deploy: app.py + requirements + README + fasttransform stub (avoid fastai at inference).
c9e7f4b verified
# app.py — Gradio app for "Phone Before Bed" classifier (tabular, AutoGluon)
# Strategy: FORCE a non-fastai child model (LightGBM/XGB/Cat/Sklearn) to avoid fastai unpickling.
import os, pathlib, shutil, zipfile, traceback
import pandas as pd
import gradio as gr
from huggingface_hub import hf_hub_download
from autogluon.tabular import TabularPredictor
# --- MODEL ARTIFACT ON HUB ---
MODEL_REPO_ID = "jennifee/classical_automl_model"
ZIP_FILENAME = "autogluon_predictor_dir.zip"
CACHE_DIR = pathlib.Path("hf_assets")
EXTRACT_DIR = CACHE_DIR / "predictor_dir"
# predictor expects these 5 features
REQUIRED_FEATURES = ["phone_hours", "computer_hours", "device_count", "sleep_time", "sleep_hours"]
# extra (ignored by model but kept in UI)
SLEEP_QUALITY_KEY = "sleep_quality"
SLEEP_QUALITY_CHOICES = ["Poor", "Fair", "Good", "Excellent"]
LABEL_MAP = {
0: "Does NOT use phone before bed",
1: "Uses phone before bed",
"0": "Does NOT use phone before bed",
"1": "Uses phone before bed",
}
def _extract_dir(local_zip: str) -> str:
if EXTRACT_DIR.exists():
shutil.rmtree(EXTRACT_DIR)
EXTRACT_DIR.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(local_zip, "r") as zf:
zf.extractall(str(EXTRACT_DIR))
items = list(EXTRACT_DIR.iterdir())
return str(items[0] if len(items)==1 and items[0].is_dir() else EXTRACT_DIR)
def _load_predictor():
local_zip = hf_hub_download(
repo_id=MODEL_REPO_ID,
filename=ZIP_FILENAME,
repo_type="model",
local_dir=str(CACHE_DIR),
local_dir_use_symlinks=False,
)
predictor_dir = _extract_dir(local_zip)
# relax version checks to be robust in Spaces
return TabularPredictor.load(
predictor_dir,
require_version_match=False,
require_py_version_match=False,
)
PREDICTOR = _load_predictor()
# choose a safe (non-fastai) child model
SAFE_PATTERNS = ["LIGHTGBM", "XGBOOST", "CATBOOST", "LR", "RF", "XT", "KNN", "SKLEARN", "GBM", "XGB"]
AVOID_PATTERNS = ["FASTAI", "NN", "NEURAL"]
def _choose_safe_model(pred):
try:
names = pred.get_model_names()
except Exception:
return None, "ℹ️ Could not list child models; using default."
def is_safe(name: str) -> bool:
u = (name or "").upper()
if any(a in u for a in AVOID_PATTERNS):
return False
if any(s in u for s in SAFE_PATTERNS):
return True
# allow plain sklearn models (often have short names)
return True
# prefer known good
preferred = [m for m in names if any(s in m.upper() for s in SAFE_PATTERNS)]
if preferred:
return preferred[0], f"✅ Using model: {preferred[0]}"
# else first non-fastai
fallback = [m for m in names if is_safe(m)]
if fallback:
return fallback[0], f"✅ Using model: {fallback[0]}"
return None, "⚠️ No non-fastai child models found; using default."
def predict_once(phone_hours, computer_hours, device_count, sleep_time, sleep_hours, sleep_quality):
try:
row = {
"phone_hours": float(phone_hours),
"computer_hours": float(computer_hours),
"device_count": int(device_count),
"sleep_time": float(sleep_time),
"sleep_hours": float(sleep_hours),
SLEEP_QUALITY_KEY: str(sleep_quality),
}
X = pd.DataFrame([row], columns=REQUIRED_FEATURES + [SLEEP_QUALITY_KEY])
model_name, note = _choose_safe_model(PREDICTOR)
# prediction
if model_name:
raw_label = PREDICTOR.predict(X, model=model_name).iloc[0]
else:
raw_label = PREDICTOR.predict(X).iloc[0]
label = LABEL_MAP.get(raw_label, str(raw_label))
# proba (best-effort)
proba_text = ""
try:
if model_name:
proba_df = PREDICTOR.predict_proba(X, model=model_name)
else:
proba_df = PREDICTOR.predict_proba(X)
if proba_df is not None:
row0 = proba_df.iloc[0]
lines = [f"{LABEL_MAP.get(k, str(k))}: {int(round(float(v)*100))}%" for k, v in row0.items()]
proba_text = "\n" + "\n".join(lines)
except Exception as e:
proba_text = f"\nℹ️ Could not compute probabilities: {e}"
out = f"Prediction: {label}{proba_text}\n{note}"
return out
except Exception as e:
# show error in the UI, not a crash
return "❌ Prediction error: " + str(e) + "\n" + traceback.format_exc()
# ----------- Gradio UI -----------
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# 📱 Use-Before-Bed Predictor")
gr.Markdown("Predict whether a student uses their phone before bed based on device use and sleep habits.")
with gr.Row():
phone_hours = gr.Slider(0, 24, step=0.1, value=2.5, label="Phone Hours / day")
computer_hours = gr.Slider(0, 24, step=0.1, value=4.0, label="Computer Hours / day")
device_count = gr.Number(value=3, precision=0, label="Device Count")
with gr.Row():
sleep_time = gr.Slider(0, 24, step=0.5, value=23.0, label="Weekday Sleep Start (0–24h)")
sleep_hours = gr.Slider(0, 12, step=0.5, value=7.0, label="Sleep Duration (hours)")
sleep_quality = gr.Radio(choices=SLEEP_QUALITY_CHOICES, value="Good", label="Sleep Quality")
out = gr.Textbox(lines=8, label="Prediction & Confidence")
inputs = [phone_hours, computer_hours, device_count, sleep_time, sleep_hours, sleep_quality]
for c in inputs:
c.change(fn=predict_once, inputs=inputs, outputs=out)
gr.Examples(
examples=[
[2.5, 4, 3, 23.0, 7.0, "Good"],
[1.0, 8, 5, 1.0, 5.0, "Poor"],
[5.0, 2, 2, 22.5, 8.5, "Excellent"],
[0.5,10, 4, 0.0, 6.0, "Fair"],
],
inputs=inputs,
label="Representative examples",
examples_per_page=5,
cache_examples=False,
)
if __name__ == "__main__":
demo.launch()