Skip to main content

Private work · Request a walkthrough

ComplianceGuard, anti-money-laundering monitoring for Kenya

A pre-pilot platform that turns M-Pesa-style payments into holds, cases and drafted reports under Kenyan anti-money-laundering law.

Role
Engineer
Period
Mar 2026 to Oct 2026
Stack
Python, FastAPI, PostgreSQL, Redis, Kafka, scikit-learn, River, Next.js, Docker

Problem#Link to this section

Kenyan banks, SACCOs and payment providers must spot money laundering in fast, high-volume mobile-money traffic, then report it to the Financial Reporting Centre within strict deadlines. Off-the-shelf tools are built for card and wire data. They don't model M-Pesa patterns such as agent cash-outs or split transfers, and they don't follow Kenyan law, POCAMLA, out of the box.

Constraints#Link to this section

  • The law sets the deadlines. A suspicious-activity report is due within 3 days of a hold.
  • Rules must stay in charge. FATF guidance says a machine-learning score can't be the only reason to report someone.
  • Personal data is regulated. The Kenya Data Protection Act 2019 limits who can see national ID numbers and phone numbers.
  • Every decision must be auditable, by an examiner who reads the trail months later.

Approach#Link to this section

How one payment is monitoredA payment from M-Pesa, CSV or core banking is normalised and ingested through Kafka. 35 typology rules run, alongside behavioural, velocity and anomaly scores from Isolation Forest and River. Sanctions, PEP and other context is added, and the engine scores are fused into one weighted composite score, with four advisory engines recorded at weight 0. Above the threshold a hold is created with a drafted SAR, and an analyst works it through case management with four-eyes approval.Payment eventM-Pesa, CSV or core banking, via KafkaTypology rules35 rules in a versioned registryBehaviour and anomalyvelocity, Isolation Forest, RiverComposite scoreweighted fusion; advisory ML at weight 0Hold and SAR draftcase, analyst, four-eyes approvalContext: sanctions, PEP and adverse-media screening.Every decision is written to a hash-chained audit ledger.
One payment, from event to hold. The rules carry the decision; machine-learning scores only add to it.

Payments arrive from M-Pesa, CSV files or core banking. A normaliser turns each into one canonical shape, and Kafka carries it to detection. There, 35 typology rules check for known laundering patterns, such as structuring a large sum into many small transfers.

Behaviour and velocity profiles in Redis add context, and an Isolation Forest plus a River online model add an anomaly score. A composite scorer fuses the engine scores with reviewed weights. Some outcomes can't be tuned away: a strong sanctions match always escalates, and a politically exposed person is always treated as high risk.

Above the hold threshold, the system creates a hold, opens a case and drafts the report. An analyst investigates, and a second person must approve before anything is filed.

Evaluation#Link to this section

There's no real-world accuracy figure, and I don't claim one. No institution's transaction data has gone through the system. The rules and scoring are tested against a synthetic M-Pesa-style dataset with all 35 typologies planted in it, which shows the logic works as designed, not how often it would catch real laundering.

Measured results with the data each was measured on and its caveat
MetricValueMeasured onCaveat
Typology rules35Versioned JSON registry, exercised by a synthetic M-Pesa-style generator (seed 42)Synthetic data only. The rules have never scored a real institution's transactions.
Sanctions names screened against285,503Live OFAC SDN, UN Consolidated and OpenSanctions downloads on 2026-10-20, after deduplicationOne full download, checked against a SHA-256 manifest. There's no incremental update feed yet.
PEP names screened against835,168OpenSanctions PEP list downloaded on 2026-10-20, after deduplicationOpenSanctions data is free for non-commercial use only; a commercial pilot needs a licence.
Test filesabout 240Unit and integration suites, with a 90% coverage gate in CITests run in-process and on SQLite. Several database paths have never run on real infrastructure.

Results#Link to this section

The full analyst journey runs end to end in a one-command demo: a payment comes in, raises an alert and a hold, becomes a case, gets a drafted report and passes four-eyes approval. The sanctions and PEP screening lists were synced from live public sources on 2026-10-20.

Security#Link to this section

Personal identifiers go into a reversible vault encrypted with AES-GCM. Revealing one needs a stated purpose and leaves an audit record, and keys can be rotated or erased. Logs scrub national IDs, phone numbers and names automatically.

Decisions are written to an append-only audit ledger in which each entry carries the hash of the one before, so a changed record breaks the chain. An API gateway checks a signed token and a role on every call, and rate-limits each client. Production settings fail closed when a required secret is missing.

Limits#Link to this section

  • It's a pre-pilot build, not a production deployment. It has never run on real infrastructure or real customer data.
  • The Postgres audit triggers and the last three database migrations have never run on a live Postgres.
  • National ID and credit bureau checks run against mocks; real access needs agreements with those bodies.
  • Live filing with the Financial Reporting Centre has never been tried with institution credentials.
  • The graph engine isn't live, so its score is missing and its weight is spread across the other engines.

What I'd do next#Link to this section

  • Bring the stack up on a real Postgres and Docker host, and prove the audit triggers refuse updates and deletes.
  • Run a shadow pilot on one institution's historical data, to measure real alert rates and false positives.
  • Replace full watchlist downloads with incremental updates.
  • Bring the graph engine live, so related accounts count towards risk.

Evidence

  • Typology registry and synthetic data generator (Private work)
  • Watchlist sync manifest (2026-10-20) (Private work)
  • Test suite and coverage gate (Private work)