on-task-api / app.py
ethnmcl's picture
Create app.py
873080c verified
Raw
History Blame Contribute Delete
3.46 kB
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sentence_transformers import SentenceTransformer
from huggingface_hub import hf_hub_download
import joblib
import json
import re
import os
app = FastAPI(title="On-Task Participation Detector")
# ── Load model files from HuggingFace Hub ────────────────────
REPO_ID = "ethnmcl/ontask-participation-classifier"
print("Loading model files...")
clf_path = hf_hub_download(repo_id=REPO_ID, filename="classifier.pkl")
metadata_path = hf_hub_download(repo_id=REPO_ID, filename="metadata.json")
clf = joblib.load(clf_path)
metadata = json.load(open(metadata_path))
THRESHOLD = metadata.get("threshold", 0.40)
print("Loading embedder...")
embedder = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
print(f"βœ… Model loaded β€” threshold: {THRESHOLD}")
# ── Utils ─────────────────────────────────────────────────────
def clean_text(text: str) -> str:
text = str(text).strip()
text = re.sub(r'http\S+|www\S+', ' ', text)
text = re.sub(r'<[^>]+>', ' ', text)
text = re.sub(r'\s+', ' ', text)
return text.strip()
# ── Request / Response schemas ────────────────────────────────
class CheckinRequest(BaseModel):
yesterday_checkin: str
today_checkin: str
class CheckinResponse(BaseModel):
on_task: bool
confidence: float
threshold_used: float
label: str
# ── Routes ────────────────────────────────────────────────────
@app.get("/")
def root():
return {
"name": "On-Task Participation Detector",
"repo": REPO_ID,
"threshold": THRESHOLD,
"auc_roc": metadata.get("auc_roc"),
"status": "ready"
}
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/predict", response_model=CheckinResponse)
def predict(req: CheckinRequest):
if not req.today_checkin.strip():
raise HTTPException(status_code=400, detail="today_checkin cannot be empty")
text = f"Yesterday: {clean_text(req.yesterday_checkin)} Today: {clean_text(req.today_checkin)}"
emb = embedder.encode([text])
proba = float(clf.predict_proba(emb)[0][1])
label = proba >= THRESHOLD
return CheckinResponse(
on_task = bool(label),
confidence = round(proba, 3),
threshold_used = THRESHOLD,
label = "on_task" if label else "not_on_task"
)
@app.post("/predict/batch")
def predict_batch(requests: list[CheckinRequest]):
if len(requests) > 100:
raise HTTPException(status_code=400, detail="Max 100 requests per batch")
texts = [
f"Yesterday: {clean_text(r.yesterday_checkin)} Today: {clean_text(r.today_checkin)}"
for r in requests
]
embs = embedder.encode(texts)
probas = clf.predict_proba(embs)[:, 1]
return [
{
"on_task": bool(p >= THRESHOLD),
"confidence": round(float(p), 3),
"threshold_used": THRESHOLD,
"label": "on_task" if p >= THRESHOLD else "not_on_task"
}
for p in probas
]