The problem with "the algorithm decided"

Fuzzy matching isn't magic

Ask an analyst why a screening alert fired and the honest answer is often "the algorithm decided." That isn't a good enough answer, and treated as a final word rather than a starting point, it isn't even true. Fuzzy matching, the mechanism behind almost every sanctions and PEP screening hit, is not a black box handed down from somewhere unaccountable. It is documented, public, and tunable. The gap between an analyst who can explain why a match fired and one who can only accept or dismiss it is understanding, roughly, how that mechanism actually works.

Fuzzy matching means comparing a submitted name against a reference list not by exact string equality but by a calculated similarity score. Real names arrive misspelled, transliterated, reordered, or shortened, sometimes several of those at once, and a system that only catches an exact character-for-character match catches almost nothing that actually matters in practice.

This guide grounds every claim in one real, currently running production system rather than a textbook abstraction: OpenSanctions, the sanctions and PEP data provider behind a meaningful share of the industry's screening tools, including FinCrimeRadar's own. OpenSanctions documents its matching approaches openly, and the actual options are worth naming precisely rather than treating as interchangeable. Two older approaches, name-based and name-qualified, compare names using Jaro-Winkler string similarity combined with Soundex phonetic matching. Both are now deprecated, and OpenSanctions' own documentation is explicit that neither one reaches parity with OFAC's public Sanctions List Search tool [1]. A separate algorithm exists specifically for that purpose, reverse-engineered from OFAC's own scoring FAQ and calibrated to emulate the live public tool's behaviour [2]. The current recommended default is logic-v2, a deterministic, rule-based system that annotates parts of a name using curated, multilingual reference data before comparing anything, purpose-built to improve cross-language and cross-script matching over its predecessor [3]. Alongside it sits a regression-based approach, a logistic regression model scored across 18 separate features spanning name similarity, dates, and identifiers, not the name string alone [4].

Underneath every one of these sits a tension that no amount of tuning removes. Tighten the match threshold to cut false positives and a genuine match can slip through unflagged. Loosen it to catch more genuine matches and analysts drown in noise that was never a real hit. There is no setting that solves both problems at once. There is only an informed trade-off, made deliberately, rather than one made by accident and never revisited.

What this guide is not

Not a computer-science specification of exact scoring formulas, and not a claim that any single number here is precise to three decimal places on every possible input. Every technical figure is sourced to a real, current primary document, listed in this site's verification ledger, and flagged honestly where OpenSanctions itself gives a range rather than a fixed value.

The clearest illustration in the field

The 0.98 same-name problem

In a benchmark released in February 2026, researchers working with OpenSanctions' own large-scale labelled pair dataset surfaced a case that illustrates the ceiling of name-only matching better than any constructed example could. Two individuals appear on Pakistan's proscribed persons list under the identical name Khalid Mehmood. Same name, same country, same sanctions programme. They are not the same person: one record carries a national identity number ending 9885 and lists a father's name of Muhammad Khan, the other carries an ID ending 0181 and lists a father's name of Haji Muhammad. A standard rule-based matching baseline, the same category of algorithm many production screening systems still run today, scored this pair 0.98 out of a top score of roughly 1.0, functionally treating two different people as the same entity [5].

This is not a story about a badly built algorithm. Jaro-Winkler, Soundex, Levenshtein distance, whatever string-comparison method sits underneath a name-only matcher, none of them can resolve identity between two people who genuinely share a name, because the information needed to tell them apart was never encoded in the name string to begin with. That is exactly why OpenSanctions' newer algorithms don't stop at the name: name-qualified, logic-v2, and the regression-based approach all add a second identifying feature on top of the name score, a date of birth, a national ID, an address, precisely because the name alone runs out of resolving power once a name is common enough.

The fix is not a better string algorithm

No amount of refining how two name strings get compared changes the outcome of the Khalid Mehmood case, because both names are identical strings. The only thing that can distinguish these two people is a second, independent piece of identity: a date of birth, a national ID number, an address. That is the entire argument for qualified and regression-based scoring over name-only matching.

What actually happens when a query runs

The two-stage pipeline

Real production screening does not run a single calculation. It runs two distinct stages, and conflating them is the source of a lot of "why did this even fire" confusion among analysts.

Stage one is retrieval: a broad, deliberately high-recall search across the reference database that pulls in every plausible candidate, tuned to avoid missing anything, even at the cost of pulling in candidates that have no real chance of turning out to be a genuine match. Stage two is scoring: each retrieved candidate is compared against the query using whichever matching algorithm is configured, and assigned a numeric score [6]. Only candidates whose score crosses the configured threshold, 0.7 by default for OpenSanctions-backed screening, get surfaced to an analyst as a match [7].

The reason this two-stage shape matters in practice: a candidate that should never have been retrieved in stage one can still produce a specific-looking, confident score in stage two. An analyst who only ever looks at the final score has no way to tell the difference between "this is a strong match" and "this is a strong score on a candidate that shouldn't have made the candidate list in the first place." Understanding that a score is downstream of a retrieval decision, not a single self-contained judgement, is the first step toward being able to explain an alert rather than just react to it.

Not a solved layer underneath the algorithm

Transliteration and cross-script matching

Comparing a Latin-alphabet name against its Arabic, Cyrillic, or CJK rendering is a live, actively maintained engineering problem, not solved theory sitting quietly beneath whichever algorithm a screening tool advertises.

A concrete, dated example: OpenSanctions' underlying matching engine shipped a fix in May 2026 for a specific class of bug in cross-script candidate handling. Before the fix, a Latin-script candidate that already carried strong supporting evidence of a genuine match could still be silently outranked at the retrieval stage by an unrelated candidate whose name happened to render in Arabic, Cyrillic, or another script, purely because of how script-based weighting was calculated when candidates were first pulled in, not because the final score itself was wrong. The fix aligned that retrieval-stage weighting with how the scoring stage actually values evidence, so a same-script candidate with genuine supporting evidence is no longer pushed out by a same-name candidate in a different script that carries none [8].

The lesson for a practitioner is not the specific bug, which is already fixed. It is that transliteration and cross-script handling are not a fixed, solved layer sitting underneath whichever algorithm a vendor advertises. They are actively maintained logic that can and does change between versions, which is exactly why pinning a specific algorithm version matters for regulatory consistency: a compliance team that cannot say which version of the matching logic produced a given day's alerts cannot fully explain why alert volume shifted.

The everyday version of the same problem

Corporate suffix and patronymic noise

A more everyday version of the Khalid Mehmood problem shows up in two places: corporate suffixes and patronymics.

Corporate suffixes, LLC, OJSC, SA, Trading, Limited, and the like, are exactly the kind of variation a naive matcher can treat as a meaningful difference when it usually isn't one. "Global Trading Ltd" and "Global Trading OJSC" are, in the large majority of real cases, worth comparing as the same underlying entity name with an incorporation-type suffix attached, not two unrelated companies. Patronymics, name elements built from a parent's given name and common across Slavic, Arabic, and other naming conventions, create a related kind of noise: they look like meaningful, distinguishing name content to a naive matcher but often carry less weight in practice than the rest of the name.

Stripping this noise reduces false positives caused by suffix and patronymic variation, and current matching approaches do some version of it deliberately [9]. OpenSanctions' logic-v2 approach explicitly annotates and de-prioritises generic terms like these using curated reference data, rather than removing them outright [3]. Done carelessly, though, the same stripping logic can cut too deep and remove a part of a name that was genuinely doing distinguishing work, the same failure mode as the case study above, approached from the opposite direction: instead of a name-only score missing a real difference because two people share a common core name, an over-aggressive stripping rule can manufacture a false similarity by deleting the exact part of a name that would have told two entities apart.

The most practical section in this guide

Building a test set before changing anything in production

Everything covered so far points to the same practical conclusion: never change a threshold or an algorithm version based on a handful of recent alerts. Measure it first.

A golden test set is a fixed, held-out collection of known true-positive and true-negative name pairs, used specifically to check whether a proposed tuning change actually improves matching before it reaches production, not after. At minimum, a usable one should contain:

  • Known sanctioned names across multiple scripts, so a cross-script regression gets caught before it reaches production.
  • Common-name collision pairs, like the Khalid Mehmood case above, so name-only overconfidence gets caught rather than assumed away.
  • Real historical false positives from the reader's own alert history, because every screening population has its own recurring noise patterns a generic test set will not cover.
  • Deliberately difficult transliteration variants, chosen specifically to stress-test the matcher rather than flatter it.

The discipline this replaces is dangerous precisely because it feels reasonable in the moment: a threshold nudged up because analysts complained about noise last week, or nudged down because someone worried about a near miss, with no measurement of what that change actually does to the true-positive and false-positive rate against a fixed reference. A golden test set turns "I think this change helped" into something a compliance team can actually show a regulator.

See the two stages run on real examples

The Algorithm Pipeline Visualizer

Four name pairs, walked through both pipeline stages exactly as described above. Every score and feature value below is a fixed, pre-written illustration of how each pair is scored, not a live recalculation, so the numbers stay identical no matter how many times you replay them.

The Spelling Variant
Should match
Query
Mohammed Al-Rashid
vs
Candidate
Muhamad Al Rashed
Stage 1 · Retrieval
Pulled into the candidate set despite three separate spelling differences, because retrieval is deliberately high-recall.
✓ Retrieved
Stage 2 · Scoring
Jaro-Winkler name similarity0.89
Phonetic (Soundex) matchYes
Token order matchYes
Supporting identifiersNone provided
0.91
Above threshold, correctly matched
Three spelling differences (Mohammed/Muhamad, Al-Rashid/Al Rashed, hyphen vs space) are exactly what Jaro-Winkler and phonetic matching exist to absorb. This is fuzzy matching doing its intended job.

All four feature values above are fixed illustrative figures written to demonstrate how each stage reasons about a pair, based on the real mechanics described in the sections above. They are not live recalculations and will not change between visits.

Now you try

Three fresh name pairs. Predict match or no match before revealing the answer.

Running score 0 / 0
Aleksandr Volkov vs Alexander Volkov
Maria Santos (DOB 1990, Philippines) vs Maria Santos (DOB 1958, Portugal)
Al-Faisal Group LLC vs Al Faisal Group
Where this runs for real

FinCrimeRadar's own screening tool calls OpenSanctions' live /match/default endpoint, the same retrieval-and-scoring pipeline described above, not a simulation of it. Try a real name against it →

Four things to watch for

Four ways tuning goes wrong

The risk each pattern creates, the signal that tells you it's happening, and the response that actually fits.

👥
The Common-Name Collision
A name-only score can look decisive even when it is comparing two entirely different people who happen to share a common name.
Risk
A high-confidence-looking score gets treated as identity confirmation when it's only confirming the name string.
Signal
The score is high but no date of birth, national ID, or address was actually compared, only the name matched.
Response
Require a second identifying feature before treating a common name as confirmed, not a tighter name-string algorithm.
🌐
The Silent Script Swap
A genuine same-script match can lose ranking priority to an unrelated candidate that happens to render in a different script, before scoring ever runs.
Risk
A real match gets pushed down the candidate list at retrieval time and never reaches an analyst's screen at all.
Signal
Cross-script alert volume shifts after a matching-engine version change, with no change in the underlying watchlist.
Response
Pin and log the matching engine version in use, and re-run the golden test set's cross-script pairs after every upgrade.
✂️
The Over-Aggressive Strip
Stripping corporate suffixes and generic terms cuts false positives, right up until it strips a word that was actually doing distinguishing work.
Risk
Two genuinely different companies get merged into a false match because a generic-sounding word was treated as noise.
Signal
A new match fires between two entities whose only shared text is the exact word your stripping list just added.
Response
Test every addition to a stripping or noise-term list against the golden test set before it ships, not just the case that motivated it.
💬
The Anecdotal Retune
A threshold nudged because of a handful of recent alerts, with no measurement of what that change actually does to the true and false positive rate.
Risk
A change that felt reasonable in the moment silently shifts the real precision and recall trade-off with nobody able to say by how much.
Signal
A threshold or algorithm-version change went live with no before-and-after comparison against a fixed reference set.
Response
Run every proposed change against a golden test set first, and keep the before-and-after numbers as the record of why it shipped.
Common Questions

FAQ

Is a higher match score always more trustworthy than a lower one? +
Not on its own. A high score means the query matched strongly on whatever features the algorithm compared, and if the only feature available was the name, a high score can mean nothing more than "these two names are very similar strings," which the Khalid Mehmood case above shows can happen between two completely different people. A high score backed by a matching date of birth or national ID carries a different kind of confidence than a high score built from name similarity alone. Read what was actually compared, not just the final number.
Should our team default to a lower or higher match threshold than 0.7? +
There is genuine, ongoing disagreement in the industry on this, not a settled answer, and this guide won't pretend otherwise. Some practitioners argue for tuning toward fewer false positives, on the view that analyst attention is a scarce resource and alert fatigue itself causes real matches to get missed in the noise. Others argue for tuning toward fewer false negatives, on the view that a missed sanctions match carries a categorically worse downside than an extra few minutes of review time. Both are defensible institutional positions, not factual claims, and the right choice depends on a given team's risk appetite, alert volumes, and review capacity. What isn't defensible is picking either position without measuring the trade-off against a golden test set first.
Does a name-matching algorithm ever need to see a date of birth or ID number to work at all? +
Name-only matching works and is genuinely useful for a large share of screening traffic, most names aren't common enough to produce a collision like the case study above. The point isn't that name-only matching is broken, it's that name-only matching has a known, structural ceiling once a name is common enough, and no amount of string-algorithm refinement raises that ceiling. A second identifying feature is what raises it, which is exactly why qualified and regression-based approaches exist alongside name-only ones rather than replacing them outright.
Why would a screening tool's results change without anyone touching a threshold? +
Because the matching engine itself is actively maintained software, not a fixed formula. The cross-script ranking fix described above shipped in a routine engine release, not a threshold change, and it measurably shifted which candidates got prioritised for scoring. Any team relying on a hosted or managed screening provider should expect matching behaviour to shift periodically for reasons that have nothing to do with their own configuration, and should re-run their golden test set after any engine or algorithm-version upgrade, not just after their own configuration changes.
Is it enough to just pick the "best" algorithm OpenSanctions offers and stop thinking about tuning? +
No single algorithm removes the need for ongoing measurement. logic-v2 is the current recommended default and genuinely improves on older approaches in several dimensions, but "recommended default" is not the same as "correct for every population, every threshold, forever." A newer algorithm can still produce a collision like the case study above on a sufficiently common name, still needs a threshold set deliberately for a given team's risk appetite, and still benefits from being checked against a golden test set before and after any version change. The algorithm choice is one tuning decision among several, not a substitute for the discipline around all of them.
Knowledge check
Five questions. Feedback appears after each answer.
1. What does the 0.7 figure referenced throughout this guide actually represent?
2. In the Khalid Mehmood case study, what specifically did the 0.98 score demonstrate, and what actually fixes it?
3. Why does the two-stage retrieval-then-scoring pipeline matter for an analyst reading an alert?
4. What did the documented May 2026 cross-script matching fix actually change?
5. Why should a threshold or algorithm-version change be tested against a golden test set rather than judged on recent alerts alone?
0/5
Verification

Sources

Each numbered claim above is checked against the specific source below it. Figures without a bracketed number are illustrative examples rather than verified facts; the surrounding text says which.

  1. OpenSanctions, Matcher: Deprecated Matching Algorithms (name-based, name-qualified; Jaro-Winkler and Soundex, do not reach OFAC parity). opensanctions.org/matcher
  2. OpenSanctions, Matcher: ofac Algorithm (reverse-engineered from OFAC FAQ #249 and parity fixtures against the live public tool). opensanctions.org/matcher
  3. OpenSanctions, Matcher (logic-v2) and API: Tuning the Matching System (rule-based, cultural reference data, recommended for new integrations). opensanctions.org/matcher / opensanctions.org/docs/api/tuning
  4. OpenSanctions, Matcher: regression-v1, 18-feature table (name, phone, email, identifier, DOB, gender, country, address features), confirmed by manual count. opensanctions.org/matcher
  5. Konstantin, N. et al., OpenSanctions Pairs: Large-Scale Entity Matching with LLMs, arXiv:2603.11051, February 2026, Figure 1. arxiv.org/abs/2603.11051
  6. OpenSanctions, API: Tuning the Matching System (two-stage retrieval-then-scoring pipeline). opensanctions.org/docs/api/tuning
  7. OpenSanctions, API: Tuning the Matching System (0.7 default match-score threshold). opensanctions.org/docs/api/tuning
  8. opensanctions/yente, GitHub Releases, v5.4.0, 13 May 2026 (cross-script candidate ranking fix). github.com/opensanctions/yente/releases/tag/v5.4.0
  9. OpenSanctions, Changelog #32, API Service Backend Upgrades, effective 8 September 2025 (company suffix/prefix matching, cross-script retrieval). opensanctions.org/changelog
Continue Reading

Related topics