← All notes

Is That Record You? Seven Bugs in a Name Matcher

From the workshop. This first appeared in the build log of Privacy Guardian, a tool we are building to find and remove personal data from people-search sites. It has not launched. The numbers below were run again on its code today: its 228 tests pass.

A tool that finds your data on people-search sites has one job that decides whether it can be trusted: looking at a record on a broker's page and saying whether it is you. Get it wrong one way and someone is told their data is not on a site that is still listing them, and they stop worrying. Get it wrong the other way and they are shown a stranger's address and walked through filing to delete it.

The mock broker server the matcher was built against was too polite. Every site returned the same clean record: a first name, a last name, a number for the age, a city and a two-letter state. Real pages are not like that. So the variations a real record appears in were written out and run through, sixteen ways for names and then every way for the other fields. Seven came back wrong.

A nickname made the record invisible

A search for Robert Johnson found nothing filed as Bob Johnson. String distance puts "bob" and "robert" about as far apart as two words get (Jaro-Winkler scores them 0.500), so the name gate threw the record out.

That is the worst failure this product has. A missed record is not a neutral gap: the tool says your data is not on a site that is still listing you, under the name your friends use.

There is now a table of established English diminutives, 61 groups: Bob for Robert, Bill for William, Peggy for Margaret, Dick for Richard and the rest. It is deliberately conservative. Every pair is a well-known equivalence, because a speculative one turns a tool that finds your records into one that finds a stranger's. A single initial also stands in for a given name, since "R Johnson" is how plenty of these sites list people. Bob Johnson and R Johnson both now score 1.00 against Robert Johnson.

Half the sites write the name backwards

"Johnson, Robert" matched nothing either. Family name first with a comma is one of the most common formats on these pages, and reading it left to right produced a person called Johnson Robert, who does not exist.

Names are parsed properly now: the comma is detected and the order corrected, suffixes such as Jr. and Sr. dropped, case ignored, middle names separated out. Apostrophes and hyphens are kept, because they are part of a name rather than punctuation to strip. O'Brien and Garcia-Lopez are not edge cases.

Roberta was matching as Robert

The other direction was worse. Roberta Johnson in the same city at the same age matched as Robert: the build log recorded a score of 0.99. She is a different person, and a false match here would have shown a stranger's address and relatives as yours.

The cause is a quirk of the string measure. Jaro-Winkler gives a bonus for a shared beginning, which is exactly right for typos and exactly wrong for names where the longer one is a different name that starts the same way: Robert and Roberta, John and Johnathan, Christina and Christin. On its own it scores robert against roberta at 0.971.

Handling nicknames explicitly meant the fuzzy threshold no longer had to be loose enough to catch them, so it could be tightened. A given name that is a longer word beginning with yours is now capped below the match line: Roberta Johnson scores 0.70 against a threshold of 0.85, and so does Janet Doe against Jane Doe, a known false match the tests had pinned since the first day.

"Age 42" was not an age

The layer underneath is how fields are read off the page at all. An age appears as 42, Age 42, Age: 42, 42 years old, 40s, 40-44, Born 1984, Unknown, or nothing. Only the bare number and "42 years old" were read correctly. Age 42, probably the most common form, came out as nothing, because turning that string into a number gives nothing. All the exact forms read as 42 now.

The interesting decision is the ranges. 40-44 and 40s are read as unknown, not as 40. Taking the first number is tempting, but a page saying 40-44 about a 44-year-old, read as exactly 40, is four years out, and the matcher's age tolerance is two years: the record is real, the person is right, and they never hear about it. An honest absence is better than a wrong precise value. The matcher already treats an unknown age as neutral and leans harder on location.

Minnesota was not a state, and Portland had none

A state appears as MN, Minnesota or MN 55401. Only the first worked. The other two produced a state that matched nothing, and since location is half of what corroborates a match, that quietly turned real matches into misses. All three now read as MN, and anything unrecognisable reads as nothing rather than a guess.

A place written as Portland, Oregon produced neither city nor state, because the splitting code wanted exactly two letters after the comma. That one was not found by reading the code. It was found by making the mock messy, which is the argument for a messy mock.

Saint Paul, St. Paul and St Paul are three spellings of one city and these sites use all three. Compared as raw strings they are three cities, which drops a current-address match to a state-level one. Cities are now compared through a key that folds Saint, Mount, Fort and the compass points, and all three come out as one. Springfield and Springville still differ, which is the part that matters.

A message that was quietly misleading

The smallest fix. A record with your exact name but no age was marked uncertain with the explanation "name not exact". The name was exactly yours; the age was missing. Someone reading that would reasonably decide the record was someone else's and skip it.

It now says which part is uncertain: an exact name with no age reads "age unknown" and "less certain: confirm before removing".

None of this came from a clever idea

Seven bugs in two days. Five would have hidden people's records from them, one would have shown them a stranger's, and one would have told them something false about why.

Not one was found by reasoning about the code. Every one came from writing down what the input actually looks like, running all of it, and reading the column of results. The mock now has a switch that makes every site write its fields the way real ones do: ages labelled, approximated or missing, states spelled out or carrying a zip, names reversed, shouted or suffixed. Clean output was the unrealistic case all along.