Skip to main content

sanctions-screening-benchmark

A reproducible, open benchmark for measuring how well a name-screening engine recovers sanctioned parties from varied name strings, and how well it discriminates against structurally similar strings that are not sanctioned.

Screening engines are usually assessed by asking whether they are deployed and configured. That question is nearly uninformative. This project asks a different one: against which specific classes of name variation does this engine fail?

  PERTURBATION          FAMILY             N   HITS   RECALL
  ----------------------------------------------------------
  invisible_char        adversarial       10      4   40.0%  <-- blind spot
  homoglyph             adversarial       20     10   50.0%
  double_collapse       adversarial       17     17  100.0%
  token_drop            benign            12      0    0.0%  <-- blind spot
  translit              benign            16     16  100.0%
  name_order            benign            20     20  100.0%

The headline recall figure is not the point. The class-wise profile is: it says which failure you have, which tells you who owns the fix. A miss on an adversarial class is an evasion exposure. A miss on a benign class is a data quality problem producing false negatives against ordinary customers.


Why this exists

Public sanctions enforcement records repeatedly describe institutions that had a screening program, had documented policies, and still failed, because a control that exists is not a control that works. There is no open, shared way to measure whether a given screening configuration actually catches the variation it will encounter. Vendors publish accuracy claims against undisclosed test sets. Institutions test against their own historical alerts, which are by construction the cases their current configuration already catches.

This benchmark is an attempt at a common, inspectable, reproducible alternative.

Design commitments

No list data is redistributed. The benchmark fetches the current sanctions list from the publishing authority at generation time. A generated benchmark records the retrieval timestamp and endpoint in its manifest, so a result is always attributable to a specific published list state. See docs/LICENSING.md.

Fully deterministic. Every perturbation is a pure function of (input, seed). Two people running the same command against the same list state get byte-identical benchmarks.

Zero dependencies. Pure Python 3.9+ standard library, including the Jaro-Winkler implementation. It runs on a clean interpreter with no install step, which matters because screening people work on locked-down machines.

Negative controls are mandatory. A benchmark that measures only recall is trivially gamed by an engine that matches everything. Precision is measured against generated near-miss strings, with an explicit statement of what that number does and does not mean.

Honest about its limits. See Limitations. The precision figure is not a production alert-volume estimate and is never presented as one.

Adopters

See ADOPTERS.md. Using this benchmark? Open a PR to add your organisation.

Install and run

From source (no install):

git clone https://github.com/Divine16/sanctions-screening-benchmark
cd sanctions-screening-benchmark

# See every perturbation class and what it models
PYTHONPATH=src python3 -m ssb.cli classes

# Run end-to-end against the bundled synthetic fixture, no network needed
PYTHONPATH=src python3 -m ssb.cli demo

# Build a benchmark from the live OFAC SDN list
PYTHONPATH=src python3 -m ssb.cli generate --limit 500 --out benchmark.json

# Score the reference matcher against it
PYTHONPATH=src python3 -m ssb.cli evaluate benchmark.json --threshold 0.85

From PyPI (once published):

pip install sanctions-screening-benchmark
ssb classes
ssb demo
ssb generate --limit 500 --out benchmark.json
ssb evaluate benchmark.json --threshold 0.85

Or install this repo editable:

pip install -e .
ssb demo

Run the tests:

python3 -m unittest discover -s tests -t .

Scoring your own engine

Any callable of the shape (query: str, candidate: str) -> float in [0, 1] can be scored. See examples/evaluate_your_engine.py.

from ssb.benchmark import Benchmark, score, format_scorecard

def my_engine(query: str, candidate: str) -> float:
    return your_screening_api.similarity(query, candidate)

bench = Benchmark.from_json("benchmark.json")
print(format_scorecard(score(bench, my_engine, threshold=0.85, matcher_name="acme-v4")))

Nothing leaves your machine. The benchmark is a local JSON file and the harness makes no network calls during scoring.

The perturbation classes

Eighteen classes across three families. Full rationale and sourcing in docs/METHODOLOGY.md.

Benign, innocent variation

Class Models
translit Competing romanisations (Muhammad / Mohammed / Mohamad)
diacritics_strip Diacritics lost in transit (José to Jose)
diacritics_expand German-style expansion (Müller to Mueller)
name_order Given/family order reversal; comma-delimited source fields
token_drop Missing middle name, patronymic, or nasab element
particle Particle handling (al-Zawahiri / al Zawahiri / Zawahiri)
initials Given names reduced to initials
title_affix Honorifics and generational suffixes
punctuation Hyphen, apostrophe and spacing variation
case_fold Casing differences

Adversarial, deliberate evasion

Class Models
homoglyph Cyrillic codepoints that render identically to Latin
invisible_char Zero-width and non-breaking characters inside tokens
vowel_drop Internal vowels removed; still humanly legible
double_collapse Repeated letters collapsed or doubled
phonetic Phonetically neutral respelling

Degraded, upstream system loss

Class Models
ocr Known OCR confusion pairs from document capture
typo Keyboard-adjacency substitution
transpose Adjacent character transposition
truncate Fixed-width field truncation in legacy systems

Limitations

Read these before citing any number this tool produces.

  1. Negative controls are synthetic. They measure discrimination against structurally adjacent strings. They do not estimate a production false-positive rate, which depends on the customer-name distribution an institution actually sees.

  2. Names only. Real screening decisions use date of birth, nationality, document numbers, and address. This benchmark models the name-matching layer in isolation. An engine that scores poorly here may perform acceptably in production with strong secondary identifiers, and the reverse.

  3. The common-name problem is out of scope. Where a listed name is also borne by millions of unlisted people, the discriminating work is done by secondary identifiers, not by string matching. This benchmark does not measure it.

  4. Perturbation realism is argued, not measured. The classes are grounded in documented naming conventions, published OCR confusion sets, and known Unicode evasion techniques. What is not established is the relative frequency of each class in live data. Do not read the class weights as a risk ranking. See docs/METHODOLOGY.md.

  5. The reference matcher is a baseline, not a recommendation. It exists so the benchmark produces a number without a commercial licence.

Contributing

See CONTRIBUTING.md. Additional perturbation classes are the most useful contribution, especially ones grounded in a citable naming convention or a documented evasion technique. Coverage of non-Latin scripts is currently the weakest area.

Licence

Code: MIT. See LICENSE. Sanctions list data is not redistributed; see docs/LICENSING.md.

Citation

See CITATION.cff.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sanctions_screening_benchmark-0.1.0.tar.gz (26.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sanctions_screening_benchmark-0.1.0-py3-none-any.whl (24.6 kB view details)

Uploaded Python 3

File details

Details for the file sanctions_screening_benchmark-0.1.0.tar.gz.

File metadata

File hashes

Hashes for sanctions_screening_benchmark-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ee4ae984b5af968bcb85e8abd29092b3b945fe0154663043f6b4a72afccb984b
MD5 e53e8ca467eef9679c35e01c327ff690
BLAKE2b-256 83bc5d096d29ca2fd2783b285cb167293905330a75fafa77d9ec9701ad76dce6

See more details on using hashes here.

File details

Details for the file sanctions_screening_benchmark-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sanctions_screening_benchmark-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 37cb1eebd2a887f2253ae83c8a4eb9af6b8cc420eff242f9c584b96d3bf468d1
MD5 15ce580b8bd5b85b2651042ac934f9d7
BLAKE2b-256 2349f4018072a9c371ecb7883b38ba045295b310c562a68d477ee65e40d22245

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page