Spaces:
Running
Running
| """ | |
| Prediction endpoints. | |
| Thin router layer — HTTP handling only. | |
| All business logic lives in predictor.py. | |
| Endpoints: | |
| POST /predict/features — predict from engineered features | |
| GET /predict/example — return example high-risk patient | |
| """ | |
| import logging | |
| from fastapi import APIRouter, HTTPException, status | |
| from src.api.models import PatientFeatures, PredictionResponse | |
| from src.api.predictor import predictor | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter( | |
| prefix="/predict", | |
| tags=["Prediction"], | |
| ) | |
| async def predict_from_features( | |
| patient: PatientFeatures, | |
| ) -> PredictionResponse: | |
| """ | |
| Run readmission risk prediction for a single patient. | |
| The request body should contain all 31 engineered features. | |
| See the /predict/example endpoint for a sample high-risk patient. | |
| """ | |
| if not predictor.is_loaded: | |
| logger.error("Prediction requested but model not loaded") | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail={ | |
| "error": "Model not available", | |
| "message": "The prediction model is not loaded. " | |
| "Please try again in a few seconds.", | |
| } | |
| ) | |
| try: | |
| logger.info( | |
| f"Prediction request received — " | |
| f"age={patient.age_at_admission}, " | |
| f"los={patient.los_days}, " | |
| f"charlson={patient.charlson_score}" | |
| ) | |
| result = predictor.predict(patient) | |
| logger.info( | |
| f"Prediction complete — " | |
| f"risk={result.risk_percent}%, " | |
| f"category={result.risk_category}" | |
| ) | |
| return result | |
| except Exception as e: | |
| logger.error(f"Prediction failed: {e}", exc_info=True) | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail={ | |
| "error": "Prediction failed", | |
| "message": "An error occurred during prediction. " | |
| "Please check your input and try again.", | |
| } | |
| ) | |
| async def get_example_patient() -> dict: | |
| """ | |
| Return example high-risk patient features. | |
| This is the patient from the SHAP waterfall plot — | |
| predicted 99.6% readmission risk, actually readmitted. | |
| """ | |
| return { | |
| "description": "Highest-risk patient from model evaluation", | |
| "predicted_risk": "99.6%", | |
| "actual_outcome": "Readmitted", | |
| "clinical_profile": ( | |
| "Male, age 66.6, 3 prior admissions in 6 months, " | |
| "47 medications, Charlson score 2, 29 active conditions" | |
| ), | |
| "features": { | |
| "los_days": 6.0, | |
| "los_days_log": 1.946, | |
| "admission_month": 2, | |
| "admission_dow": 1, | |
| "is_emergency": 0, | |
| "n_admissions_prior_6m": 3, | |
| "n_admissions_prior_12m": 3, | |
| "n_ed_visits_prior_6m": 1, | |
| "days_since_last_admission": 28.0, | |
| "has_prior_admission": 1, | |
| "charlson_score": 2, | |
| "n_active_conditions": 29, | |
| "has_heart_failure": 0, | |
| "has_diabetes": 0, | |
| "has_diabetes_complex": 0, | |
| "has_copd": 0, | |
| "has_ckd": 1, | |
| "has_mi": 0, | |
| "has_cancer": 1, | |
| "has_dementia": 0, | |
| "has_cerebrovascular": 0, | |
| "has_pvd": 0, | |
| "n_medications_capped": 47.0, | |
| "is_high_polypharmacy": 1, | |
| "has_insulin": 0, | |
| "has_anticoagulant": 1, | |
| "has_diuretic": 1, | |
| "has_ace_inhibitor": 0, | |
| "age_at_admission": 66.6, | |
| "gender_male": 1, | |
| "income": 125393.0 | |
| } | |
| } |