The OFAC SDN list is public data. If your software touches counterparties, screening names against it is something you can prototype in an afternoon. This post does exactly that, then looks honestly at where the afternoon version stops being enough.
Get the list
OFAC publishes the SDN in several formats. The plain CSV is a few megabytes:
import urllib.request
urllib.request.urlretrieve(
"https://www.treasury.gov/ofac/downloads/sdn.csv", "sdn.csv")
The file has no header row. Columns of interest: entry id, name, type, program, and remarks. Names arrive upper-cased, sometimes reversed ("SMITH, John"), sometimes with aliases folded into separate AKA records.
The naive version
import csv
def load_names(path):
with open(path, newline='', encoding='utf-8', errors='replace') as f:
for row in csv.reader(f):
if len(row) > 1 and row[1]:
yield row[1].strip().upper()
names = set(load_names("sdn.csv"))
def is_listed(name):
return name.strip().upper() in names
print(is_listed("SMITH, John")) # True only on an exact string hit
This works as a demo and fails as screening. Exact string equality misses "John Smith" vs "SMITH, John", every transliteration variant, every typo, and every alias.
One step better: normalized fuzzy matching
import re
from rapidfuzz import fuzz
def norm(s):
s = re.sub(r'[^A-Z ]', '', s.upper())
return ' '.join(sorted(s.split()))
candidates = {norm(n) for n in names}
def score(name, threshold=90):
n = norm(name)
best = max(((fuzz.token_set_ratio(n, c), c) for c in candidates),
key=lambda x: x[0], default=(0, None))
return best if best[0] >= threshold else (0, None)
Normalization (case, punctuation, token order) plus a token-set ratio catches reorderings and small variants. RapidFuzz makes a full scan of the list fast enough for interactive use.
Where DIY breaks
- Thresholds are a judgment call with compliance consequences. 90 misses things 85 catches; 85 floods you with false positives on common names.
- The SDN is not one flat name list: aliases, weak aliases, vessels, aircraft, and address data all carry signal the CSV's second column does not.
- The list changes. Entries are added and removed, and the file you downloaded last month is stale. Your screen needs the download date attached to every result, or you cannot say what you actually checked.
- The SDN is one list. UK, EU, and UN lists are separate publications with separate formats.
- A match is not a verdict. Common names collide with listed persons constantly; real screening ends in a human or a well-documented resolution step.
The prototype is worth building: it teaches exactly what a screening result must carry (list version, match score, matched alias) and why "we checked OFAC" without a retrieval date is not a check.
This article was generated by an AI system.
Top comments (0)