Pack contents
In short
Section titled “In short”These are the rules that ship with SpeculaPro today: 24 in core 1.4.1, 11 in fraud 1.4.0,
14 in gh-psp 1.2.0 and 10 in gh-bank 1.1.0. Four of them (I1, I2, I3 and E4) are in both
gh-psp and gh-bank, so there are 55 different rules. The values below are the pack defaults,
exactly as installed in a cedi workspace (core and fraud install
other amounts in a workspace
whose base currency is one of nine others). Every rule arrives switched off.
Fifteen more packs follow a jurisdiction’s rules and have their own pages:
| Pack | Version | Rules | Page |
|---|---|---|---|
global-fatf |
1.2.0 | 8 | Pack contents: FATF baseline |
ng-psp, ng-bank |
1.2.0 | 9 and 8 (15 different) | Pack contents: Nigeria |
uemoa-emi, uemoa-bank |
1.1.0 and 1.0.0 | 8 and 8 (15 different) | Pack contents: UEMOA |
sl-psp, sl-bank |
1.0.0 | 8 and 8 (14 different) | Pack contents: Sierra Leone |
lr-psp, lr-bank |
1.0.0 | 9 and 8 (14 different) | Pack contents: Liberia |
gm-psp, gm-bank |
1.0.0 | 8 and 8 (14 different) | Pack contents: The Gambia |
gn-emi, gn-bank |
1.0.0 | 8 and 8 (14 different) | Pack contents: Guinea |
ke-psp, ke-bank |
1.0.0 | 12 and 9 (18 different) | Pack contents: Kenya |
Some of their rules reuse a rule on this page with other numbers, by reference. All nineteen packs together hold 167 different rules. The values in the Sierra Leone, Liberia, Gambia, Guinea and Kenya packs are unverified research, marked to verify with counsel.
- The 33 post-event rules run seconds after each transaction and raise alerts.
- The 13 real-time rules (all of
fraud, plusgh.sim_swap_payoutandgh.kyc_tier_limit_realtime) run inside a real-time decision and answerblock,revieworstep_up. Each hit also raises a fraud alert. They do nothing for transactions sent only through the ingest API. - The 9 scheduled rules run on a timetable over the stored transaction history and raise alerts. See Scheduled rules.
Amounts are in your base currency (cedis for Ghanaian workspaces). Time windows over 48 hours are rounded to whole days. See the features reference.
All rules at a glance
Section titled “All rules at a glance”| Key | Library | Name | Pack | Mode · action | Alert about | Severity · score | Lists |
|---|---|---|---|---|---|---|---|
core.large_transaction |
A1 | Large single transaction | core |
post-event · alert | customer | medium · 40 | — |
core.large_cash |
A2 | Large cash transaction (CTR candidate) | core |
post-event · alert | customer | low · 20 | — |
core.large_cross_border |
A3 | Large cross-border transfer | core |
post-event · alert | customer | medium · 35 | — |
core.structuring |
B1 | Repeated deposits just below a threshold | core |
post-event · alert | customer | high · 70 | — |
core.value_spike |
C2 | Spending spike against the customer’s own history | core |
post-event · alert | customer | medium · 45 | — |
core.dormant_reactivation |
C3 | Dormant account suddenly active | core |
post-event · alert | customer | medium · 45 | — |
core.new_account_high_value |
C4 | New account receiving high value | core |
post-event · alert | customer | high · 60 | — |
core.pass_through |
C5 | Rapid pass-through of funds | core |
post-event · alert | customer | high · 65 | — |
core.fan_in |
D1 | Many senders into one account | core |
post-event · alert | customer | high · 60 | — |
core.fan_out |
D2 | One account paying many recipients | core |
post-event · alert | customer | medium · 50 | — |
core.many_payers_one_counterparty |
D7 | Many of our customers paying one counterparty | core |
post-event · alert | counterparty | high · 65 | — |
core.high_risk_jurisdiction |
E1 | High-risk jurisdiction | core |
post-event · alert | customer | high · 60 | high_risk_countries |
core.sanctioned_party |
F1 | Sanctioned party involved | core |
post-event · alert | customer | critical · 100 | — |
core.pep_threshold |
F2 | PEP transaction above threshold | core |
post-event · alert | customer | high · 70 | — |
core.high_risk_customer |
F3 | High-risk customer, large transaction | core |
post-event · alert | customer | medium · 50 | — |
core.high_risk_mcc |
H4 | High-risk merchant category | core |
post-event · alert | customer | medium · 40 | high_risk_mcc |
core.small_deposits_large_withdrawal |
B3 | Many small deposits, then one large withdrawal | core |
post-event · alert | account | high · 65 | — |
core.round_amounts |
B4 | Repeated round-figure payments in | core |
post-event · alert | account | medium · 45 | — |
core.same_amount_many_recipients |
B5 | Same amount sent to many recipients | core |
post-event · alert | customer | medium · 50 | — |
core.count_spike |
C1 | Transaction count spike against the account’s own history | core |
post-event · alert | account | medium · 40 | — |
core.mule_fan_in_fan_out |
D3 | Collect from many, pay out to many (mule account) | core |
scheduled · alert | account | high · 75 | — |
core.circular_flow |
D4 | Money going round in a circle | core |
scheduled · alert | account | high · 70 | — |
core.counterparty_confirmed_case |
D6 | Counterparty linked to a confirmed case | core |
post-event · alert | customer | high · 70 | — |
core.declared_profile |
F5 | Activity above the declared profile | core |
scheduled · alert | customer | medium · 50 | — |
fraud.card_testing |
H1 | Card testing: many small card payments | fraud |
real-time · block | customer | high · 80 | — |
fraud.new_device_new_payee |
H2 | New device paying a new payee a large amount | fraud |
real-time · review | customer | high · 70 | — |
fraud.failed_auth_then_payout |
H3 | Payout after repeated failed OTP or PIN | fraud |
real-time · review | customer | high · 65 | — |
fraud.profile_change_then_payout |
H5 | Payout soon after a profile or credential change | fraud |
real-time · review | customer | high · 70 | — |
fraud.new_country |
E2 | First transaction in or to a new country | fraud |
real-time · step-up | customer | medium · 40 | — |
fraud.impossible_travel |
E3 | Impossible travel | fraud |
real-time · review | customer | high · 70 | — |
fraud.shared_device |
D5 | Device shared by many customers | fraud |
real-time · review | customer | high · 60 | — |
fraud.payout_burst |
C7 | Burst of payouts | fraud |
real-time · step-up | customer | medium · 50 | — |
fraud.sanctioned_party |
F1-RT | Sanctioned customer or counterparty | fraud |
real-time · block | customer | critical · 100 | — |
fraud.deny_listed_counterparty |
F4 | Counterparty on your deny list | fraud |
real-time · block | customer | high · 90 | blocked_countries, blocked_mcc |
fraud.unusual_hour |
C6 | Payout at an hour this customer never uses | fraud |
real-time · step-up | customer | medium · 40 | — |
gh.sim_swap_payout |
G3 | Payout soon after a SIM swap | gh-psp |
real-time · block | customer | critical · 85 | — |
gh.kyc_tier_limit_realtime |
A4-RT | Decline above KYC tier limit | gh-psp |
real-time · block | customer | medium · 50 | — |
gh.kyc_tier_single_limit |
A4 | Transaction above KYC tier limit | gh-psp |
post-event · alert | customer | medium · 45 | — |
gh.kyc_tier_daily_limit |
A5 | Daily outflow above KYC tier limit | gh-psp |
post-event · alert | customer | medium · 45 | — |
gh.split_cash_in_agents |
B2 | Cash-in split across many agents | gh-psp |
post-event · alert | customer | high · 70 | — |
gh.cash_out_after_cash_in |
G2 | Cash-out right after cash-in | gh-psp |
post-event · alert | customer | high · 60 | — |
gh.wallet_sweep_to_bank |
G4 | Wallet swept to bank after many inbound payments | gh-psp |
post-event · alert | customer | high · 60 | — |
gh.reversal_abuse |
G5 | Repeated reversals or refunds | gh-psp |
post-event · alert | customer | medium · 45 | — |
gh.night_large_momo |
G6 | Large mobile money transfer at night | gh-psp |
post-event · alert | customer | medium · 40 | — |
gh.agent_peer_anomaly |
G1 | Agent cash-in volume far above peer agents | gh-psp |
scheduled · alert | agent | medium · 55 | — |
gh.merchant_turnover |
I1 | Merchant takings far above declared turnover | gh-psp and gh-bank |
scheduled · alert | customer | medium · 55 | — |
gh.merchant_refund_ratio |
I2 | Merchant with many refunds or chargebacks | gh-psp and gh-bank |
scheduled · alert | customer | medium · 50 | — |
gh.personal_business_volume |
I3 | Personal account trading like a business | gh-psp and gh-bank |
scheduled · alert | customer | medium · 45 | — |
gh.border_region |
E4 | Activity in a border or high-risk area | gh-psp and gh-bank |
post-event · alert | customer | medium · 40 | high_risk_regions |
ghb.branch_structuring |
GB1 | Cash deposits split across branches | gh-bank |
scheduled · alert | customer | high · 75 | — |
ghb.fx_cash |
GB2 | Large foreign-currency cash | gh-bank |
post-event · alert | customer | medium · 40 | — |
ghb.cheque_then_cash |
GB3 | Cheque credits withdrawn in cash | gh-bank |
post-event · alert | account | high · 65 | — |
ghb.gip_new_payee_large |
GB4 | Large instant transfer to a new payee | gh-bank |
post-event · alert | customer | medium · 50 | — |
ghb.correspondent_high_risk |
GB5 | Cross-border flow through a high-risk correspondent | gh-bank |
post-event · alert | customer | high · 60 | high_risk_correspondents |
ghb.trade_sector_mismatch |
I4 | Trade payments abroad outside the customer’s line of business | gh-bank |
scheduled · alert | customer | medium · 55 | — |
The core pack (24 rules)
Section titled “The core pack (24 rules)”Market-neutral rules for every institution. Thresholds are starting points: calibrate with backtesting before go-live. Amounts are starting points in Ghana cedis (GHS); workspaces in NGN, XOF, USD, EUR, SLE, LRD, GMD, GNF and KES get equivalent defaults at install (rounded; the large-transaction and large-cash rules take the local cash reporting threshold). In any other currency the cedi figures are installed as they are: set them for your currency before enabling.
Version 1.1.0 added eight rules: B3, B4, B5, C1 and D6 (post-event), and D3, D4 and F5 (scheduled). Version 1.2.0 makes B4’s round_to a choice of 100 or 1,000; the release that ships it also checks D4’s hop range when a version is saved.
Version 1.3.0 states that the pack’s amounts are in Ghana cedis (GHS) and adds equivalent starting amounts for workspaces whose base currency is NGN, XOF, USD or EUR; no rule’s logic changed. The values in each rule’s section below are the cedi defaults. In a currency listed in this table, installing or upgrading uses its amounts instead; in any other currency the cedi figures are installed unchanged and the pack’s row warns you. They are round starting points, not precise conversions: calibrate them before go-live. See Packs written in another currency.
Version 1.4.0 adds starting amounts for five more currencies: SLE, LRD, GMD, GNF and KES. They are the US dollar figures scaled at a rough exchange rate and rounded, so treat them as unverified starting points and review them for your customers before you enable a rule. No rule’s logic changed.
Version 1.4.1 changes one sentence: the explanation of A2 (large cash transaction) no longer names Ghana’s FIC, and tells the analyst to confirm the threshold with your financial intelligence unit’s current guidance. No expression, parameter or amount changed. Because the explanation is part of the rule, upgrading drafts a new version of A2 for approval, unless your workspace has changed the rule itself.
Default amounts by currency
| Rule | Parameter | GHS (as written) | NGN | XOF | USD | EUR | SLE | LRD | GMD | GNF | KES |
|---|---|---|---|---|---|---|---|---|---|---|---|
core.large_transaction |
threshold |
50,000 | 5,000,000 | 15,000,000 | 10,000 | 10,000 | 30,000 | 1,000,000 | 450,000 | 100,000,000 | 1,300,000 |
core.large_cash |
threshold |
50,000 | 5,000,000 | 15,000,000 | 10,000 | 10,000 | 30,000 | 1,000,000 | 450,000 | 100,000,000 | 1,300,000 |
core.large_cross_border |
threshold |
20,000 | 2,500,000 | 1,000,000 | 1,500 | 1,500 | 30,000 | 300,000 | 100,000 | 12,500,000 | 200,000 |
core.structuring |
threshold |
10,000 | 1,250,000 | 500,000 | 800 | 800 | 20,000 | 150,000 | 60,000 | 7,500,000 | 100,000 |
core.value_spike |
min_amount |
5,000 | 600,000 | 250,000 | 400 | 400 | 10,000 | 80,000 | 30,000 | 3,000,000 | 50,000 |
core.dormant_reactivation |
min_amount |
2,000 | 250,000 | 100,000 | 150 | 150 | 3,000 | 30,000 | 10,000 | 1,250,000 | 20,000 |
core.new_account_high_value |
threshold |
20,000 | 2,500,000 | 1,000,000 | 1,500 | 1,500 | 30,000 | 300,000 | 100,000 | 12,500,000 | 200,000 |
core.pass_through |
min_amount |
10,000 | 1,250,000 | 500,000 | 800 | 800 | 20,000 | 150,000 | 60,000 | 7,500,000 | 100,000 |
core.pep_threshold |
threshold |
10,000 | 1,250,000 | 500,000 | 800 | 800 | 20,000 | 150,000 | 60,000 | 7,500,000 | 100,000 |
core.high_risk_customer |
threshold |
10,000 | 1,250,000 | 500,000 | 800 | 800 | 20,000 | 150,000 | 60,000 | 7,500,000 | 100,000 |
core.high_risk_mcc |
min_amount |
1,000 | 125,000 | 50,000 | 80 | 80 | 2,000 | 15,000 | 6,000 | 750,000 | 10,000 |
core.small_deposits_large_withdrawal |
min_amount |
5,000 | 600,000 | 250,000 | 400 | 400 | 10,000 | 80,000 | 30,000 | 3,000,000 | 50,000 |
core.round_amounts |
min_amount |
1,000 | 125,000 | 50,000 | 80 | 80 | 2,000 | 15,000 | 6,000 | 750,000 | 10,000 |
core.same_amount_many_recipients |
min_amount |
200 | 25,000 | 10,000 | 15 | 15 | 300 | 3,000 | 1,000 | 125,000 | 2,000 |
core.mule_fan_in_fan_out |
min_in_amount |
5,000 | 600,000 | 250,000 | 400 | 400 | 10,000 | 80,000 | 30,000 | 3,000,000 | 50,000 |
core.circular_flow |
min_amount |
5,000 | 600,000 | 250,000 | 400 | 400 | 10,000 | 80,000 | 30,000 | 3,000,000 | 50,000 |
core.declared_profile |
min_volume |
5,000 | 600,000 | 250,000 | 400 | 400 | 10,000 | 80,000 | 30,000 | 3,000,000 | 50,000 |
core.declared_profile |
occupation_caps |
student 3,000; unemployed 2,000; pensioner 5,000; artisan 15,000; civil servant 20,000 | student 400,000; unemployed 250,000; pensioner 600,000; artisan 2,000,000; civil servant 2,500,000 | student 150,000; unemployed 100,000; pensioner 250,000; artisan 750,000; civil servant 1,000,000 | student 250; unemployed 150; pensioner 400; artisan 1,250; civil servant 1,500 | student 250; unemployed 150; pensioner 400; artisan 1,250; civil servant 1,500 | student 6,000; unemployed 3,000; pensioner 10,000; artisan 30,000; civil servant 30,000 | student 50,000; unemployed 30,000; pensioner 80,000; artisan 250,000; civil servant 300,000 | student 15,000; unemployed 10,000; pensioner 30,000; artisan 80,000; civil servant 100,000 | student 2,000,000; unemployed 1,250,000; pensioner 3,000,000; artisan 10,000,000; civil servant 12,500,000 | student 30,000; unemployed 20,000; pensioner 50,000; artisan 150,000; civil servant 200,000 |
core.large_transaction
Section titled “core.large_transaction”Large single transaction · library A1 · Unusually large value · alert about the customer · severity medium, score 40
Fires when a transaction is at or above threshold. If channels lists any channels, only those count; an empty list means every channel.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold (base currency) | 50,000 |
channels |
Only these channels (empty = all) | [] |
Explanation shown to analysts: Transaction of {{txn.base_currency}} {{txn.amount}} is at or above the {{txn.base_currency}} {{params.threshold}} threshold.
Expression
txn.amount >= params.threshold && (size(params.channels) == 0 || txn.channel in params.channels)core.large_cash
Section titled “core.large_cash”Large cash transaction (CTR candidate) · library A2 · Cash reporting threshold · alert about the customer · severity low, score 20
Fires on a cash-type transaction (cash_in, cash_out, deposit or withdrawal) through a cash channel (cash, agent, branch or atm) at or above threshold.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Cash threshold (base currency) | 50,000 |
Explanation shown to analysts: Cash transaction of {{txn.base_currency}} {{txn.amount}} meets the cash reporting threshold of {{txn.base_currency}} {{params.threshold}}. Confirm the threshold with your financial intelligence unit’s current guidance.
Expression
txn.type in ['cash_in', 'cash_out', 'deposit', 'withdrawal'] && txn.channel in ['cash', 'agent', 'branch', 'atm'] && txn.amount >= params.thresholdcore.large_cross_border
Section titled “core.large_cross_border”Large cross-border transfer · library A3 · Cross-border value · alert about the customer · severity medium, score 35
Fires when the counterparty’s country is known and isn’t home_country, and the amount is at or above threshold.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold (base currency) | 20,000 |
home_country |
Home country (ISO code) | "GH" |
Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} to/from {{txn.counterparty_country}} is above the cross-border threshold of {{txn.base_currency}} {{params.threshold}}.
Expression
txn.counterparty_country != '' && txn.counterparty_country != params.home_country && txn.amount >= params.thresholdcore.structuring
Section titled “core.structuring”Repeated deposits just below a threshold · library B1 · Structuring / smurfing · alert about the customer · severity high, score 70
Fires on a cash-in that is just below threshold (within margin, so GHS 9,000 to 9,999.99 by default) when the account has had at least min_count cash-ins in 48 hours adding up to the threshold or more.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold being avoided | 10,000 |
margin |
Margin below threshold (0.10 = 10%) | 0.1 |
min_count |
Minimum cash-ins in 48h | 3 |
Features: cash_in_count_48h (account, count, in, type cash_in, 48 hours); cash_in_sum_48h (account, sum, in, type cash_in, 48 hours).
Explanation shown to analysts: {{features.cash_in_count_48h}} cash-ins totalling {{txn.base_currency}} {{features.cash_in_sum_48h}} in 48h; this one ({{txn.base_currency}} {{txn.amount}}) is just below the {{txn.base_currency}} {{params.threshold}} threshold.
Expression
txn.type == 'cash_in' && txn.amount >= params.threshold * (1.0 - params.margin) && txn.amount < params.threshold && features.cash_in_count_48h >= double(params.min_count) && features.cash_in_sum_48h >= params.thresholdcore.value_spike
Section titled “core.value_spike”Spending spike against the customer’s own history · library C2 · Behaviour change · alert about the customer · severity medium, score 45
Fires on an outflow when the account’s outflows in the last 24 hours reach min_amount and are more than multiplier times its average daily outflow over the other 29 days of the last 30.
| Parameter | Meaning | Default |
|---|---|---|
multiplier |
Times the daily average | 5 |
min_amount |
Ignore below (base currency) | 5,000 |
Features: out_24h (account, sum, out, 24 hours); out_30d (account, sum, out, 30 days).
Explanation shown to analysts: Outflows of {{txn.base_currency}} {{features.out_24h}} in 24h are over {{params.multiplier}}× the 30-day daily average ({{txn.base_currency}} {{features.out_30d}} over 30 days).
Expression
txn.direction == 'out' && features.out_24h >= params.min_amount && features.out_30d > features.out_24h && features.out_24h > params.multiplier * ((features.out_30d - features.out_24h) / 29.0)core.dormant_reactivation
Section titled “core.dormant_reactivation”Dormant account suddenly active · library C3 · Dormant account reactivation · alert about the customer · severity medium, score 45
Fires when an account older than dormant_days has had no activity in the last 90 days except within the last 24 hours, on a transaction of at least min_amount.
| Parameter | Meaning | Default |
|---|---|---|
dormant_days |
Minimum account age (days); the quiet period is fixed at 90 days | 90 |
min_amount |
Minimum amount (base currency) | 2,000 |
Features: count_90d (account, count, 90 days); count_24h (account, count, 24 hours).
Explanation shown to analysts: No activity in the 90 days before today on an account {{account.age_days}} days old, then {{txn.base_currency}} {{txn.amount}}.
Expression
account.age_days > params.dormant_days && features.count_90d == features.count_24h && txn.amount >= params.min_amountcore.new_account_high_value
Section titled “core.new_account_high_value”New account receiving high value · library C4 · Mule onboarding · alert about the customer · severity high, score 60
Fires on any transaction when the account’s age is known and at most max_age_days, and it has received at least threshold in 7 days.
| Parameter | Meaning | Default |
|---|---|---|
max_age_days |
Account age up to (days) | 30 |
threshold |
Inflow over 7 days (base currency) | 20,000 |
Features: in_7d (account, sum, in, 7 days).
Explanation shown to analysts: Account opened {{account.age_days}} days ago (within the {{params.max_age_days}}-day window) has received {{txn.base_currency}} {{features.in_7d}} in 7 days.
Expression
account.age_days >= 0 && account.age_days <= params.max_age_days && features.in_7d >= params.thresholdcore.pass_through
Section titled “core.pass_through”Rapid pass-through of funds · library C5 · Layering / pass-through · alert about the customer · severity high, score 65
Fires on an outflow when the account received at least min_amount in 24 hours and has sent out at least out_ratio of that in the same 24 hours.
| Parameter | Meaning | Default |
|---|---|---|
min_amount |
Minimum inflow (base currency) | 10,000 |
out_ratio |
Out as share of in (0.9 = 90%) | 0.9 |
Features: in_24h (account, sum, in, 24 hours); out_24h (account, sum, out, 24 hours).
Explanation shown to analysts: {{txn.base_currency}} {{features.in_24h}} came in and {{txn.base_currency}} {{features.out_24h}} went out within 24h.
Expression
txn.direction == 'out' && features.in_24h >= params.min_amount && features.out_24h >= params.out_ratio * features.in_24hcore.fan_in
Section titled “core.fan_in”Many senders into one account · library D1 · Fan-in / collection account · alert about the customer · severity high, score 60
Fires on an inflow when the account has been paid by at least min_senders different senders in 7 days.
| Parameter | Meaning | Default |
|---|---|---|
min_senders |
Distinct senders in 7 days | 25 |
Features: senders_7d (account, distinct counterparties, in, 7 days).
Explanation shown to analysts: {{features.senders_7d}} different senders paid this account in 7 days.
Expression
txn.direction == 'in' && features.senders_7d >= double(params.min_senders)core.fan_out
Section titled “core.fan_out”One account paying many recipients · library D2 · Fan-out / distribution · alert about the customer · severity medium, score 50
Fires on an outflow when the account has paid at least min_recipients different recipients in 7 days.
| Parameter | Meaning | Default |
|---|---|---|
min_recipients |
Distinct recipients in 7 days | 25 |
Features: recipients_7d (account, distinct counterparties, out, 7 days).
Explanation shown to analysts: This account paid {{features.recipients_7d}} different recipients in 7 days.
Expression
txn.direction == 'out' && features.recipients_7d >= double(params.min_recipients)core.many_payers_one_counterparty
Section titled “core.many_payers_one_counterparty”Many of our customers paying one counterparty · library D7 · Mule / scam collection point · alert about the counterparty · severity high, score 65
Fires on a payment to an outside party when at least min_payers of your accounts have paid that same party in 7 days. The alert is about the counterparty.
| Parameter | Meaning | Default |
|---|---|---|
min_payers |
Distinct paying accounts in 7 days | 15 |
Features: payers_7d (counterparty, distinct counterparties, out, 7 days).
Explanation shown to analysts: {{features.payers_7d}} of our accounts paid this counterparty in 7 days.
Expression
txn.direction == 'out' && txn.has_counterparty && !counterparty.is_own_account && features.payers_7d >= double(params.min_payers)core.high_risk_jurisdiction
Section titled “core.high_risk_jurisdiction”High-risk jurisdiction · library E1 · High-risk corridor · alert about the customer · severity high, score 60
Fires when the transaction’s country or the counterparty’s country is on the high_risk_countries list and the amount is at least min_amount.
| Parameter | Meaning | Default |
|---|---|---|
min_amount |
Minimum amount (base currency) | 0 |
Needs the list high_risk_countries.
Explanation shown to analysts: Funds to/from {{txn.counterparty_country}}, which is on the workspace’s high-risk country list.
Expression
has(lists.high_risk_countries) && txn.amount >= params.min_amount && (txn.counterparty_country in lists.high_risk_countries || txn.country in lists.high_risk_countries)core.sanctioned_party
Section titled “core.sanctioned_party”Sanctioned party involved · library F1 · Sanctions · alert about the customer · severity critical, score 100
Fires on any transaction where the customer or the counterparty carries a current sanctioned designation.
No parameters.
Explanation shown to analysts: A party to this transaction carries a sanctioned designation.
Expression
customer.is_sanctioned || 'sanctioned' in counterparty.designationscore.pep_threshold
Section titled “core.pep_threshold”PEP transaction above threshold · library F2 · Politically exposed person · alert about the customer · severity high, score 70
Fires when the customer is a PEP or a relative or close associate of one (pep or pep_rca) and the amount is at or above threshold.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold (base currency) | 10,000 |
Explanation shown to analysts: Customer is a PEP; transaction of {{txn.base_currency}} {{txn.amount}} is at or above {{txn.base_currency}} {{params.threshold}}.
Expression
customer.is_pep && txn.amount >= params.thresholdcore.high_risk_customer
Section titled “core.high_risk_customer”High-risk customer, large transaction · library F3 · Enhanced due diligence · alert about the customer · severity medium, score 50
Fires when the customer’s risk rating is in ratings, or they have a high_risk designation, and the amount is at or above threshold.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold (base currency) | 10,000 |
ratings |
Risk ratings | ["high", "unacceptable"] |
Explanation shown to analysts: High-risk customer (risk rating: {{customer.risk_rating}}); transaction of {{txn.base_currency}} {{txn.amount}}. A customer flagged only by a high_risk designation, with a lower rating, reads for example “High-risk customer (risk rating: medium); …”.
Expression
(customer.risk_rating in params.ratings || customer.is_high_risk) && txn.amount >= params.thresholdcore.high_risk_mcc
Section titled “core.high_risk_mcc”High-risk merchant category · library H4 · High-risk merchant · alert about the customer · severity medium, score 40
Fires on a card payment whose merchant category code is on the high_risk_mcc list and whose amount is at least min_amount.
| Parameter | Meaning | Default |
|---|---|---|
min_amount |
Minimum amount (base currency) | 1,000 |
Needs the list high_risk_mcc.
Explanation shown to analysts: Card payment of {{txn.base_currency}} {{txn.amount}} at MCC {{txn.mcc}}, on the high-risk MCC list.
Expression
txn.mcc != '' && has(lists.high_risk_mcc) && txn.mcc in lists.high_risk_mcc && txn.amount >= params.min_amountcore.small_deposits_large_withdrawal
Section titled “core.small_deposits_large_withdrawal”Many small deposits, then one large withdrawal · library B3 · Structuring / smurfing · alert about the account · severity high, score 65
Fires on an outgoing withdrawal, cash_out or transfer of at least min_amount when, over the 7 days before it, the account received at least min_deposits payments, the outgoing amount is at least withdrawal_ratio of what came in, and no single payment in was larger than max_deposit_share of the outgoing amount. The alert is about the account.
| Parameter | Meaning | Default |
|---|---|---|
min_deposits |
Payments in over 7 days, at least | 10 |
withdrawal_ratio |
Withdrawal as share of the 7-day inflow (0.8 = 80%) | 0.8 |
max_deposit_share |
Largest payment in, as share of the withdrawal, at most | 0.2 |
min_amount |
Withdrawal at or above (base currency) | 5,000 |
Features: in_count_7d (account, count, in, 7 days), in_7d (account, sum, in, 7 days), in_max_7d (account, max, in, 7 days).
Explanation shown to analysts: {{features.in_count_7d}} payments in totalling {{txn.base_currency}} {{features.in_7d}} over 7 days (largest {{txn.base_currency}} {{features.in_max_7d}}), then a single {{txn.type}} of {{txn.base_currency}} {{txn.amount}}.
Expression
txn.direction == 'out' && txn.type in ['withdrawal', 'cash_out', 'transfer'] && txn.amount >= params.min_amount && features.in_count_7d >= double(params.min_deposits) && txn.amount >= params.withdrawal_ratio * features.in_7d && features.in_max_7d <= params.max_deposit_share * txn.amountcore.round_amounts
Section titled “core.round_amounts”Repeated round-figure payments in · library B4 · Structuring / round-figure placement · alert about the account · severity medium, score 45
Fires on an incoming payment of at least min_amount that is a whole multiple of round_to in your base currency, with no minor units, when the account has received at least min_count such round payments in 7 days, this one included. The alert is about the account.
| Parameter | Meaning | Default |
|---|---|---|
round_to |
Round to (100 or 1,000): one of the two | 1,000 |
min_count |
Round payments in over 7 days, at least | 5 |
min_amount |
This payment at or above (base currency) | 1,000 |
Features: round100_7d (account, count, in, round to 100, 7 days), round1000_7d (account, count, in, round to 1,000, 7 days).
round_to is a choice of 100 or 1000 (since 1.2.0). Round-amount counters start with version 1.1.0, so payments from before that upgrade aren’t counted.
Explanation shown to analysts:
{{= string(int(params.round_to == 100 ? features.round100_7d : features.round1000_7d)) }} payments in of round multiples of {{txn.base_currency}} {{params.round_to}} in 7 days; this one is {{txn.base_currency}} {{txn.amount}}.Expression
txn.direction == 'in' && txn.amount >= params.min_amount && txn.amount == double(int(txn.amount)) && int(txn.amount) % (params.round_to == 100 ? 100 : 1000) == 0 && (params.round_to == 100 ? features.round100_7d : features.round1000_7d) >= double(params.min_count)core.same_amount_many_recipients
Section titled “core.same_amount_many_recipients”Same amount sent to many recipients · library B5 · Distribution to mules · alert about the customer · severity medium, score 50
Fires on an outgoing payment by a customer whose type is in customer_types when, over the last 24 hours, they made at least min_recipients payments to at least min_recipients different recipients, averaging at least min_amount, with the largest no more than amount_tolerance above the average, and this payment no more than amount_tolerance below it.
| Parameter | Meaning | Default |
|---|---|---|
min_recipients |
Distinct recipients in 24 hours, at least | 5 |
amount_tolerance |
Amount tolerance (0.05 = within 5%) | 0.05 |
min_amount |
Average payment at or above (base currency) | 200 |
customer_types |
Customer types | ["individual"] |
Features: out_count_24h (customer, count, out, 24 hours), recipients_24h (customer, distinct counterparties, out, 24 hours), out_avg_24h (customer, avg, out, 24 hours), out_max_24h (customer, max, out, 24 hours).
An approximation from running totals: it compares the largest payment and the current one with the 24-hour average, not every payment with every other.
Explanation shown to analysts: {{features.out_count_24h}} payments to {{features.recipients_24h}} different recipients in 24 hours, all close to {{txn.base_currency}} {{features.out_avg_24h}} (largest {{txn.base_currency}} {{features.out_max_24h}}).
Expression
txn.direction == 'out' && customer.type in params.customer_types && features.recipients_24h >= double(params.min_recipients) && features.out_count_24h >= double(params.min_recipients) && features.out_avg_24h >= params.min_amount && features.out_max_24h <= features.out_avg_24h * (1.0 + params.amount_tolerance) && txn.amount >= features.out_avg_24h * (1.0 - params.amount_tolerance)core.count_spike
Section titled “core.count_spike”Transaction count spike against the account’s own history · library C1 · Behaviour change · alert about the account · severity medium, score 40
Fires on any transaction when the account is at least min_baseline_days days old, has had at least min_count transactions in the last 24 hours, and that is more than multiplier times its average day over the 29 days before (the 30-day count minus the last 24 hours, divided by 29). Accounts with no opening date never match. The alert is about the account.
| Parameter | Meaning | Default |
|---|---|---|
multiplier |
Times the daily average | 5 |
min_count |
Transactions in 24 hours, at least | 10 |
min_baseline_days |
Account age before it applies (days) | 30 |
Features: count_24h (account, count, 24 hours), count_30d (account, count, 30 days).
Explanation shown to analysts: {{features.count_24h}} transactions in 24 hours, over {{params.multiplier}}× this account’s daily average ({{features.count_30d}} in 30 days).
Expression
account.age_days >= params.min_baseline_days && features.count_24h >= double(params.min_count) && features.count_30d > features.count_24h && features.count_24h > params.multiplier * ((features.count_30d - features.count_24h) / 29.0)core.mule_fan_in_fan_out
Section titled “core.mule_fan_in_fan_out”Collect from many, pay out to many (mule account) · library D3 · Money mule / pass-through account · scheduled, daily 02:00 · alert about the account · severity high, score 75
Over the last window_hours, finds accounts paid by at least in_senders different senders, min_in_amount or more in total, which then (after the first payment in) paid at least out_recipients different recipients, passing on at least min_out_ratio of what came in. Only completed transactions with an account and a counterparty count. The alert is about the account.
| Parameter | Meaning | Default |
|---|---|---|
window_hours |
Window (hours) | 72 |
in_senders |
Distinct senders, at least | 10 |
out_recipients |
Distinct recipients afterwards, at least | 10 |
min_in_amount |
Inflow at or above (base currency) | 5,000 |
min_out_ratio |
Paid on, as share of the inflow (0.7 = 70%) | 0.7 |
Each hit records senders, recipients, in_amount, out_amount, out_ratio, hours for the explanation, as {{features.<name>}}.
Explanation shown to analysts: {{features.senders}} senders paid {{txn.base_currency}} {{features.in_amount}} into this account, then {{features.recipients}} recipients were paid {{txn.base_currency}} {{features.out_amount}} ({{features.out_ratio}} of the inflow) within {{features.hours}} hours.
Runs the built-in scenario fan_in_fan_out (scenario:fan_in_fan_out) on the schedule daily 02:00, in your workspace’s time zone. See Scheduled rules.
core.circular_flow
Section titled “core.circular_flow”Money going round in a circle · library D4 · Round-tripping / layering · scheduled, daily 03:00 · alert about the account · severity high, score 70
Over the last window_hours, finds money that left an account and came back to it in min_hops to max_hops hops, each leg at least min_amount, later than the one before, within window_hours of the first and within amount_tolerance of the first leg’s amount. A counterparty whose account number is one of your accounts is that account, so loops through several of your customers join up. Accounts that paid more than 25 different counterparties in the window aren’t walked through. The alert is about the account the loop starts and ends at.
| Parameter | Meaning | Default |
|---|---|---|
window_hours |
Whole loop within (hours) | 168 |
min_hops |
Hops, at least (2 = straight back) | 3 |
max_hops |
Hops, at most | 5 |
amount_tolerance |
Each leg within (0.1 = 10%) of the first | 0.1 |
min_amount |
Legs at or above (base currency) | 5,000 |
Each hit records hops, amount_out, amount_back, hours, cycles for the explanation, as {{features.<name>}}, and path as {{subject.<name>}}.
min_hops can’t be more than max_hops: a version that sets them that way is refused when it is saved (since 1.2.0).
Explanation shown to analysts: {{txn.base_currency}} {{features.amount_out}} left this account and {{txn.base_currency}} {{features.amount_back}} came back {{features.hours}} hours later after {{features.hops}} hops (loops found in the window: {{features.cycles}}).
Runs the built-in scenario circular_flow (scenario:circular_flow) on the schedule daily 03:00, in your workspace’s time zone. See Scheduled rules.
core.counterparty_confirmed_case
Section titled “core.counterparty_confirmed_case”Counterparty linked to a confirmed case · library D6 · Known bad actor · alert about the customer · severity high, score 70
Fires on a transaction of at least min_amount with a counterparty whose latest case, closed with one of dispositions within the last lookback_days days, was about the counterparty itself or about your own account or customer behind the same account number. Post-event only.
| Parameter | Meaning | Default |
|---|---|---|
lookback_days |
Case closed within (days) | 365 |
dispositions |
Case outcomes that count | ["true_positive_no_report", "report_filed", "customer_exited"] |
min_amount |
Amount at or above (base currency) | 0 |
Explanation shown to analysts:
{{txn.base_currency}} {{txn.amount}} {{txn.direction}} with a counterparty whose case was closed as {{counterparty.confirmed_case_disposition}} {{= string(int(counterparty.confirmed_case_days)) }} days ago.Expression
txn.has_counterparty && counterparty.confirmed_case_days >= 0.0 && counterparty.confirmed_case_days <= double(params.lookback_days) && counterparty.confirmed_case_disposition in params.dispositions && txn.amount >= params.min_amountcore.declared_profile
Section titled “core.declared_profile”Activity above the declared profile · library F5 · Profile inconsistency · scheduled, daily 04:00 · alert about the customer · severity medium, score 50
Over the last period_days, sums each customer’s completed transactions in direction (any means in and out), scales the total to 30 days, and compares it with their declared monthly volume (when declared in your base currency) or, with none, with the ceiling in occupation_caps for their occupation. Fires when the scaled total is more than multiplier times that figure and the period’s total is at least min_volume. customer_types and segments narrow it; empty means all. Customers with neither a declaration nor a listed occupation are skipped.
| Parameter | Meaning | Default |
|---|---|---|
period_days |
Period (days) | 30 |
multiplier |
Times the declared monthly volume | 3 |
min_volume |
Turnover at or above (base currency) | 5,000 |
direction |
Count money in, out, or both | "any" |
customer_types |
Customer types (empty = all) | ["individual"] |
segments |
Segments (empty = all) | [] |
occupation_caps |
Expected monthly turnover by occupation, when none was declared (base currency) | {"student": 3000, "unemployed": 2000, "pensioner": 5000, "artisan": 15000, "civil servant": 20000} |
Each hit records volume, monthly_volume, declared, ratio for the explanation, as {{features.<name>}}.
The occupation ceilings are illustrations. Agree your own with your MLRO before relying on this rule.
Explanation shown to analysts: Turnover of {{txn.base_currency}} {{features.volume}} over {{params.period_days}} days (about {{txn.base_currency}} {{features.monthly_volume}} a month) is {{features.ratio}}× the declared or expected {{txn.base_currency}} {{features.declared}} a month.
Runs the built-in scenario declared_volume (scenario:declared_volume) on the schedule daily 04:00, in your workspace’s time zone. See Scheduled rules.
The fraud pack (11 rules)
Section titled “The fraud pack (11 rules)”Rules for the real-time decision API: card testing, account takeover, new device or payee, impossible travel, shared devices and payout bursts. They answer block, review or step_up before the money moves. Thresholds are starting points: run them in shadow first and tune against your own traffic. Amounts are starting points in Ghana cedis (GHS); workspaces in NGN, XOF, USD, EUR, SLE, LRD, GMD, GNF and KES get equivalent defaults at install (rounded; the large-transaction and large-cash rules take the local cash reporting threshold). In any other currency the cedi figures are installed as they are: set them for your currency before enabling.
Version 1.1.0 added C6, fraud.unusual_hour. Version 1.2.0 makes C6 compare like with like, so it no longer over-fires after the upgrade that introduced hour counters (see below).
Version 1.3.0 states that the pack’s amounts are in Ghana cedis (GHS) and adds equivalent starting amounts for workspaces whose base currency is NGN, XOF, USD or EUR; no rule’s logic changed. The values in each rule’s section below are the cedi defaults. In a currency listed in this table, installing or upgrading uses its amounts instead; in any other currency the cedi figures are installed unchanged and the pack’s row warns you. They are round starting points, not precise conversions: calibrate them before go-live. See Packs written in another currency.
Version 1.4.0 adds starting amounts for five more currencies: SLE, LRD, GMD, GNF and KES. They are the US dollar figures scaled at a rough exchange rate and rounded, so treat them as unverified starting points and review them for your customers before you enable a rule. No rule’s logic changed.
Default amounts by currency
| Rule | Parameter | GHS (as written) | NGN | XOF | USD | EUR | SLE | LRD | GMD | GNF | KES |
|---|---|---|---|---|---|---|---|---|---|---|---|
fraud.card_testing |
small_amount |
20 | 2,500 | 1,000 | 2 | 2 | 50 | 400 | 150 | 15,000 | 250 |
fraud.new_device_new_payee |
threshold |
2,000 | 250,000 | 100,000 | 150 | 150 | 3,000 | 30,000 | 10,000 | 1,250,000 | 20,000 |
fraud.profile_change_then_payout |
min_amount |
500 | 60,000 | 25,000 | 40 | 40 | 1,000 | 8,000 | 3,000 | 300,000 | 5,000 |
fraud.new_country |
min_amount |
1,000 | 125,000 | 50,000 | 80 | 80 | 2,000 | 15,000 | 6,000 | 750,000 | 10,000 |
fraud.payout_burst |
min_total |
1,000 | 125,000 | 50,000 | 80 | 80 | 2,000 | 15,000 | 6,000 | 750,000 | 10,000 |
fraud.unusual_hour |
min_amount |
1,000 | 125,000 | 50,000 | 80 | 80 | 2,000 | 15,000 | 6,000 | 750,000 | 10,000 |
fraud.card_testing
Section titled “fraud.card_testing”Card testing: many small card payments · library H1 · Card testing · real-time, answers block · fraud alert about the customer · severity high, score 80
Fires on a card transaction when the customer has made at least max_auths outgoing card payments in the last 10 minutes, this one included, and their average is at most small_amount. merchants_10m is only shown in the explanation.
| Parameter | Meaning | Default |
|---|---|---|
max_auths |
Card payments in 10 minutes | 5 |
small_amount |
Average amount at or below (base currency) | 20 |
Features: card_10m (customer, count, out, card, 10 minutes); card_avg_10m (customer, avg, out, card, 10 minutes); merchants_10m (customer, distinct_counterparties, out, 10 minutes).
Explanation shown to analysts: {{features.card_10m}} card payments averaging {{txn.base_currency}} {{features.card_avg_10m}} in 10 minutes, at {{features.merchants_10m}} merchants.
Expression
txn.channel == 'card' && features.card_10m >= double(params.max_auths) && features.card_avg_10m <= params.small_amountfraud.new_device_new_payee
Section titled “fraud.new_device_new_payee”New device paying a new payee a large amount · library H2 · Account takeover · real-time, answers review · fraud alert about the customer · severity high, score 70
Fires on an outgoing transaction of at least threshold that is both the customer’s first from this device and their first with this counterparty.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Amount at or above (base currency) | 2,000 |
Needs device_fingerprint and a counterparty on the transaction. See What “new” means.
Explanation shown to analysts: First payment from this device and first payment to this payee: {{txn.base_currency}} {{txn.amount}} out.
Expression
txn.direction == 'out' && device.known && device.is_new && txn.has_counterparty && counterparty.is_new_for_customer && txn.amount >= params.thresholdfraud.failed_auth_then_payout
Section titled “fraud.failed_auth_then_payout”Payout after repeated failed OTP or PIN · library H3 · Account takeover · real-time, answers review · fraud alert about the customer · severity high, score 65
Fires on an outgoing transaction when the customer had at least max_failures failed OTPs and failed PINs, added together, in the last 30 minutes. It doesn’t wait for a successful attempt.
| Parameter | Meaning | Default |
|---|---|---|
max_failures |
Failed OTPs and PINs in 30 minutes | 3 |
Features: otp_failed_30m (customer, count, otp_failed events, 30 minutes); pin_failed_30m (customer, count, pin_failed events, 30 minutes).
Needs otp_failed and pin_failed account events.
Explanation shown to analysts: {{features.otp_failed_30m}} failed OTPs and {{features.pin_failed_30m}} failed PINs in the last 30 minutes before this payout.
Expression
txn.direction == 'out' && features.otp_failed_30m + features.pin_failed_30m >= double(params.max_failures)fraud.profile_change_then_payout
Section titled “fraud.profile_change_then_payout”Payout soon after a profile or credential change · library H5 · Account takeover · real-time, answers review · fraud alert about the customer · severity high, score 70
Fires on an outgoing transaction of at least min_amount when any event in change_events happened less than window_hours hours before it.
| Parameter | Meaning | Default |
|---|---|---|
window_hours |
Hours since the change | 24 |
min_amount |
Payout at or above (base currency) | 500 |
change_events |
Changes that count | ["phone_change", "email_change", "password_reset", "password_change", "device_added"] |
Needs account events of the types in change_events. The explanation lists every change in change_events that happened within the window, with how long ago (in minutes under an hour, otherwise in whole hours), for example Payout of GHS 2,400 after phone change 20h ago, email change 35m ago (rule window: 24 hours). Changes the rule doesn’t watch, such as a SIM swap, aren’t listed. It uses a computed placeholder.
Explanation shown to analysts:
Payout of {{txn.base_currency}} {{txn.amount}} after {{= params.change_events.filter(e, e in customer.hours_since && customer.hours_since[e] < double(params.window_hours)).map(e, e.replace('_', ' ') + ' ' + (customer.hours_since[e] < 1.0 ? string(int(customer.hours_since[e] * 60.0)) + 'm ago' : string(int(customer.hours_since[e])) + 'h ago')).join(', ') }} (rule window: {{params.window_hours}} hours).Expression
txn.direction == 'out' && txn.amount >= params.min_amount && params.change_events.exists(e, e in customer.hours_since && customer.hours_since[e] < params.window_hours)fraud.new_country
Section titled “fraud.new_country”First transaction in or to a new country · library E2 · Unusual geography · real-time, answers step-up · fraud alert about the customer · severity medium, score 40
Fires on a transaction of at least min_amount, in either direction, that involves a country this customer has never transacted with, when they have more than min_history transactions in the last 90 days, this one included. The country is the counterparty’s when sent, otherwise the transaction’s.
| Parameter | Meaning | Default |
|---|---|---|
min_amount |
Amount at or above (base currency) | 1,000 |
min_history |
Earlier transactions (90 days) before it counts as a change | 10 |
Features: txns_90d (customer, count, 90 days).
Needs counterparty_country or country. Every country is new for a customer’s first transactions, which is why min_history exists.
Explanation shown to analysts: First transaction involving this country for a customer with {{features.txns_90d}} transactions in 90 days.
Expression
txn.country_is_new && txn.amount >= params.min_amount && features.txns_90d > double(params.min_history)fraud.impossible_travel
Section titled “fraud.impossible_travel”Impossible travel · library E3 · Account takeover / cloned credentials · real-time, answers review · fraud alert about the customer · severity high, score 70
Fires when the transaction carries a location more than min_km km from the customer’s previous known location, and getting there in the time between would need more than max_kmh km/h.
| Parameter | Meaning | Default |
|---|---|---|
max_kmh |
Speed above (km/h) | 900 |
min_km |
Distance above (km) | 100 |
Needs geo_lat and geo_lon on transactions. A transaction dated at or before the latest known location has no speed (-1), so it never fires this rule; see What “new” means.
Explanation shown to analysts: {{txn.km_from_last}} km from the previous location in {{txn.minutes_since_last_location}} minutes ({{txn.kmh_from_last}} km/h).
Expression
txn.has_location && txn.km_from_last > params.min_km && txn.kmh_from_last > params.max_kmhfraud.shared_device
Section titled “fraud.shared_device”Device shared by many customers · library D5 · Mule network / account farm · real-time, answers review · fraud alert about the customer · severity high, score 60
Fires when the transaction’s device has been used by more than max_customers different customers in 30 days, counting transactions and account events that carried its fingerprint.
| Parameter | Meaning | Default |
|---|---|---|
max_customers |
Customers per device in 30 days, above | 3 |
Features: device_customers_30d (device, distinct_customers, 30 days).
Needs device_fingerprint. Family members sharing a phone, and agents’ devices, are the usual false positives.
Explanation shown to analysts: This device has been used by {{features.device_customers_30d}} customers in 30 days.
Expression
device.known && features.device_customers_30d > double(params.max_customers)fraud.payout_burst
Section titled “fraud.payout_burst”Burst of payouts · library C7 · Account takeover / mule cash-out · real-time, answers step-up · fraud alert about the customer · severity medium, score 50
Fires on an outgoing transaction when the customer has made at least max_payouts outgoing transactions in the last 10 minutes, this one included, totalling at least min_total.
| Parameter | Meaning | Default |
|---|---|---|
max_payouts |
Payouts in 10 minutes | 5 |
min_total |
Total at or above (base currency) | 1,000 |
Features: out_10m (customer, count, out, 10 minutes); out_sum_10m (customer, sum, out, 10 minutes).
Explanation shown to analysts: {{features.out_10m}} payouts totalling {{txn.base_currency}} {{features.out_sum_10m}} in 10 minutes.
Expression
txn.direction == 'out' && features.out_10m >= double(params.max_payouts) && features.out_sum_10m >= params.min_totalfraud.sanctioned_party
Section titled “fraud.sanctioned_party”Sanctioned customer or counterparty · library F1-RT · Sanctions · real-time, answers block · fraud alert about the customer · severity critical, score 100
Fires on any transaction where the customer or the counterparty carries a current sanctioned designation. The logic is the same as core.sanctioned_party, but in a real-time decision it blocks the payment before it happens; the post-event rule still raises the AML alert.
No parameters.
Explanation shown to analysts: A party to this transaction is designated sanctioned.
Expression
customer.is_sanctioned || 'sanctioned' in counterparty.designationsfraud.deny_listed_counterparty
Section titled “fraud.deny_listed_counterparty”Counterparty on your deny list · library F4 · Deny list · real-time, answers block · fraud alert about the customer · severity high, score 90
Fires when the counterparty’s country is on the blocked_countries list, or the merchant category code is on the blocked_mcc list. Despite its name, it doesn’t check named counterparties: put a person or wallet you want stopped under a sanctioned designation instead.
No parameters.
Needs the lists blocked_countries or blocked_mcc.
Explanation shown to analysts: The counterparty country or merchant category is on lists.blocked_countries or lists.blocked_mcc.
Expression
(txn.counterparty_country != '' && txn.counterparty_country in lists.blocked_countries) || (txn.mcc != '' && txn.mcc in lists.blocked_mcc)fraud.unusual_hour
Section titled “fraud.unusual_hour”Payout at an hour this customer never uses · library C6 · Account takeover · real-time, answers step-up · fraud alert about the customer · severity medium, score 40
Fires on an outgoing payment of at least min_amount by a customer with at least min_history transactions in the last 90 days, when no more than max_prior_at_hour of those were in the same clock hour as this one (this one not counted).
| Parameter | Meaning | Default |
|---|---|---|
min_history |
Transactions in 90 days before it applies | 30 |
max_prior_at_hour |
Earlier transactions at this hour, at most | 0 |
min_amount |
Payout at or above (base currency) | 1,000 |
Features: hour_counted_90d (customer, count, every transaction the hour counters have seen, 90 days), same_hour_90d (customer, count, same hour of the day in the workspace’s time zone, 90 days).
History is measured with the hour counters themselves, so the rule doesn’t apply until they hold min_history of the customer’s transactions (version 1.2.0; 1.1.0 compared with the full 90-day count and over-fired after the upgrade).
Explanation shown to analysts:
{{txn.hour}}:00 is unusual for this customer: {{= string(int(features.same_hour_90d) - 1) }} of their {{features.hour_counted_90d}} transactions in 90 days were at this hour. Payout of {{txn.base_currency}} {{txn.amount}}.Expression
txn.direction == 'out' && txn.amount >= params.min_amount && features.hour_counted_90d >= double(params.min_history) && features.same_hour_90d - 1.0 <= double(params.max_prior_at_hour)The gh-psp pack (14 rules)
Section titled “The gh-psp pack (14 rules)”Typologies for Ghanaian payment service providers, mobile money operators and agent networks. Calibrate thresholds against your own data and current Bank of Ghana / FIC guidance.
Version 1.1.0 added the two real-time rules, G3 and A4-RT. Version 1.2.0 added E4 (post-event) and G1, I1, I2 and I3 (scheduled); the last four of those are shared with gh-bank.
gh.sim_swap_payout
Section titled “gh.sim_swap_payout”Payout soon after a SIM swap · library G3 · SIM swap · real-time, answers block · fraud alert about the customer · severity critical, score 85
Fires on an outgoing transaction of at least threshold when the customer’s latest sim_swap account event happened less than sim_change_hours hours before it. A customer with no SIM swap on record never matches.
| Parameter | Meaning | Default |
|---|---|---|
sim_change_hours |
Hours since the SIM swap | 48 |
threshold |
Payout at or above (base currency) | 200 |
Needs sim_swap account events. The explanation gives the actual hours since the swap, with the rule’s window for comparison.
Explanation shown to analysts: SIM swapped {{customer.hours_since.sim_swap}} hours before this payout of {{txn.base_currency}} {{txn.amount}} (rule window: {{params.sim_change_hours}} hours).
Expression
txn.direction == 'out' && txn.amount >= params.threshold && 'sim_swap' in customer.hours_since && customer.hours_since.sim_swap < params.sim_change_hoursgh.kyc_tier_limit_realtime
Section titled “gh.kyc_tier_limit_realtime”Decline above KYC tier limit · library A4-RT · Tier limit breach · real-time, answers block · fraud alert about the customer · severity medium, score 50
Fires on an outgoing transaction above the single-transaction limit for the customer’s KYC tier. Customers whose tier is unknown, or not listed in limits, are skipped. It is the real-time partner of gh.kyc_tier_single_limit, which also checks incoming money but can only alert.
| Parameter | Meaning | Default |
|---|---|---|
limits |
Single-transaction limit by KYC tier (base currency) | {"1": 1000, "2": 5000, "3": 20000} |
Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} exceeds the single-transaction limit for this customer’s KYC tier.
Expression
txn.direction == 'out' && string(customer.kyc_level) in params.limits && txn.amount > double(params.limits[string(customer.kyc_level)])gh.kyc_tier_single_limit
Section titled “gh.kyc_tier_single_limit”Transaction above KYC tier limit · library A4 · Tier limit breach · alert about the customer · severity medium, score 45
Fires when a transaction is above the single-transaction limit for the customer’s KYC tier. Customers whose tier is unknown, or not listed in limits, are skipped.
| Parameter | Meaning | Default |
|---|---|---|
limits |
Single-transaction limit by KYC tier (base currency) | {"1": 1000, "2": 5000, "3": 20000} |
Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} exceeds the single-transaction limit for this customer’s KYC tier.
Expression
string(customer.kyc_level) in params.limits && txn.amount > double(params.limits[string(customer.kyc_level)])gh.kyc_tier_daily_limit
Section titled “gh.kyc_tier_daily_limit”Daily outflow above KYC tier limit · library A5 · Tier limit breach · alert about the customer · severity medium, score 45
Fires on an outflow when the customer’s outflows in the last 24 hours, across all their accounts, are above the daily limit for their KYC tier.
| Parameter | Meaning | Default |
|---|---|---|
limits |
24h outflow limit by KYC tier (base currency) | {"1": 2000, "2": 10000, "3": 50000} |
Features: out_24h (customer, sum, out, 24 hours).
Explanation shown to analysts: Outflows of {{txn.base_currency}} {{features.out_24h}} in 24h exceed the daily limit for this customer’s KYC tier.
Expression
txn.direction == 'out' && string(customer.kyc_level) in params.limits && features.out_24h > double(params.limits[string(customer.kyc_level)])gh.split_cash_in_agents
Section titled “gh.split_cash_in_agents”Cash-in split across many agents · library B2 · Smurfing through agents · alert about the customer · severity high, score 70
Fires on a cash-in when the account has received money from at least min_agents different parties in 24 hours and its cash-ins in those 24 hours add up to at least min_total.
| Parameter | Meaning | Default |
|---|---|---|
min_agents |
Distinct agents in 24h | 4 |
min_total |
Total cash-in in 24h (base currency) | 10,000 |
Features: agents_24h (account, distinct counterparties, in, 24 hours); cash_in_24h (account, sum, in, type cash_in, 24 hours).
Explanation shown to analysts: Cash-ins via {{features.agents_24h}} different agents totalling {{txn.base_currency}} {{features.cash_in_24h}} in 24h.
Expression
txn.type == 'cash_in' && features.agents_24h >= double(params.min_agents) && features.cash_in_24h >= params.min_totalgh.cash_out_after_cash_in
Section titled “gh.cash_out_after_cash_in”Cash-out right after cash-in · library G2 · Wallet used as pass-through · alert about the customer · severity high, score 60
Fires on a cash-out when the account cashed in at least min_amount in the last 2 hours and has cashed out at least ratio of it in the same 2 hours.
| Parameter | Meaning | Default |
|---|---|---|
min_amount |
Minimum cash-in (base currency) | 3,000 |
ratio |
Cash-out as share of cash-in | 0.8 |
Features: cash_in_2h (account, sum, in, type cash_in, 2 hours); cash_out_2h (account, sum, out, type cash_out, 2 hours).
Explanation shown to analysts: {{txn.base_currency}} {{features.cash_in_2h}} cashed in and {{txn.base_currency}} {{features.cash_out_2h}} cashed out within 2 hours.
Expression
txn.type == 'cash_out' && features.cash_in_2h >= params.min_amount && features.cash_out_2h >= params.ratio * features.cash_in_2hgh.wallet_sweep_to_bank
Section titled “gh.wallet_sweep_to_bank”Wallet swept to bank after many inbound payments · library G4 · Collection wallet · alert about the customer · severity high, score 60
Fires on a bank transfer or instant payment out of the account when at least min_senders different senders paid it in 7 days and this payment is at least sweep_ratio of its 7-day inflow.
| Parameter | Meaning | Default |
|---|---|---|
min_senders |
Distinct senders in 7 days | 10 |
sweep_ratio |
Transfer as share of 7-day inflow | 0.7 |
Features: senders_7d (account, distinct counterparties, in, 7 days); in_7d (account, sum, in, 7 days).
Explanation shown to analysts: {{features.senders_7d}} senders paid in {{txn.base_currency}} {{features.in_7d}} over 7 days; {{txn.base_currency}} {{txn.amount}} is now leaving to a bank.
Expression
txn.direction == 'out' && txn.channel in ['bank_transfer', 'instant_payment'] && features.senders_7d >= double(params.min_senders) && txn.amount >= params.sweep_ratio * features.in_7dgh.reversal_abuse
Section titled “gh.reversal_abuse”Repeated reversals or refunds · library G5 · Reversal fraud · alert about the customer · severity medium, score 45
Fires on a reversal when the customer has had at least max_reversals reversals (including this one) in 30 days.
| Parameter | Meaning | Default |
|---|---|---|
max_reversals |
Reversals in 30 days | 5 |
Features: reversals_30d (customer, count, type reversal, 30 days).
Explanation shown to analysts: {{features.reversals_30d}} reversals in 30 days.
Expression
txn.type == 'reversal' && features.reversals_30d >= double(params.max_reversals)gh.night_large_momo
Section titled “gh.night_large_momo”Large mobile money transfer at night · library G6 · Account takeover / social engineering · alert about the customer · severity medium, score 40
Fires on a mobile money outflow of at least threshold made between from_hour and to_hour (inclusive, workspace time).
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold (base currency) | 5,000 |
from_hour |
Night starts (hour) | 0 |
to_hour |
Night ends (hour) | 5 |
Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} sent by mobile money at {{txn.hour}}:00.
Expression
txn.channel == 'momo' && txn.direction == 'out' && txn.amount >= params.threshold && txn.hour >= int(params.from_hour) && txn.hour <= int(params.to_hour)gh.agent_peer_anomaly
Section titled “gh.agent_peer_anomaly”Agent cash-in volume far above peer agents · library G1 · Agent misuse / cash placement · scheduled, daily 05:00 · alert about the agent (its account, or the agent as a counterparty) · severity medium, score 55
Over the last window_days, totals the cash-ins at each agent (the counterparty of your customers’ cash_in transactions) and compares each agent with the others in its peer group, read from the attribute named in peer_group on the agent’s own account, then that account’s customer, then the cash-in itself (all when there is none). Fires when an agent’s volume is at least min_volume and at least zscore standard deviations above the average of at least min_peers other agents, its own volume left out of the average. The alert is about the agent’s account when the agent is one of your accounts, otherwise about the agent as a counterparty.
| Parameter | Meaning | Default |
|---|---|---|
window_days |
Window (days) | 7 |
zscore |
Standard deviations above the peers, at least | 3 |
min_peers |
Other agents in the peer group, at least | 10 |
min_volume |
Cash-in volume at or above (base currency) | 50,000 |
peer_group |
Peer group attribute (e.g. region or tier) | "region" |
Each hit records volume, cash_ins, peer_mean, peer_sd, zscore, peer_count for the explanation, as {{features.<name>}}, and peer_group as {{subject.<name>}}.
Explanation shown to analysts: Customers cashed in {{txn.base_currency}} {{features.volume}} at this agent in {{params.window_days}} days ({{features.cash_ins}} cash-ins): {{features.zscore}} standard deviations above the {{features.peer_count}} other agents in {{subject.peer_group}}, who average {{txn.base_currency}} {{features.peer_mean}}.
Runs the built-in scenario peer_volume_anomaly (scenario:peer_volume_anomaly) on the schedule daily 05:00, in your workspace’s time zone. See Scheduled rules.
gh.merchant_turnover
Section titled “gh.merchant_turnover”Merchant takings far above declared turnover · library I1 · Front business / merchant laundering · scheduled, daily 04:30 · alert about the customer · severity medium, score 55
Runs the same check as core.declared_profile, tuned for businesses and money in: over the last period_days, a business customer’s takings scaled to 30 days are more than multiplier times its declared monthly turnover, and at least min_volume. With occupation_caps empty, customers without a declared turnover in your base currency are skipped.
| Parameter | Meaning | Default |
|---|---|---|
period_days |
Period (days) | 30 |
multiplier |
Times the declared monthly turnover | 2 |
min_volume |
Takings at or above (base currency) | 20,000 |
direction |
Count money in, out, or both | "in" |
customer_types |
Customer types | ["business"] |
segments |
Segments (empty = all) | [] |
occupation_caps |
Expected monthly turnover by occupation, when none was declared (base currency) | {} |
Each hit records volume, monthly_volume, declared, ratio for the explanation, as {{features.<name>}}.
Explanation shown to analysts: Received {{txn.base_currency}} {{features.volume}} in {{params.period_days}} days (about {{txn.base_currency}} {{features.monthly_volume}} a month): {{features.ratio}}× the declared monthly turnover of {{txn.base_currency}} {{features.declared}}.
Runs the built-in scenario declared_volume (scenario:declared_volume) on the schedule daily 04:30, in your workspace’s time zone. See Scheduled rules.
gh.merchant_refund_ratio
Section titled “gh.merchant_refund_ratio”Merchant with many refunds or chargebacks · library I2 · Refund / chargeback abuse · scheduled, daily 05:30 · alert about the customer · severity medium, score 50
Over the last period_days, counts each merchant’s sales (incoming payment and transfer), refunds (outgoing refund) and chargebacks (every chargeback). Fires when it had at least min_sales sales and refunds plus chargebacks are more than max_ratio of them, by count. A merchant is a business customer, or any customer with an account of type merchant.
| Parameter | Meaning | Default |
|---|---|---|
period_days |
Period (days) | 30 |
max_ratio |
Refunds and chargebacks per sale, above (0.1 = 10%) | 0.1 |
min_sales |
Sales in the period, at least | 50 |
Each hit records sales, sales_amount, refunds, chargebacks, refund_amount, ratio for the explanation, as {{features.<name>}}.
Explanation shown to analysts: {{features.refunds}} refunds and {{features.chargebacks}} chargebacks against {{features.sales}} sales in {{params.period_days}} days (ratio {{features.ratio}}; {{txn.base_currency}} {{features.refund_amount}} paid back).
Runs the built-in scenario refund_ratio (scenario:refund_ratio) on the schedule daily 05:30, in your workspace’s time zone. See Scheduled rules.
gh.personal_business_volume
Section titled “gh.personal_business_volume”Personal account trading like a business · library I3 · Undeclared business / collection account · scheduled, daily 04:45 · alert about the customer · severity medium, score 45
Over the last period_days, fires on an individual customer who received incoming transfer, payment or deposit transactions from at least min_distinct_payers different counterparties, totalling at least min_inflow. Cash-ins aren’t counted.
| Parameter | Meaning | Default |
|---|---|---|
period_days |
Period (days) | 30 |
min_distinct_payers |
Distinct payers, at least | 40 |
min_inflow |
Inflow at or above (base currency) | 20,000 |
Each hit records payers, payments, inflow for the explanation, as {{features.<name>}}.
Explanation shown to analysts: {{features.payers}} different payers made {{features.payments}} payments totalling {{txn.base_currency}} {{features.inflow}} into this personal account in {{params.period_days}} days.
Runs the built-in scenario distinct_payers (scenario:distinct_payers) on the schedule daily 04:45, in your workspace’s time zone. See Scheduled rules.
gh.border_region
Section titled “gh.border_region”Activity in a border or high-risk area · library E4 · Border / cash courier risk · alert about the customer · severity medium, score 40
Fires on a transaction of at least min_amount, on one of channels (empty means all), whose region, district, town or branch_region attribute names an area in regions or on the workspace list high_risk_regions, ignoring case.
| Parameter | Meaning | Default |
|---|---|---|
regions |
Areas (towns, districts or regions) | ["Aflao", "Akanu", "Kpetoe", "Elubo", "Sampa", "Paga", "Hamile", "Kulungugu", "Tumu", "Bawku", "Nkwanta"] |
channels |
Only these channels (empty = all) | ["cash", "agent", "branch", "momo"] |
min_amount |
Amount at or above (base currency) | 5,000 |
The default areas are illustrative crossing points, not a regulatory list. Replace them with your own risk assessment.
Explanation shown to analysts:
{{txn.base_currency}} {{txn.amount}} by {{txn.channel}} in {{= ['region', 'district', 'town', 'branch_region'].filter(k, k in txn.attributes).map(k, string(txn.attributes[k])).join(', ') }}, a border or high-risk area.Expression
txn.amount >= params.min_amount && (size(params.channels) == 0 || txn.channel in params.channels) && ['region', 'district', 'town', 'branch_region'].exists(k, k in txn.attributes && (params.regions.exists(r, r.lowerAscii() == string(txn.attributes[k]).lowerAscii()) || lists.high_risk_regions.exists(r, r.lowerAscii() == string(txn.attributes[k]).lowerAscii())))The gh-bank pack (10 rules)
Section titled “The gh-bank pack (10 rules)”Typologies for Ghanaian universal and rural banks: branch cash and structuring across branches, foreign-currency cash, cheques, GhIPSS instant (GIP) transfers, trade payments and correspondent exposure, plus the merchant and border-area rules shared with gh-psp. Thresholds are starting points: calibrate them against your own data, and confirm every reporting threshold with current Bank of Ghana and FIC guidance before go-live.
Version 1.1.0 lets GB2 read amounts with each currency’s own decimals and adds an optional base-currency threshold, and makes GB5 match the correspondent list ignoring capitals.
ghb.branch_structuring
Section titled “ghb.branch_structuring”Cash deposits split across branches · library GB1 · Structuring across branches · scheduled, hourly :20 · alert about the customer · severity high, score 75
Over the last window_hours, finds customers who made at least min_deposits cash deposits (deposit or cash_in through branch or cash), each between margin below threshold and just under it, at at least min_branches different branches, together at least threshold. The branch is the deposit’s branch or branch_code attribute; deposits without one aren’t counted.
| Parameter | Meaning | Default |
|---|---|---|
window_hours |
Window (hours) | 48 |
threshold |
Reporting threshold being avoided (base currency) | 50,000 |
margin |
Just below = within (0.15 = 15%) of the threshold | 0.15 |
min_branches |
Different branches, at least | 2 |
min_deposits |
Deposits, at least | 3 |
Each hit records deposits, branches, total, largest for the explanation, as {{features.<name>}}, and branch_list as {{subject.<name>}}.
The GHS 50,000 default mirrors the FIC cash threshold as we understand it. Confirm it with the FIC.
Explanation shown to analysts: {{features.deposits}} cash deposits just below {{txn.base_currency}} {{params.threshold}} at {{features.branches}} branches ({{subject.branch_list}}) within {{params.window_hours}} hours, together {{txn.base_currency}} {{features.total}} (largest {{txn.base_currency}} {{features.largest}}).
Runs the built-in scenario branch_structuring (scenario:branch_structuring) on the schedule hourly :20, in your workspace’s time zone. See Scheduled rules.
ghb.fx_cash
Section titled “ghb.fx_cash”Large foreign-currency cash · library GB2 · Foreign-currency cash reporting · alert about the customer · severity medium, score 40
Fires on a cash deposit, withdrawal, cash_in or cash_out through cash, branch or atm in one of currencies (never your base currency) when the amount in that currency, with that currency’s own decimals, is at least threshold, or, when threshold_base is above 0, when its base-currency value is at least threshold_base.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Threshold in the transaction’s currency (major units, with that currency’s decimals) | 10,000 |
threshold_base |
Or at or above this in the base currency (0: off) | 0 |
currencies |
Currencies | ["USD", "EUR", "GBP"] |
The 10,000 default applies to each currency as is; set threshold_base for an “or its equivalent” threshold. Confirm the threshold, and whether it is per currency or a US-dollar equivalent, with the FIC.
Explanation shown to analysts: Cash {{txn.type}} in {{txn.currency}} worth {{txn.base_currency}} {{txn.amount}}: at or above the {{params.threshold}} foreign-currency cash threshold. Confirm the threshold with current FIC guidance.
Expression
txn.currency in params.currencies && txn.currency != txn.base_currency && txn.type in ['deposit', 'withdrawal', 'cash_in', 'cash_out'] && txn.channel in ['cash', 'branch', 'atm'] && (txn.amount_original >= params.threshold || (params.threshold_base > 0.0 && txn.amount >= params.threshold_base))ghb.cheque_then_cash
Section titled “ghb.cheque_then_cash”Cheque credits withdrawn in cash · library GB3 · Cheque fraud / kiting · alert about the account · severity high, score 65
Fires on an outgoing withdrawal or cash_out when at least min_cheques arrived on the account through the cheque channel in the last 48 hours and the withdrawal is at least ratio of it. The alert is about the account.
| Parameter | Meaning | Default |
|---|---|---|
min_cheques |
Cheque credits in 48 hours, at least (base currency) | 20,000 |
ratio |
Cash withdrawn as share of the cheque credits | 0.7 |
Features: cheque_in_48h (account, sum, in, channel cheque, 2 days).
Explanation shown to analysts: {{txn.base_currency}} {{features.cheque_in_48h}} in cheques was credited in 48 hours and {{txn.base_currency}} {{txn.amount}} is now withdrawn in cash.
Expression
txn.direction == 'out' && txn.type in ['withdrawal', 'cash_out'] && features.cheque_in_48h >= params.min_cheques && txn.amount >= params.ratio * features.cheque_in_48hghb.gip_new_payee_large
Section titled “ghb.gip_new_payee_large”Large instant transfer to a new payee · library GB4 · Authorised push-payment fraud · alert about the customer · severity medium, score 50
Fires on an outgoing instant_payment (GhIPSS GIP) of at least threshold to a counterparty the customer has never dealt with before.
| Parameter | Meaning | Default |
|---|---|---|
threshold |
Amount at or above (base currency) | 20,000 |
Explanation shown to analysts: First instant transfer to this beneficiary: {{txn.base_currency}} {{txn.amount}}.
Expression
txn.direction == 'out' && txn.channel == 'instant_payment' && txn.has_counterparty && counterparty.is_new_for_customer && txn.amount >= params.thresholdghb.correspondent_high_risk
Section titled “ghb.correspondent_high_risk”Cross-border flow through a high-risk correspondent · library GB5 · Correspondent banking exposure · alert about the customer · severity high, score 60
Fires on a transaction of at least threshold, in either direction, with a counterparty in a country other than home_country whose provider is on the workspace list high_risk_correspondents.
| Parameter | Meaning | Default |
|---|---|---|
home_country |
Home country (ISO code) | "GH" |
threshold |
Amount at or above (base currency) | 10,000 |
List entries are matched ignoring capitals and surrounding spaces (since 1.1.0). Maintaining the list is your institution’s responsibility; confirm the expected scope with the Bank of Ghana.
Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} {{txn.direction}} with {{txn.counterparty_country}} through {{counterparty.provider}}, on the workspace’s high-risk correspondent list.
Expression
txn.counterparty_country != '' && txn.counterparty_country != params.home_country && counterparty.provider != '' && lists.high_risk_correspondents.exists(c, c.trim().lowerAscii() == counterparty.provider.lowerAscii()) && txn.amount >= params.thresholdghb.trade_sector_mismatch
Section titled “ghb.trade_sector_mismatch”Trade payments abroad outside the customer’s line of business · library I4 · Trade-based money laundering · scheduled, daily 05:15 · alert about the customer · severity medium, score 55
Over the last window_days, takes each business customer’s completed payments abroad (country other than home_country) of at least threshold_amount that carry a sector or isic attribute. The customer’s industry_code picks the longest matching key in sector_map; a payment whose sector doesn’t start with one of that key’s values is a mismatch. Fires when a customer’s mismatched payments total at least min_total. Customers whose industry isn’t in the map, and payments without a sector, aren’t judged.
| Parameter | Meaning | Default |
|---|---|---|
window_days |
Window (days) | 7 |
home_country |
Home country (ISO code) | "GH" |
threshold_amount |
Payments at or above (base currency) | 50,000 |
min_total |
Total at or above (base currency) | 100,000 |
sector_map |
Customer industry (ISIC prefix) → sectors it plausibly pays (ISIC prefixes) | Eight industries, below |
Default sector_map, customer industry → sectors it plausibly pays:
{ "01": ["01", "02", "10", "20", "28", "46", "49", "52"], "10": ["01", "03", "10", "11", "17", "20", "22", "28", "46", "49", "52"], "41": ["16", "22", "23", "24", "25", "27", "28", "41", "42", "43", "46", "49", "71", "77"], "42": ["16", "22", "23", "24", "25", "27", "28", "41", "42", "43", "46", "49", "71", "77"], "46": ["10", "11", "13", "14", "15", "17", "20", "21", "22", "23", "25", "26", "27", "28", "29", "46", "49", "50", "52"], "47": ["10", "13", "14", "15", "20", "21", "26", "27", "46", "47", "49", "52"], "62": ["26", "58", "61", "62", "63", "70", "72"], "86": ["21", "26", "32", "46", "86"]}Each hit records payments, amount, countries for the explanation, as {{features.<name>}}, and industry, sectors, countries_list as {{subject.<name>}}.
The default map is a short illustration covering eight industries. Extend it for your book before relying on this rule.
Explanation shown to analysts: {{features.payments}} payments abroad totalling {{txn.base_currency}} {{features.amount}} in {{params.window_days}} days to sector(s) {{subject.sectors}} in {{subject.countries_list}}, outside what the sector map expects for the customer’s industry ({{subject.industry}}).
Runs the built-in scenario trade_sector_mismatch (scenario:trade_sector_mismatch) on the schedule daily 05:15, in your workspace’s time zone. See Scheduled rules.
Also in this pack, described above under gh-psp:
| Key | Library | Name |
|---|---|---|
gh.merchant_turnover |
I1 | Merchant takings far above declared turnover |
gh.merchant_refund_ratio |
I2 | Merchant with many refunds or chargebacks |
gh.personal_business_volume |
I3 | Personal account trading like a business |
gh.border_region |
E4 | Activity in a border or high-risk area |