Skip to main content

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:
FieldTypeDescription
transaction_idstringUnique transaction identifier
amountnumberTransaction amount
currencystringCurrency code (e.g., “USD”)
sourcestringSource account/entity
destinationstringDestination account/entity
timestampstring (RFC3339)When the transaction occurred
descriptionstringFree-text description of the transaction
metadataobjectArbitrary 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.
OperatorDSL SyntaxInternal NameDescription
Equal==eqTrue if left equals right
Not Equal!=neTrue if left does not equal right
Greater Than>gtTrue if left is greater than right
Greater Than or Equal>=gteTrue if left is greater than or equal to right
Less Than<ltTrue if left is less than right
Less Than or Equal<=lteTrue 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.
OperatorBehavior
andTrue only if both the left and right conditions are true.
orTrue 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:
when A or B and C
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.