Instructions to use thanatchon36/bureau-to-workout with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use thanatchon36/bureau-to-workout with Scikit-learn:
# β οΈ Model filename not specified in config.json
- Notebooks
- Google Colab
- Kaggle
bureau-to-workout: credit-scoring and debt-workout models trained on simulated data
Trained on synthetic data only. These models were fitted to the paper's simulators, not to NCB or SAM data. They reproduce the paper's experiments and serve as baselines for the dataset. They are not credit models to score real people. They have not been validated on real data, and the simulators' group effects are arbitrary design choices.
Models for the preprint From Bureau to Workout: Credit Scoring and Causal Debt Resolution under Policy-Shaped Labels by Thanatchon Chongmankhong (read online). Code: https://github.com/thanatchon36/bureau-to-workout; archived on Zenodo: 10.5281/zenodo.24102251.
Independent personal research. Does not represent the views of any employer or institution.
Files
All models come from the paper's first seed (bureau seeds 7 and 1007, workouts seed 107), fitted by the same
code as make experiments. scikit-learn models are saved with skops (no pickle);
the scorecard is plain JSON.
| path | model | paper |
|---|---|---|
bureau/woe_scorecard.json, bureau/woe_points_table.csv |
weight-of-evidence scorecard: monotone bins, WoE, logistic fit, points (600 at odds 50:1, 20 points to double) | E1, E5 |
bureau/monotone_gbm.skops |
gradient-boosted classifier with monotone constraints on the 15 bureau attributes that vary without a moratorium | E1 |
bureau/ssl_encoder.skops |
masked self-supervised encoder of 24-month bureau sequences (scaler + MLP; the embedding is the bottleneck layer) | E1 |
bureau/ssl_linear_probe.skops, bureau/gbm_ssl.skops |
logistic probe on the embedding alone; GBM on attributes + embedding | E1 |
bureau/e2_{approved_only,approved_only_ipw,fuzzy,bureau_augmented,bureau_augmented_ipw,oracle}.skops |
monotone GBMs for reject inference at lender 0: approved applicants only, with IPW, classic fuzzy augmentation, bureau-augmented labels (rejects whose outcome the bureau sees), with IPW for unobserved rejects, and the oracle on every applicant's true outcome | E2 |
bureau/e3_{naive,exclude,delayed,oracle}.skops |
logistic models trained through a moratorium on reported labels, without deferred windows, on delayed labels, and on the no-deferral truth | E3 |
bureau/features.json |
feature lists, monotone constraints, embedding columns | |
workout/outcome_models.skops |
expected recovery under each of the four offers (list of 4 regressors; T-learner) | R1 |
workout/propensity.skops |
multinomial propensity model of the logged offers (scaler + logistic) | R1, R4 |
workout/dr_learner.skops |
cross-fitted DR-learner: effect of offers 1β3 against standard collection (dict of 3 regressors) | R1βR3 |
workout/predictive_score.skops |
P(recovery β₯ 60% under standard collection), the predictive-targeting baseline | R2 |
metrics.json, versions.json |
the metrics below in full; library versions |
Usage
import json
import pandas as pd
import skops.io as sio
from huggingface_hub import snapshot_download
from bureau_to_workout.bureau import WoEScorecard # pip install git+https://github.com/thanatchon36/bureau-to-workout
path = snapshot_download("thanatchon36/bureau-to-workout")
oot = pd.read_parquet(
snapshot_download("thanatchon36/bureau-to-workout", repo_type="dataset") + "/scoring/e1_oot.parquet")
feats = json.load(open(f"{path}/bureau/features.json"))["e1_features"]
def load(file):
untrusted = sio.get_untrusted_types(file=file) # inspect this list before trusting it
return sio.load(file, trusted=untrusted)
gbm = load(f"{path}/bureau/monotone_gbm.skops")
pd_gbm = gbm.predict_proba(oot[feats])[:, 1]
e2 = pd.read_parquet(
snapshot_download("thanatchon36/bureau-to-workout", repo_type="dataset") + "/scoring/e2_test.parquet")
ba = load(f"{path}/bureau/e2_bureau_augmented.skops")
pd_ba = ba.predict_proba(e2[json.load(open(f"{path}/bureau/features.json"))["e2_features"]])[:, 1]
scorecard = WoEScorecard.from_dict(json.load(open(f"{path}/bureau/woe_scorecard.json")))
points = scorecard.score(oot[feats])
reasons = scorecard.reason_codes(oot[feats].head(3))
dr = load(f"{path}/workout/dr_learner.skops") # {"1": ..., "2": ..., "3": ...}
Metrics of these exact models
Out-of-time test (E1): months 36β45, after the shock. The paper column is the paper's seed-7 result. These uploads were refitted with newer library versions (python 3.12.15, numpy 2.5.4, pandas 3.1.1, scikit-learn 1.9.1, skops 0.16.0, bureau-to-workout 0.1.0), which moves some numbers in the third decimal.
| model | OOT AUC | paper, seed 7 | PSI dev β OOT |
|---|---|---|---|
| WoE scorecard | 0.806 | 0.806 | 0.12 |
| Monotone GBM | 0.813 | 0.815 | 0.12 |
| SSL embedding only (linear probe) | 0.773 | 0.772 | 0.59 |
| GBM + SSL embedding | 0.819 | 0.818 | 0.37 |
Reject inference (E2), test half of lender 0's applicants. Predicted Γ· observed default among rejected applicants shows how far each model under-predicts the risk it never saw (ideal 1):
| training labels | AUC, all applicants | predicted Γ· observed, rejected | paper, seed 7 |
|---|---|---|---|
| Approved-only (AO) | 0.690 | 0.58 | 0.58 |
| AO + IPW on observables | 0.683 | 0.56 | 0.55 |
| Fuzzy augmentation | 0.693 | 0.58 | 0.58 |
| Bureau-augmented (BA) | 0.706 | 0.92 | 0.92 |
| BA + IPW for unobserved rejects | 0.704 | 0.91 | 0.91 |
| Oracle (all applicants, true Y) | 0.710 | 0.96 | 0.96 |
After a moratorium (E3), test months 39β45:
| labels used in training | OOT AUC | relative calibration of deferred borrowers (ideal 1) |
|---|---|---|
| Naive: reported labels | 0.638 | 0.17 |
| Exclude deferred windows | 0.827 | 0.74 |
| Delayed labels (+6m after exit) | 0.824 | 0.70 |
| Oracle: no-deferral counterfactual | 0.829 | 0.93 |
Workout policies at the status-quo write-down budget (R2), true values on the held-out half:
| policy | recovery, % of balance | write-downs, % of balance |
|---|---|---|
| Status quo (logged offers) | 55.53 | 10.6 |
| All standard | 56.59 | 0.0 |
| All term extension | 55.39 | 0.0 |
| Predictive targeting | 56.42 | 10.6 |
| Causal targeting (T-learner) | 57.17 | 7.2 |
| Causal targeting (DR-learner) | 57.25 | 2.8 |
| Oracle | 57.99 | 3.0 |
Limitations
The simulators decide what these models can learn: in the bureau, repayment at other lenders is a perfect proxy for the new loan; in the workouts, unconfoundedness and overlap hold by construction. The SSL encoder is a small CPU stand-in for sequence models trained at bureau scale. See Section 8 of the paper.
Citation
@misc{chongmankhong2026bureau,
author = {Chongmankhong, Thanatchon},
title = {From Bureau to Workout: Credit Scoring and Causal Debt Resolution under Policy-Shaped Labels},
year = {2026},
doi = {10.5281/zenodo.24102251},
url = {https://thanatchon36.github.io/bureau-to-workout/paper/},
note = {Preprint; models trained on synthetic data}
}
- Downloads last month
- -