Write Your First Rule
Build, run, and validate a realistic fraud rule end to end, then extend it with an OR branch and inspect every result field.
This walkthrough takes a single rule from YAML to an evaluated decision. You will write a minimal rule file, evaluate a two-record batch, read the result fields, validate the routing, and then extend the rule with an or branch.
Goal
Build a rule named high_amount_emulator that blocks any transaction over 2000 made from an emulator device, with severity HIGH and a score weight of 40.
Prerequisites
- A built
blazerulesPython module on yourPYTHONPATH. See Quickstart or Installation. - A text editor for the YAML file.
Step 1 — Write a minimal rule file
Create my_rules.yaml. It declares the rule format version, a few optional field hints, and one rule:
schema_version: "2.1"
fields:
card_token: {type: entity_key, nullable: false}
amount: {type: float32, nullable: false}
device_type:
type: categorical
values: [ios, android, web, emulator]
ruleset:
name: First Rule
version: "1.0.0"
rules:
- id: high_amount_emulator
action: block
severity: HIGH
weight: 40
conditions:
and:
- field: amount
op: gt
value: 2000
- field: device_type
op: eq
value: emulatorThe fields: block is optional. It declares card_token as an entity key and pins device_type to a closed set of categorical values. Without these hints, BlazeRules infers referenced fields from the first batch.
Rules can load before a schema existsYou do not need to define a schema up front. When you call
load_rules(...)the rules are parsed, and the first evaluated batch samples the rule-referenced fields, infers their types, binds the schema, then compiles and activates the rules. Thefields:hints above simply remove ambiguity for entity keys and categoricals.
Step 2 — Prepare a small batch
Create two newline-delimited JSON records — one that should match the rule, one that should not:
{"card_token":"card_1","amount":2500.0,"device_type":"emulator"}
{"card_token":"card_2","amount":50.0,"device_type":"ios"}The first record (amount 2500 on an emulator) satisfies both conditions. The second (amount 50 on ios) satisfies neither.
Step 3 — Evaluate the batch
import blazerules
config = blazerules.EngineConfig()
config.output_detail = blazerules.OutputDetail.DECISIONS
engine = blazerules.RuleEngine(config)
engine.load_rules("my_rules.yaml")
payload = b"""
{"card_token":"card_1","amount":2500.0,"device_type":"emulator"}
{"card_token":"card_2","amount":50.0,"device_type":"ios"}
"""
result = engine.evaluate_ndjson(payload)
print("records: ", result.n_records)
print("matched: ", result.n_matched)
print("decisions: ", result.decisions)
print("decision codes: ", result.decision_codes)
print("scores: ", result.scores)
print("winning rules: ", result.winning_rule_ids)
print("match counts: ", result.match_counts)Expected output
n_recordsis2.n_matchedis1— only the first record matched a rule.decisions[0]is the block verdict anddecisions[1]is the defaultapprove— no rule fired on the second record, so it falls through to the ruleset default.winning_rule_ids[0]ishigh_amount_emulator; for record 1 there is no winning rule.match_countsreportshigh_amount_emulatormatched one record.
The first record's score includes the rule's weight (40). The second record's score remains 0.
Step 4 — Validate the routing
Rather than looping over every record in Python, ask the result for grouped indices. This is the idiomatic way to route a batch:
blocked = result.indices_for_decision("BLOCK")
approved = result.indices_for_decision("APPROVE")
not_appr = result.indices_for_not_decision("APPROVE")
groups = result.grouped_decision_indices()
print("blocked indices: ", blocked) # -> [0]
print("approved indices:", approved) # -> [1]indices_for_decision("BLOCK") returns the row positions that resolved to a block; indices_for_not_decision("APPROVE") returns everything that did not approve. grouped_decision_indices() returns every decision bucket at once — convenient for fanning a batch out to different downstream sinks.
Step 5 — Extend with an OR branch
Suppose you also want to catch high-value transactions from outside your core markets. Add an or branch so the rule fires when the amount is high and the transaction is either from an emulator or from a country outside US/GB:
conditions:
and:
- field: amount
op: gt
value: 2000
- or:
- field: device_type
op: eq
value: emulator
- field: country_code
op: not_in
values: [US, GB]Logical forms nest freely: and, or, and not can wrap any condition or each other. Add country_code to your records (and optionally to the fields: hints as a categorical) and re-run Step 3 to see the broader rule in action.
Where to go next
The full YAML structure: conditions, nesting, SQL form, and per-rule fields.
All 50 operators, grouped by family, with copy-pasteable YAML.
How winning rules, precedence, weights, and risk bands combine.
The vocabulary behind rules, decisions, and batches.