Binary classification: predict whether a US domestic flight departs 15+ minutes late (DEP_DEL15), using the Kaggle dataset 2019 Airline Delays and Cancellations (~3M+ flight records).
Historical delay averages (carrier, airport, day-of-week, time-block) were self-computed from the training data rather than using the dataset's built-in historical columns, which risk leakage from future information.
Categorical variables were encoded via .cat.codes rather than one-hot encoding — appropriate for a tree-based model that handles high-cardinality categories natively.
Class imbalance (~19% delayed) was addressed via scale_pos_weight, correcting the loss function's bias toward the majority class without resampling.
v1 used 21 features, including PLANE_AGE, CONCURRENT_FLIGHTS, monthly flight-volume counts, and PREVIOUS_AIRPORT. Once building the live API, the question became: could a real user actually provide these? No — they require aircraft-tracking infrastructure or data no free API exposes.
| v1 (21 features) | v2 (15 features) | |
|---|---|---|
| Recall (delayed) | 0.64 | 0.65 |
| Precision (delayed) | 0.31 | 0.33 |
SHAP (TreeExplainer) identified DEP_TIME_BLK as the most decisive feature by a wide margin — consistent with real airline operations, where delays cascade through the day as aircraft and crew schedules compound.
Manual error analysis on the confusion matrix found a concrete pattern:
Three concrete fixes were tested against the time-of-day over-reliance found above, rather than left as untested theory:
timeblock_avg_delay, which is derived directly from DEP_TIME_BLK, to test whether the model was "double-counting" it. Result: also unchanged.Threshold tuning, by contrast, produced a real, usable gain with zero retraining risk:
| Threshold | Precision | Recall |
|---|---|---|
| 0.50 (previous default) | 0.33 | 0.65 |
| 0.60 (deployed) | 0.40 | 0.40 |
| 0.65 | 0.45 | 0.28 |
0.60 was chosen as the operating point: a meaningful precision gain (fewer false alarms) while recall stays usable, rather than collapsing at higher thresholds.
The API (FastAPI) only asks users for what they'd actually know: month, day of week, departure time block, distance group, carrier, and departing airport.
Everything else is fetched server-side: live weather from Open-Meteo (via a 96-airport coordinate table), and precomputed historical priors looked up from reference tables. Unknown carriers or airports are explicitly rejected rather than silently mispredicted.
| Component | Tool | Purpose |
|---|---|---|
| Experiment tracking | MLflow (SQLite backend) | Logs params, metrics, and model artifacts per run for comparison across versions |
| Interpretability | SHAP (TreeExplainer) | Global feature importance and the basis for the v3 error-analysis experiments |
| Testing | Pytest + FastAPI TestClient | Health check, valid predictions, rejection of unknown categories |
| CI/CD | GitHub Actions | Runs tests on every push; deploys to the VM via SSH on success |
| Containerization | Docker + Docker Compose | Reproducible environment, identical locally and in deployment |
| Deployment | Azure VM (Ubuntu, Docker) | Publicly reachable live API, manually started/stopped to manage student credit |