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
| Payment rail | Payments |
|---|---|
| str | u32 |
| "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
| Lifecycle event | Events |
|---|---|
| str | u32 |
| "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}