Skip to content

Repository files navigation

credit-scoring-api

CI Python 3.11+ License: MIT

A production-grade Credit Scoring ML API. Predicts loan default probability using an ensemble ML model (Random Forest + Gradient Boosting), tracks experiments with MLflow, and serves predictions via a FastAPI REST API with full audit logging.

Built as a portfolio project applicable to fintech, banking, insurance, and any risk assessment platform.


Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                      CREDIT SCORING PIPELINE                        │
│                                                                     │
│   Training (offline)              Inference (online)                │
│   ──────────────────              ─────────────────                 │
│                                                                     │
│   Synthetic data (2000+ rows)     POST /api/v1/predict              │
│          │                               │                          │
│          ▼                               ▼                          │
│   FeatureEngineer                  FeatureEngineer                  │
│   (26 features: DTI, LTI,          (same pipeline —                 │
│    payment score, one-hot...)       no train/test leakage)          │
│          │                               │                          │
│          ▼                               ▼                          │
│   sklearn Pipeline:               CreditScorePredictor.predict()    │
│   StandardScaler +                       │                          │
│   VotingClassifier                       ▼                          │
│   (RF + GradientBoosting)         default_probability (0–1)         │
│          │                        risk_score (300–850)              │
│          ▼                        decision (APPROVED/REVIEW/REJECT) │
│   MLflow tracking:                feature_contributions             │
│   - params, metrics                      │                          │
│   - model artifact                       ▼                          │
│   - model registry                 PostgreSQL audit log             │
│          │                         (regulatory compliance)          │
│          ▼                                                           │
│   AUC-ROC, Gini, KS-stat          Drift monitoring                  │
│   Precision, Recall, F1           (distribution tracking)           │
└─────────────────────────────────────────────────────────────────────┘

ML Model

Component Details
Algorithm Ensemble: Random Forest + Gradient Boosting (VotingClassifier)
Preprocessing StandardScaler inside sklearn Pipeline
Features 26 engineered features (see Feature Engineering)
Class imbalance class_weight="balanced" in Random Forest
Evaluation AUC-ROC, Gini coefficient, KS statistic, Precision/Recall
Tracking MLflow experiments + model registry
Explainability Feature contributions per prediction

Feature Engineering

Feature Description
debt_to_income_ratio existing debt / monthly income — #1 credit metric
loan_to_income_ratio loan amount / annual income
monthly_payment_estimate estimated monthly payment on new loan (annuity formula)
total_monthly_debt_ratio (existing + new) monthly debt / monthly income
payment_history_score penalizes late payments relative to history length
credit_density number of loans per year of credit history
+ 20 more one-hot encodings, raw features

Evaluation Metrics (typical results on synthetic data)

Metric Value Description
AUC-ROC ~0.82 Discriminatory power (1 = perfect)
Gini coefficient ~0.64 2×AUC − 1, industry standard
KS statistic ~0.50 Max separation between good/bad
Accuracy ~0.77 Overall classification accuracy

Getting Started

Prerequisites

  • Python 3.11 (pinned dependency versions in requirements.txt may lack prebuilt wheels on newer Python versions without a C compiler installed)
  • Docker & Docker Compose

Quick start

git clone https://github.com/M-TOUITI/credit-scoring-api.git
cd credit-scoring-api

# Start infrastructure (PostgreSQL + MLflow)
docker-compose up -d postgres mlflow

# Install dependencies
pip install -r requirements.txt

# Train the model (generates synthetic data + logs to MLflow)
make train

# Run migrations + start API
make migrate
make dev

# Open docs
open http://localhost:8000/docs
open http://localhost:5000  # MLflow UI

Run tests (no model or DB needed)

make test-unit        # 25+ unit tests, instant
make test             # all tests with coverage

API Reference

Predict a single application

POST /api/v1/predict
{
  "age": 35,
  "annual_income": 55000,
  "loan_amount": 12000,
  "loan_term_months": 36,
  "credit_history_months": 72,
  "num_existing_loans": 1,
  "existing_debt_monthly": 400,
  "num_late_payments": 0,
  "num_credit_inquiries": 1,
  "employment_years": 6.0,
  "employment_type": "employed",
  "home_ownership": "mortgage",
  "loan_purpose": "personal"
}

Response:

{
  "prediction_id": "uuid",
  "default_probability": 0.1832,
  "risk_score": 749,
  "risk_category": "LOW",
  "decision": "APPROVED",
  "confidence": 0.87,
  "feature_contributions": [
    {
      "feature": "Debt To Income Ratio",
      "value": 0.0873,
      "contribution": -0.142,
      "direction": "reduces_risk"
    },
    {
      "feature": "Payment History Score",
      "value": 1.0,
      "contribution": -0.089,
      "direction": "reduces_risk"
    }
  ],
  "model_version": "mlflow:Production",
  "predicted_at": "2025-06-01T14:30:00Z"
}

Batch predict

POST /api/v1/predict/batch
{
  "applications": [...],
  "return_explanations": false
}

Model monitoring

GET /api/v1/models/info               # model version + top features
GET /api/v1/models/monitoring         # drift detection + distribution stats
GET /api/v1/models/features           # global feature importances
POST /api/v1/models/reload            # hot-reload from MLflow registry

Audit trail

GET  /api/v1/models/predictions/{id}/audit   # full audit record
POST /api/v1/models/predictions/{id}/outcome  # record actual outcome

Risk Scale

Probability Risk Score Category Decision
< 0.35 657–850 🟢 LOW / MEDIUM APPROVED
0.35–0.64 497–657 🟡 MEDIUM / HIGH MANUAL REVIEW
≥ 0.65 300–496 🔴 HIGH / VERY HIGH REJECTED

MLflow Integration

After training, open the MLflow UI at http://localhost:5000 to:

  • Compare experiments (different hyperparameters, feature sets)
  • Inspect model artifacts (feature importances, ROC curve)
  • Promote a model version to Production
# Promote a model to Production via CLI
mlflow models set-model-version-tag \
  --model-name credit-scoring-model \
  --version 1 \
  --key stage --value Production

Then reload the API model: POST /api/v1/models/reload


Project Structure

credit-scoring-api/
├── app/
│   ├── main.py                    # FastAPI app, model loading at startup
│   ├── core/                      # config, logging
│   ├── domain/schemas.py          # Pydantic request/response models
│   ├── ml/
│   │   ├── features/engineer.py   # 26-feature engineering pipeline
│   │   └── models/
│   │       ├── trainer.py         # sklearn training + MLflow logging
│   │       └── predictor.py       # inference + explainability
│   ├── infrastructure/            # SQLAlchemy models + repositories
│   └── api/routers/               # predict, models (monitoring)
├── scripts/
│   └── train_model.py             # CLI training script
├── tests/
│   ├── unit/ml/                   # 25+ unit tests (no model needed)
│   └── integration/               # API tests with mock predictor
├── alembic/                       # DB migrations
├── docker-compose.yml             # PostgreSQL + MLflow + API
├── Makefile
└── requirements.txt

Design Decisions

Why Random Forest + Gradient Boosting ensemble? Both models capture different aspects of the data. Random Forest is robust to noise and overfitting via bagging. Gradient Boosting captures sequential patterns. VotingClassifier(voting="soft") averages probabilities — reducing variance while keeping the discriminatory power of each model.

Why engineer features before sklearn Pipeline? The FeatureEngineer applies identical transformations at training and inference time. Putting it inside sklearn's Pipeline would complicate serving (serialization issues with Pydantic inputs). Keeping it separate as a stateless class is cleaner.

Why store input features in the audit log as JSON? Regulatory compliance (GDPR Art. 22, ECOA) requires that automated credit decisions can be explained. Storing the full input allows reconstructing the decision and generating explanations after the fact.

Why Gini and KS instead of just accuracy? Accuracy is misleading for imbalanced classes (25% default rate). Gini coefficient (2×AUC−1) and KS statistic are the standard metrics used by banks and credit bureaus — they measure how well the model separates good from bad applicants.


Author

M-TOUITIGitHub · hello@mtouiti.dev

License

MIT — see LICENSE.

About

Production-grade credit scoring ML API using FastAPI and scikit-learn. Predicts loan default risk with a Random Forest + Gradient Boosting ensemble, tracks experiments via MLflow, and logs predictions to PostgreSQL for audit trails. Includes drift monitoring and explainable predictions.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages