Problem
A bureau scorecard can only judge people the bureau knows. Thin-file applicants get rejected or referred by default, and nobody measures how many of them would have paid. I wanted that number, on real data, from a model a regulator would accept.
Approach
- 01Home Credit Default Risk data, 307,511 applications. Sixteen features picked from 122 raw columns. Missing EXT_SOURCE values get their own WOE bin instead of imputation, because missingness carries signal.
- 02WOE/IV binning and points scaling written from scratch. No scorecardpy. Scores run 300 to 850 with PDO 20 and base 600, and accept/refer/reject cutoffs come from score percentiles.
- 03Two instances of the same pipeline class. Traditional uses 16 features including bureau scores. Alternative uses 10 non-bureau features. An XGBoost benchmark with SHAP runs on the combined features but never makes decisions.
- 04FastAPI backend on Render, React dashboard on Vercel. Each applicant gets two scores, two decisions, and a variable-level explanation.
Results
Traditional scorecard: AUC 0.737, Gini 0.473, KS 0.355. Alternative: AUC 0.631, Gini 0.261, KS 0.203. XGBoost reached AUC 0.753, but the scorecard stays the decision model because the NCA wants decisions you can explain.
14.7% of the holdout (9,021 applicants) sit in the inclusion gap: the bureau model rejects or refers them, the alternative model accepts. They default at 12.1%. That is 3.4x agreed accepts (3.6%) and well under agreed rejects (20.8%). The bureau model is partly right about them, not entirely.
The reverse group, bureau accepts and alternative rejects, defaults at 5.1%. Neither model has a monopoly on the truth.
Next
Reject inference on the through-the-door population, vintage stability tests, and a South African bureau sample instead of Home Credit.