Skip to content

Commit 062635f

Browse files
committed
Add Kaggle-trained BRFSS risk model and production deployment path.
Train on CDC diabetes indicators with CV, tuning, and calibration; load artifacts in the API/UI; add AWS App Runner Terraform and Kaggle automation scripts.
1 parent 567d0b9 commit 062635f

30 files changed

Lines changed: 2194 additions & 56 deletions

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ API_TOKEN=dev-token-change-me
33
REQUIRE_API_TOKEN=false
44
REQUEST_TIMEOUT_SECONDS=25
55
LOG_DIR=./logs
6+
MODEL_ARTIFACT_PATH=./artifacts/risk_model.joblib

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ __pycache__/
55
logs/
66
.coverage
77
data/chroma/
8+
artifacts/*.joblib
9+
artifacts/*.log

README.md

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ An autonomous AI system that analyzes patient data, estimates health risk, expla
66

77
This project combines:
88

9-
- A machine learning risk model (`LogisticRegression`) for baseline probability estimation
9+
- A machine learning risk model for baseline probability estimation
10+
- A Kaggle-trained production model path using the CDC Diabetes Health Indicators dataset
1011
- An orchestrator agent that coordinates specialized agents
1112
- LLM-powered explanation and recommendation generation
1213
- Retrieval-augmented context from a local medical knowledge base
@@ -33,7 +34,9 @@ Detailed diagram: [`docs/architecture.md`](docs/architecture.md)
3334

3435
## Key Features
3536

36-
- **Risk prediction** from patient vitals and demographic inputs
37+
- **Risk prediction** from patient vitals, demographics, lifestyle, and access inputs
38+
- **Production training pipeline** for CDC BRFSS diabetes indicators on Kaggle
39+
- **Persisted model artifact loading** with local CSV fallback for development
3740
- **Internal BMI computation** from height and weight
3841
- **Risk classification** into Low / Medium / High
3942
- **SHAP explainability** to show feature impact on risk
@@ -49,10 +52,12 @@ Detailed diagram: [`docs/architecture.md`](docs/architecture.md)
4952

5053
- **Language**: Python 3.10+
5154
- **ML**: scikit-learn, pandas, shap
55+
- **Training**: Kaggle, persisted `joblib` artifacts, model card + metrics JSON
5256
- **LLM**: OpenAI API
5357
- **Memory/Vector Store**: ChromaDB
5458
- **Backend API**: FastAPI, Uvicorn
5559
- **Frontend**: Streamlit
60+
- **Deployment**: Docker, AWS App Runner, ECR, Terraform
5661

5762
## Project Structure
5863

@@ -63,12 +68,22 @@ healthcare-agent-system/
6368
│ ├── explainer.py
6469
│ ├── recommender.py
6570
│ └── retriever.py
71+
├── artifacts/
72+
│ └── risk_model.joblib
6673
├── api/
6774
│ └── app.py
6875
├── data/
6976
│ ├── patients.csv
7077
│ ├── medical_knowledge.jsonl
7178
│ └── chroma/
79+
├── docs/
80+
│ ├── aws_deployment.md
81+
│ └── kaggle_training.md
82+
├── infra/
83+
│ └── aws/apprunner/
84+
├── kaggle/
85+
│ ├── kaggle_train.py
86+
│ └── kernel-metadata.json
7287
├── memory/
7388
│ └── store.py
7489
├── models/
@@ -87,11 +102,11 @@ healthcare-agent-system/
87102

88103
## How the System Works
89104

90-
1. User provides patient attributes (`age`, `height_cm`, `weight_kg`, `blood_pressure`, optional `glucose`)
105+
1. User provides patient attributes (`age`, `height_cm`, `weight_kg`, `bp`, optional `glucose`, and optional BRFSS-style health indicators)
91106
2. System computes BMI internally (`kg / m^2`)
92107
3. Risk model predicts probability of elevated risk
93108
4. Risk classifier maps probability -> `Low` / `Medium` / `High`
94-
5. SHAP computes feature contribution scores
109+
5. Feature explainer computes SHAP or local sensitivity contribution scores
95110
6. Retrieval agent fetches relevant medical context
96111
7. Memory agent retrieves similar historical cases and stores new case
97112
8. Explanation and recommendation agents generate human-friendly outputs
@@ -127,10 +142,45 @@ Create a `.env` file (or export directly):
127142

128143
```bash
129144
export OPENAI_API_KEY="your_api_key_here"
145+
export MODEL_ARTIFACT_PATH="./artifacts/risk_model.joblib"
130146
```
131147

132148
> The explanation/recommendation agents require a valid OpenAI key.
133149
150+
If `MODEL_ARTIFACT_PATH` points to a trained Kaggle artifact, the app loads it at
151+
startup. If the artifact is missing, the app falls back to `data/patients.csv`.
152+
153+
## Train The Production Model On Kaggle
154+
155+
Training runs on Kaggle against the CDC Diabetes Health Indicators dataset
156+
(`alexteboul/diabetes-health-indicators-dataset`). Your machine only uploads the script
157+
and downloads the trained artifact.
158+
159+
```bash
160+
chmod +x scripts/kaggle_run.sh
161+
./scripts/kaggle_run.sh
162+
```
163+
164+
Kernel: `aditya2402/healthcare-ai-diabetes-risk-training`
165+
Script: [`kaggle/kaggle_train.py`](kaggle/kaggle_train.py)
166+
167+
See [`docs/kaggle_training.md`](docs/kaggle_training.md) and [`kaggle/README.md`](kaggle/README.md).
168+
169+
## Deploy To AWS
170+
171+
The API deploys as a Dockerized FastAPI service on AWS App Runner with ECR and Terraform.
172+
173+
```bash
174+
cd infra/aws/apprunner
175+
terraform init
176+
terraform apply -target=aws_ecr_repository.api
177+
terraform apply -var "api_token=replace-with-a-strong-token"
178+
```
179+
180+
After the first targeted apply creates ECR, build and push the Docker image from the
181+
repo root, then run the full apply. See [`docs/aws_deployment.md`](docs/aws_deployment.md) and
182+
[`infra/aws/apprunner/README.md`](infra/aws/apprunner/README.md).
183+
134184
## Run the Project
135185

136186
### Run CLI demo
@@ -177,7 +227,7 @@ Typical `/predict` response includes:
177227

178228
- Risk probability + risk label
179229
- Calculated BMI
180-
- SHAP feature impacts
230+
- Feature impact scores
181231
- Retrieved medical context
182232
- Similar past cases
183233
- Explanation text
@@ -213,7 +263,7 @@ Each record includes timestamp, request ID, normalized patient summary, risk res
213263
"age": 45,
214264
"height_cm": 175,
215265
"weight_kg": 78,
216-
"blood_pressure": 130,
266+
"bp": 130,
217267
"glucose": 165
218268
}
219269
```
@@ -227,10 +277,10 @@ Always consult qualified healthcare professionals for real medical advice.
227277

228278
## Current Limitations
229279

230-
- Model training data is currently small/synthetic
280+
- Kaggle artifact must be trained/downloaded before production deployment
231281
- Recommendations are generated by LLM and may require strict clinical guardrails
232282
- No full production security/compliance pipeline yet (PHI/HIPAA hardening)
233-
- Evaluation and fairness benchmarking should be expanded
283+
- Fairness, calibration, and drift monitoring should be expanded
234284

235285
## Roadmap
236286

@@ -244,11 +294,10 @@ Always consult qualified healthcare professionals for real medical advice.
244294

245295
### Planned (Phase 3+)
246296

247-
- Better datasets and stronger model validation
297+
- Fairness reports, calibration plots, and drift monitoring
248298
- Safety guardrails and escalation logic
249299
- Automated tests and CI pipeline
250300
- Better observability and decision tracing
251-
- Containerized deployment (Docker)
252301
- Feedback loops for continuous learning
253302

254303
### Implemented in Phase 3
@@ -274,6 +323,14 @@ Always consult qualified healthcare professionals for real medical advice.
274323
- Added local pre-commit hooks for code quality
275324
- Added Dependabot automation for pip and GitHub Actions updates
276325

326+
### Implemented in Phase 5
327+
328+
- Added Kaggle training workflow for the CDC Diabetes Health Indicators dataset
329+
- Added persisted `joblib` artifact loading with local fallback
330+
- Expanded API/UI inputs for BRFSS-style health indicators
331+
- Added production training script with model comparison and metrics output
332+
- Added AWS App Runner Terraform deployment stack
333+
277334
## Security Notes
278335

279336
See [`docs/security.md`](docs/security.md) for PII handling guidance, retention policy, and hardening recommendations.

agents/orchestrator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ def run(self, patient_data):
7272
"safety": safety,
7373
"explanation": explanation,
7474
"recommendation": recommendation,
75+
"model": self.model.artifact_metadata,
7576
}

api/app.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ class PatientRequest(BaseModel):
2626
bmi: Optional[float] = Field(None, ge=0, le=80)
2727
bp: int = Field(..., ge=0, le=260)
2828
glucose: Optional[float] = Field(None, ge=0, le=500)
29+
high_chol: Optional[bool] = None
30+
chol_check: Optional[bool] = None
31+
smoker: Optional[bool] = None
32+
stroke: Optional[bool] = None
33+
heart_disease_or_attack: Optional[bool] = None
34+
phys_activity: Optional[bool] = None
35+
fruits: Optional[bool] = None
36+
veggies: Optional[bool] = None
37+
heavy_alcohol_consump: Optional[bool] = None
38+
any_healthcare: Optional[bool] = None
39+
no_doc_bc_cost: Optional[bool] = None
40+
general_health: Optional[int] = Field(None, ge=1, le=5)
41+
mental_health_days: Optional[int] = Field(None, ge=0, le=30)
42+
physical_health_days: Optional[int] = Field(None, ge=0, le=30)
43+
diff_walk: Optional[bool] = None
44+
sex: Optional[str] = Field(None, pattern="^(female|male|0|1|F|M|f|m)$")
45+
education: Optional[int] = Field(None, ge=1, le=6)
46+
income: Optional[int] = Field(None, ge=1, le=8)
2947

3048

3149
class ChatRequest(BaseModel):

artifacts/.gitkeep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

artifacts/metrics.json

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
{
2+
"schema_version": 2,
3+
"trained_at": "2026-05-18T04:25:44.982333+00:00",
4+
"dataset_slug": "alexteboul/diabetes-health-indicators-dataset",
5+
"dataset_path": "/kaggle/input/datasets/alexteboul/diabetes-health-indicators-dataset/diabetes_binary_health_indicators_BRFSS2015.csv",
6+
"target": "Diabetes_binary",
7+
"rows_total": 253680,
8+
"rows_train": 202944,
9+
"rows_test": 50736,
10+
"feature_names": [
11+
"HighBP",
12+
"HighChol",
13+
"CholCheck",
14+
"BMI",
15+
"Smoker",
16+
"Stroke",
17+
"HeartDiseaseorAttack",
18+
"PhysActivity",
19+
"Fruits",
20+
"Veggies",
21+
"HvyAlcoholConsump",
22+
"AnyHealthcare",
23+
"NoDocbcCost",
24+
"GenHlth",
25+
"MentHlth",
26+
"PhysHlth",
27+
"DiffWalk",
28+
"Sex",
29+
"Age",
30+
"Education",
31+
"Income"
32+
],
33+
"selected_model": "hist_gradient_boosting",
34+
"selection_method": "5-fold_cv_then_holdout_test",
35+
"calibration": "isotonic_cv3",
36+
"tuned_models": [
37+
"hist_gradient_boosting",
38+
"lightgbm"
39+
],
40+
"lightgbm_available": true,
41+
"metrics": {
42+
"logistic_regression": {
43+
"cv_roc_auc_mean": 0.8233,
44+
"cv_roc_auc_std": 0.00144,
45+
"roc_auc": 0.8196,
46+
"average_precision": 0.39165,
47+
"brier_score": 0.09928,
48+
"accuracy_at_0_5": 0.86239,
49+
"precision_at_0_5": 0.53234,
50+
"recall_at_0_5": 0.10129,
51+
"f1_at_0_5": 0.17019,
52+
"selected_threshold": 0.22077,
53+
"accuracy": 0.78506,
54+
"precision": 0.35327,
55+
"recall": 0.65327,
56+
"f1": 0.45857,
57+
"confusion_matrix": {
58+
"true_negative": 35213,
59+
"false_positive": 8454,
60+
"false_negative": 2451,
61+
"true_positive": 4618
62+
}
63+
},
64+
"hist_gradient_boosting": {
65+
"cv_roc_auc_mean": 0.83059,
66+
"cv_roc_auc_std": 0.00232,
67+
"best_params": {
68+
"min_samples_leaf": 40,
69+
"max_iter": 350,
70+
"max_depth": null,
71+
"learning_rate": 0.05,
72+
"l2_regularization": 0.1
73+
},
74+
"cv_roc_auc_tune": 0.83034,
75+
"roc_auc": 0.82698,
76+
"average_precision": 0.42299,
77+
"brier_score": 0.09739,
78+
"accuracy_at_0_5": 0.86536,
79+
"precision_at_0_5": 0.56503,
80+
"recall_at_0_5": 0.14627,
81+
"f1_at_0_5": 0.23239,
82+
"selected_threshold": 0.22379,
83+
"accuracy": 0.80075,
84+
"precision": 0.37302,
85+
"recall": 0.63163,
86+
"f1": 0.46904,
87+
"confusion_matrix": {
88+
"true_negative": 36162,
89+
"false_positive": 7505,
90+
"false_negative": 2604,
91+
"true_positive": 4465
92+
}
93+
},
94+
"random_forest": {
95+
"cv_roc_auc_mean": 0.82118,
96+
"cv_roc_auc_std": 0.00215,
97+
"roc_auc": 0.82434,
98+
"average_precision": 0.41728,
99+
"brier_score": 0.09791,
100+
"accuracy_at_0_5": 0.86509,
101+
"precision_at_0_5": 0.56154,
102+
"recall_at_0_5": 0.14457,
103+
"f1_at_0_5": 0.22995,
104+
"selected_threshold": 0.23549,
105+
"accuracy": 0.80527,
106+
"precision": 0.3767,
107+
"recall": 0.60744,
108+
"f1": 0.46502,
109+
"confusion_matrix": {
110+
"true_negative": 36562,
111+
"false_positive": 7105,
112+
"false_negative": 2775,
113+
"true_positive": 4294
114+
}
115+
},
116+
"lightgbm": {
117+
"cv_roc_auc_mean": 0.82868,
118+
"cv_roc_auc_std": 0.00235,
119+
"best_params": {
120+
"subsample": 0.8,
121+
"num_leaves": 31,
122+
"n_estimators": 200,
123+
"min_child_samples": 40,
124+
"learning_rate": 0.03,
125+
"colsample_bytree": 1.0
126+
},
127+
"cv_roc_auc_tune": 0.83017,
128+
"roc_auc": 0.8269,
129+
"average_precision": 0.42152,
130+
"brier_score": 0.09745,
131+
"accuracy_at_0_5": 0.86528,
132+
"precision_at_0_5": 0.5627,
133+
"recall_at_0_5": 0.14854,
134+
"f1_at_0_5": 0.23503,
135+
"selected_threshold": 0.23289,
136+
"accuracy": 0.80487,
137+
"precision": 0.37788,
138+
"recall": 0.61961,
139+
"f1": 0.46945,
140+
"confusion_matrix": {
141+
"true_negative": 36456,
142+
"false_positive": 7211,
143+
"false_negative": 2689,
144+
"true_positive": 4380
145+
}
146+
}
147+
},
148+
"sklearn_version": "1.6.1"
149+
}

0 commit comments

Comments
 (0)