""" Generates SYNTHETIC credit-application data with a KNOWN ground-truth relationship between features and outcome. This is deliberate: because we know the true generating weights, we can later verify that the model recovers a sensible approximation of them, and that the explanation layer (SHAP-style + LIME-style) correctly attributes the decision to the features that actually drove it -- a property you can't verify against messy real data. No real applicant, credit bureau, or financial data is used. """ import csv import os import random random.seed(7) N = 600 FEATURES = [ "annual_income_k", "debt_to_income_pct", "credit_history_years", "num_late_payments_2y", "employment_years", "loan_amount_k", "num_open_accounts", ] # True (synthetic) generating weights -- unknown to the model at training time, # used only to label the data and later sanity-check what the model/explainer recovers. TRUE_WEIGHTS = { "annual_income_k": 0.028, "debt_to_income_pct": -0.055, "credit_history_years": 0.09, "num_late_payments_2y": -0.65, "employment_years": 0.05, "loan_amount_k": -0.012, "num_open_accounts": -0.04, } TRUE_INTERCEPT = -1.0 def sigmoid(x): import math return 1 / (1 + math.exp(-x)) def make_row(): income = max(18, random.gauss(65, 28)) dti = max(2, min(70, random.gauss(28, 12))) history = max(0, random.gauss(9, 6)) late = max(0, int(random.gauss(1.1, 1.4))) employment = max(0, random.gauss(6, 5)) loan = max(3, random.gauss(18, 12)) open_accts = max(0, int(random.gauss(5, 2.5))) features = { "annual_income_k": round(income, 1), "debt_to_income_pct": round(dti, 1), "credit_history_years": round(history, 1), "num_late_payments_2y": late, "employment_years": round(employment, 1), "loan_amount_k": round(loan, 1), "num_open_accounts": open_accts, } logit = TRUE_INTERCEPT + sum(TRUE_WEIGHTS[k] * v for k, v in features.items()) logit += random.gauss(0, 0.4) # irreducible noise p_approve = sigmoid(logit) approved = 1 if random.random() < p_approve else 0 features["approved"] = approved return features def main(): rows = [make_row() for _ in range(N)] out_path = os.path.join(os.path.dirname(__file__), "data", "credit_applications.csv") with open(out_path, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=FEATURES + ["approved"]) writer.writeheader() writer.writerows(rows) approve_rate = sum(r["approved"] for r in rows) / len(rows) print(f"Wrote {len(rows)} synthetic applications to {out_path} (approve rate: {approve_rate:.1%})") if __name__ == "__main__": main()