Synthetic AML transaction dataset with labelled typologies and ground truth
A bank's customers, accounts and transactions, with the laundering typologies labelled, the rule-based alerts raised, and the cases and SARs that followed.
A bank's first six months: 300 customers, their accounts, tens of thousands of transactions and a set of launderers whose behaviour follows named typologies (structuring, mules, layering, round-tripping and more). Rule-based alerts fire with real false positives, and investigators turn some of them into cases and suspicious activity reports. The labels are the ground truth, so you can score a detector honestly.
- anti-money-laundering
- AML
- fraud detection
- ground truth
- fintech
- machine learning
Explore
Every table, profiled
Each column's type, spread, empties and most common values, measured from the CSVs in the download. Switch to the first rows to see the data exactly as it sits in the file.
transactions.csv
42,203 rows · 15 columns · 3 foreign keys
- card_purchase21%
- supplier_payment13%
- transfer_out13%
- transfer_in12%
- customer_receipt7.7%
- 9 more values · 34%
- debit62%
- credit38%
- online37%
- card27%
- mobile15%
- wire11%
- branch5.1%
- 1 more value · 3.8%
- USD100%
- United States99%
- Russia<0.1%
- Spain<0.1%
- Canada<0.1%
- Panama<0.1%
- 25 more values · 0.6%
- cash_commingling63%
- mule_funnel14%
- dormant_reactivation9.5%
- structuring8.3%
- layering3.6%
- 2 more values · 1.0%
- R0001141%
- R0000522%
- R000127.0%
- R000045.4%
- R000074.3%
- 10 more values · 20%
Keys
Every join resolves
8 foreign keys, 111,978 references checked against the table each one points at. None points at a row that does not exist.
accounts
- customer_idcustomers.customer_id487 rows checked0 orphans
alerts
- customer_idcustomers.customer_id84 rows checked0 orphans
- account_idaccounts.account_id84 rows checked0 orphans
cases
- customer_idcustomers.customer_id38 rows checked0 orphans
- first_alert_idalerts.alert_id38 rows checked0 orphans
transactions
- account_idaccounts.account_id42,203 rows checked0 orphans
- customer_idcustomers.customer_id42,203 rows checked0 orphans
- counterparty_idcounterparties.counterparty_id26,841 rows checked0 orphans
Checks
What we checked
- 8 foreign-key relationships checked, 0 orphaned rows
- No row is dated before the row it belongs to
- 15 of 300 customers (5.0%) are launderers, and the rate asked for is the rate in the data
- 726 suspicious transactions, each labelled with its typology (cash_commingling, dormant_reactivation, layering, mule_funnel, round_tripping, structuring, trade_based) so a detector can be scored against the truth
- Alerts are rule-based, not oracles: 60% of 84 alerts are true positives and the rest are false alarms
- 19 of 38 cases end in a filed suspicious activity report, and every one is filed after its case opened
- Structuring is visible: 44% of the 57 structured cash deposits sit just under the 10,000 reporting threshold
- Honest payments favour a few counterparties: the busiest 20% are on 63% of them, and every salaried customer is paid by one employer
The same checks ship in the zip as INTEGRITY.txt, so you can rerun them in any SQL engine. How we verify
Questions, answered
Worth asking it
Each answer is the query's result on these exact files. Open one for its SQL and rows, then check your own query against it.
1What precision and recall does a simple amount-threshold rule reach against the typology labels?Flagging amounts of $10,000 or more: precision 16.1%, recall 6.1%.
with thresholds as ( select 5000 as threshold union all select 9000 union all select 10000 union all select 50000 ) select t.threshold, sum(case when x.amount >= t.threshold then 1 else 0 end) as flagged, sum(case when x.amount >= t.threshold and x.is_suspicious then 1 else 0 end) as true_hits, round(100.0 * sum(case when x.amount >= t.threshold and x.is_suspicious then 1 else 0 end) / nullif(sum(case when x.amount >= t.threshold then 1 else 0 end), 0), 1) as precision_pct, round(100.0 * sum(case when x.amount >= t.threshold and x.is_suspicious then 1 else 0 end) / sum(case when x.is_suspicious then 1 else 0 end), 1) as recall_pct from thresholds t cross join transactions x group by t.threshold order by t.thresholdthreshold flagged true_hits precision_pct recall_pct 5,000 927 135 14.6 18.6 9,000 347 71 20.5 9.8 10,000 274 44 16.1 6.1 50,000 13 10 76.9 1.4 2Which typology is hardest to catch with rules alone?layering is caught least: 10 of 12 customers using it drew a true alert.
with launderers as ( select distinct customer_id, typology from transactions where is_suspicious ), alerted as ( select distinct customer_id from alerts where is_true_positive ) select l.typology, count(*) as customers, sum(case when a.customer_id is not null then 1 else 0 end) as alerted, round(100.0 * sum(case when a.customer_id is not null then 1 else 0 end) / count(*), 1) as detected_pct from launderers l left join alerted a on a.customer_id = l.customer_id group by l.typology order by detected_pct, customers desctypology customers alerted detected_pct layering 12 10 83.3 mule_funnel 3 3 100 structuring 3 3 100 dormant_reactivation 2 2 100 cash_commingling 2 2 100 trade_based 1 1 100 round_tripping 1 1 100 3How many alerts does an investigator work for each true launderer found?84 alerts find 22 of the 24 customers in suspicious activity: 3.8 alerts for each one found.
select count(*) as alerts, sum(case when is_true_positive then 1 else 0 end) as true_alerts, count(distinct case when is_true_positive then customer_id end) as launderers_found, (select count(distinct customer_id) from transactions where is_suspicious) as customers_in_suspicious_activity, round(1.0 * count(*) / count(distinct case when is_true_positive then customer_id end), 1) as alerts_per_launderer_found from alertsalerts true_alerts launderers_found customers_in_suspicious_activity alerts_per_launderer_found 84 50 22 24 3.8 4Do structured cash deposits cluster in the days before a case opens?Structured deposits by when they fell against their customer's case: 8-30 days before 25, 0-7 days before 25, after the case opened 10.
with d as ( select extract(epoch from (c.opened_at - t.txn_at)) / 86400 as days_before from transactions t join cases c on c.customer_id = t.customer_id where t.typology = 'structuring' ) select case when days_before < 0 then 'after the case opened' when days_before <= 7 then '0-7 days before' when days_before <= 30 then '8-30 days before' when days_before <= 90 then '31-90 days before' else 'over 90 days before' end as timing, count(*) as deposits from d group by 1 order by min(days_before) desctiming deposits 8-30 days before 25 0-7 days before 25 after the case opened 10
Every query runs on the files in the download as they are. ANSWERS.sql holds all of them; run it in DuckDB from the dataset's folder.
Full version · $5
This sample, or the full AML Transaction Monitoring
A UAE bank's whole trail from KYC to STR, with the laundering nobody caught. Try its free preview before you decide.
| This sample | AML Transaction Monitoring | |
|---|---|---|
| Rows | 44,612 | 11,711,393 |
| Tables | 6 | 16 |
| Columns | 63 | 172 |
| Formats | CSV | CSV |
| Audit | Keys and the checks listed above | 129 checks, all listed |
| Licence | CC0, free | Commercial use, $5 once |
| Built for | anti-money-laundering, AML, fraud detection, ground truth | Transaction monitoring and model benchmarks, Alert triage and false positives, Case management and STR workflow, Targeted financial sanctions, Drift and robustness, Graph and network detection, KYC, beneficial ownership and screening, A bank that lives in the UAE, Teaching and demos |
Use it
Take it, or make one shaped like yours
Rebuild it byte for byte
The zip includes RECIPE.json: the Studio pack, the overrides and seed 20260921 that made these exact rows.
Your own tables, your own numbers
Describe the data you need in Studio and get connected tables with the totals you state, profiled like this and checked before you download.
The full version, and what goes with it
All premium- transactions, rows per monthMay 2025 – Oct 2026
AML Transaction Monitoring
$5launch priceOne UAE bank, 10,000 customers, eighteen months
A UAE bank's whole trail from KYC to STR, with the laundering nobody caught
Answer key
- is_suspicious
- typology
- ring_id
- +7 more
- Rows
- 11,711,393
- Tables
- 16
- Checks
- 129 of 129
Free previewProfile and preview Card spending each month, debit and credit
2,500 US households at five banks and seven card issuers, two years
Five banks' raw statement lines, with every label behind them
Answer key
- description
- merchant_name
- category
- +6 more
- Rows
- 7,627,186
- Tables
- 14
- Checks
- 50 of 50
Free previewProfile and previewWeekly sales, at regular price and on promotion
Grocery chain, 10 stores, fiscal 2024 and 2025
Two fiscal years of a grocery chain, from the shelf to the receipt
Answer key
- baseline_units
- price_effect
- promo_lift
- +7 more
- Rows
- 2,790,852
- Tables
- 14
- Checks
- 117 of 117
Free preview18-question projectProfile and preview
Related free datasets
All 12- Credit risk portfolio3,000 borrowers and 4,200 commercial loans, where a credit rating actually predicts whether the loan defaults, at the rate S&P's own data says it should.7,200 rows · 2 tables
- Factory sensors with failure labelsA year of hourly sensor readings from six machines, with the degradation stage, remaining useful life, and the failures and work orders they lead to.52,669 rows · 5 tables
- Retail star schemaA dimensional model you can actually practise joins on: one fact table, four dimensions, 47,051 rows, every join resolving and every total known.47,051 rows · 5 tables
- Machine degradation100 machines monitored from commissioning to failure, with an exact remaining-useful-life label on every one of 23,118 readings.46,336 rows · 3 tables
- Ecommerce storefrontA year of orders across five joined tables, with best sellers, heavy buyers, a real Q4 peak and totals that reconcile to the cent.49,493 rows · 5 tables
- B2B SaaS subscription analyticsAccounts, seats, MRR, churn and support load, where company size actually drives the plan.42,240 rows · 5 tables
Fully synthetic. No real person, company or transaction is represented, and no production data was read to make it. CC0: use it anywhere, no attribution needed.

