Three brands, 1,603 customer records, no shared ID. This is the working system I built to decide who is the same person, which values to trust, who shares a household, and who can be marketed to. Every number below is real output from the pipeline.
The rules started as a spec: points for a shared email, phone, name and zip, a merge line at 70, and a guard for family inboxes. I ran them against the answer key, changed what the results showed was wrong, and ran again. Each row is a full run.
| Run | What changed | Wrong merges | Missed | In review | Wrong households | Customers in audience |
|---|---|---|---|---|---|---|
| 1 | The spec as written | 0 | 166 | 45 | 23 | 9 |
| 2 | Phone 30 → 35, typo-aware guard, surname-gated households, hold possible buyers | 0 | 117 | 13 | 0 | 1 |
| 2* | Same rules, harder data: couples on one phone, 21 fathers and sons | 41 | 118 | 165 | 0 | 1 |
| 3 | Email needed to auto-merge, guard covers phones | 1 | 161 | 73 | 0 | 1 |
Run 2 looked finished, and that was the problem. Zero wrong merges meant the test data never tested the rules. Real customer lists have couples who give the same home phone and sons named after their fathers, so I added both. Sixteen sons were fused with their fathers. Raising the merge line couldn't fix it: at 75 the wrong merges go away, but 413 real matches are missed. The review queue also filled with 151 couples. What fixed it was admitting the rules can't tell a son from a father, and handing those pairs to a person, and letting the guard treat a shared phone like a shared inbox.
What that cost. 43 more real matches stay unmerged until someone reviews them, because one person with two emails looks exactly like a father and son. I'd rather a person spend a minute on each than merge a family's consent and addresses.
What I left alone. One wrong merge remains: two newsletter signups, both "Brian" on the Fitzgerald family inbox in the same zip. Every field the rules can see is identical, so the fix is upstream: ask the signup form for a last name. And Amanda Wilson's profile says Willson, because "newest value wins" keeps her newer, misspelled order. The same rule lets one customer into the audience: Grace Baker's newer order says Bauer, so the possible-buyer check misses her. I kept it anyway. A rule that second-guesses the newest value would make every profile harder to explain, and the cost is one email.
The data is synthetic, generated with an answer key the pipeline never sees. The app's numbers come from 2,800 runs of the real pipeline (Python, DuckDB, one test per rule), done ahead of time; the page does no matching of its own.