SoundCloud showed up under my Google Cloud rule on device. That's when part-based matching replaced the algorithm.

The Google Cloud rule had two parts: GOOGLE and CLOUD, joined by ANY. Device QA of the Phase R v1 match builder surfaced SoundCloud under it. Also two Google One rows.

ANY-match on two parts is exactly what it says: any transaction containing either word. SoundCloud contains “cloud.” Google One contains “google.” The algorithm had no way to know I meant the cloud-compute billing entries, not the music platform or the storage subscription. That distinction lives in my head, not in the payee string.

That gap is what Phase R v2 closed.

How we got here

Phase Q shipped the app’s first persistent categorisation layer. The origin was B-051, a device-QA observation: three identical “Driver & Vehicle Licensing Agency” −£17.06 transactions where only one had been categorised, and the obvious expectation was that setting a category on one should propagate to the rest. Phase Q delivered that: after you categorise a transaction, the app surfaces a list of candidate rows for review. You accept or skip each one. Fuzzy similarity drives the matching.

The flaw in the fuzzy approach isn’t that it’s inaccurate. It’s that it’s opaque. You can read the candidate list; you can’t read the criterion. When it’s right, you trust it. When it’s wrong, you have no lever.

B-054 made the lever problem concrete. Every Halifax card payee ends in CD <last4>, a suffix the bank appends to every transaction from a given card. Two Halifax transactions from different merchants, both ending in CD 1234, look similar to a fuzzy engine. They aren’t the same thing. The fuzzy list would surface one as a candidate for the other, and you’d have no clean way to explain why the match was wrong without just dismissing it.

Phase R replaced the fuzzy list with an interactive Match Builder. You take the payee string, select which parts should drive matching, and choose whether the rule requires all parts (AND) or any one (ANY). The rule is transparent because it’s yours. The algorithm’s job shrinks to executing it.

The SoundCloud case

GOOGLE and CLOUD as ANY-match parts was a reasonable starting construction if the intent was “any transaction touching Google services or cloud infrastructure.” It’s the wrong construction if the intent is “the specific Google Cloud compute charges I pay monthly.”

The v1 builder can’t distinguish them. There’s no way to express the second without also accepting the first. ANY-match is the only available scope, and the only lever is which parts you select.

Phase R v2 added two tools.

Exclude words. After selecting match parts and a join mode, you can specify words that disqualify a transaction. The GOOGLE CLOUD rule with an exclude on SOUND (or SOUNDCLOUD for precision) stops the false positive without changing the core rule. The Google One false positives require a separate exclusion on ONE, or switching to AND-match so both GOOGLE and CLOUD must appear. Both approaches are now expressible. Neither required adjusting a similarity threshold.

Per-row select-all. A smaller addition. When reviewing match results, committing or dismissing an entire group was a multi-tap operation in v1. V2 adds a per-row control that collapses a matched group into a single action. Correctness isn’t improved; friction is reduced. Device QA surfaced this alongside the over-reach issue in the same session.

What changes about the categorisation model

Phase Q’s fuzzy list is a recommendation engine: the intelligence sits in the algorithm and you approve or reject its outputs. Fast for one-off decisions, opaque on failure.

The Phase R builder inverts that. The intelligence is in the criterion you author; the app executes it deterministically. A rule that pulls in SoundCloud is readable; you can see ANY(GOOGLE, CLOUD) and understand immediately why it fired. The fix is to the criterion: add an exclusion, switch to AND, refine the parts. No black box to retune.

The trade-off is up-front effort. Building a match rule takes longer than accepting a fuzzy suggestion. For a one-off categorisation, Phase Q is faster. For a recurring payee (monthly billing, regular subscriptions, regular DVLA charges), the builder is the right tool. A persistent rule that misfires silently across six months of imported transactions is worse than no rule.

Phase R v2’s exclusions cut the effort cost for cases where AND-match is over-broad and you want ANY-match with precision. The alternative is asking the user to decompose the payee further, which doesn’t always work: SOUNDCLOUD doesn’t share a clean part boundary with GOOGLE CLOUD. Excluding by substring is the direct fix.

What this doesn’t include

Out of scope: cross-bank payee normalisation. CD <last4> is one bank’s formatting choice; other banks format the same merchant differently. The builder lets you work around this by selecting stable parts of the payee string, but it doesn’t abstract it. That’s a future sprint.

Out of scope: rule suggestions seeded from existing matches. When you build a rule for GOOGLE CLOUD, the app doesn’t offer to seed a similar rule for AWS or Azure. There’s a reasonable future there. Not Phase R.

Where categorisation sits now

Three layers, operating at different points in the workflow:

  • Phase Q. Fuzzy propagation, surfaced immediately after categorisation. Fast, one-shot, no persistent state.
  • Phase R. User-defined rule, part-based, persistent. Runs against every imported transaction for that account.
  • Exclusions (Phase R v2). Precision control on top of the rule. Adds specificity without rewriting the core criterion.

They’re not alternatives. Phase Q catches quick wins in the moment; the builder is for payees you know will recur; exclusions tighten rules that are almost right.

The SoundCloud case was a clean failure. A false positive on the first device QA run of the first real rule, with two distinct failure modes in one example: one wrong match by word, one by substring. Better to find it there than after six months of misfiled transactions.

All writing