Predictive Pulse is a full-stack Machine Learning web application that classifies a patient's hypertension stage — Normal, Stage 1, Stage 2, or Hypertensive Crisis — from 13 clinical and lifestyle inputs, and returns stage-specific risk factors, urgency level, and medical recommendations in real time.
Built with Flask, scikit-learn, and a complete Docker + GitHub Actions CI/CD pipeline, deployed live on Render.
🔗 Live Demo: predictive-pulse-l0tw.onrender.com
📦 Container Image: ghcr.io/sreesyam064/predictive-pulse
- 4-Stage Hypertension Classification — Normal / Stage 1 / Stage 2 / Hypertensive Crisis, based on 13 patient features (demographics, symptoms, BP ranges, medical history).
- Recall-Prioritized Model Selection — Since missing a hypertensive patient (a false negative) is clinically costlier than a false alarm, model comparison goes beyond accuracy to include precision, recall, and F1 (macro-averaged, so the rare Hypertensive Crisis class isn't overshadowed by the larger classes), plus 5-fold cross-validated accuracy and recall for stability. 7 candidate models were trained and compared; Decision Tree, Random Forest, and SVM scored a suspicious 100% across every metric and were rejected as overfitted. Logistic Regression was selected — not for the single highest recall (KNN edges it out marginally), but for the best combination of generalization (smallest train→test gap), CV-stable recall (98.0% ± 0.42), calibrated probabilities, and interpretable coefficients.
- Class Probability Breakdown — Returns prediction confidence across all four stages, not just the top class.
- Automated Risk Factor Detection — Flags contributing factors (age, family history, symptoms, medication adherence, diet) with High/Moderate/Low impact labels.
- Stage-Specific Clinical Guidance — Each prediction returns urgency level, medical recommendations, and (for Hypertensive Crisis) emergency-response instructions.
- Model Transparency Endpoint —
/model-infoexposes full model comparison results (accuracy, precision, recall, F1 — macro and weighted — plus cv scores), feature importances, and the overfitting rejection rationale. - Production-Ready DevOps — Multi-stage Docker builds (prod/dev), Docker Compose profiles, health checks, and a 3-job GitHub Actions pipeline (test → build & push → deploy).
- 32-Test Automated Suite — Covers routes, model selection logic, prediction correctness, dataset integrity, and model artifact validation.
| Layer | Technology |
|---|---|
| Backend | Flask 3.0, Gunicorn |
| ML / Data | scikit-learn 1.4, NumPy, Pandas, joblib |
| Frontend | HTML5, CSS3, Vanilla JavaScript |
| Visualization (training) | Matplotlib |
| Testing | Pytest |
| Linting | Flake8 |
| Containerization | Docker, Docker Compose |
| CI/CD | GitHub Actions |
| Container Registry | GitHub Container Registry (GHCR) |
| Deployment | Render (Docker environment) |
| Config | python-dotenv |
┌────────────────────────┐
│ train_models.py │
│ (offline / CI step) │
│ │
│ Load patient_data.csv │
│ → Clean & encode │
│ → 80/20 split │
│ → Train 7 models │
│ → Overfitting check │
│ → Select best model │
└──────────┬─────────────┘
│ saves
▼
models/best_model.pkl, scaler.pkl, metadata.json
│ loads at startup
▼
┌────────────┐ HTTP ┌────────────────────────┐
│ Browser │◄─────────►│ Flask App │
│ (index.html│ │ (app.py) │
│ + app.js) │ /predict │ │
└────────────┘ POST │ encode_input() │
│ scaler.transform() │
│ best_model.predict() │
│ build stage response │
└──────────┬─────────────┘
│
┌──────────▼──────────────┐
│ STAGE_INFO lookup │
│ (recommendations, │
│ urgency, color, icon) │
└─────────────────────────┘
Request flow: the browser form (index.html / app.js) collects patient inputs → sends JSON to POST /predict → Flask encodes the inputs to match training-time encoding → scales features with the saved StandardScaler → runs inference with the saved best model → looks up stage metadata → returns a JSON response rendered back into the UI.
All 7 candidate models are trained and scored on accuracy, precision, recall, and F1 (macro-averaged, so the rare Hypertensive Crisis class carries equal weight to the more common stages rather than being statistically overshadowed), plus 5-fold cross-validated accuracy and recall.
| Model | Accuracy | Precision (macro) | Recall (macro) | F1 (macro) | Status |
|---|---|---|---|---|---|
| Decision Tree | 100.0% | 100.0% | 100.0% | 100.0% | ❌ Rejected — overfit |
| Random Forest | 100.0% | 100.0% | 100.0% | 100.0% | ❌ Rejected — overfit |
| SVM | 100.0% | 100.0% | 100.0% | 100.0% | ❌ Rejected — overfit |
| KNN | 99.2% | 98.8% | 99.4% | 99.1% | |
| Logistic Regression | 97.3% | 95.7% | 97.9% | 96.6% | ✅ Selected |
| Ridge Classifier | 95.3% | 93.6% | 94.3% | 93.9% | |
| Gaussian Naive Bayes | 88.5% | 88.3% | 91.2% | 87.1% |
5-fold cross-validated recall (macro) for the selected model: 98.0% (± 0.42).
Why Logistic Regression, and not the model with the single highest recall:
- Decision Tree, Random Forest, and SVM are overfit. A perfect score across every metric on tabular clinical data signals memorization, not real separability — there's no train/test gap to inspect because both scores are 100%, which is itself the warning sign.
- KNN edges out Logistic Regression on raw recall (99.4% vs 97.9%), but is structurally riskier for deployment. It has no learned decision boundary — its predictions depend on which training rows happen to be nearest a new patient — it produces no interpretable output, and it must store the entire training set at inference time. All of this makes it fragile once real-world patient data drifts from the training distribution.
- Logistic Regression generalizes cleanly (97.4% train → 97.3% test, the smallest gap of any strong performer) and its recall is the most stable across cross-validation folds.
- Its coefficients are directly interpretable per feature — important for clinical trust and auditability — and it produces calibrated class probabilities, which the app uses to explain each prediction (
probabilitiesin the/predictresponse), unlike Ridge Classifier (no native probability estimates) or KNN (poorly calibrated neighbor-vote ratios).
The table above is also available as charts — generated by generate_visualizations.py directly from models/metadata.json and the saved model artifacts, so they always reflect the current training run rather than hand-typed numbers.
Metric comparison across all 7 models:
Recall isolated (the clinically prioritized metric — false negatives are the costliest error here):
Metric trend across models, useful for seeing at a glance how tightly accuracy/precision/recall/F1 track each other per model:
Confusion matrix — Logistic Regression (the deployed model), on the 365-patient test set:
Its only errors are 10 STAGE-2 patients predicted as CRISIS — it never under-predicts severity (no CRISIS or STAGE-2 patient is ever misclassified as NORMAL or STAGE-1), which is the safer failure direction for a clinical screening tool.
Confusion matrices — all 7 models side by side, for comparing exactly where each model's errors fall:
Regenerate all of these at any time with:
python train_models.py # regenerates models/metadata.json and model artifacts
python generate_visualizations.py # writes PNGs into plots/predictive_pulse/
├── .github/
│ └── workflows/
│ └── main.yaml # CI/CD pipeline (test → docker → deploy)
├── data/
│ ├── patient_data.csv # Raw dataset (1,825 records)
│ ├── hypertension_train.csv # 80% train split (1,460 rows)
│ └── hypertension_test.csv # 20% test split (365 rows)
├── models/
│ ├── best_model.pkl # Selected production model (Logistic Regression)
│ ├── scaler.pkl # Fitted StandardScaler
│ ├── metadata.json # Model comparison, feature importance, dataset info
│ ├── decision-tree.pkl
│ ├── random-forest.pkl
│ ├── svm.pkl
│ ├── knn.pkl
│ ├── ridge-classifier.pkl
│ └── gaussian-naive-bayes.pkl
├── static/
│ ├── css/style.css
│ └── js/app.js # Form handling, API calls, result rendering
├── templates/
│ └── index.html # Single-page UI
├── plots/ # Evaluation charts
│ ├── model_comparison_bar.png
│ ├── recall_focus_bar.png
│ ├── metric_trend_line.png
│ ├── confusion_matrix_lr.png
│ └── confusion_matrix_all_models.png
├── app.py # Flask application & API routes
├── train_models.py # Data cleaning, training, model comparison, export
├── generate_visualizations.py # Generates all plots in plots/ from metadata + saved models
├── test_api.py # Pytest suite (32 tests)
├── requirements.txt
├── Dockerfile # Production image (trains model at build time)
├── Dockerfile.dev # Development image
├── docker-compose.yml # prod + dev service profiles
├── render.yaml # Render deployment config
├── .env.example
├── .dockerignore
├── .gitignore
└── LICENSE
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Renders the main UI (index.html) with app metadata. |
POST |
/predict |
Accepts patient data as JSON, returns predicted hypertension stage, probabilities, risk factors, and recommendations. |
GET |
/model-info |
Returns full model comparison results, feature importances, dataset info, and overfitting analysis. |
GET |
/health |
Health check endpoint used by Docker/Render ({"status": "ok", "model": "..."}). |
{
"gender": "male",
"age": "2",
"history": "1",
"patient": "1",
"when_diagnosed": "1",
"severity": "1",
"breath_shortness": "1",
"visual_changes": "0",
"nose_bleeding": "0",
"systolic": "2",
"diastolic": "2",
"take_medication": "1",
"controlled_diet": "0"
}{
"success": true,
"prediction": 2,
"stage_map": "Hypertension Stage 2",
"risk_level": "High ",
"description": "...",
"urgency": "Urgent: See physician within 1 week",
"recommendations": ["..."],
"probabilities": { "Normal": 2.1, "Hypertension Stage 1": 5.4, "...": "..." },
"risk_factors": [{ "factor": "Family History of HTN", "impact": "High" }],
"bp_range": { "systolic": "≥ 140", "diastolic": "≥ 90" },
"model_used": "Logistic Regression",
"model_accuracy": 97.3,
"model_recall": 97.9,
"model_precision": 95.7,
"model_f1": 96.6
}Field encodings (age brackets, systolic/diastolic bands, severity levels, etc.) are defined in
models/metadata.jsonunderencoders.
- Python 3.12+
- Docker & Docker Compose (optional, for containerized setup)
- Git
git clone https://github.com/sreesyam064/predictive_pulse.git
cd predictive_pulsepython -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # then set SECRET_KEY in .env
python train_models.py # trains all 7 models, saves best_model.pkl, scaler.pkl, metadata.json
python app.py # runs at http://127.0.0.1:5000# Production image (trains model during build)
docker compose up app-prod --build
# → available at http://localhost:5000
# Development image (hot-reload, mounted volume)
docker compose --profile dev up app-dev --build
# → available at http://localhost:5001Set SECRET_KEY in a .env file in the project root before running Compose — it's passed through to both services.
- Open the app in your browser (
http://localhost:5000or deployed URL). - Fill out the patient form: demographics, blood pressure ranges, symptoms, medical history, medication and diet status.
- Submit the form — it sends a
POSTrequest to/predict. - View the result: predicted hypertension stage, color-coded risk level, class probabilities, contributing risk factors, and stage-specific medical recommendations.
- Visit
/model-infoto inspect how the production model was selected over the other six candidates, including accuracy, cross-validation scores, and rejection reasons for overfitted models. - Use
/healthto verify the app and model are loaded correctly (used by Docker health checks and Render).
The project ships with a 32-test Pytest suite (test_api.py) covering:
- Route availability — homepage, health check, model-info, 404/405 handling
- Overfitting analysis correctness — verifies Logistic Regression is the sole selected model, and Decision Tree/Random Forest/SVM are correctly flagged as overfitted
- Prediction correctness — valid stage output, complete response schema, probabilities summing to ~100%, correct crisis-stage classification
- Dataset integrity — row counts (1,825 total / 1,460 train / 365 test), no nulls, correct columns, valid stage labels
- Model artifact integrity — presence and correctness of
best_model.pkl,scaler.pkl,metadata.json
Run the suite:
python train_models.py # required first — generates model artifacts used by the tests
pytest test_api.py -v --tb=shortLint check (matches CI):
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=venv,__pycache__,.github,models,dataGitHub Actions workflow (.github/workflows/main.yaml) runs on every push/PR to main/master, in three sequential jobs:
1. Test
- Installs dependencies, lints with Flake8
- Runs
train_models.pyto regenerate models - Runs the full Pytest suite
- Verifies model/dataset artifacts and asserts the expected model-selection outcome (Logistic Regression selected, ≥95% accuracy, 1,825 total samples, 13 features)
- Uploads trained model and dataset split artifacts (30-day retention)
2. Docker Build & Push (push to main only, after tests pass)
- Builds the production image (trains the model inside the image)
- Pushes to GitHub Container Registry (
ghcr.io/sreesyam064/predictive-pulse) tagged aslatest, branch name, and short commit SHA - Uses GitHub Actions layer caching to cut rebuild time from ~5 min to ~1 min
3. Deploy to Render (after test + docker jobs succeed)
- Triggers a deploy via Render's deploy-hook webhook (
RENDER_DEPLOY_HOOK_URLsecret) - Prints a deployment summary (branch, commit, model, dataset)
Required GitHub Secrets:
| Secret | Purpose |
|---|---|
SECRET_KEY |
Flask app secret key (used during test job) |
RENDER_DEPLOY_HOOK_URL |
Render deploy hook to trigger auto-deploy |
Render is configured via render.yaml as a Docker-environment web service with healthCheckPath: /health and autoDeploy: false (deploys are triggered explicitly by the CI pipeline).
- Dataset:
patient_data.csv— 1,825 patient records covering demographics, symptoms, and clinical history used to train the hypertension staging models. - Built with scikit-learn, Flask, and Render for hosting.
- CI/CD powered by GitHub Actions and GitHub Container Registry.
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
Pathakota Megha Sri Syam
- 📧 sreesyam064@gmail.com
- 📍 Vijayawada, Andhra Pradesh
- 🔗 GitHub
⚠️ Disclaimer: Predictive Pulse is a portfolio/educational ML project. It is not a certified medical device and should not be used as a substitute for professional medical diagnosis or advice.




