Skip to content

Repository files navigation

Churn Prediction API

A production-ready MLOps project that predicts and explains customer churn for a telecom company.

Live API: https://churn-api-0cwo.onrender.com/docs

CI/CD


Tech Stack

Layer Technology
API FastAPI + Uvicorn
ML Model scikit-learn Logistic Regression + LIME explanations
Experiment Tracking MLflow (SQLite backend)
Containerisation Docker (multi-stage, non-root user)
Deployment Render (free tier)
CI/CD GitHub Actions — test → deploy on every push
Monitoring Data drift detection (PSI + Chi-Squared)
Authentication API key via X-API-Key header

Project Structure

churn_api/
├── main.py                   # FastAPI app — endpoints, auth, drift logging
├── inference.py              # ModelService — loads model, predict/explain
├── churnexplainer.py         # ExplainedModel + CategoricalEncoder
├── mlflow_logger.py          # MLflow tracking helpers (best-effort)
├── monitoring.py             # DriftMonitor — PSI + Chi-Squared detection
├── auth.py                   # API key authentication dependency
├── config.py                 # All settings via environment variables
├── train.py                  # Full training pipeline — train → save → MLflow
├── simulate_traffic.py       # Send realistic traffic to test drift monitor
├── models/
│   └── telco_linear/
│       ├── telco_linear.pkl       # Pre-trained model (pickle)
│       └── reference_stats.json  # Training distribution stats for drift
├── tests/
│   ├── conftest.py                # Mocks ModelService — no pickle needed in CI
│   ├── test_main.py               # Root + health endpoint tests
│   └── test_predict_explain.py    # Predict, explain, validation tests
├── data/
│   └── README.md                  # Instructions to download Telco dataset
├── Dockerfile                     # Multi-stage production image
├── docker-compose.yml             # Local dev: API + MLflow server
├── render.yaml                    # Render deployment config
├── requirements.txt               # Pinned dependencies
├── .env.example                   # Copy to .env for local dev
└── .github/
    └── workflows/
        ├── deploy.yml             # CI/CD: test → deploy to Render
        └── mlflow-server.yml      # One-time MLflow server setup (optional)

Architecture

Developer pushes to main
        │
        ▼
┌─────────────────────────────────────┐
│          GitHub Actions             │
│  1. pytest — 7 tests                │
│  2. Trigger Render deploy hook      │
└─────────────────────────────────────┘
        │
        ▼
   Render (free tier)
   https://churn-api-0cwo.onrender.com
   └── Docker container (non-root)
       ├── FastAPI + Uvicorn
       ├── MLflow → SQLite (/tmp/mlruns.db)
       └── Drift log → SQLite (same DB)

Quick Start — Local Development

1. Clone and configure

git clone https://github.com/S-Shetty/churn-api.git
cd churn_api
cp .env.example .env
# Edit .env — set API_KEY and other values

2. Run with Docker Compose (recommended)

Starts both the API and a local MLflow server:

docker compose up --build
Service URL
Churn API http://localhost:8000
Swagger UI http://localhost:8000/docs
MLflow UI http://localhost:5000

3. Run without Docker

pip install -r requirements.txt
uvicorn main:app --reload --port 8000

API Reference

All endpoints except /health require an X-API-Key header.

curl -H "X-API-Key: your-key" https://churn-api-0cwo.onrender.com/health

GET /health

Public. Returns {"status": "ok"}. Used by Render health checks.

POST /predict

Request:

{
  "gender": "Female",
  "SeniorCitizen": 0,
  "Partner": "Yes",
  "Dependents": "No",
  "tenure": 5,
  "PhoneService": "Yes",
  "MultipleLines": "No",
  "InternetService": "DSL",
  "OnlineSecurity": "Yes",
  "OnlineBackup": "No",
  "DeviceProtection": "Yes",
  "TechSupport": "No",
  "StreamingTV": "No",
  "StreamingMovies": "No",
  "Contract": "Month-to-month",
  "PaperlessBilling": "Yes",
  "PaymentMethod": "Electronic check",
  "MonthlyCharges": 75.5,
  "TotalCharges": 400.2
}

Response:

{ "prediction": 1, "probability": 0.73 }

prediction: 1 = likely to churn, 0 = likely to stay

POST /explain

Same request body. Returns LIME feature importance weights showing which factors drove the prediction.

{
  "probability": 0.73,
  "explanations": {
    "tenure": -0.21,
    "Contract": 0.18,
    "MonthlyCharges": 0.14,
    "InternetService": -0.09
  }
}

Positive weight = pushes towards churn. Negative = pushes away from churn.

GET /drift

Returns data drift report comparing recent traffic against training distribution.

{
  "status": "no_drift",
  "samples": 120,
  "features": {
    "tenure":         { "psi": 0.03, "type": "numeric" },
    "MonthlyCharges": { "psi": 0.07, "type": "numeric" },
    "Contract":       { "chi2": 0.04, "type": "categorical" }
  },
  "alerts": [],
  "warnings": []
}

Running Tests

pip install -r requirements.txt
pytest -v

7 tests — model is mocked in conftest.py so no pickle file or AWS credentials needed.

Test Type
test_root_endpoint Smoke
test_health_endpoint Smoke
test_predict_endpoint Integration
test_explain_endpoint Integration
test_predict_missing_field_returns_422 Validation
test_predict_invalid_senior_citizen_returns_422 Validation
test_predict_negative_tenure_returns_422 Validation

Training the Model

The pre-trained model is already in models/telco_linear/. To retrain:

1. Download the dataset

https://www.kaggle.com/datasets/blastchar/telco-customer-churn

Save to data/WA_Fn-UseC_-Telco-Customer-Churn.csv

2. Train

python train.py
python train.py --test-size 0.2 --no-mlflow   # skip MLflow

train.py outputs:

  • models/telco_linear/telco_linear.pkl — model pickle
  • models/telco_linear/reference_stats.json — drift reference statistics

Model metrics (20% test set, class_weight="balanced")

Metric Score
Accuracy ~0.76
ROC AUC ~0.84
Precision ~0.58
Recall ~0.75
F1 ~0.65

class_weight="balanced" prioritises recall — catching true churners matters more than overall accuracy for a retention use case.


Drift Monitoring

Every prediction request is logged to SQLite. The /drift endpoint compares recent traffic against reference statistics saved during training.

# Test with realistic traffic
python simulate_traffic.py --mode normal  --n 60  # expect: no_drift
python simulate_traffic.py --mode shifted --n 60  # expect: drift_detected
Method Used for Thresholds
PSI (Population Stability Index) Numeric features >0.1 warning, >0.2 alert
Chi-Squared distance Categorical features >0.2 warning, >0.5 alert

CI/CD Pipeline

push to main → Run Tests ✅ → Deploy to Render ✅
Job Trigger What it does
Run Tests Every push + PR Runs 7 pytest tests
Deploy to Render Push to main only Triggers Render deploy hook

Deployment — Render (Free)

Required Environment Variables (set in Render dashboard)

Variable Value
API_KEY Any secret string — used for X-API-Key auth
MODEL_NAME telco_linear
MLFLOW_TRACKING_URI sqlite:////tmp/mlruns.db
MLFLOW_EXPERIMENT_NAME churn-prediction
LOG_LEVEL INFO

Required GitHub Secrets

Secret Description
RENDER_DEPLOY_HOOK_URL From Render → Settings → Deploy Hook

MLflow

All inferences logged under the churn-prediction experiment.

Event What's logged
API startup Model registered in MLflow Model Registry
/predict probability + prediction as metrics
/explain probability as metric + explanations.json artifact

View locally:

mlflow ui --backend-store-uri sqlite:///mlruns.db
# Open http://localhost:5000

Environment Variables

Variable Default Description
API_KEY (empty — auth disabled) Secret key for X-API-Key header
MLFLOW_TRACKING_URI sqlite:////tmp/mlruns.db MLflow backend
MLFLOW_EXPERIMENT_NAME churn-prediction MLflow experiment name
MODEL_NAME telco_linear Model artifact to load
LOG_LEVEL INFO Python logging level

Copy .env.example to .env for local development.

About

Production MLOps API predicting telecom customer churn. FastAPI + scikit-learn + LIME explanations, MLflow experiment tracking, multi-stage Docker, GitHub Actions CI/CD, data drift monitoring with PSI, API key auth. Deployed on Render.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages