An end-to-end MLOps pipeline for loan approval, built to mirror a standard "manual exploratory workflow -> automated pipeline" architecture:
Manual/Exploratory: Ingest -> Prepare/Transform -> Process -> Feature Store -> Bias Detection -> Train & Tune -> Register Model -> Deploy -> Online Feature Store -> Run Inference
Automated: Airflow DAG -> Monitor (drift) -> Retrain (scheduled DAG) -> Scale Inference
Everything runs locally with pip + venv. No Docker required.
One exception: Airflow needs WSL2 (see section 3) since it doesn't run
natively on Windows — everything else is pure Windows Python.
A Jenkins version (jenkins/Jenkinsfile) is also included as a fallback
if WSL2 setup goes sideways before Monday — same pipeline, no WSL needed.
You don't need deep Airflow expertise — you need to fluently use these five concepts, which map directly onto what's already built in this repo:
- DAG (Directed Acyclic Graph) — a pipeline definition: a set of tasks
and the order they must run in. This repo has two:
loan_approval_full_pipelineandloan_approval_retrain, both defined inairflow_home/dags/loan_approval_pipeline.py. - Task / Operator — one step in the DAG.
BashOperatorruns a shell command (used here to call your.pyscripts);PythonOperatorruns a Python function directly in-process (used here for the model hot-swap call). Every diagram box became one task. - Dependencies (
>>) —ingest >> prepare >> feature_store >> trainmeans "prepare only starts after ingest succeeds." This is the actual arrows in your architecture diagram, expressed in code. - Scheduler — the process that watches all DAGs and decides when to
run them, based on each DAG's
schedule(None= manual trigger only;timedelta(hours=6)= run automatically every 6 hours). This is the literal mechanism behind the "Automated Workflow" box. - Webserver / UI — the dashboard at localhost:8080 showing DAG runs, a live Graph view of tasks turning green/red as they execute, and logs per task. This is what you'll have open during the demo.
If asked anything deeper (XComs, sensors, executors, backfills) it's fine to say "I focused on getting DAGs, task dependencies, and scheduling solid for this project — happy to go deeper on [topic] if useful," which is honest and still shows you know the landscape.
cd C:\
REM copy this folder to C:\ml-mlops-demo
cd ml-mlops-demo
py -3.12 -m venv venv
venv\Scripts\activate
pip install -r requirements.txtOpen PowerShell as Administrator:
wsl --installReboot when prompted. This installs Ubuntu by default. After reboot, it'll open a terminal asking you to create a Linux username/password — do that, then you're in a real Linux shell running inside Windows.
Your Windows C:\ drive is auto-mounted inside WSL2 at /mnt/c/, so
C:\ml-mlops-demo becomes /mnt/c/ml-mlops-demo — this is why the DAG
file uses that path format.
Inside the WSL2 terminal:
sudo apt update && sudo apt install -y python3-venv python3-pip
cd /mnt/c/ml-mlops-demo
python3 -m venv airflow_venv
source airflow_venv/bin/activate
AIRFLOW_VERSION=2.10.4
PYTHON_VERSION=3.12
CONSTRAINT_URL="https://raw.githubusercontent.com/apache/airflow/constraints-${AIRFLOW_VERSION}/constraints-${PYTHON_VERSION}.txt"
pip install "apache-airflow==${AIRFLOW_VERSION}" --constraint "${CONSTRAINT_URL}"Set Airflow's home and disable the clutter of built-in example DAGs:
export AIRFLOW_HOME=/mnt/c/ml-mlops-demo/airflow_home
airflow db migrate
sed -i 's/^load_examples = True/load_examples = False/' $AIRFLOW_HOME/airflow.cfgAlso point the DAG at your actual venv paths (only needed if you didn't
put the project at C:\ml-mlops-demo — the defaults already match that
path):
export LOAN_DEMO_PYTHON=/mnt/c/ml-mlops-demo/venv/Scripts/python.exe
export LOAN_DEMO_SRC=/mnt/c/ml-mlops-demo/srcNote: if calling the Windows venv's python.exe from inside WSL2 gives you
grief, the simpler path is to also pip install -r requirements.txt
inside a second, WSL2-native venv and point LOAN_DEMO_PYTHON at
that instead — then everything (Airflow + your scripts) runs natively in
Linux and you sidestep any Windows/WSL interop issues entirely. This is
what I'd actually recommend if you have the extra 15 minutes.
Add these export lines to ~/.bashrc inside WSL2 so you don't
retype them every session:
echo 'export AIRFLOW_HOME=/mnt/c/ml-mlops-demo/airflow_home' >> ~/.bashrc
echo 'export LOAN_DEMO_PYTHON=/mnt/c/ml-mlops-demo/venv/Scripts/python.exe' >> ~/.bashrc
echo 'export LOAN_DEMO_SRC=/mnt/c/ml-mlops-demo/src' >> ~/.bashrcOpen 1 WSL2 terminal and 4 PowerShell windows. In each PowerShell
window: cd C:\ml-mlops-demo and venv\Scripts\activate.
PowerShell 1 — MLflow tracking server (leave running)
mlflow server --host 127.0.0.1 --port 5000Open http://localhost:5000 — you should see the MLflow UI.
WSL2 terminal — start Airflow (leave running)
cd /mnt/c/ml-mlops-demo
source airflow_venv/bin/activate
airflow standaloneThis starts both the webserver and scheduler, and prints an admin
password in the terminal output — copy it. Open http://localhost:8080,
log in as admin with that password.
You should see two DAGs: loan_approval_full_pipeline and
loan_approval_retrain. Un-pause loan_approval_full_pipeline (toggle on
the left), click it, then Trigger DAG (play button, top right). Watch
the Graph view — tasks turn green left to right as they complete:
ingest_data -> prepare_process_data -> store_in_feature_store -> train_and_tune_models -> detect_and_mitigate_bias -> deploy_hotswap_model -> monitor_for_drift.
Check MLflow UI: Experiments tab shows loan_approval with new runs.
Models tab shows loan_approval_model with a champion alias.
PowerShell 2 — start the inference API (leave running)
python -m uvicorn src.deploy_api:app --host 127.0.0.1 --port 8001Open http://localhost:8001/docs — try POST /predict with
{"applicant_id": 5}.
Back in Airflow UI — trigger the retrain DAG live
Un-pause and trigger loan_approval_retrain. Watch it run, then check
/health on port 8001 again — model_version should have incremented,
proving the DAG hot-swapped the live API with zero downtime, orchestrated
entirely by Airflow's scheduler rather than a manual script call.
Open reports\monitoring\drift_report.html (generated by the
monitor_for_drift task) in a browser — this is your Evidently drift
dashboard.
PowerShell 3 & 4 — scale inference demo
REM PowerShell 3
python -m uvicorn src.deploy_api:app --host 127.0.0.1 --port 8002REM PowerShell 4
python src\scale_inference_demo.pyShows requests round-robining across two replicas.
For the scheduled retrain story: point out that loan_approval_retrain
has schedule=timedelta(hours=6) set in the DAG — in the Airflow UI's DAG
list you can see its next scheduled run time, which is the concrete
"automated retraining" proof point interviewers usually want to see.
| Diagram box | What you show | Where |
|---|---|---|
| Ingest / Prepare / Process | ingest.py, prepare_features.py as Airflow tasks |
Airflow Graph view |
| Store Data in Feature Store | Feast local repo, feast apply output |
Terminal + src/feature_store/ |
| Detect and Mitigate Bias | Fairlearn demographic parity/equalized odds, before/after mitigation | bias_check.py output + MLflow run |
| Train & Tune Models | Two candidate models, best picked by AUC | MLflow experiment UI |
| Associate Lineage / Deposit in Registry | mlflow.register_model, version tags, champion alias |
MLflow Models UI |
| Deploy Models / Run Inference | FastAPI /predict, live Feast online lookup |
http://localhost:8001/docs |
| Online Feature Store | Feast SQLite online store, materialized data | materialize.py |
| Build Pipeline that integrates Steps | Airflow DAG, explicit task dependency graph | http://localhost:8080 |
| Monitor Models | Evidently drift report | reports/monitoring/drift_report.html |
| Retrain Models | Second Airflow DAG on a timedelta(hours=6) schedule, hot-swap |
Airflow UI + /health |
| Scale Inference | Two replicas + round-robin script | scale_inference_demo.py |
Is this production-grade?": this is a local, single-node reference implementation of the pattern — in production you'd swap SQLite/file stores for Redis/S3/a real data warehouse, standalone Airflow for a managed instance (MWAA, Composer, Astronomer) with a proper executor (Celery/Kubernetes instead of the local SequentialExecutor), and add authentication, autoscaling, and canary deployments. The point was to demonstrate you understand every stage of the lifecycle and how they connect, not to reinvent a managed platform.
Why Airflow over Jenkins/other schedulers": DAGs give you explicit dependency graphs (not just a linear script), per-task retries and backfills, a scheduler that's aware of run history, and it's the de-facto standard for data/ML pipeline orchestration — which is why it shows up in almost every MLOps job posting.
feast,mlflow, and the API all show harmlessDeprecationWarninglines on some versions — cosmetic, doesn't affect functionality- The bias-mitigation step (
ThresholdOptimizer) needs the sensitive attribute (gender) passed at prediction time too — that's realistic: fairness-aware post-processing generally requires it, and it's a good talking point about the practical trade-offs of different mitigation techniques (pre-processing vs. in-processing vs. post-processing) - If port 5000/8001/8002/8080 are already in use on your machine, change
the port in the relevant command AND in
deploy_api.py/loan_approval_pipeline.py(MLFLOW_TRACKING_URI, thereload_modelURL) - Airflow's local
SequentialExecutor(the default inairflow standalone) runs one task at a time — totally fine for this demo, just don't expect true parallelism without swapping executors src/retrain_trigger.pystill exists as a manual, Airflow-free way to trigger a full retrain cycle from the command line (python src\retrain_trigger.py) — useful for quick local testing without spinning up Airflow, or as a second fallback if both WSL2 and Jenkins give you trouble
If WSL2/Airflow setup isn't working in time, jenkins/Jenkinsfile runs
the identical pipeline stages using Jenkins instead, which installs
natively on Windows with no WSL required. Install from
https://www.jenkins.io/download/, create a Pipeline job, paste the
Jenkinsfile contents, edit PROJECT_DIR at the top, and click Build
Now. Same demo story, same scripts, different orchestrator.