Documentation Index
Fetch the complete documentation index at: https://docs.finwatch.finance/llms.txt
Use this file to discover all available pages before exploring further.
This section provides a deep dive into every mechanism available for constructing conditions.
2.1 Accessing Transaction Data
Transaction data is represented as a flat or nested JSON object. You access fields using dot notation.
Top-level fields: These are the standard fields on the transactions table:
| Field | Type | Description |
|---|
transaction_id | string | Unique transaction identifier |
amount | number | Transaction amount |
currency | string | Currency code (e.g., “USD”) |
source | string | Source account/entity |
destination | string | Destination account/entity |
timestamp | string (RFC3339) | When the transaction occurred |
description | string | Free-text description of the transaction |
metadata | object | Arbitrary JSON metadata |
Accessing nested metadata: The metadata field is a JSON object that can contain any custom data your system sends. You access its properties with dot notation:
// Access a top-level field
when amount > 10000
// Access a nested metadata field
when metadata.destination_country == "IR"
// Access deeply nested metadata
when metadata.device.fingerprint == "abc123"
How it works internally: The interpreter uses a dig() function that splits the path by . and traverses the JSON tree. If any part of the path does not exist, the condition evaluates to false (it does not throw an error).
Pitfall: If a field is missing from the transaction data, the condition silently fails. This is by design — it prevents rules from crashing on incomplete data. But it means a typo in a field name (e.g., ammount instead of amount) will cause a rule to never trigger without any error message.
2.2 Comparison Operators
These operators form the backbone of simple conditions. Each compares a field’s value against a target value.
| Operator | DSL Syntax | Internal Name | Description |
|---|
| Equal | == | eq | True if left equals right |
| Not Equal | != | ne | True if left does not equal right |
| Greater Than | > | gt | True if left is greater than right |
| Greater Than or Equal | >= | gte | True if left is greater than or equal to right |
| Less Than | < | lt | True if left is less than right |
| Less Than or Equal | <= | lte | True if left is less than or equal to right |
Type Coercion Rules: The interpreter attempts to compare values numerically first. If both sides can be parsed as float64, a numeric comparison is performed. If not, both sides are converted to strings and compared lexicographically. This means:
amount > 1000 -> Numeric comparison (correct).
currency == "USD" -> String comparison (correct).
"100" > "99" -> String comparison ("1" < "9", so this is false). Be careful with stringified numbers.
Important: For string comparisons, only eq and ne are supported. Using >, <, >=, or <= on non-numeric strings will return false.
Examples:
// Numeric: Check if amount exceeds a threshold
when amount > 10000
// String: Check for a specific currency
when currency == "USD"
// String: Exclude a specific status
when metadata.status != "verified"
// Numeric: Check a range (using two conditions)
when amount >= 5000
and amount <= 50000
2.3 Logical Operators: and / or
Logical operators combine multiple conditions into a single boolean result.
| Operator | Behavior |
|---|
and | True only if both the left and right conditions are true. |
or | True if either the left or right condition is true. |
Syntax:
// AND: Both conditions must be true
when amount > 10000
and currency == "USD"
// OR: At least one condition must be true
when hour_of_day(timestamp) >= 23
or hour_of_day(timestamp) <= 4
Operator Precedence: Currently, and and or have equal precedence and are evaluated left-to-right. There is no implicit grouping that makes and bind tighter than or.
For the rule:
This is parsed as (A or B) and C, not A or (B and C). This is a critical detail. If you need specific grouping, structure your rule conditions carefully or split into multiple rules.
Multi-line conditions: The parser supports conditions that span multiple lines. You can place and/or at the beginning of a new line for readability:
when metadata.destination_country in $sanctioned_countries
and amount > 10000
and currency == "USD"
Short-circuit evaluation: The interpreter uses short-circuit logic:
- For
and: If the left side is false, the right side is not evaluated.
- For
or: If the left side is true, the right side is not evaluated.
This is important for performance — expensive aggregate checks placed on the right side of an and will be skipped if a cheap check on the left already fails.
2.4 Set Operators: in
The in operator checks whether a field’s value exists within a given list. This is essential for checking against blacklists, whitelists, or known-bad values.
Syntax:
// Check against a variable list
when metadata.destination_country in $sanctioned_countries
// Check against an inline array
when metadata.mcc in ("7995", "6012", "4829")
How it works: The interpreter converts the field value to a string and iterates through the list, comparing each element as a string. This means in is a string membership check.
Example: Sanctioned Country Check
rule SanctionedCountryCheck {
description "Blocks transactions to sanctioned countries."
when metadata.destination_country in $sanctioned_countries
then block
score 1.0
reason "Destination country is on global sanctions list"
}
Example: Suspicious MCC Check
rule SuspiciousMCCCheck {
description "Flags transactions with suspicious merchant category codes."
when metadata.mcc in ("7995", "6012", "4829")
then review
score 0.4
reason "Transaction uses a high-risk merchant category code"
}
2.5 Pattern Operators: regex and not_regex
The regex operator performs regular expression matching on string fields. This is powerful for detecting suspicious patterns in free-text fields like transaction descriptions.
Syntax:
when <field> regex "<pattern>"
when <field> not_regex "<pattern>"
Key Facts:
- The pattern is a standard Go regular expression (RE2 syntax).
regex returns true if the pattern matches anywhere in the field value.
not_regex returns true if the pattern does not match.
- The pattern must be a double-quoted string.
- Use
(?i) at the start for case-insensitive matching.
Example: Detecting Suspicious Descriptions
rule SuspiciousDescriptionCheck {
description "Detects suspicious keywords or patterns in transaction descriptions."
when description regex "(?i)(btc|bitcoin|crypto|wallet|transfer|gift.?card|western.?union)"
and amount > 1000
then review
score 0.2
reason "Suspicious description pattern."
}
Pitfall: Complex regex patterns can be expensive. Avoid catastrophic backtracking patterns like (.+)+ on large strings. Keep patterns as simple and specific as possible.