Two companies with the same first name
My filing monitor matched an exchange announcement to a company I hold — from a company I don't. The matcher had quietly thrown away every word that told them apart.
Part of my daily pipeline reads the exchange’s corporate-announcement feed and flags filings from companies in my book — results, large orders, pledge changes, the things that force a thesis re-check. One morning it flagged a material filing for a holding. The re-check ran, the session took it seriously, and a public post about it very nearly rendered.
The filing was real. It just belonged to a different listed company — same business family, same first word in the name, separate entity, separate shareholders, not in my book.
Three small sins, one confident lie
Unpicking the matcher found three decisions, each defensible alone:
- Generic corporate words were stop words. Industries, enterprises, corp, limited — noise words, stripped before matching. Reasonable. Except once you strip them, two related companies both reduce to the same single token. Everything that distinguished them was, by design, discarded.
- The header parser knew a third of the book. The regex that extracted company names from the feed had been written against a sample and matched only four of eleven holdings’ filing formats. It failed silently on the rest — no error, just absence.
- A truncated-prefix test did the matching. The final check compared the first six characters of the symbol against the company name as a substring. Six characters is exactly enough to be confident and exactly short enough to be wrong.
Stack them and the system’s false positives and false negatives were both invisible: filings it missed produced nothing, and filings it mis-attributed produced plausible-looking hits. The tightened matcher — require the distinguishing extra tokens, score name similarity on a ratio with a threshold instead of substring luck — cut live matches from 282 to 241. Every one of those forty-one was a mis-attribution the book had been reacting to.
Names are display strings; identifiers are joins
The durable fix isn’t a cleverer name matcher at all. Every row in the exchange feed carries a numeric scrip code — the exchange’s own primary key for the security. Fuzzy name matching was solving, badly, a problem the data source had already solved. Join on the identifier; keep the name for humans.
I now treat this as a design smell with a name: matching on a rendering. Names, labels, titles — these exist for people to read. The moment one appears in a join condition, the question to ask is which identifier you are avoiding and why. The answer is usually “the identifier needed one extra lookup,” which is another way of saying the correctness was traded for a convenience nobody priced.
Accountancy trained me for this one, though I didn’t notice in time: you do not post a journal entry to whichever ledger account sounds similar. Account codes exist precisely because two accounts can share a name and an auditor will not accept “it matched the string” as a control. My matcher was doing string-sounds-right accounting against a feed that had proper account codes in every row.
The uncomfortable coda: the false flag was caught not by a validation layer but by the re-check session noticing the filing didn’t fit the holding’s business. The last line of defence was reading comprehension. It held that day. The fix is for the days it doesn’t.