Configure a Simulation#

A FraudTwin simulation is controlled by a YAML configuration. Start with the small example configuration, change a few values, and generate a new deterministic run.

1. Choose a configuration#

FraudTwin includes a small default configuration inside the installed package. Use it directly here; custom YAML files can be passed to generate() when needed.

import fraudtwin

print(fraudtwin.__version__)
print("Using the bundled minimal configuration")
0.34.0
Using the bundled minimal configuration

2. Generate the configured world#

Generation stays in memory by default, so this does not create files.

data = fraudtwin.generate()
print(data.run_id)
RUN-19652188a1efbe6c

3. See what the configuration produced#

The manifest records the seed, configuration hash, and generated counts. For a custom file, use fraudtwin.generate("my-config.yaml").

print("Seed:", data.manifest.seed)
print("Configuration hash:", f"{data.manifest.scenario_config_hash[:12]}...")
print("Customers:", data.manifest.entity_counts["customers"])
print("Payments:", data.manifest.event_counts["payments"])
Seed: 42
Configuration hash: 19652188a1ef...
Customers: 10
Payments: 100

4. Compare payment rails#

The same configured world can contain card and PIX-like payments. Each rail has its own lifecycle events, which are useful when building event-based fraud pipelines.

Card, PIX, and account transfers are separate payment rails. This table shows how many payments were generated for each rail.

import polars as pl

payments_by_rail = (
    pl.DataFrame({"Payment rail": [p.payment_rail for p in data.behavior.payments]})
    .group_by("Payment rail")
    .len()
    .rename({"len": "Payments"})
    .sort("Payments", descending=True)
)

payments_by_rail
shape: (3, 2)
Payment railPayments
stru32
"CARD"63
"ACCOUNT_TRANSFER"25
"PIX"12

Each payment can produce several events as it moves through its rail. These are the eight most common lifecycle events in this run.

events = [event.event_type for event in data.behavior.payment_events]
event_counts = (
    pl.DataFrame(
        {
            "Lifecycle event": events,
        }
    )
    .group_by("Lifecycle event")
    .len()
    .rename({"len": "Events"})
    .sort("Events", descending=True)
    .head(8)
)

event_counts
shape: (8, 2)
Lifecycle eventEvents
stru32
"CARD_PAYMENT_INITIATED"63
"CARD_AUTHORIZATION_REQUESTED"63
"CARD_AUTHORIZED"50
"CARD_CAPTURED"47
"CARD_CLEARED"47
"CARD_SETTLED"47
"TRANSFER_COMPLETED"25
"CARD_DECLINED"13

5. Reproducibility#

The same configuration and seed produce the same run ID. Changing the seed creates a new reproducible run.

same_run = fraudtwin.generate()
print(data.run_id == same_run.run_id)
True

Record the generated shape and tutorial contract.#

summary = {
    "payments": len(data.behavior.payments),
    "events": len(data.behavior.payment_events),
}
print(summary)
assert summary["payments"] >= 0
{'payments': 100, 'events': 433}

Inspect stable payment identities.#

ids = [item.payment_id for item in data.behavior.payments]
assert len(ids) == len(set(ids))
print({"unique_payment_ids": len(ids)})
{'unique_payment_ids': 100}

Record the generated shape and tutorial contract.#

summary = {
    "payments": len(data.behavior.payments),
    "events": len(data.behavior.payment_events),
}
print(summary)
assert summary["payments"] >= 0
{'payments': 100, 'events': 433}

Inspect stable payment identities.#

ids = [item.payment_id for item in data.behavior.payments]
assert len(ids) == len(set(ids))
print({"unique_payment_ids": len(ids)})
{'unique_payment_ids': 100}