GLAM_Web_App / backend.py
ronnie-katende's picture
Update backend.py
f548611 verified
Raw
History Blame Contribute Delete
7.07 kB
import os
import time
import tempfile
import traceback
from pathlib import Path
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from interface import (
save_patient_registry,
load_patient_registry,
run_end_to_end,
monitoring_table_rows,
search_reasoning_records,
load_history_records,
RESULTS_DIR,
)
from live_analyze_route import router as live_analyze_router
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# NOTE: an eager @app.on_event("startup") model preload was tried here and
# reverted β€” loading SepFormer + Wav2Vec2 + the GNN checkpoint all at once
# during boot risks exceeding this Space's memory tier, which can crash
# the whole container (symptom: even /health, which touches no models,
# starts failing too). Lazy-loading on first use is slower for that first
# request but far safer. If cold-start latency needs solving, do it by
# upgrading the Space's hardware tier, or by loading models one at a time
# with memory checks in between β€” not all at once on every boot.
UPLOAD_DIR = RESULTS_DIR / "uploads"
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
def save_upload(upload: UploadFile, subfolder: str = "general") -> str:
folder = UPLOAD_DIR / subfolder
folder.mkdir(parents=True, exist_ok=True)
dest = folder / f"{int(time.time())}_{upload.filename}"
with open(dest, "wb") as f:
f.write(upload.file.read())
return str(dest)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.post("/api/patients/")
async def register_patient(
patient_code: str = Form(...),
full_name: str = Form(default=""),
age: int = Form(...),
gender: str = Form(...),
ward: str = Form(...),
bed_id: str = Form(...),
baseline_audio: UploadFile = File(...),
):
try:
audio_path = save_upload(baseline_audio, "baselines")
display_name = full_name.strip() if full_name.strip() else patient_code
save_patient_registry([{
"patient_id": patient_code,
"name": display_name,
"reference_audio": audio_path,
"age": age,
"gender": gender,
"ward": ward,
"bed_id": bed_id,
}])
return {
"patient_id": patient_code,
"full_name": display_name,
"status": "registered",
"bed_id": bed_id,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/patients/")
async def get_patients():
try:
registry = load_patient_registry()
# Make sure bed_id is included in the response
patients = []
for p in registry:
patients.append({
"patient_id": p.get("patient_id", ""),
"name": p.get("name", ""),
"age": p.get("age"),
"gender": p.get("gender", ""),
"ward": p.get("ward", ""),
"bed_id": p.get("bed_id", ""),
"reference_audio": p.get("reference_audio"),
})
return {"patients": patients}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/analyze/")
async def analyze_audio(
bed_id: str = Form(...),
file: UploadFile = File(...),
):
try:
audio_path = save_upload(file, "mic_uploads")
from interface import load_audio, predict_sources
waveform, sr = load_audio(audio_path)
prediction, _ = predict_sources(waveform, sr)
source = prediction[0].numpy()
duration = len(source) / 16000
from gnn import estimate_breathing_rate_bpm
bpm = estimate_breathing_rate_bpm(source, 16000, duration)
if bpm < 12 or bpm > 25:
severity = 0.75
condition = "abnormal"
elif bpm < 16 or bpm > 20:
severity = 0.45
condition = "mild"
else:
severity = 0.15
condition = "normal"
return {
"bed_id": bed_id,
"condition": condition,
"severity_score": severity,
"respiratory_rate": round(bpm, 1),
"confidence": 87,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# ── Live monitoring: one request per mic chunk, covering all beds that
# mic serves (see live_analyze_route.py / interface.analyze_live_chunk_for_beds).
# Registered here, before the catch-all static mount below.
app.include_router(live_analyze_router)
@app.post("/api/pipeline/")
async def run_pipeline(
mix_audio: UploadFile = File(...),
patient_1: str = Form(default=""),
patient_2: str = Form(default=""),
patient_3: str = Form(default=""),
):
try:
audio_path = save_upload(mix_audio, "mix_uploads")
patient_names = [patient_1 or None, patient_2 or None, patient_3 or None]
registry = load_patient_registry()
reference_audio_paths = [None, None, None]
for i, name in enumerate(patient_names):
if name:
for entry in registry:
if entry.get("name") == name:
reference_audio_paths[i] = (
entry.get("local_reference_audio")
or entry.get("reference_audio")
)
break
outputs, reasoning_summaries, history_record = run_end_to_end(
audio_path,
patient_names=patient_names,
reference_audio_paths=reference_audio_paths,
)
return {
"status": "complete",
"separated_sources": outputs[:3],
"reasoning_count": len(reasoning_summaries),
"timestamp": history_record.get("timestamp"),
"summaries": reasoning_summaries,
}
except Exception as e:
raise HTTPException(status_code=500, detail=traceback.format_exc())
@app.get("/api/monitoring/")
async def get_monitoring():
try:
rows = monitoring_table_rows()
return {"rows": rows}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/history/")
async def get_history():
try:
records = load_history_records()
return {"records": records}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/history/search/")
async def search_history(query: str = ""):
try:
results = search_reasoning_records(query)
return {"results": results}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# ── Serve React (MUST be last) ────────────────────────────────
app.mount("/", StaticFiles(directory="dist", html=True), name="static")