Skip to content

Banking

A retail-banking and credit-risk warehouse. Customers open accounts, apply for loans, transact, and are monitored for default. Credit score band is tracked with SCD2 versioning; loan applications produce child document records; accounts hold multiple products via an M:N bridge. Disbursements get CDC audit. Student-t noise reflects heavy-tailed transaction amounts. A holdout split reserves the last quarter for credit-scoring validation.

What it produces

Table Type Grain Rows (default) Notes
dim_date dim per_period 24 24 monthly periods, Jan 2023 – Dec 2024
dim_customer dim per_entity (+ SCD2 + geo) 100–400 98 customers, expanded by SCD2 credit-score band moves
dim_product dim per_reference 8 checking, savings, credit_card, ..., heloc
dim_merchant_category dim per_reference 10 grocery, fuel, dining, ..., cash_advance
fct_account_activity fact per_entity_per_period 2,352 seven metrics + loan officer narrative column
fct_loan_applications fact variable (proportional, CDC) ~loan_vol one row per application; CDC for amendments
fct_loan_documents fact per_parent_row 1–3× parent child docs per application
evt_transaction event variable (proportional) ~12,000 scaled 5× transaction_volume
evt_default event variable (threshold) 5–25 default_risk above 0.65 for 2 consecutive periods
bridge_customer_product bridge static M:N 100–490 each customer holds 1–5 products
<fct>_train, <fct>_holdout fact holdout split one pair per per_entity_per_period fact (3-period split)

98 entities across six segments: prime_borrower (22), subprime_improving (18), mass_market (24), deteriorating (12), hnw (8), new_customer (14, half in a treatment cohort).

Features exercised

  • Heavy-tailed noisenoise_family: student_t, degrees_of_freedom: 4.0. Transaction-amount outliers reflect real retail-banking tail risk.
  • Seasonality — Holiday spend Q4 (+0.30), tax-refund surge Mar–Apr (+0.20), summer slowdown Jun–Jul (-0.10).
  • Causal lagdelinquency_risk follows credit_utilization with delay 2 periods.
  • SCD2credit_score_band on dim_customer tracking default_risk with thresholds [0.25, 0.55, 0.80].
  • Parent / child factsfct_loan_applications (variable-grain, driven by loan_volume) → fct_loan_documents (per_parent_row, 1–3 docs per application).
  • CDCfct_loan_applications carries audit columns for disbursement amendments.
  • Bridgebridge_customer_product (M:N), trajectory-driven by account_balance so high-balance customers hold more products.
  • Geo bundlebranch_country, branch_country_code, branch_region, branch_city on dim_customer.
  • Narrative columnloan_officer_notes on fct_account_activity, with per-segment lexicons (uses YAML anchors to share a base block across segments).
  • A/B treatmentnew_customer segment 50/50 split: half get a credit-line increase at period 6, half don't.
  • Holdout — last three months reserved for credit-scoring validation; emits _train and _holdout file pairs per per_entity_per_period fact.
  • Lifecycle stagesperforming / watch / past_due / default on default_risk.

Quality injection is omitted to keep the credit-scoring teaching surface clean. holdout and quality combine cleanly if you want to layer corruption on top — corrupted rows are sliced by date_key, so both train and holdout splits carry their share of issues.

Use it

from plotsim import load_template, generate_tables, write_tables

config = load_template("banking")
tables = generate_tables(config)
write_tables(tables, config, output_dir="./output")
plotsim template banking -o banking.yaml
plotsim run banking.yaml -o ./output

Common customizations

Shorten the holdout window

Reserve one month instead of three:

from plotsim.types import HoldoutConfig

config = load_template("banking")
config = config.model_copy(update={
    "holdout": HoldoutConfig(enabled=True, target_metric="default_risk", holdout_periods=1),
})
holdout:
  target:  default_risk
  periods: 1

Swap student-t for gaussian noise

Heavier-tailed transactions are realistic but produce extreme outliers that some downstream pipelines reject. To use plain gaussian:

from plotsim.types import NoiseConfig

config = config.model_copy(update={
    "noise": NoiseConfig(gaussian_sigma=0.05, outlier_rate=0.01, mcar_rate=0.0),
})
noise:
  gaussian_sigma: 0.05
  outlier_rate:   0.01
  mcar_rate:      0.0
  # noise_family + degrees_of_freedom omitted, defaults to gaussian

Increase the loan-document fan-out

Default is 1–3 documents per application. Bump to 2–8:

# Re-author with create() — children_per_row is immutable on the loaded config.
facts=[
    # ... fct_loan_applications unchanged ...
    {
        "name":             "fct_loan_documents",
        "parent_table":     "fct_loan_applications",
        "children_per_row": (2, 8),
        "metrics":          [],
        "columns":          [...],   # same as template
    },
]
facts:
  - name: fct_loan_documents
    parent_table: fct_loan_applications
    children_per_row: [2, 8]
    # ... columns unchanged ...

Where it shines

  • Credit-scoring training pipelines — default_risk as target, fct_account_activity joined to dim_customer for features, the holdout split for validation.
  • Fraud/AML rule tuning — evt_transaction joined to dim_merchant_category produces transaction streams with realistic amount distributions (heavy-tailed) and seasonal patterns.
  • M:N entity-resolution exercises — bridge_customer_product gives a many-to-many surface to practice bridge-resolution SQL.