Back to home

Screening platform

Sanctions and PEP screening for regulated entities

Role
Full-Stack Developer
Status
Delivered

The problem

Peruvian regulated entities — currency exchanges, notaries, real estate firms, fintechs — are required by the Financial Intelligence Unit to screen every client against sanctions and politically-exposed-person lists before doing business, and to retain evidence of that check. In practice that meant opening each source one by one (OFAC SDN, OFAC Consolidated, UK OFSI, the EU consolidated list, the UN list, plus Peruvian electoral registers and comptroller asset declarations), searching the name in each, and saving screenshots to a folder. The platform reduces that to one query by name or ID that crosses the five international lists, the Peruvian PEP master and each client’s own private lists, with automatic source synchronisation and API access so screening can be embedded in another system’s onboarding.

Decisions

01

The evidence is the product, not the search

To a regulator the question is not "is this person sanctioned?" but "what did you check on 14 March, and against which version of OFAC?". So every query is persisted to an audit trail with the normalized name, the result, and the exact version of the list it ran against. Synchronisers version each download instead of overwriting, so a search from six months ago remains reconstructible against the list as it stood that day.

audit schema            → version per match
synchronisers            → version each download, never overwrite

  query(14-Mar) ──► result ──► the OFAC version of that day
                              (reconstructible six months later)
02

Deterministic matching over fuzzy scoring

A 0.87 similarity threshold cannot be defended in an audit; a written rule can. Matching is deterministic and explainable: Unicode normalisation, identifiers that preserve leading zeros — Peruvian national IDs often start with 0 and parsing to an integer destroys them — and name variants covering paternal/maternal surname order, which differs between sources.

// normalization utilities
normIdentifier()         // preserves leading zeros: "07654321" ≠ 7654321
generateNameVariations() // paternal/maternal surnames swap between sources
03

One adapter per source, not a generic parser

Five XML feeds with schemas that look nothing alike and change without notice. Each source has its own adapter and mapper with its own tests, so a source that breaks format breaks one file rather than the whole pipeline. A generic parser would have been shorter to write and more fragile on every OFAC update.

The product

One query crosses every source at once. Each match carries its official source and the date of the list version it ran against: that is the evidence the regulator asks for.
Multi-tenant dashboard with the sync status of every source.
A search with no matches is persisted too: in compliance, proving you checked and found nothing is worth as much as finding something.
Per-tenant access: each regulated entity sees only its own queries and its own private lists.

Figures

5
international sanctions lists cross-checkedOFAC SDN · OFAC Consolidated · UK OFSI · EU · UN
+1
Peruvian PEP master (electoral + comptroller data)Project description
1
query replaces the source-by-source searchProject description
API
to embed screening in third-party onboardingProject description

What it demonstrates

SkillEvidence
Judgement in a regulated domainVersioned audit trail per query: the evidence is the deliverable, not the search result
Matching designDeterministic, explainable rules instead of a similarity score no auditor would accept
Integration architectureOne adapter per source with its own tests: a broken feed does not take down the pipeline
Local domain detailLeading zeros in Peruvian national IDs · variable surname order across sources
Full-stack deliveryMulti-tenant, containerised with Docker, consumable over an API

Stack

  • Next.js
  • NestJS
  • MongoDB
  • Docker
  • Multi-tenant
  • Public API