Transaction categorization assigns each bank line a category from a fixed taxonomy, turning a string like “TFR 4829 CONTACTLESS PRET A MANGER LONDON” into food and drink with a known merchant attached. Most of the accuracy comes from merchant enrichment, not from the classifier. Deterministic rules cover the recurring cases and a model handles the long tail, but the feedback loop that corrects mistakes matters more than either.
The reason categorization is hard is that the input is deliberately lossy. Bank descriptions are truncated, abbreviated, padded with terminal IDs and payment-scheme noise, and formatted differently by every institution. Open banking data helps because it is structured and standardised across providers, but the merchant string inside it is still whatever the acquirer stamped on the transaction. Two coffees from the same shop can arrive as three different strings depending on which POS terminal rang them up. Before any model sees a transaction, most of the useful signal has already been mangled, and getting it back is the actual engineering problem.
Merchant enrichment is where the accuracy lives
Enrichment is entity resolution against a messy, drifting reference set. The goal is to map a raw descriptor to a stable merchant identity, and then to attach attributes to that identity: a normalised name, a category hint, a website, a logo, a registered legal entity where you can find one.
The pipeline we build looks roughly like this:
- Normalise the raw string: strip scheme prefixes, terminal numbers, dates, city padding and reference codes. This is unglamorous regex and lookup work, and it moves accuracy more than any model choice.
- Resolve to a candidate merchant using a combination of exact and fuzzy matching against a reference set, disambiguated by MCC, amount patterns and geography.
- Attach the category hint from the resolved merchant, which becomes a strong feature for the classifier rather than the final answer.
The reference set drifts. Merchants rebrand, get acquired, change acquirers, and open under new legal entities. Treat it as a maintained asset with lineage: every enrichment record should carry where the mapping came from and when it was last confirmed, so that when a category looks wrong six months later you can trace which rule or match produced it. Without that lineage you are debugging categorisation by guessing.
A note on point-in-time correctness, because it bites people who build enrichment carelessly. If your reference set says a merchant is “groceries” today, you cannot retroactively apply that label to a transaction from two years ago if the merchant was a restaurant back then. For budgeting nobody cares, but for underwriting it is leakage: you are using information that did not exist at the decision point. Version the reference set and join transactions against the state it had on the transaction date.
Rules and model, in that order
Split the work by confidence. A recurring salary credit from a known employer, a monthly subscription to a named provider, a loan repayment to your own institution: these do not need a probabilistic model. They need a deterministic rule that fires the same way every time and writes an audit trail. Rules give you straight-through processing on the boring majority and a reason string you can show a reviewer or a regulator.
The model earns its place on the long tail, the descriptions that do not match anything cleanly. Here the practical choices are:
- A gradient-boosted classifier over engineered features (normalised tokens, MCC, amount buckets, sign, recurrence signals, the enrichment category hint) is a strong, cheap, explainable baseline. Start here.
- A fine-tuned text classifier over the description helps on genuinely novel strings, at higher cost and lower interpretability. Reach for it when the boosted model plateaus, not before.
Set a false-positive budget per category and route by confidence. High-confidence predictions post automatically. Everything below the threshold goes to a review queue, and those human decisions are the labels that retrain the next version. The threshold is a business lever: an underwriting pipeline tolerates far fewer silent miscategorisations than a spend-analytics dashboard, so the two can share a model but not a threshold.
Keep the taxonomy small and stable. Every time someone adds a leaf category the boundaries blur and your per-class precision drops. Changing the taxonomy is a migration, not a config edit, because it silently reinterprets every historical prediction.
The feedback loop is the product
A categorization engine is only as good as the loop that corrects it, and that loop needs to be built as deliberately as the model.
- Maintain a version-controlled eval set: a stratified, hand-labelled sample that reflects real traffic, not a convenient one. Report precision and recall per category. Overall accuracy is close to useless here, because 90% overall can hide a category you care about sitting at 40%.
- Capture every user or reviewer correction as a labelled event, with the input as it was at prediction time. These corrections are your most valuable training data because they concentrate on exactly the cases the model got wrong.
- Watch for drift. Merchant behaviour, description formats and spend mix all shift, and quarter-end and seasonal patterns move category distributions. Monitor the confidence distribution and the review-queue rate over time; a rising queue rate is usually drift announcing itself before your accuracy metric catches up.
- Version the whole stack together: reference set, rules, model and taxonomy. When a category flips, you want to answer which change caused it, and you can only do that if each is versioned and the lineage is intact.
Retrain on a signal, not a schedule. When the eval set or the review-queue rate shows the model has slipped, retrain; a date on the calendar is not a reason on its own. Build the loop so that shipping a corrected reference mapping or a new rule is a low-ceremony change with a clear audit trail. In production most of your accuracy gains come from those small corrections, not from a new model architecture.
FAQ
Do I need a machine-learning model, or will rules do?
Both. Rules resolve the recurring, high-confidence merchants deterministically and give you an audit trail, while a model handles the long tail of messy descriptions. Most of the accuracy gain in the first months comes from merchant enrichment, not from a fancier classifier.
How many categories should the taxonomy have?
Fewer than you think. Ten to fifteen top-level categories with a second level underneath covers most budgeting and underwriting needs. A taxonomy with two hundred leaf nodes looks precise but drives your accuracy down because the boundaries between classes stop being learnable.
How do you measure accuracy when there is no ground truth?
Build a labelled eval set from a stratified sample of transactions, hand-labelled and version-controlled. Report per-category precision and recall separately from overall accuracy, because a single headline number hides the categories that actually matter for the downstream decision.