A demonstration risk-scoring platform covering five conditions — diabetes, heart disease, stroke, liver disease, and kidney disease — built to show what a genuinely end-to-end system looks like: synthetic data generation, model selection, explainability, rule-based clinical recommendations, a FastAPI backend, a Streamlit dashboard, and a PDF report generator, all wired to the same prediction service so the two frontends can never drift apart.
This is a demonstration system trained on synthetic data. It is not a medical device, has not been validated on real patients, and must not be used for real clinical decision-making.
No real patient data is used anywhere in this project. Each disease's
dataset is generated to match the shape of well-known public screening
datasets — feature names, plausible clinical ranges, and known
correlations (Pima diabetes, Cleveland heart disease, the common
stroke-prediction schema, the Indian Liver Patient Dataset, and the UCI
chronic kidney disease schema) — then labeled from a weighted, noisy
combination of the real risk factors, so the models learn genuine,
explainable structure instead of noise. See src/data_generation.py.
Synthetic data generator (src/data_generation.py)
│
▼
Training pipeline (src/training.py) — LogisticRegression vs RandomForest,
│ 5-fold CV, best model persisted per disease
▼
Prediction service (src/prediction.py) ──────────────┐
│ │
▼ ▼
Explainability (src/explainability.py, SHAP) Recommendations (src/recommendations.py)
│ │
└──────────────────┬───────────────────────────┘
▼
FastAPI backend (api/) + Streamlit dashboard (dashboard/)
│
▼
PDF report generator (src/pdf_report.py)
Why recommendations are rules, not another model: stacking a second black-box model on top of the risk model would make the "explanation" just as opaque as the prediction it explains. Instead, each disease has a short list of named clinical thresholds (e.g. "ALT ≥ 80" for liver disease) that a reviewer can check directly against the patient's values.
The FastAPI app is split by responsibility rather than living in one file:
api/main.py— assembly only: builds the app, wires middleware, mounts routersapi/routers/health.py—/healthz(liveness) and/readyz(readiness — reports per-disease model availability, suitable for an orchestrator's readiness gate)api/routers/diseases.py— catalog, schema, and metrics endpointsapi/routers/predict.py— one POST route per disease, dynamically generated fromDiseaseSpecso/docscan never drift from what the model actually requiresapi/deps.py— dependency-injection providers (ModelRegistry,Settings) so routers are testable viaapp.dependency_overrides, not real disk/model accessapi/middleware.py— request-ID correlation and latency logging on every requestsrc/exceptions.py— typed domain errors (UnknownDiseaseError,ModelNotTrainedError,PredictionError), each mapped to the correct HTTP status in exactly one place (api/main.py's exception handlers) instead of scatteredtry/except HTTPExceptionblocks per route
Configuration is centralized in src/config.py's Settings (via
pydantic-settings), reading from environment variables prefixed
HDPP_ or a .env file — see .env.example. Predict endpoints are
rate-limited separately from read endpoints (HDPP_RATE_LIMIT_PREDICT,
default 20/minute).
healthcare-platform/
├── data/raw/ synthetic per-disease CSVs (generated, not committed)
├── models/ trained pipeline + metrics per disease (generated)
├── src/
│ ├── config.py disease specs (features/targets/paths) + env-driven Settings
│ ├── exceptions.py typed domain error hierarchy shared by API + dashboard
│ ├── logger.py shared logging setup
│ ├── data_generation.py synthetic data generator for all 5 diseases
│ ├── training.py LogisticRegression vs RandomForest, CV, persistence
│ ├── explainability.py generic SHAP wrapper (works for either model type)
│ ├── recommendations.py rule-based doctor recommendation engine
│ ├── prediction.py ModelRegistry + unified prediction service (used by API + dashboard)
│ └── pdf_report.py one-page patient PDF report (reportlab)
├── api/
│ ├── main.py app assembly: middleware, exception handlers, router mounting
│ ├── deps.py FastAPI dependency-injection providers
│ ├── middleware.py request-ID + latency logging middleware
│ └── routers/
│ ├── health.py /healthz, /readyz
│ ├── diseases.py /v1/diseases catalog, schema, metrics
│ └── predict.py /v1/predict/{disease} — rate-limited
├── dashboard/
│ ├── app.py Home / executive summary — search, KPI cards, dark mode
│ ├── theme.py CSS-variable theming (light/dark), sticky nav, skeleton loaders
│ ├── common.py shared per-disease page renderer (no duplication)
│ └── pages/ Diabetes, Heart Disease, Stroke, Liver Disease,
│ Kidney Disease, Model Insights, About
├── tests/ 78 pytest tests across every module
├── requirements.txt
├── .env.example
├── Dockerfile
├── docker-compose.yml
└── README.md
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Generate the synthetic datasets (one CSV per disease, ~4,000 rows each)
python -m src.data_generation
# Train and select a model per disease (compares LogisticRegression vs
# RandomForest by 5-fold CV, persists the winner)
python -m src.training
# API — docs at http://localhost:8000/docs, health at /healthz, readiness at /readyz
uvicorn api.main:app --reload --port 8000
# Dashboard (separate terminal) — http://localhost:8501
streamlit run dashboard/app.pydocker compose up --build
# API: http://localhost:8000/docs
# Dashboard: http://localhost:8501The image runs data generation and training once at build time, so both
containers start with ready-to-serve models sharing a models-data volume.
pytest tests/ -v78 tests across data generation (schema, label balance, missing values), training (artifact creation, minimum performance bar, split integrity), prediction (output shape, SHAP direction sanity-check), the recommendation engine (band thresholds, rule triggers), PDF report generation (valid file signature), and the FastAPI backend (all endpoints, validation, error codes).
GET / service info + list of available diseases
GET /v1/diseases feature list per disease
GET /v1/diseases/{key}/metrics trained-model metrics for one disease
POST /v1/predict/{key} run a prediction (key = diabetes | heart_disease |
stroke | liver_disease | kidney_disease)
Each disease's POST body schema is generated directly from its feature
list — see /docs for the exact required fields per disease.
curl -X POST http://localhost:8000/v1/predict/diabetes \
-H "Content-Type: application/json" \
-d '{
"pregnancies": 2, "glucose": 150, "blood_pressure": 80,
"skin_thickness": 25, "insulin": 120, "bmi": 33,
"diabetes_pedigree": 0.6, "age": 45
}'Response:
{
"disease": "Diabetes",
"probability": 0.42,
"risk_category": "Moderate",
"top_contributors": [
{"feature": "glucose", "shap_value": 0.81},
{"feature": "bmi", "shap_value": 0.34}
],
"general_advice": "Recommend a follow-up consultation within 3 months and repeat panel testing.",
"specific_flags": ["Fasting/random glucose is elevated — recommend HbA1c confirmation test."],
"disclaimer": "This output is generated by a demonstration model trained on synthetic data...",
"model_used": "logistic_regression",
"model_test_roc_auc": 0.9726
}| Page | What it's for |
|---|---|
| Home | Executive summary — live KPIs computed from the trained models' metrics files. |
| Diabetes / Heart Disease / Stroke / Liver Disease / Kidney Disease | Patient input form → risk score, SHAP explanation chart, recommendations, PDF download. |
| Model Insights | Cross-disease comparison of chosen model, ROC-AUC, F1, precision, recall. |
| About | Design decisions, data provenance, and explicit limitations. |
- Add a sixth disease: add one
DiseaseSpecentry tosrc/config.py, one generator function insrc/data_generation.py, and one rule list insrc/recommendations.py. Training, the API endpoint, explainability, and the PDF report all pick it up automatically — only the dashboard needs a new thin page file that callsrender_disease_page("your_key"). - Swap in real data: replace the CSVs
src/data_generation.pyproduces indata/raw/with real (properly consented, de-identified) clinical data using the same column names, then re-runpython -m src.training. - Add a model type: extend the
candidatesdict insrc/training.py::train_one_disease; SHAP explainability and the API/ dashboard require no changes since they only depend onpredict_proba.
- Not validated on real patients; all reported metrics are against a held-out split of the same synthetic generator, not an independent real-world population.
- The recommendation engine's thresholds are illustrative demonstrations, not sourced from a clinical guideline body.
- SHAP values explain what the model learned from the (synthetic) training data, not a causal medical claim.
Shubham Panchal
— Data Analytics | Data Science | AI | Machine Learning | Business Intelligence