Software Feature Shipment Delay Prediction
Predicts the delay (in days) for a planned software feature shipment, and from that, the actual expected delivery window.
Project structure
data/ raw dataset
notebooks/eda.py exploratory data analysis (stats, correlations, plots)
src/data_prep.py loading, cleaning, feature engineering, train/test split
src/train.py baseline models + tuned gradient boosting model
src/predict.py inference: predict delay + next shipment date for new input
models/ saved model (.joblib) and metrics (.json)
Setup
pip install -r requirements.txt
Important: models/ starts empty. Run src/train.py yourself before predict.py or interactive.py. The model is regenerated in your environment to match your scikit-learn/XGBoost versions. Never copy a .joblib trained elsewhere into this folder.
Run
python notebooks/eda.py # prints stats, saves plots to notebooks/eda_outputs/
python src/train.py # trains baseline + tuned model, saves both
python src/predict.py # example prediction for a new shipment
python src/interactive.py # ask about a feature one question at a time
Web frontend
cd src
python3 app.py
Then open http://localhost:5000. app.py is a small Flask API at /api/predict that wraps the trained model and serves frontend/index.html.
The browser sends form values to /api/predict as JSON. It never touches the Python .joblib model directly.
interactive.py provides a conversational feel, asking one question at a time and validating each answer. Every prediction still comes from the trained regression model in predict.py, not a language model.
Testing
python3 -m unittest discover -s tests -v
Covers missing-value checks, expected feature columns, full-rank regression protection, chronological train/test splitting with no overlap, and sanity/directional checks for predictions.
Data notes
- 1,300 rows, 10 features, no missing values; no imputation was needed.
planned_shipment_date is spread almost uniformly from 2021 to 2045, indicating synthetically generated dates. Calendar features are still engineered for completeness but carry little signal here.
- IQR checks flagged
holidays_in_sprint heavily, but it is a binary flag (0/1), so those "outliers" are simply the minority class.
Key finding
Correlation with delay_days:
| Feature | Correlation |
| feature_complexity | 0.82 |
| num_blockers | 0.41 |
| num_dependencies | 0.27 |
| holidays_in_sprint | 0.17 |
| past_avg_delay_days | 0.15 |
| estimated_bug_count | 0.14 |
| priority_encoded | 0.01 |
| sprint_length_weeks | -0.02 |
| team_size | -0.02 |
The relationship between feature_complexity and delay is close to linear, so plain linear regression outperformed a tuned gradient boosting model on this dataset.
| Model | MAE | RMSE |
| Baseline (median) | 4.19 | 5.28 |
| Baseline (ridge regression) | 0.88 | 1.08 |
| Tuned gradient boosting | 0.96-1.00 | 1.18-1.23 |
Gradient boosting is useful for nonlinear interactions, but when a target is largely explained by one near-linear driver, the simpler model wins and is easier to explain. The current recommendation is to ship the linear model, or an ensemble of the two, and use tree feature importances as a validation check.
Baseline model note: An early engineered feature, num_blockers + num_dependencies, was an exact linear combination of existing columns and made the design matrix singular. The fix was to drop that redundant feature in data_prep.py and use Ridge regression with L2 regularization.
team_size, sprint_length_weeks, and priority_encoded show approximately zero correlation with delay and are candidates to drop in v2.
Model backend
train.py uses XGBoost when installed. If XGBoost is unavailable, it falls back to scikit-learn's HistGradientBoostingRegressor.