Rule engine
Ce contenu n’est pas encore disponible dans votre langue.
In short
Section titled “In short”A rule is a yes-or-no question SpeculaPro asks about each transaction, such as “Is this a cash deposit above GHS 50,000?”. When the answer is yes, the rule fires (records a hit) and takes an action: raise an alert, ask for review, block the payment, and so on.
Rules in SpeculaPro are data, not code. Changing a threshold or adding a rule does not need a new software release. You edit the rule, someone else approves it, and it takes effect.
Every rule has three parts:
- the logic, a short expression that rarely changes,
- the features it needs: named running totals from the feature store, and
- the parameters, the numbers you tune for your institution.
Here is rule B1, Repeated deposits just below a threshold, as it ships in the core pack:
Features (declared by the rule) cash_in_count_48h count of this account's cash-ins in 48h cash_in_sum_48h sum of this account's cash-ins in 48h
Logic Parameters (you tune)─────────────────────────────────────────────────────── ─────────────────────txn.type == 'cash_in' threshold: 10000 && txn.amount >= params.threshold * (1.0 - params.margin) margin: 0.1 && txn.amount < params.threshold min_count: 3 && features.cash_in_count_48h >= double(params.min_count) && features.cash_in_sum_48h >= params.thresholdRead the logic as: “if this is a cash-in just under the threshold (within 10% of it), and the account has had at least 3 cash-ins in 48 hours adding up to more than the threshold, then fire.” Amounts are in the base currency’s major units: 10000 means GHS 10,000.
The full syntax is in Managing rules.
The rule language: CEL
Section titled “The rule language: CEL”Rules are written in CEL, the Common Expression Language (used through Google’s cel-go library). You will rarely need to write it yourself: the shipped rule packs provide ready-made rules that you tune through parameters, and the visual rule builder writes CEL for you from conditions you pick from lists. Power users can write custom rules in CEL directly.
CEL was chosen because it is:
| Property | What it means in practice |
|---|---|
| Sandboxed | A rule cannot read files, call the network or change data. It can only answer its question. |
| Always finishes | CEL has no unbounded loops, and every evaluation has a cost limit, so a badly written rule fails on its own instead of freezing the system. |
| Checked when saved | Typos and type mistakes (comparing an amount to a country, say) are caught when you save the rule, not at 2 a.m. on a live payment. |
| Fast | Each rule is compiled once and then evaluated in microseconds. |
What a rule can see
Section titled “What a rule can see”| Name in the rule | Contents |
|---|---|
txn |
The transaction: amount (base currency, major units), amount_minor, currency, direction, type, channel, status, country, counterparty_country, mcc, hour (0–23) and weekday (1 = Monday) in your workspace’s time zone, has_counterparty, attributes, and the real-time signals has_device, has_location, country_is_new, ip_is_new, km_from_last, kmh_from_last, minutes_since_last_location |
customer |
type, status, risk_rating, segment, kyc_level, age_days, designations (active risk designations), is_pep, is_sanctioned, is_high_risk, is_stub, attributes, hours_since (hours since the latest account event of each type), and last_change and hours_since_last_change (the most recent profile or credential change, and how long ago) |
account |
type, status, provider, age_days, is_stub, attributes |
counterparty |
country, type, provider, is_own_account (the “outside” party is really one of your accounts), designations, is_new_for_customer, customer_first_seen_hours |
device |
known, is_new (for this customer), customer_age_hours |
features |
The running totals the rule declares, by name. See the features reference |
params |
This rule version’s parameter values |
lists |
Your lists, by key: lists.high_risk_countries… |
The parties’ details (risk rating, designations, age) are read when the transaction is evaluated: a few seconds after it arrives for post-event rules, or from a short-lived cache (30 seconds by default) in a real-time decision. The variables are built the same way in both modes, so a rule means the same thing in each. Every field is described in the CEL reference.
When a rule runs: three modes
Section titled “When a rule runs: three modes”| Mode | Runs | Typical use | Example | Status |
|---|---|---|---|---|
post_event |
Seconds after each transaction is received, in the stream evaluator; can look back 180 days | Most AML monitoring | Several deposits just below the reporting threshold this week | Runs today |
realtime |
Inside the decision API, before the payment is approved; can look back 180 days too | Fraud | New device + new payee + high value | Runs today |
scheduled |
On a timetable (hourly, daily or weekly) in the worker role, over the stored history of many transactions; can look back up to 92 days |
Network, peer-group and profile patterns | Money going round in a circle across several accounts | Runs today |
A scheduled rule must have a schedule. The database will not accept one without it.
Scheduled rules work differently from the other two. Their logic isn’t a CEL expression but a
scenario: a fixed, reviewed SQL query built into SpeculaPro, which a version names as
scenario:<key>, for example scenario:circular_flow. Workspaces tune its parameters, which are
checked against hard limits and passed to the query as values, never as SQL. Each run reads one
rule window of completed transactions from Postgres, with a 5-minute limit, and its hits go
through the same suppressions and alert merging as post-event hits. Runs are leased, so one worker
runs each window however many are running; hit IDs are derived from the version, subject and
window, so a retried run never duplicates a hit; and after downtime a single run catches up on the
missed span, up to 31 days. See Scheduled rules.
A rule’s mode is chosen when it is created and never changes. Post-event rules never run inside a decision, and real-time rules never run on transactions that only came through the ingest API. A transaction that came through a decision is monitored post-event afterwards, so both kinds see it, and the post-event rules reuse the feature values the decision read where they ask for the same ones.
What a rule produces
Section titled “What a rule produces”Every rule version declares three outputs:
| Output | Values | Used for |
|---|---|---|
| Severity | low, medium, high, critical |
How urgent the resulting alert is |
| Score | A number | Ranking alerts and cases in queues |
| Action | Real-time: allow, review, block, step_up, tag, score_only. Post-event and scheduled: alert, tag, score_only. A version whose action doesn’t fit its rule’s mode is refused |
What happens when the rule fires |
The action decides whether a live hit reaches an analyst:
| Action | Hit recorded | Alert raised |
|---|---|---|
alert, review, block, step_up |
Yes | Yes |
tag, score_only, allow |
Yes | No |
So tag and score_only rules build up evidence (every hit is stored with its explanation and feature values, and can be queried later) without adding to the alert queue. A version saved without an action is treated as alert.
Each version also carries an explanation template, the sentence analysts see on the hit and the alert. Placeholders such as {{features.cash_in_sum_48h}}, {{txn.amount}}, {{txn.base_currency}} and {{params.threshold}} are filled in with the real values, with thousands separators: “4 cash-ins totalling GHS 38,600 in 48h; this one (GHS 9,700) is just below the GHS 10,000 threshold.” See Explanations.
Versions: nothing is ever overwritten
Section titled “Versions: nothing is ever overwritten”Editing a rule always creates a new version. Once a version is approved its logic and parameters are frozen by the database itself, and every hit points to the exact version that produced it. A new version can run in shadow next to the live one, recording what it would have flagged without creating alerts.
This is covered in full in Rules and versions.
Backtesting
Section titled “Backtesting”Before a version is approved, it can be run over up to 180 days of the workspace’s own history and compare what it would have flagged with what the live version did, without raising any alert. It works for real-time and post-event rules. See Backtesting.
Rule packs
Section titled “Rule packs”Library rules are delivered in packs, grouped by country and sector:
| Pack | For |
|---|---|
core |
Every institution |
gh-psp |
Ghanaian payment service providers and mobile money operators |
fraud |
Anyone using the real-time decision API |
gh-bank |
Ghanaian banks |
Four packs ship today, built into the program: core 1.1.0 (24 rules), fraud 1.1.0 (11 real-time
rules), gh-psp 1.2.0 (14 rules) and gh-bank 1.0.0 (10 rules). gh-psp and gh-bank share four
rules, so there are 55 different rules. See Pack contents for
exactly which rules.
Installing a pack copies its rules into your workspace as approved first versions, disabled, so nothing starts alerting until you choose. See Managing rules.
Packs are versioned, and SpeculaPro records a SHA-256 digest of each pack’s contents when it is installed. A new pack version reaches an installation in one of two ways: built into a SpeculaPro release, or as a signed pack file, which SpeculaPro verifies (Ed25519, with SpeculaPro’s pack-signing keys, separate from its licence keys) before reading anything in it. Stored pack files are verified again every time they are read. See Signed pack updates.
Once installed, you tune the rules as your own through new versions. The pack’s original definition stays recorded, so you can always compare against it. Upgrading a pack adds its new rules, disabled, and for each rule whose packaged definition changed drafts a new version with your tuning carried over, which a second person must approve before it runs. Rules whose logic you changed are left alone.
Why it’s built this way
Section titled “Why it’s built this way”- Rules as data let compliance teams respond to a new typology in hours, without waiting for a software release.
- Logic and parameters are separated, so most tuning is a safe change of numbers, not a rewrite.
- Frozen versions mean an alert raised in March can be explained in November, even if the rule has changed five times since.
What this means for you
Section titled “What this means for you”- Compliance: you own the thresholds. The library defaults are placeholders and must be calibrated for your customers. See the rule library.
- Separation of duties: the person who writes or edits a rule cannot approve it. See Rules and versions.
- Self-hosted operators: packs arrive with each SpeculaPro release, or as signed pack files you load on the Rules screen or with
speculapro packs install. SpeculaPro refuses any file whose signature doesn’t check out.