A prototype that segments a CPG client's paying accounts by HOW they pay, using accounts receivable and collections behaviour, and attaches a differentiated collection strategy to each discovered segment. It is the second of two models in a Finance Transformation customer segmentation workstream: Model 1 (separate repository) segments the same customers by who they are as organisations; Model 2 layers payment behaviour on top of the same accounts. Everything here runs on synthetic data; no real client data appears anywhere.
Instead of one long list worked oldest-and-largest first, the collections team gets segments like "prompt reliable payers", "erratic slow payers", and "deduction-heavy payers", each with a playbook, so effort goes where it changes the outcome.
- Discovers behavioural segments by unsupervised clustering (k-means primary, k-prototypes as a config fallback), with the number of segments chosen analytically by elbow plus silhouette plus collections-actionability rules, never hardcoded.
- Profiles each segment in plain language and original units (days, ratios, currency), with machine-readable traits.
- Maps a collection strategy playbook to each segment AFTER clustering: cadence, channel, tone, escalation, and commercial levers (early-payment discount, credit hold, stop-ship, dispute-resolution routing).
- Assigns a new account to an existing segment day one, by nearest centroid against persisted artifacts, with a distance-based confidence proxy. No refitting at inference.
- Validates itself: a mandatory synthetic-data recovery gate before the pipeline, stability across seeds and bootstrap resamples, a Pareto check, a risk-versus-value read per segment, a regression test on a fresh larger synthetic set, and three worked case studies.
These come from the AR parameters domain document (reference/) and are enforced in code, not just documented:
- Collection strategy and the segments themselves are model OUTPUTS, never features. Preprocessing guardrails raise if either leaks into the matrix.
- Deductions are not delinquency. In CPG, trade spend runs 15 to 25 percent of gross sales, and much apparent past due is unresolved deductions. The deduction rate is a first-class feature and a high-deduction segment gets a dispute-resolution playbook, not dunning.
- Both the mean AND the standard deviation of days-to-pay are engineered. A customer who always pays ten days late is predictable and low risk; the same average with wild swings is genuinely risky. Variance is the signal.
- Aging bucket shares use due-date aging, not invoice-date aging.
- Statement of account and invoice documents enter only as engineered flags.
Modules in build order; each is independently runnable and prints its result.
| Module | Role |
|---|---|
config.py |
Every tunable: paths, seed, the hidden generator recipe, feature lists, K-selection knobs, thresholds. |
data_generation.py |
Synthetic AR behaviour per account (customer x company code) on the Model 1 customer base, with a hidden source_profile QA tag and a size knob. |
validate_dataset.py |
The mandatory gate: clustering recovery vs the hidden recipe (ARI/NMI in configured bands) before anything is built on the data. |
preprocessing.py |
Fit-then-transform split: impute, log-transform, scale, one-hot; persisted artifacts plus a hash manifest so training and inference cannot drift. |
training.py |
K sweep, combined elbow-plus-silhouette choice with actionability rules, final fit, atomic artifact persistence, manifest written last. |
inference.py |
Nearest-centroid assignment for batches or a single account dict, from persisted artifacts only. |
profiling.py |
Per-segment profiles in original units, traits, and a plain-language label. |
strategy.py |
The output layer: ordered trait rules map each segment to a collection playbook. |
output.py |
Run summary, segment distribution, per-account assignment CSV, single new-account response. |
evaluation.py |
Silhouette, seed and bootstrap stability, Pareto check, risk-versus-value read, recovery QA. |
main.py |
End-to-end orchestration with a held-out day-one account demo. |
visuals.py |
Presentation figure set (segment value map, variance insight, K selection, Pareto, risk-vs-value, deductions, aging) to outputs/figures/. |
regression_test.py |
Applies the persisted model to a fresh 1,000-account set with a different seed; PASS/FAIL. |
case_studies.py |
Three worked accounts: a deductor, an erratic payer, a day-one new account. |
Python 3.9 or later.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python main.py # full pipeline: generate, gate, train, outputs, evaluate
python regression_test.py # generalisation check on a fresh synthetic set
python case_studies.py # three worked accounts
python visuals.py # presentation charts to outputs/figures/Or step by step, in build order:
python data_generation.py
python validate_dataset.py # must PASS before anything else
python preprocessing.py
python training.py
python inference.py
python profiling.py
python strategy.py
python output.py
python evaluation.pyAll results land in outputs/ (untracked; everything regenerates
deterministically from the fixed seed).
- 236 accounts from 200 customers; validation gate PASS (ARI 0.537, NMI 0.639 against the hidden recipe, inside the configured bands).
- K = 3 chosen analytically: elbow knee at K=3, silhouette band {3, 4}, tied K closest to the knee.
- Segments and playbooks:
| Segment | Share | Value share | Playbook |
|---|---|---|---|
| Prompt reliable payers | 27% | 60% | light touch, pre-due reminders only |
| Erratic slow payers, high variance | 31% | 3% | intensive recovery, credit hold, stop-ship |
| Moderate payers, high deductions | 42% | 37% | dispute resolution, not dunning |
- Stability: ARI 1.000 across 5 seeds, 0.92 mean across 20 bootstrap resamples. Pareto: top 20 percent of accounts hold 89 percent of value.
- Regression test PASS: fresh 1,000-account set, mean centroid distance ratio 1.03 vs training (limit 1.5).
- One fixed, recorded seed (
config.RANDOM_SEED) drives generation, training, and evaluation; runs reproduce byte-identically. requirements.txtpins the exact library versions the artifacts were built with.- Training writes
outputs/artifacts/manifest.jsonLAST, recording method, chosen K, silhouette, seed, library versions, and a SHA-256 of every artifact. Inference verifies the manifest before scoring and fails loudly on a missing, stale, or mixed artifact set.
reference/01_model2_design.md: the implementation blueprint this code follows (objective, grain, feature set, synthetic-data spec, method, outputs, build order).reference/AR_Collections_Parameters_3.docx: the domain source defining the eleven AR and collections parameters and their roles.reference/model1_recap.md: the upstream Model 1 context.CLAUDE.md: operating instructions and norms for working on this repository.
Open questions for the engagement mentor (design Section 11) are tracked in the blueprint: whether risk enters as a supplied score or stays a behavioural read, whether the Model 1 segment may join as a weak feature, and confirmation of the playbook contents.
All data is synthetic, generated from a hidden recipe for a GENERIC CPG
client. The source_profile tag in the generated dataset is internal QA
provenance: the model never sees it, and it is never a feature.
MIT. See LICENSE.