This repository was built as the production-grade implementation of Home Credit's default-risk workflow.
The goal mirrors the Kaggle brief: "Predict how capable each applicant is of repaying a loan so Home Credit can extend responsible financing to borrowers with limited credit histories."
Everything here treats the Kaggle exports as stand-ins for the lender's feeds and delivers an end-to-end MLOps system: deterministic data lineage, governed training, registry-backed promotion, and deployment-ready artifacts.
- Give credit, servicing, and collections teams an auditable probability-of-default score at application time.
- Move the research-grade notebook (
notebooks/another_copy_of_home_credit_default_risk_eda (3).py) into governed, repeatable pipelines. - Meet the proposal's success criteria: reproducible end-to-end runs, MLflow-governed promotions, >95 % nightly pipeline success, and deployment-ready deliverables (batch + FastAPI).
- Embed observability and data contracts so schema drift, missingness spikes, or modelling regressions are caught before they affect lending decisions.
- Deterministic pipelines – DVC codifies every stage (ingest → build feature store → split → train → test → validate → monitor) so
dvc repro validate_modelalways rebuilds the same artefacts, lineage, and metrics. - Governed experimentation – MLflow captures parameters, metrics, artefacts, and stages model versions in the registry; the training/validation/reporting stack is identical whether you run locally or in CI/CD.
- Data contracts + monitoring – Pandera checks guard raw feeds, feature stores, and inference payloads. Evidently drift reports, production drift monitors, and freshness checks (
reports/data_freshness.json) provide early warning signals. - Deployment-ready assets – FastAPI and batch CLIs share helpers, structured logging, and inference governance. Dockerfiles build immutable images, and the CD workflow can push directly to ECS (or any platform that can pull GHCR tags).
- Automation-first – CI enforces lint/tests/DVC dry runs, CD reruns the DAG + promotion + container builds + deploy/smoke tests, and the nightly workflow keeps datasets fresh while running the production drift monitor.
-
Create an environment & install deps
python -m venv .venv .\.venv\Scripts\activate # Windows (use `source .venv/bin/activate` on macOS/Linux) pip install -e .
Dependencies live in
requirements.txt(notably DVC, Pandera, PyArrow, MLflow). -
Fetch / validate the raw datasets
# ensure your Kaggle API token lives under %USERPROFILE%\.kaggle\kaggle.json (or ~/.kaggle/kaggle.json) python -m creditrisk.pipelines.ingest_data --config configs/creditrisk_pd.yamlThe ingestion CLI uses the connector declared in
configs/creditrisk_pd.yaml(Kaggle competition downloads by default, but S3/Azure/DVC remotes are also supported) and enforces MD5 checksums before writingreports/ingestion_summary.json. If you already track the CSVs with DVC, flip thetypetodvcand the same command willdvc pullthe pinned bronze snapshot. Freshness metadata is derived from that file later. -
Run the full pipeline
dvc repro validate_model
This executes the modular DAG:
ingest_data -> build_feature_store -> split_data -> fit_model -> evaluate_train -> evaluate_test -> validate_model -> register_model \ -> monitor_driftingest_datavalidates or fetches the raw Kaggle extracts and writesreports/ingestion_summary.jsonso downstream lineage captures the exact snapshot used for the run. Outputs land in:data/processed/*.parquet(feature store + deterministic splits)models/creditrisk_pd_model.joblib
reports/metrics.json,reports/test_metrics.json, andreports/evaluation/reports/drift_report.json+reports/drift_report.htmlreports/data_freshness.json(generated viapython -m creditrisk.utils.data_freshness)reports/registry_transition.json(written bypython -m creditrisk.pipelines.auto_promote)reports/canary_report.json(optional canary validation summary)reports/deploy_manifest.json(deployment-ready manifest)mlruns/(MLflow experiment + registry metadata)
-
Inspect / iterate
- Launch
mlflow uito review runs, metrics, and registered model versions. - Update configs in
configs/creditrisk_pd.yaml(thresholds, registry behavior, validation toggles) and re-rundvc repro. - Use
python -m creditrisk.pipelines.promote_model --version <n> --stage Production --archive-existingafter governance sign-off.
- Launch
-
Operate on GCP
- The live environment runs on a Compute Engine VM with Dockerised FastAPI, self-hosted Airflow, and an on-demand MLflow UI secured via SSH tunnelling. See
docs/cloud_setup.mdfor the exact provisioning steps (VM creation, dependency install, Airflow bootstrap, MLflow tunnel, firewall notes).
- The live environment runs on a Compute Engine VM with Dockerised FastAPI, self-hosted Airflow, and an on-demand MLflow UI secured via SSH tunnelling. See
-
Monitor production pulls
python -m creditrisk.monitoring.production \ --config configs/creditrisk_pd.yaml \ --current data/production/<YYYY-MM-DD>.parquet \ --publish-metrics
This generates
reports/production_drift_report.{json,html}, pushes drift metrics to CloudWatch when AWS credentials are available, and recordsreports/retrain_trigger.jsonso you know whether the automation would have kicked off a retrain.
- Docker image:
Dockerfile.api(uvicorn on port 8080, exposed as 80 on the VM). - Endpoints:
/health,/metadata,/schema,/validate,/predict,/metrics. Payload schema and curl examples live indocs/api_usage.md. - To redeploy locally on the VM:
docker run -d --name creditrisk-api \ -p 80:8080 \ -v $PWD/configs:/app/configs \ -v $PWD/models:/app/models \ -e CONFIG_PATH=configs/creditrisk_pd.yaml \ -e MLFLOW_TRACKING_URI=mlruns \ creditrisk-api:latest
- Full local pipeline + UIs: see
docs/local_pipeline.mdfor venv setup, data hydration options,dvc repro, uvicorn/Docker serving, MLflow UI, and optional local Airflow.
- See
docs/governance_artifacts.mdfor what each report/JSON (lineage, drift, freshness, registry, canary, deploy manifest) contains and how it fits the pipeline.
- Cloud deployment (Compute Engine, Airflow, MLflow tunnel):
docs/cloud_setup.md - Local runs (venv, DVC, uvicorn/Docker, local Airflow):
docs/local_pipeline.md - Local stack setup w/ Windows troubleshooting + Dockerised Airflow:
docs/local_stack_setup.md - API usage (endpoints, payloads, redeploy):
docs/api_usage.md - Artefacts (lineage, drift, freshness, registry, canary, manifest):
docs/governance_artifacts.md
| Stage | CLI Entry Point | Description | Artifacts |
|---|---|---|---|
ingest_data |
python -m creditrisk.pipelines.ingest_data |
Uses the configured connector (Kaggle API, S3, Azure Blob, or DVC) to pull each raw table, enforces MD5 checksums, optionally decompresses archives, and writes reports/ingestion_summary.json. |
reports/ingestion_summary.json, validated raw CSVs |
build_feature_store |
python -m creditrisk.pipelines.build_feature_store |
Loads all seven Kaggle extracts, enforces Pandera contracts, replays the DuckDB SQL feature engineering, and persists data/processed/feature_store.parquet. |
Feature store parquet (165 cols) |
split_data |
python -m creditrisk.pipelines.split_data |
Validates the feature store, stratifies on TARGET, enforces no-leakage guarantees, and writes deterministic train/test parquet files. |
data/processed/train.parquet, data/processed/test.parquet |
fit_model |
python -m creditrisk.pipelines.train_creditrisk_pd --skip-artifacts |
Loads cached splits, rebalances (SMOTE + downsampling), trains the XGBoost pipeline, and logs to MLflow/registry without emitting evaluation artifacts. | models/creditrisk_pd_model.joblib, MLflow run & registry candidate |
evaluate_train |
python -m creditrisk.testing.test_dataset --split train |
Runs the trained pipeline against the cached train split, writes reports/metrics.json, and refreshes reports/evaluation/. |
reports/metrics.json, reports/evaluation/ |
evaluate_test |
python -m creditrisk.testing.test_dataset --split test |
Scores the held-out test split, persists per-entity predictions, and generates evaluation plots. | reports/test_metrics.json, reports/test_evaluation/, reports/test_predictions.parquet |
validate_model |
python -m creditrisk.testing.post_training |
Applies governance checks (metric thresholds, artifact integrity, lineage presence, MLflow alignment) before promotion. | reports/post_training_validation.json |
register_model |
python -m creditrisk.pipelines.auto_promote |
Reads validation, train/test metrics, drift, and canary reports before transitioning the MLflow model version; writes a promotion summary for audit trails. | reports/registry_transition.json |
monitor_drift |
python -m creditrisk.monitoring.drift |
Runs Evidently’s drift preset on the persisted train vs. test splits to quantify distribution shifts and emit HTML/JSON dashboards. | reports/drift_report.json, reports/drift_report.html |
canary_validation |
python -m creditrisk.pipelines.canary_validation |
Compares the candidate model against the current production model on a reference dataset before deploy; fails when approval-rate deltas exceed the tolerance. | reports/canary_report.json |
deploy_manifest |
python -m creditrisk.pipelines.deploy_manifest |
Packages pointers to the trained model, Dockerfiles, CI/CD workflow, and registry metadata into a JSON manifest consumed by deployment automation. | reports/deploy_manifest.json |
You can invoke any stage independently (e.g., dvc repro split_data) for debugging or lightweight experimentation.
creditrisk.pipelines.ingest_data– fetches Kaggle/S3/Azure/DVC sources, enforces checksums, and writesreports/ingestion_summary.json.creditrisk.pipelines.build_feature_store– replays the DuckDB SQL + Pandera contracts to createdata/processed/feature_store.parquet.creditrisk.pipelines.split_data– stratifies, deduplicates, and validates the train/test splits.creditrisk.pipelines.train_creditrisk_pd– balances classes, trains/logs to MLflow, registers a candidate version; use--skip-artifactswhen the evaluation stage will handle metrics.creditrisk.testing.test_dataset --split train|test– evaluates the cached splits, writes metrics/plots/predictions, and feeds downstream governance.creditrisk.testing.post_training– regression suite that confirms score parity with MLflow entries before promotion.creditrisk.pipelines.batch_predict– CLI used by schedulers to score CSVs into parquet/JSON (sharing the same pipeline + threshold).creditrisk.monitoring.drift– compares cached train vs. test splits for drift and emits Evidently visualisations.creditrisk.monitoring.production– loads reference vs. production pulls, logsproduction_drift_report.{json,html}, pushes CloudWatch metrics (when configured), and writesreports/retrain_trigger.json; add--auto-retrainto execute the configured retrain command.creditrisk.utils.data_freshness– inspects ingestion metadata and fails CI/nightly runs when the feeds grow stale.creditrisk.pipelines.promote_model/creditrisk.pipelines.auto_promote– bridge MLflow registry stages into deployment workflows and log promotion results.creditrisk.pipelines.canary_validation– compare the newly trained model against the last production build before shipping it to ECS/batch runners.creditrisk.pipelines.deploy_manifest– emitreports/deploy_manifest.json, a manifest describing the model, Dockerfiles, CI/CD workflow, and secrets needed for ECS deployment.
configs/creditrisk_pd.yaml enumerates bronze datasets under ingestion.sources. Each entry specifies a type, optional uri, and connector-specific options so python -m creditrisk.pipelines.ingest_data knows where to fetch the table:
kaggle/kaggle_competition/kaggle_dataset– authenticate with the Kaggle API token in%USERPROFILE%\.kaggle\kaggle.json(or~/.kaggle/kaggle.json). Provideoptions.competitionandoptions.filefor competition downloads, oroptions.datasetfor dataset sources.s3/aws_s3– pull froms3://bucket/keyusing the default AWS credential chain (env vars, shared credentials, IAM).optionsaccepts overrides likebucket,key,profile,region,endpoint_url, andversion_id.azure_blob/azure– download from Azure Blob Storage viaoptions.connection_stringor (options.account_url+options.credential/sas_token). Always specifyoptions.containerandoptions.blob.dvc/dvc_remote– shell out todvc pull <target>so nightly builds can hydrate the tracked snapshot without running ad-hoc scripts.
All connectors funnel through the same materialization layer: archives are optionally decompressed (decompress: true), MD5 checksums are enforced (checksum: ...), and the results are logged to reports/ingestion_summary.json along with resolved URIs and file sizes. Example:
ingestion:
sources:
- name: application_train
type: kaggle_competition
output_path: data/raw/application_train.csv
decompress: true
skip_if_exists: true # drop the CSV manually to bypass the Kaggle call
checksum: 793a017f41fbac1dc28176b26dbab30e
options:
competition: home-credit-default-risk
file: application_train.csvDownstream lineage (reports/data_lineage.json) links back to the same snapshot so every run remains auditable.
creditrisk.serve.api now exposes multiple governance-friendly endpoints:
GET /health– liveness.GET /metadata– model path, decision threshold, experiment/tags, and artifact timestamps.GET /schema– expected feature columns and entity id so clients can validate payloads.POST /validate– run the Pandera/ValidationRunner checks on a payload without scoring.POST /predict– score a batch, log inference metrics to MLflow, and return predictions + probabilities.GET /metrics– lightweight request counters and timestamps for dashboards or scraping. When AWS credentials andmonitoring.cloudwatch_namespaceare set, the service also publishes latency/error metrics to CloudWatch via the shared observability helpers.
The job-ready Dockerfile.api builds and serves the app on port 8080; ECS deployment is automated through the CD workflow.
Before promoting a newly trained model, run:
python -m creditrisk.pipelines.canary_validation \
--config configs/creditrisk_pd.yaml \
--production-model /mlops/models/prod.joblib \
--candidate-model models/creditrisk_pd_model.joblib \
--dataset data/processed/test.parquet \
--max-metric-delta 0.02The CLI scores the same reference dataset with both pipelines and fails if approval rate or mean probability drift beyond the allowed delta. Airflow (or GH Actions) can insert this gate between validation and deployment, giving you an automated approval step plus audit-friendly reports/canary_report.json.
- Pandera schemas (
src/creditrisk/validation/contracts.py) cover every raw table plus the engineered feature store and persisted splits. They enforce ID integrity, allowable sentinel values (DAYS_EMPLOYED), numeric constraints, and binary targets. - ValidationRunner (
src/creditrisk/validation/runner.py) wires those contracts into each pipeline stage. It also checks for duplicate entity IDs, train/test leakage, NaNs/inf in feature matrices, and missing engineered columns. - Configurable enforcement lives under the
validationsection ofconfigs/creditrisk_pd.yaml. Toggleenforce_raw_contracts,enforce_feature_store_contract,enforce_split_contracts, orenforce_model_io_contractsto relax checks in ad-hoc environments without touching the code. - Outputs fail fast: any schema drift, missing field, or leakage raises a descriptive exception with sample failure rows so you can troubleshoot before a model trains on bad data.
- The training stage logs each run to MLflow and, when
registry.enabled: true, automatically registers the serialized pipeline asregistry.model_name(defaultCreditRiskBaseline). registry.promote_on_metricandregistry.promote_min_valuegate automatic staging. Example: withroc_aucand0.78, only runs at or above 0.78 move into theStagingstage; others remain unpromoted but still versioned.- The registry helper lives in
src/creditrisk/mlops/registry.pyand handles:- Creating the registered model if missing.
- Registering new versions from
runs:/<run_id>/model. - Transitioning stages and optionally archiving the previous occupant.
- Manual promotions use
python -m creditrisk.pipelines.promote_model --version <n> --stage Production --archive-existing. Because stage transitions are config-driven, CI/CD workflows can call the same CLI or import the helper.
configs/ # YAML configs (paths, training, validation, registry)
data/ # Raw/interim/processed storage tracked with DVC
docs/ # Architecture notes, proposal, operating guides
notebooks/ # Research + EDA history
reports/ # Metrics JSON + evaluation plots (DVC-tracked)
src/creditrisk/ # Python package
data/ # Dataset loaders & split helpers
features/ # DuckDB feature store + preprocessing utilities
models/ # Model factories, balancing, evaluation helpers
pipelines/ # CLI entry points (ingest, feature build, split, train, promote, batch)
prediction/ # Batch prediction helpers
monitoring/ # Drift monitoring (train/test + production)
observability/ # Structured logging + CloudWatch publishers
serve/ # FastAPI app + governance layer
testing/ # Post-training + dataset tests
utils/ # Evaluation, lineage, filesystem helpers
validation/ # Pandera contracts + runner
dvc.yaml # Multi-stage pipeline definition
requirements.txt # Environment spec (DVC, Pandera, PyArrow, MLflow, etc.)
- DVC – deterministic DAGs (
dvc repro) with cached feature-store/split artefacts and lineage baked intodvc.lock. - Pandera + ValidationRunner – schema/missingness guardrails across ingestion, feature store, splits, and inference payloads.
- MLflow – experiment tracking + registry promotions, consumed by the CLI helpers and CI/CD automation.
- Observability – structured JSON logs, CloudWatch metric publishers, drift dashboards, and freshness summaries.
- Serving governance – FastAPI shares the same contracts/thresholds/pipeline as batch, emits MLflow inference runs, and exposes metadata endpoints for downstream systems.
- Trigger: every pull request and non-
mainpush. - Steps: checkout, set up Python 3.11 with pip caching, install dependencies, run
ruff,bandit,python -m compileall,pytest, and finallydvc repro --dry-run validate_model. - Outcome: fails fast on lint/test/schema regressions before artefacts or MLflow runs are created.
- Trigger: pushes to
main. - Core steps: checkout + dependency install → optional DVC remote config →
dvc pull→dvc repro validate_model→dvc repro monitor_drift→ MLflow auto-promotion (whenreports/registry_promotion.jsonindicates success) → build/push Docker images for API and batch (ghcr.io/<repo>/creditrisk-{api|batch}:{latest,sha}). - Deployment: when AWS secrets are set the workflow configures credentials, captures the live ECS task definition, swaps in the new image tag, deploys, waits for stability, and smoke-tests
/health+/predict. Failures trigger automatic rollback to the prior task definition. - Registry/Canary:
dvc repro register_modeltransitions the MLflow version only when the validation summary passes, anddvc repro canary_validationcompares the candidate model against the current production artefact before ECS rollout. Both steps leave JSON breadcrumbs (reports/registry_transition.json,reports/canary_report.json) for auditors. - Freshness gate:
python -m creditrisk.utils.data_freshness --max-age-hours 48 --fail-on-staleblocks releases when the upstream feeds go stale. - Artefacts: models, metrics, lineage, ingestion summaries, drift bundles, registry promotion reports, data freshness JSON, production drift metrics, canary summaries, and the deployment manifest are uploaded for auditors:
reports/metrics.json,reports/test_metrics.json,reports/evaluation/**reports/ingestion_summary.json,reports/data_lineage.json,reports/data_freshness.jsonreports/drift_report.{json,html},reports/production_drift_report.{json,html},reports/drift_metrics.jsonreports/post_training_validation.json,reports/registry_promotion.json,reports/registry_transition.jsonreports/canary_report.json,reports/retrain_trigger.json,reports/deploy_manifest.json
- Secrets:
| Secret | Purpose |
|---|---|
MLFLOW_TRACKING_URI / MLFLOW_TRACKING_TOKEN |
Remote tracking + registry access. |
DVC_REMOTE_URL / DVC_REMOTE_CREDENTIALS |
Optional remote for pulling large datasets. |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION |
Allow ECS deploy + CloudWatch publishing. |
ECS_CLUSTER_NAME / ECS_SERVICE_NAME / PREDICT_ENDPOINT_URL |
Target service + smoke-test endpoint. |
When the AWS/ECS secrets are omitted, the workflow still runs the pipeline and publishes artefacts without attempting a deployment.
- Trigger: cron (
0 6 * * *) plus manualworkflow_dispatch. - Steps: fetch data via DVC, run
dvc repro --force validate_modelandmonitor_drift, executepython -m creditrisk.monitoring.production --publish-metrics(optionally pointing to a production parquet path via thePRODUCTION_CURRENT_PATHsecret), run the freshness CLI with a 36h SLA, and upload a trimmed artefact bundle. - Purpose: ensures data freshness, pushes drift metrics to CloudWatch when credentials exist, and produces
reports/production_drift_report.{json,html},reports/drift_metrics.json, andreports/retrain_trigger.json. Ifmonitoring.auto_retrain: true, exceeding the configured drift threshold automatically kicks off themonitoring.retrain_command. - Operator view: the artefacts and CloudWatch metrics reveal whether retraining or intervention is required without shelling into the runner.
- Why: when you outgrow GitHub Actions + cron, drop this DAG into an Airflow environment. It mirrors the entire DVC pipeline, production monitor, and the new canary validation gate so you get stateful runs, retries, approvals, and failure visibility.
- Highlights: each stage shells out to the same CLIs we use locally (
python -m creditrisk.pipelines.ingest_data,dvc repro ...,python -m creditrisk.monitoring.production,python -m creditrisk.pipelines.canary_validation). Airflow Variables supply paths such ascreditrisk_repo,production_model_path, and the production dataset, so you can promote between dev/staging/prod clusters without editing code. - Notifications: pair the DAG with native Airflow alerting/approvals (Slack, Email, PagerDuty) or Step Functions/ECS operators when you need cross-account rollouts.
See docs/ci_cd.md for a deeper breakdown plus manual run instructions.
Dockerfile.apibuilds a uvicorn-powered FastAPI image:docker build -f Dockerfile.api -t creditrisk-api .thendocker run -p 8080:8080 creditrisk-api.Dockerfile.batchpackages the batch scorer (creditrisk.pipelines.batch_predict):docker build -f Dockerfile.batch -t creditrisk-batch .and pass CLI args at runtime..dockerignorekeeps datasets, reports, and caches out of the image layers for faster reproducible builds.
- Data freshness – the nightly workflow emits
reports/data_freshness.jsonand fails when the ingestion snapshot exceeds the SLA. Locally you can runpython -m creditrisk.utils.data_freshness --max-age-hours 24 --fail-on-staleto get the same JSON (useful for dashboards or alerts). - Production drift –
python -m creditrisk.monitoring.production --current data/production/<date>.parquet --publish-metrics --auto-retraincompares reference vs. production pulls, writesreports/production_drift_report.{json,html}, publishes CloudWatch metrics (when AWS creds exist), and, whenmonitoring.auto_retrain: true, executes the configuredmonitoring.retrain_command. - Retrain transparency –
reports/retrain_trigger.jsonrecords whether a retrain was kicked off (and why) so governance teams can audit the decision. Pair it withreports/drift_metrics.jsonfor dashboards. - Structured logging –
creditrisk.observability.loggingsets JSON logging globally and exposes CloudWatch metric helpers so batch jobs, FastAPI, and monitoring scripts emit consistent telemetry (request_id, latency, status_code, stage, entity counts). - Dashboards/alerts – CloudWatch (or any sink you choose) can alarm on drift share, stale snapshots, or error rates without wiring additional orchestration; everything surfaces from the Actions logs + artefacts.
- Pipe nightly/CD artefacts (freshness report, drift metrics, ECS deploy status) plus serving logs into Slack/webhooks or Grafana for operator awareness.
- Layer in SHAP/explainability snapshots or chaos drills if your governance team requests deeper transparency or failover rehearsal.
Questions? See docs/architecture.md for deeper architectural decisions or reach out via the project Slack channel. Any run issues can usually be diagnosed by checking the validation failures, DVC logs, or the MLflow UI.