The US Treasury's Office of Foreign Assets Control (OFAC) publishes the core sanctions lists everyone screens against: the Specially Designated Nationals and Blocked Persons List (SDN), and the Consolidated Non-SDN List (Cons).
The compliance world largely orbits around these two lists. Despite this, guidance on how regulated firms should screen against them is limited. OFAC operates a public search interface that produces score results for name matches. It also published a high-level description of how these scores are calculated, listed as OFAC FAQ #249.
Over the years, that FAQ item has acquired strange regulatory gravity, producing an industry obsession with phonetic matching and a specific string comparison algorithm, Jaro-Winkler. But this was simply because the FAQ item was all anyone really had to go on.
A clue in the scraping logs
Until now, OpenSanctions has been offering two screening matchers, called name-based and name-qualified, that aim to emulate the OFAC score. However, using modern Python versions of the Jaro-Winkler algorithm just never produced quite the right scores, and we couldn’t figure out why.
So, we decided to investigate. We used an LLM agent to sample a few hundred name-match scores from OFAC's public search tool and attempted to reproduce them with our own implementation. The agent got close, up to 90% equivalence – but not quite close enough for a compliance product.
Eventually, we found an extra clue in the agent’s own scraping logs. Claude had used workarounds to crawl ancient Microsoft ASP.NET sites. The score-filter slider bar uses AjaxControlToolkit, a web script that was widely used between 2007 and 2012. So: if you were building an ASP.NET site in those years, how would you implement Jaro-Winkler? Probably using SimMetrics.NET, a C# port of SimMetrics-Java.
Turns out SimMetrics.NET skipped a step in the algorithm used in the original Jaro-Winkler paper. An extra threshold factor would apply a bonus for matching name prefixes only when the base Jaro score is already greater than 0.7. Modern string comparison libraries (rapidfuzz, jellyfish) all apply this threshold. With a custom implementation of Jaro-Winkler that always applies the scoring bonus, the numbers suddenly added up, and we'd got ourselves a 96% accurate OFAC screening emulator!
From weekend hack to production
What started as a problem we attempted to solve one weekend has become a product – the emulator will ship in the OpenSanctions matching API as a new algorithm, ofac. It pairs the parity-tuned name score with a set of mismatch qualifiers — penalties applied when a candidate's country, date of birth, gender, or registration/tax identifier contradict the query — so you get OFAC-comparable name scoring with the obvious disqualifiers layered on top.
This also enables us clean up some history. We have two older algorithms, name-based (phonetic plus Jaro-Winkler, loosely based on FAQ #249) and name-qualified (the same, with mismatch penalties), neither of which actually reaches OFAC parity. In a couple of months, both will start redirecting to ofac. If you're calling either of these today, you don't need to do anything — you'll simply start getting scores that track the real OFAC tool more closely. And if you want to opt in early, you can request ofac by name right away.
