Expanic
Update code
1eb43b0
Raw
History Blame Contribute Delete
8.79 kB
from fastapi import APIRouter, Depends, HTTPException, Body, Request
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session
from sqlalchemy import func, String, cast
from database import get_db, PatientData
from schemas import PredictionInput, PredictionOutput
from typing import List, Dict, Any, Optional
router = APIRouter()
@router.get("/stats")
def get_dataset_stats(db: Session = Depends(get_db)):
try:
# Improve performance by caching or simplified info?
# For now, standard queries.
# 1. Total Patients
subq = db.query(PatientData.age, PatientData.f0_, PatientData.sepsis, PatientData.stay_id).group_by(PatientData.stay_id).subquery()
total_patients = db.query(func.count()).select_from(subq).scalar()
# 2. Avg Age
avg_age = db.query(func.avg(subq.c.age)).scalar()
# 3. Gender Distribution
male_count = db.query(func.count()).select_from(subq).filter(subq.c.f0_.in_(['M', 'Male'])).scalar()
female_count = db.query(func.count()).select_from(subq).filter(subq.c.f0_.in_(['F', 'Female'])).scalar()
# 4. Sepsis Distribution
sepsis_count = db.query(func.count()).select_from(subq).filter(subq.c.sepsis == 1).scalar()
normal_count = (total_patients or 0) - (sepsis_count or 0)
# 5. Age Distribution (Simplified Buckets for Charts)
# 0-18, 19-40, 41-60, 61-80, 80+
# This is hard in SQLite without CASE. We can do separate counts or fetch all ages and bucket in python?
# Fetching all ages for 100k might be heavy? No, just 100k ints.
# But aggregate in SQL is better.
age_groups = {
"0-18": db.query(func.count()).select_from(subq).filter(subq.c.age <= 18).scalar(),
"19-40": db.query(func.count()).select_from(subq).filter(subq.c.age > 18, subq.c.age <= 40).scalar(),
"41-60": db.query(func.count()).select_from(subq).filter(subq.c.age > 40, subq.c.age <= 60).scalar(),
"61-80": db.query(func.count()).select_from(subq).filter(subq.c.age > 60, subq.c.age <= 80).scalar(),
"80+": db.query(func.count()).select_from(subq).filter(subq.c.age > 80).scalar(),
}
return {
"total_patients": total_patients,
"avg_age": round(avg_age, 1) if avg_age else 0,
"gender_distribution": {
"Male": male_count,
"Female": female_count
},
"sepsis_cases": {
"Sepsis": sepsis_count,
"Normal": normal_count
},
"age_distribution": age_groups
}
except Exception as e:
print(f"Stats error: {e}")
return {
"total_patients": 0,
"avg_age": 0,
"gender_distribution": {"Male": 0, "Female": 0},
"sepsis_cases": {"Sepsis": 0, "Normal": 0},
"age_distribution": {}
}
@router.get("/patients/emergency")
def get_emergency_patients(limit: int = 50, db: Session = Depends(get_db)):
try:
# Find patients with Sepsis=1
# Group by stay_id to get unique patients
query = db.query(PatientData.stay_id, PatientData.subject_id, PatientData.age, PatientData.f0_, PatientData.sepsis)\
.filter(PatientData.sepsis == 1)\
.group_by(PatientData.stay_id)
patients = query.limit(limit).all()
return [{"stay_id": p.stay_id, "subject_id": p.subject_id, "age": p.age, "gender": p.f0_, "sepsis": p.sepsis} for p in patients]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/patients")
def get_patients(search: Optional[str] = None, db: Session = Depends(get_db)):
try:
query = db.query(PatientData.stay_id, PatientData.subject_id, PatientData.age, PatientData.f0_)\
.group_by(PatientData.stay_id)
if search:
# Filter by stay_id (cast to string for wildcards if needed, or exact match)
# For simplicity in sqlite:
query = query.filter(cast(PatientData.stay_id, String).contains(search))
patients = query.limit(50).all()
return [{"stay_id": p.stay_id, "subject_id": p.subject_id, "age": p.age, "gender": p.f0_} for p in patients]
except Exception as e:
print(e)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/patient/{stay_id}")
def get_patient_history(stay_id: int, db: Session = Depends(get_db)):
rows = db.query(PatientData).filter(PatientData.stay_id == stay_id).order_by(PatientData.hr).all()
if not rows:
raise HTTPException(status_code=404, detail="Patient not found")
return rows
@router.post("/patient")
def add_patient_data(data: Dict[str, Any] = Body(...), db: Session = Depends(get_db)):
try:
valid_cols = {c.name for c in PatientData.__table__.columns}
# 1. Logic for Auto-Increment HR
stay_id = data.get('stay_id')
if not stay_id:
raise HTTPException(status_code=400, detail="stay_id is required")
# Get last record for this stay
from sqlalchemy import desc
last_record = db.query(PatientData)\
.filter(PatientData.stay_id == stay_id)\
.order_by(desc(PatientData.hr))\
.first()
new_hr = (last_record.hr + 1) if last_record else 1
# 2. Logic for Forward Fill (ffill)
# If a field is missing in new data, use value from last_record
final_data = {}
# Pre-fill with last record's data if it exists
if last_record:
# We copy all valid columns from last_record
for col in valid_cols:
if col not in ['id', 'hr', 'starttime', 'endtime']: # Don't copy PK or time
val = getattr(last_record, col)
if val is not None:
final_data[col] = val
# Override with new data (only if not None)
# However, data dict might contain empty strings or None?
for k, v in data.items():
if k in valid_cols and k != 'id':
# If value is provided (not None), use it
if v is not None:
final_data[k] = v
# Set the calculated HR
final_data['hr'] = new_hr
# Create row
row = PatientData(**final_data)
db.add(row)
db.commit()
db.refresh(row)
return {"message": "Data added successfully", "id": row.id, "hr": new_hr}
except Exception as e:
db.rollback()
print(f"Error adding patient: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/predict/{stay_id}", response_model=PredictionOutput)
def predict_patient(stay_id: int, request: Request, window_hours: int = 6, db: Session = Depends(get_db)):
rows = db.query(PatientData).filter(PatientData.stay_id == stay_id).order_by(PatientData.hr).all()
if not rows:
raise HTTPException(status_code=404, detail="Patient data not found")
model = getattr(request.app.state, "model", None)
if not model:
raise HTTPException(status_code=503, detail="Model not loaded")
# Convert window_hours to window_id (0=6h, 1=12h, 2=24h)
window_map = {6: 0, 12: 1, 24: 2}
window_id = window_map.get(window_hours, 0)
records = []
for r in rows:
d = r.__dict__.copy()
d.pop('_sa_instance_state', None)
records.append(d)
try:
result = model.predict(records, window_id=window_id)
if not result:
raise HTTPException(status_code=500, detail="Prediction returned empty")
return result
except Exception as e:
import traceback
tb = traceback.format_exc()
print(f"Prediction Error: {tb}")
raise HTTPException(status_code=500, detail=f"Prediction logic error: {e}. Traceback: {tb}")
@router.post("/predict", response_model=PredictionOutput)
def predict_manual(data: PredictionInput, request: Request, window_hours: int = 6):
model = getattr(request.app.state, "model", None)
if not model:
raise HTTPException(status_code=503, detail="Model not loaded")
# Convert window_hours to window_id (0=6h, 1=12h, 2=24h)
window_map = {6: 0, 12: 1, 24: 2}
window_id = window_map.get(window_hours, 0)
try:
records = [data.dict()]
result = model.predict(records, window_id=window_id)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=f"Prediction error: {e}")