Brings the Python service under version control for the first time. It has
been running and serving the demo since 23 August; until now it existed only
on one developer's disk.
Contents:
training/build_dataset.py point-in-time join over the feature store, with
leakage assertions. dscr_p10 and dscr_p50 are
excluded deliberately — both are derived from
the forecast, which is derived from the
instalment, so the model would read its own
answer.
training/train_stress.py LightGBM vs XGBoost vs two single-feature
baselines, expanding-window validation over
as-of dates, publishes to the model_metric table
training/train_forecast.py three quantile heads (P10/P50/P90), horizon as a
feature rather than one model per horizon
serving/explain.py SHAP TreeExplainer folded into the five risk
families the console already speaks. The family
map mirrors RuleBasedScoringStrategy in the API
repo; where they disagree the rule engine wins.
serving/app.py /score, /score/batch, /forecast, /health, /model
The trained models, their metrics and the training dataset are committed
alongside the code. Without them the published AUC of 0.760 is a claim nobody
else can check; at ~2.2 MB together they are worth the space.
Measured, on 5,523 rows / 263 enterprises / 242 positives / 100% synthetic
labels: LightGBM AUC-ROC 0.7599, and the emi_to_inflow baseline 0.7685 on the
same folds. The baseline wins. That is recorded rather than tuned away — on
generated labels, beating it would mostly prove the model had learned the
generator.
The quantile forecast beats the deterministic strategy on all three quantiles
(pinball loss, 12-33% better) but flags every borrower as breaching, so the
deployment runs udyam.forecast.engine=DETERMINISTIC and this model is held
back pending calibration against real repayment outcomes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
27 lines
745 B
Plaintext
27 lines
745 B
Plaintext
# UdyamLinc ML service — pinned to the versions the published metrics were
|
|
# produced with (AUC 0.760 on 5,523 rows, run 289, 2026-08-24). LightGBM and
|
|
# SHAP in particular are pinned rather than floated: a different LightGBM minor
|
|
# can change tree construction, and stress_model.pkl is a pickle of a fitted
|
|
# LightGBM object, so an unpinned load is not guaranteed to work at all.
|
|
#
|
|
# py -m venv .mlvenv
|
|
# .mlvenv/Scripts/python -m pip install -r requirements.txt
|
|
# .mlvenv/Scripts/python -m uvicorn serving.app:app --port 8000
|
|
|
|
# model
|
|
lightgbm==4.7.0
|
|
xgboost==3.4.1
|
|
scikit-learn==1.9.0
|
|
shap==0.52.0
|
|
|
|
# data
|
|
pandas==3.0.5
|
|
numpy==2.5.2
|
|
pyarrow==25.0.1
|
|
psycopg[binary]==3.3.4
|
|
|
|
# service
|
|
fastapi==0.141.1
|
|
uvicorn==0.52.4
|
|
pydantic==2.13.4
|