Skip to content

1 · The alert and the fixtures

What this step adds: the input. No model, no tools — just the shape of an alert and the synthetic world it refers to.

A fixture is a fixed piece of test data that stands in for a real system. Part 2 has five: alerts, customers, devices, transactions, and similar past cases. They are ordinary JSON files.

Building the data first is not throat-clearing. The data is a design document. Deciding that an alert carries a device_id, a payee, and a raised_at timestamp is deciding what the system can reason about. Anything you leave out of the fixture is a question the agent can never answer.

Why synthetic, and why it isn’t a limitation

Section titled “Why synthetic, and why it isn’t a limitation”

You cannot practise fraud triage on real fraud data. Nobody will give it to you, and if they did you would not want it on your laptop.

But there is a stronger reason to invent it. Real data makes it impossible to tell whether your system works. With three fixtures you designed, you know what the answer should be for each one, so you can tell the difference between “the agent got it right” and “the agent got lucky”. That is the entire basis of an eval.

The three alerts are built to land on three different outcomes:

Alert Shape Should be
ALERT-001 New device, cross-border, high value, contact just changed ESCALATE
ALERT-002 Known device, home country, normal amount CLEAR
ALERT-003 Known device, home country, but a crypto merchant and 3× the usual amount MONITOR

The third one is the important one. A test set where everything is obvious tests nothing. ALERT-003 is deliberately ambiguous — the signals genuinely point both ways — so that when the system produces a middling score with low confidence, that is the correct behaviour rather than a failure.

The alert fixture, trimmed to one entry:

{
"alert_id": "ALERT-001",
"rule": "HIGH_VALUE_NEW_PAYEE_CROSS_BORDER",
"customer_id": "CUST-1001",
"amount": 18500.0,
"currency": "AUD",
"raised_at": "2026-07-18T09:14:00",
"device_id": "DEV-NEW-77",
"location": { "city": "Bucharest", "country": "RO" },
"payee": { "name": "NOVA HOLDINGS SRL", "new_payee": true }
}

Two details worth copying into your own work:

rule names the detector that fired. In a real bank the agent is not the first thing to see the transaction — a rules engine raised the alert, and which rule fired is context the agent should have.

raised_at is a real timestamp, not “now”. The customer’s contact_last_changed date is compared against it. If the fixture used the current time, that comparison would silently change meaning every day you ran it, and your eval would rot.

There is nothing to run yet — that is the point of the step. But you can look at what you’re working with:

Terminal window
cd part2
python3 -c "import json; print(json.load(open('fraud_triage/fixtures/alerts.json'))[0])"

You have data and no system. Next: the smallest possible thing that could be called an agent — one model call, no tools — which is also the fastest way to prove why the tools are necessary.