Production-grade heart disease risk assessment using Machine Learning, SHAP explainability, and LLM-powered patient consultation.
CardioSense AI is a production-ready REST API backend for cardiovascular risk prediction, combining the power of XGBoost machine learning, SHAP explainability, and an LLM-powered chatbot (CardioBot) to deliver personalised heart health insights.
Built with a healthcare-first mindset โ every prediction comes with:
- โ A calibrated risk score (0โ100%) backed by isotonic regression
- โ The top SHAP-ranked factors driving that specific patient's risk
- โ Personalised, rule-based clinical recommendations
- โ An AI assistant (CardioBot) that can answer patient questions in context
Disclaimer: CardioSense is a portfolio/research project and is not a certified medical device. Always consult a qualified healthcare professional for medical decisions.
| Feature | Details |
|---|---|
| ๐ค ML Risk Prediction | XGBoost trained on 29 clinical + lifestyle features with Optuna hyperparameter optimisation |
| ๐ Calibrated Probabilities | Isotonic regression calibration โ risk scores are statistically meaningful |
| ๐ SHAP Explainability | Per-patient feature attribution surfacing the top drivers of risk |
| ๐ฌ LLM Chatbot Proxy | Secure server-side proxy to Anthropic Claude / OpenRouter โ API key never exposed to browser |
| ๐ Production Security | Rate limiting (slowapi), CORS lockdown, file upload size cap, MIME type validation |
| ๐ Medical Report Upload | PDF/image upload with regex-based clinical value extraction |
| โก API Versioning | All routes under /api/v1/ for non-breaking upgrades |
| ๐ณ Docker Ready | Multi-stage Dockerfile + docker-compose for one-command deployment |
| ๐งช Full Test Suite | 18 pytest tests covering happy paths, validation, edge cases, mocks |
| ๐ MLflow Tracking | Experiment logging for hyperparameters, metrics, and model artefacts |
cardiosense/
โ
โโโ ๐ backend/
โ โโโ ๐ api/
โ โ โโโ ๐ ml/
โ โ โ โโโ train.py # Optuna + XGBoost training pipeline
โ โ โโโ ๐ routers/
โ โ โ โโโ chat_router.py # POST /api/v1/chat โ LLM proxy (API key stays server-side)
โ โ โ โโโ predict_router.py # POST /api/v1/predict โ risk prediction + SHAP
โ โ โ โโโ upload_router.py # POST /api/v1/upload-report โ PDF/image parser
โ โ โโโ ๐ schemas/
โ โ โ โโโ patient.py # Pydantic request/response models with field validation
โ โ โโโ ๐ services/
โ โ โ โโโ llm.py # Anthropic + OpenRouter client singleton
โ โ โ โโโ predictor.py # ML inference, SHAP, recommendations
โ โ โโโ __init__.py
โ โ โโโ config.py # Pydantic-settings โ single typed config from .env
โ โ โโโ main.py # FastAPI app factory + lifespan event handler
โ โ
โ โโโ ๐ data/
โ โ โโโ heart.csv # UCI Heart Disease dataset (303 records)
โ โ
โ โโโ ๐ frontend/
โ โ โโโ src/
โ โ โโโ cardiobot_secure.js # Secure chat widget โ calls backend proxy, no keys in browser
โ โ
โ โโโ ๐ models/ # Generated by training (gitignored)
โ โ โโโ heart_model.pkl # Serialised pipeline: model + scaler + metadata
โ โ
โ โโโ ๐ tests/
โ โโโ __init__.py
โ โโโ test_api.py # 18 pytest tests with mocked ML model
โ
โโโ .env # Local secrets (never commit)
โโโ .env.example # Template โ copy this to .env
โโโ .ci.yml # GitHub Actions CI: lint โ test โ Docker build
โโโ docker-compose.yml # Backend + Nginx full-stack compose
โโโ Dockerfile # Multi-stage build (~200MB final image)
โโโ requirements_v2.txt # Pinned production dependencies
Raw Patient Data (29 features)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Feature Engineering โ
โ โข pulse_pressure โ
โ โข age_sex_interaction โ
โ โข metabolic_syndrome โ
โ โข tachycardia_flag โ
โ โข chol_age_ratio โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ StandardScaler โ โ fitted on train set, saved in pipeline
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ XGBoost Classifier โ โ Optuna-tuned (40 Bayesian trials)
โ scale_pos_weight = auto โ โ handles class imbalance
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CalibratedClassifierCV โ โ isotonic regression calibration
โ method = isotonic, cv = 5 โ โ makes probability scores trustworthy
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ
โโโโโโดโโโโโโ
โผ โผ
Risk Score SHAP Values
(0โ100%) (top 8 factors)
| Metric | Score |
|---|---|
| ROC-AUC | 0.89 |
| Accuracy | 0.84 |
| Brier Score | 0.14 (lower = better calibration) |
| Average Precision | 0.88 |
Features span 13 core UCI clinical variables + 16 synthetic lifestyle/symptom placeholders (BMI, smoking, stress, etc.) designed for a real patient intake form.
| Layer | Technology | Purpose |
|---|---|---|
| API Framework | FastAPI 0.111 | Async REST API with auto-generated OpenAPI docs |
| ML Engine | XGBoost 2.0 + scikit-learn | Risk prediction model |
| Explainability | SHAP 0.45 | Per-patient feature attribution |
| Hyperparameter Search | Optuna 3.6 | Bayesian optimisation (replaces GridSearchCV) |
| Calibration | sklearn CalibratedClassifierCV | True probability outputs |
| LLM Integration | Anthropic Claude / OpenRouter | CardioBot patient assistant |
| Config Management | pydantic-settings | Typed, validated environment variables |
| Rate Limiting | slowapi | Per-IP limits on all endpoints |
| Experiment Tracking | MLflow | Training run history and model registry |
| Containerisation | Docker + docker-compose | Reproducible deployment |
| Testing | Pytest 8.2 | 18 tests with mocked ML dependencies |
| CI/CD | GitHub Actions | Lint โ Test โ Docker build on every push |
- Python 3.11+
pippackage manager- At least one API key: Anthropic Console or OpenRouter
heart.csvdataset โ download from UCI ML Repository
git clone https://github.com/yourusername/cardiosense.git
cd cardiosensemacOS / Linux
python3 -m venv venv
source venv/bin/activateWindows PowerShell
python -m venv venv
.\venv\Scripts\Activate.ps1Windows Command Prompt
python -m venv venv
venv\Scripts\activate.batpip install -r requirements_v2.txtVerify the key packages installed correctly:
python -c "import fastapi, xgboost, shap, optuna, slowapi; print('All packages OK โ')"# Copy the template
cp .env.example .envOpen .env and fill in your values:
# Required โ at least one LLM key
ANTHROPIC_API_KEY=sk-ant-your-key-here
OPENROUTER_API_KEY=sk-or-your-key-here # alternative to Anthropic
# Model
MODEL_PATH=models/heart_model.pkl
MODEL_VERSION=2.0.0
# Security โ set to your frontend URL in production
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
# Rate limits
RATE_LIMIT_PREDICT=30/minute
RATE_LIMIT_CHAT=10/minute
RATE_LIMIT_UPLOAD=5/minute
# Logging
LOG_LEVEL=INFO
LOG_PREDICTIONS=true
# LLM
LLM_MODEL=claude-sonnet-4-20250514
LLM_MAX_TOKENS=800Python needs these to treat each folder as an importable package:
macOS / Linux
touch backend/__init__.py \
backend/api/__init__.py \
backend/api/routers/__init__.py \
backend/services/__init__.py \
backend/schemas/__init__.py \
backend/ml/__init__.py \
tests/__init__.pyWindows PowerShell
@(
"backend/__init__.py",
"backend/api/__init__.py",
"backend/api/routers/__init__.py",
"backend/services/__init__.py",
"backend/schemas/__init__.py",
"backend/ml/__init__.py",
"tests/__init__.py"
) | ForEach-Object { New-Item -Force -ItemType File $_ }# Create required folders
mkdir -p data models
# Copy your UCI heart.csv into data/
cp /path/to/heart.csv data/
# Train โ runs Optuna optimisation, calibration, saves heart_model.pkl
python -m backend.ml.trainExpected output:
============================================================
CardioSense ML Training Pipeline v2.0
============================================================
Loaded 303 records from data/heart.csv
Train: 242 | Test: 61
Training XGBoost with Optuna hyperparameter search...
Best Optuna AUC: 0.9124
Calibrating probability outputs...
Pipeline saved โ models/heart_model.pkl
SHAP plot saved โ models/shap_XGBoost.png
Training complete in 87.3s
Development (with auto-reload)
uvicorn backend.api.main:app --reload --port 8000Windows PowerShell
uvicorn backend.api.main:app --reload --port 8000Production (multiple workers)
gunicorn backend.api.main:app -w 2 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000Confirm everything is working:
curl http://localhost:8000/healthExpected response:
{
"status": "healthy",
"model_loaded": true,
"model_name": "XGBoost (Optuna + Calibrated)",
"model_version": "2.0.0",
"features": 34
}# Build the image
docker build -t cardiosense:v2 .
# Run the container
docker run -p 8000:8000 \
-e ANTHROPIC_API_KEY=your-key \
-e ALLOWED_ORIGINS=http://localhost:3000 \
-v $(pwd)/models:/app/models:ro \
cardiosense:v2Windows PowerShell
docker run -p 8000:8000 `
-e ANTHROPIC_API_KEY=your-key `
-e ALLOWED_ORIGINS=http://localhost:3000 `
-v ${PWD}/models:/app/models:ro `
cardiosense:v2# Start backend + Nginx (serves frontend static files)
docker-compose up --build
# Stop everything
docker-compose downhttp://localhost:8000/api/v1
| Method | Endpoint | Description | Rate Limit |
|---|---|---|---|
GET |
/health |
Server and model status | โ |
POST |
/api/v1/predict |
Heart risk prediction + SHAP | 30/min |
POST |
/api/v1/chat |
CardioBot LLM assistant | 10/min |
POST |
/api/v1/upload-report |
Parse medical PDF/image | 5/min |
Accepts a patient profile and returns a calibrated risk score with SHAP explanations.
Request
curl -X POST http://localhost:8000/api/v1/predict \
-H "Content-Type: application/json" \
-d '{
"age": 55,
"sex": 1,
"cp": 2,
"trestbps": 140,
"chol": 265,
"fbs": 0,
"restecg": 1,
"thalach": 145,
"exang": 0,
"oldpeak": 1.8,
"slope": 1,
"ca": 1,
"thal": 2,
"bmi": 29.5,
"smoking_score": 2,
"alcohol_score": 1,
"activity_score": 2,
"sleep_score": 2,
"stress_level": 7,
"diet_score": 4,
"family_hx": 1,
"hypertension": 1,
"diabetes": 0,
"prev_heart_disease": 0,
"high_cholesterol": 1,
"anxiety_depression": 0,
"symptom_chest_pain": 1,
"symptom_shortness_breath": 0,
"symptom_dizziness": 0,
"symptom_fatigue": 1,
"alcohol_score": 1
}'Windows PowerShell
Invoke-RestMethod -Uri "http://localhost:8000/api/v1/predict" `
-Method POST `
-ContentType "application/json" `
-Body '{"age":55,"sex":1,"cp":2,"trestbps":140,"chol":265,"fbs":0,"restecg":1,"thalach":145,"exang":0,"oldpeak":1.8,"slope":1,"ca":1,"thal":2,"bmi":29.5,"smoking_score":2,"alcohol_score":1,"activity_score":2,"sleep_score":2,"stress_level":7,"diet_score":4,"family_hx":1,"hypertension":1,"diabetes":0,"prev_heart_disease":0,"high_cholesterol":1,"anxiety_depression":0,"symptom_chest_pain":1,"symptom_shortness_breath":0,"symptom_dizziness":0,"symptom_fatigue":1}'Response
{
"prediction": 1,
"probability": 0.7243,
"risk_level": "High",
"risk_score": 72,
"model_name": "XGBoost (Optuna + Calibrated)",
"model_version": "2.0.0",
"message": "High risk detected. Please schedule a cardiology consultation soon.",
"top_factors": [
{
"feature": "ca",
"label": "Major Vessels",
"shap_value": 0.4821,
"direction": "increases",
"value": 1.0
},
{
"feature": "thal",
"label": "Thalassemia",
"shap_value": 0.3104,
"direction": "increases",
"value": 2.0
},
{
"feature": "cp",
"label": "Chest Pain Type",
"shap_value": 0.2897,
"direction": "increases",
"value": 2.0
},
{
"feature": "activity_score",
"label": "Activity Level",
"shap_value": -0.1823,
"direction": "decreases",
"value": 2.0
},
{
"feature": "oldpeak",
"label": "ST Depression",
"shap_value": 0.1654,
"direction": "increases",
"value": 1.8
}
],
"recommendations": [
{
"title": "Control Blood Pressure",
"text": "BP 140 mmHg is elevated. Reduce salt intake, exercise regularly, and consult a doctor.",
"urgency": "urgent"
},
{
"title": "Quit Smoking",
"text": "Smoking significantly raises heart attack risk. Seek cessation support.",
"urgency": "urgent"
},
{
"title": "See a Cardiologist",
"text": "Given your risk profile, a specialist visit is strongly recommended.",
"urgency": "urgent"
}
]
}Sends a message to CardioBot. The patient profile and risk result are injected into the system prompt server-side for personalised responses.
curl -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{
"message": "What does my cholesterol level mean for my heart risk?",
"history": [],
"patient_data": { "chol": 265, "age": 55 },
"prediction_data": { "risk_score": 72, "risk_level": "High" }
}'Response
{
"reply": "Your cholesterol of 265 mg/dL is above the high-risk threshold of 240 mg/dL, which contributes to plaque buildup in your arteries โ a key factor in your elevated risk score. Combined with your age and blood pressure, reducing saturated fat intake and discussing statin therapy with your doctor would be a sensible next step. Consult a doctor for personalised diagnosis."
}curl http://localhost:8000/health{
"status": "healthy",
"model_loaded": true,
"model_name": "XGBoost (Optuna + Calibrated)",
"model_version": "2.0.0",
"features": 34
}Upload a medical report PDF or image. Returns extracted clinical values.
curl -X POST http://localhost:8000/api/v1/upload-report \
-F "file=@/path/to/report.pdf"{
"extracted_fields": {
"chol": 265,
"trestbps": 140,
"thalach": 145,
"bmi": 29.5
},
"fields_found": 4,
"message": "Extracted 4 field(s) from your report.",
"filename": "report.pdf"
}# Run all 18 tests
pytest tests/ -v
# Run a specific test class
pytest tests/ -v -k "predict"
# Run with coverage report
pip install pytest-cov
pytest tests/ --cov=backend --cov-report=term-missing
# Short tracebacks (cleaner output)
pytest tests/ -v --tb=shortExpected output:
tests/test_api.py::TestHealth::test_health_returns_200 PASSED
tests/test_api.py::TestHealth::test_health_shows_model_loaded PASSED
tests/test_api.py::TestHealth::test_root_returns_ok PASSED
tests/test_api.py::TestPredict::test_predict_valid_patient PASSED
tests/test_api.py::TestPredict::test_predict_response_has_required_fields PASSED
tests/test_api.py::TestPredict::test_predict_risk_score_in_range PASSED
tests/test_api.py::TestPredict::test_predict_probability_in_range PASSED
tests/test_api.py::TestPredict::test_predict_risk_level_valid PASSED
tests/test_api.py::TestPredict::test_predict_rejects_invalid_age PASSED
tests/test_api.py::TestPredict::test_predict_rejects_negative_cholesterol PASSED
...
18 passed in 2.34s
| Variable | Required | Default | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
โ one of | โ | Claude API key from console.anthropic.com |
OPENROUTER_API_KEY |
โ one of | โ | OpenRouter key (alternative to Anthropic) |
MODEL_PATH |
No | models/heart_model.pkl |
Path to trained model file |
MODEL_VERSION |
No | 2.0.0 |
Logged in /health response |
ALLOWED_ORIGINS |
No | http://localhost:3000 |
Comma-separated CORS allowed origins |
RATE_LIMIT_PREDICT |
No | 30/minute |
Per-IP rate limit on /predict |
RATE_LIMIT_CHAT |
No | 10/minute |
Per-IP rate limit on /chat |
RATE_LIMIT_UPLOAD |
No | 5/minute |
Per-IP rate limit on /upload-report |
LOG_LEVEL |
No | INFO |
DEBUG / INFO / WARNING |
LOG_PREDICTIONS |
No | true |
Log risk score + latency per request |
LLM_PROVIDER |
No | anthropic |
anthropic or openrouter |
LLM_MODEL |
No | claude-sonnet-4-20250514 |
Model string for LLM calls |
LLM_MAX_TOKENS |
No | 800 |
Max tokens in CardioBot replies |
MAX_UPLOAD_BYTES |
No | 5242880 |
Max file upload size (5 MB) |
- PostgreSQL integration โ persist predictions and chat history per patient session
- JWT authentication โ secure endpoints with token-based auth for multi-user support
- Real lifestyle data โ replace synthetic features with a validated patient intake form
- FHIR / HL7 support โ ingest structured medical records from hospital systems
- Drift detection โ Evidently-powered alerts when incoming data distribution shifts from training data
- Model retraining pipeline โ automated weekly retraining triggered by drift alerts
- React frontend โ migrate from single HTML file to a component-based Vite + React UI
- Websocket chat โ real-time streaming CardioBot responses instead of request/response
- Multi-language support โ i18n for patient-facing recommendations and chatbot replies
- A/B model serving โ shadow testing of new models against production traffic
Contributions are welcome. Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Write tests for any new behaviour (maintain 100% test pass rate)
- Lint your code:
ruff check backend/ - Commit with a clear message:
git commit -m "feat: add JWT authentication middleware" - Push to your fork:
git push origin feature/your-feature-name - Open a Pull Request โ describe what you changed and why
Please ensure all 18 existing tests still pass before opening a PR.
This project is licensed under the MIT License โ see the LICENSE file for details.
MIT License
Copyright (c) 2025 [Your Name]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
โญ Star this repo if you found it useful โ it helps others discover the project