Skip to content
AstreonixPublic

About

An end-to-end AI-powered heart disease risk prediction platform built with Flask, scikit-learn, SHAP explainability, Gemini AI chat, SQLAlchemy, and ReportLab PDF generation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

2 Commits

Folders and files

Repository files navigation

πŸ«€ CardioAI β€” Heart Disease Risk Prediction Platform

Python Flask scikit-learn SHAP Gemini License CI

CardioAI is an end-to-end web application that estimates a person's heart disease risk from 13 clinical measurements. It combines a trained Random Forest model, SHAP explanations for every prediction, a Gemini-powered health assistant that knows the user's result, and downloadable PDF reports.

⚠️ Disclaimer: CardioAI is an educational and research project. It is not a medical device and must not be used to diagnose, treat or make decisions about any health condition. Always consult a qualified healthcare professional.


Table of Contents

  1. Features
  2. How It Works (Pipeline)
  3. Project Structure
  4. Quick Start
  5. Configuration
  6. Input Features
  7. API Routes
  8. Database Schema
  9. Machine Learning Pipeline
  10. Docker and Deployment
  11. Security and Privacy Notes
  12. Troubleshooting
  13. Author
  14. License

✨ Features

Feature Description
🎯 Risk prediction Random Forest classifier returns a probability and a Low / Moderate / High risk level
🧠 SHAP explainability Per-prediction chart showing which features pushed the risk up or down
πŸ’¬ Gemini AI chat Assistant that receives the user's risk level, probability and key factors as context
πŸ“„ PDF reports Downloadable report with inputs, risk factors, SHAP chart and recommendations
πŸ—„οΈ Persistence SQLite via SQLAlchemy stores predictions, chat messages and generated reports
πŸ”’ Rate limiting Flask-Limiter protects every route
βœ… Input validation Server-side range and type checks on all 13 fields (rejects missing, non-numeric, NaN and out-of-range values)
πŸ“Š History dashboard Browse past assessments
πŸ›Ÿ Graceful fallback If Gemini is unavailable, the chat falls back to built-in educational answers

πŸ”„ How It Works (Pipeline)

1. End-to-end flow

flowchart LR
    A[Dataset CSV] --> B[01 EDA notebook]
    B --> C[02 Preprocessing notebook]
    C --> D[03 Model Training notebook]
    D --> E[(models/*.pkl)]
    E --> F[Flask app.py]
    F --> G[Web UI + PDF + Chat]
Loading

Offline, the notebooks turn the dataset into trained artifacts. Online, the Flask app loads those artifacts once at startup and serves predictions.

2. What happens when a user submits an assessment (POST /predict)

flowchart TD
    A[Form values as JSON] --> B[validate_input<br/>type, range, NaN checks]
    B -->|invalid| X[422 with error list]
    B --> C[build_feature_vector<br/>one-hot cp, restecg, slope, thal]
    C --> D[StandardScaler.transform]
    D --> E[Random Forest<br/>predict_proba + predict]
    E --> F[Risk level<br/>below 30% Low, below 60% Moderate, else High]
    D --> G[SHAP TreeExplainer<br/>class 1 values]
    G --> H[Top-10 SHAP bar chart<br/>base64 PNG]
    E --> I[Key factor rules<br/>age, chol, BP, HR, vessels, angina]
    F --> J[(Save to SQLite)]
    H --> J
    I --> J
    J --> K[JSON response to the browser]
Loading

3. Chat flow (POST /chat/message)

flowchart TD
    A[User message + last 6 messages] --> B{Prediction id given?}
    B -->|yes| C[Build context: risk level, probability, age, sex, key factors]
    B -->|no| D[General cardiovascular assistant context]
    C --> E[Gemini generate_content<br/>context sent as system instruction]
    D --> E
    E -->|success| F[Reply]
    E -->|error or empty| G[Built-in fallback answer]
    G --> F
    F --> H[(Save messages to SQLite)]
Loading

4. Report flow (GET /report/<id>)

The saved prediction, its input values and the stored SHAP values are loaded from the database, rendered into a PDF with ReportLab (summary table, clinical inputs, key factors, SHAP chart, risk-based recommendations, disclaimer) and streamed to the browser. Each download is logged in the report table.


πŸ“ Project Structure

CardioAI/
β”œβ”€β”€ app.py                      # Flask app: routes, validation, prediction, SHAP, Gemini chat, PDF
β”œβ”€β”€ requirements.txt            # Pinned Python dependencies
β”œβ”€β”€ Dockerfile                  # Container image
β”œβ”€β”€ render.yaml                 # Render.com deployment blueprint
β”œβ”€β”€ .env.example                # Environment variable template (copy to .env)
β”œβ”€β”€ .gitignore / .dockerignore
β”œβ”€β”€ LICENSE
β”‚
β”œβ”€β”€ .github/workflows/
β”‚   └── ci.yml                  # Continuous integration
β”‚
β”œβ”€β”€ templates/                  # Jinja2 pages
β”‚   β”œβ”€β”€ index.html              # Landing page + model metrics
β”‚   β”œβ”€β”€ predict.html            # Assessment form and results
β”‚   β”œβ”€β”€ history.html            # Past assessments
β”‚   β”œβ”€β”€ chat.html               # AI assistant
β”‚   └── 404.html                # Not-found page
β”‚
β”œβ”€β”€ models/                     # Trained artifacts loaded at startup
β”‚   β”œβ”€β”€ cardioai_model.pkl      # Trained Random Forest
β”‚   β”œβ”€β”€ scaler.pkl              # Fitted StandardScaler
β”‚   β”œβ”€β”€ feature_columns.pkl     # Exact feature order the model expects
β”‚   β”œβ”€β”€ model_metadata.pkl      # Test metrics and parameters
β”‚   └── shap_explainer.pkl      # SHAP TreeExplainer
β”‚
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ CardioAI_Merged_Heart_Dataset.csv   # Source dataset
β”‚   └── processed/              # X_train, X_test, y_train, y_test
β”‚
β”œβ”€β”€ notebooks/
β”‚   β”œβ”€β”€ 01_EDA.ipynb            # Exploratory data analysis
β”‚   β”œβ”€β”€ 02_Preprocessing.ipynb  # Encoding, scaling, train/test split
β”‚   └── 03_Model_Training.ipynb # Model comparison, final model, SHAP
β”‚
└── images/                     # Plots produced by the notebooks

A SQLite database file (cardioai.db) is created automatically on first run. It is git-ignored because it contains user-submitted health data.


πŸš€ Quick Start

Requirements: Python 3.11 (recommended) and Git.

# 1. Clone
git clone https://github.com/Astreonix/CardioAI.git
cd CardioAI

# 2. Create and activate a virtual environment
python -m venv venv
venv\Scripts\activate            # Windows (PowerShell)
# source venv/bin/activate       # macOS / Linux

# 3. Install dependencies
pip install -r requirements.txt

# 4. Configure environment variables
copy .env.example .env           # Windows
# cp .env.example .env           # macOS / Linux
# then open .env and fill in your values (see Configuration)

# 5. Run
python app.py

Open http://127.0.0.1:5000 in your browser.

The app works without a Gemini key: predictions, SHAP charts, history and PDF reports run normally, and the chat uses its built-in fallback answers.


πŸ”‘ Configuration

Set these in a .env file (never commit it) or in your hosting provider's environment settings.

Variable Required Default Description
GEMINI_API_KEY For AI chat β€” Free key from Google AI Studio
GEMINI_MODEL No gemini-2.5-flash Gemini model name. Google retires models often, so set this to one your key can use (see Troubleshooting). A lite model such as gemini-flash-lite-latest gives faster replies
FLASK_SECRET_KEY Recommended dev placeholder Generate with python -c "import secrets; print(secrets.token_hex(32))"
FLASK_ENV No development development enables debug mode; set production when deployed
HOST No 127.0.0.1 Bind address. Use 0.0.0.0 only when the app must be reachable from other machines (e.g. inside a container)
PORT No 5000 Port to listen on
DB_PATH No ./cardioai.db Location of the SQLite file (point to a persistent disk when deploying)

🩺 Input Features

The model uses 13 clinical measurements. The server validates every field.

Field Meaning Allowed range
age Age in years 1 – 120
sex 0 = female, 1 = male 0 – 1
cp Chest pain type: 1 typical angina, 2 atypical angina, 3 non-anginal pain, 4 asymptomatic 1 – 4
trestbps Resting blood pressure (mmHg) 50 – 300
chol Serum cholesterol (mg/dl) 50 – 700
fbs Fasting blood sugar above 120 mg/dl (1 = yes) 0 – 1
restecg Resting ECG: 0 normal, 1 ST-T abnormality, 2 left ventricular hypertrophy 0 – 2
thalach Maximum heart rate achieved (bpm) 40 – 250
exang Exercise-induced angina (1 = yes) 0 – 1
oldpeak ST depression induced by exercise 0 – 10
slope Slope of the peak exercise ST segment: 1 up, 2 flat, 3 down 1 – 3
ca Number of major vessels with blockage (fluoroscopy) 0 – 3
thal Thalassemia: 3 normal, 6 fixed defect, 7 reversible defect 0 – 7

Risk levels are derived from the predicted probability: Low below 30%, Moderate from 30% to below 60%, High at 60% and above.


πŸ“ API Routes

Route Method Rate limit Description
/ GET default Landing page with model metrics and usage counts
/predict GET 30/min Assessment form
/predict POST 30/min Run a prediction (JSON in, JSON out with probability, risk level, key factors, SHAP chart)
/history GET default History page
/history/data GET 60/min JSON list of the 100 most recent assessments
/chat GET default Chat page (optionally ?pred_id=<id> to attach an assessment)
/chat/message POST 20/min Send a message; returns the assistant reply
/report/<id> GET 10/min Download the PDF report for an assessment

Default limits for every route: 200 requests per day and 50 per hour per IP. Exceeding a limit returns HTTP 429 with a JSON error.

Example request

curl -X POST http://127.0.0.1:5000/predict \
  -H "Content-Type: application/json" \
  -d '{"age":58,"sex":1,"cp":4,"trestbps":150,"chol":270,"fbs":0,
       "restecg":1,"thalach":110,"exang":1,"oldpeak":2.5,"slope":2,"ca":2,"thal":7}'

πŸ—„οΈ Database Schema

SQLite, created automatically on startup.

Table Key columns
prediction id, timestamp, age, sex, probability, risk_level, prediction, input_data (JSON), key_factors (JSON), shap_values (JSON)
chat_message id, timestamp, prediction_id β†’ prediction.id, role (user / ai), content
report id, prediction_id β†’ prediction.id, generated_at, filename

πŸ€– Machine Learning Pipeline

The notebooks in notebooks/ run in order and produce everything the app needs.

Step Notebook What it does Outputs
1 01_EDA.ipynb Explores the dataset: class balance, feature distributions, correlations, disease rate per category images/target_distribution.png, numeric_distributions.png, numeric_by_target.png, categorical_disease_rates.png, correlation_heatmap.png, target_correlation_ranked.png
2 02_Preprocessing.ipynb Encodes categorical features (cp, restecg, slope, thal become one-hot columns), scales features with StandardScaler, splits into train and test sets data/processed/X_train.csv, X_test.csv, y_train.csv, y_test.csv, models/scaler.pkl, models/feature_columns.pkl
3 03_Model_Training.ipynb Compares candidate models, selects and evaluates the final Random Forest, builds the SHAP explainer images/model_comparison.png, final_model_evaluation.png, shap_summary.png, models/cardioai_model.pkl, model_metadata.pkl, shap_explainer.pkl

Results

Metric Value
ROC-AUC β‰ˆ 0.86

The full set of test metrics (accuracy, precision, recall, F1) is stored in models/model_metadata.pkl and shown on the landing page. See 03_Model_Training.ipynb for the exact figures.

Model comparison Final model evaluation

SHAP summary Correlation heatmap

Retraining

  1. Edit or replace the dataset in data/.
  2. Re-run the three notebooks in order.
  3. Commit the regenerated files in models/. The app reads the metrics, feature order and scaler from them automatically, so no code changes are needed.

The .pkl files must be produced with the same scikit-learn and numpy versions pinned in requirements.txt, otherwise loading may fail or give warnings.


🐳 Docker and Deployment

Run with Docker

docker build -t cardioai .
docker run -p 5000:5000 \
  -e GEMINI_API_KEY=your_key \
  -e GEMINI_MODEL=gemini-flash-lite-latest \
  -e FLASK_SECRET_KEY=change_me \
  cardioai

Open http://localhost:5000. If the container starts the app with python app.py rather than gunicorn, also pass -e HOST=0.0.0.0 so the port is reachable from outside the container.

Deploy to Render

  1. Push the repository to GitHub.
  2. On render.com: New β†’ Web Service β†’ connect Astreonix/CardioAI.
  3. Render reads render.yaml and the Dockerfile.
  4. In the Render dashboard β†’ Environment, add GEMINI_API_KEY, GEMINI_MODEL and FLASK_SECRET_KEY.
  5. For history to survive restarts, attach a persistent disk and set DB_PATH to a path on it.
  6. Deploy.

Free-tier instances sleep when idle, and without a persistent disk the SQLite data is lost on redeploy.


πŸ” Security and Privacy Notes

  • Never commit .env. It holds your API key. It is already in .gitignore. If a key is ever pushed by mistake, delete it in Google AI Studio and create a new one.
  • Never commit cardioai.db. It stores the health values users submit.
  • Debug mode exposes an interactive debugger, so the app binds to 127.0.0.1 by default. Only use HOST=0.0.0.0 behind a production server and with FLASK_ENV=production.
  • Error details are logged on the server and are not returned to the browser.
  • Rate limiting uses in-memory storage, so limits reset on restart and are per process.
  • Set a strong FLASK_SECRET_KEY in any deployment.

πŸ› οΈ Troubleshooting

Problem Fix
requirements.txt not found Run commands from the project folder that contains app.py
venv\Scripts\activate not recognised Activate from the folder that contains venv, then cd into the project
PowerShell blocks activate Run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned once
Chat shows generic answers The server log shows the real cause. Look for Gemini call failed in the terminal
Gemini 404 NOT_FOUND / model "no longer available" Google retired the model. List usable ones with the command below and set GEMINI_MODEL in .env, then restart
Chat replies are slow Use a lite model, e.g. GEMINI_MODEL=gemini-flash-lite-latest
Changes to .env ignored Stop the server with Ctrl+C and start it again. The reloader does not watch .env
scikit-learn warning about feature names Harmless; predictions are unaffected
Missing model file error on start Make sure models/ contains the five .pkl files, or re-run the notebooks

List the Gemini models your key can use:

python -c "from dotenv import load_dotenv; import os; load_dotenv(); from google import genai; c = genai.Client(api_key=os.getenv('GEMINI_API_KEY')); [print(m.name) for m in c.models.list()]"

Use a name containing flash, without the models/ prefix.


🧰 Tech Stack

Layer Tools
Backend Flask, Flask-SQLAlchemy, Flask-Limiter, Gunicorn
ML scikit-learn (Random Forest, StandardScaler), XGBoost (model comparison), pandas, NumPy
Explainability SHAP, Matplotlib
AI chat Google Gemini via google-genai
Reports ReportLab
Frontend Jinja2 templates, HTML, CSS, JavaScript
DevOps Docker, GitHub Actions, Render

πŸ‘¨β€πŸ’» Author

Mehr Ali β€” BS Computer Science, FAST NUCES Peshawar

GitHub


πŸ“„ License

Released under the MIT License. See LICENSE for details.

About

An end-to-end AI-powered heart disease risk prediction platform built with Flask, scikit-learn, SHAP explainability, Gemini AI chat, SQLAlchemy, and ReportLab PDF generation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages