2dfa96939e
The first environment shipped through .claude/workflows/new-environment.js: specification, three adversarial reviews (all 'fixable', none fatal), the Python environment, the TypeScript port, captured rollouts, and the demo page. Eleven agents, no errors. The proof that the platform scales is one line long. Alert Triage has a completely different shape from Word Five — JSON actions, priced lookups, an analyst screen instead of a grid — and the only change under src/components/demo/ is a comment edit, because the isolation lint refused the word "wordle" there. Zero shell code changed. 415 contract checks now pass against two demos, up from 206 against one. The environment is honest by construction. Every alert is synthetic, generated from the seed, and the banner saying so sits inside the board surface. Two of the eleven scenario templates are hidden-suspicious: generated by the same code as their benign twin with the signal overlaid only in lookup data, so the free screen is identically distributed and a screen-only policy STRUCTURALLY cannot tell them apart. The probe ladder measures it: `fast` catches 0.0 of hidden seeds. That is the counterweight made real rather than asserted. Twelve policies, thirteen ladder assertions, a genuine three-way trade: fast 0.846 wins hours (0.85), misses every hidden case targeted 0.894 wins the shipped total thorough 0.820 wins evidence (1.00), spends 2.9 hours None dominates. 92 Python tests, 35 TypeScript tests, 65 fixtures replaying at delta 0, and conformance gated on world + scorer + protocol so the browser shows the same alert for ?seed= that Python generated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
882 lines
40 KiB
Python
882 lines
40 KiB
Python
"""The world for a seed: one alert on one synthetic customer.
|
||
|
||
Everything here is synthetic. No customer, account, transaction, counterparty,
|
||
analyst or institution exists behind any of it — see `names/PROVENANCE.md`.
|
||
|
||
Portability is the whole design. `src/demos/alert-triage/` carries a port of
|
||
this file, and CI hashes the canonical JSON of every world for seeds 0–4095 on
|
||
both sides. So:
|
||
|
||
* every random draw goes through `XorShift32` seeded by FNV-1a over the
|
||
decimal seed, in the order written here — the order IS the contract;
|
||
* every amount is an integer in minor units (cents), never a float;
|
||
* every date is a day ordinal turned into YYYY-MM-DD by our own civil-date
|
||
function, never a platform date type;
|
||
* canonical JSON is sorted keys, no whitespace, `null` for absent.
|
||
|
||
The hidden tier — a suspicious alert whose free screen is drawn from a benign
|
||
template — is produced by running the benign builder to completion, freezing
|
||
the screen, and only then overlaying the suspicious signal onto data that a
|
||
lookup returns. `screen_of(world)` is byte-identical with the overlay on or
|
||
off, and `tests/test_screen_leak.py` asserts it for every seed.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import json
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from .rng import (
|
||
XorShift32,
|
||
add_months,
|
||
days_from_civil,
|
||
days_in_month,
|
||
fnv1a32,
|
||
iso_date,
|
||
month_key,
|
||
)
|
||
|
||
# ------------------------------------------------------------ constants --
|
||
|
||
MAX_TURNS = 8
|
||
WINDOW_MONTHS = 12
|
||
SCREEN_COUNTERPARTIES = 2 # top-N by volume shown on the free screen
|
||
|
||
# Mix by seed. Deliberately not the real false-positive base rate — see
|
||
# README.md, "what this is easier than".
|
||
BENIGN_PCT = 55
|
||
VISIBLE_PCT = 30
|
||
# hidden = the remaining 15
|
||
|
||
FAMILIES = ("STR", "VEL", "CASH", "WIRE", "DORM")
|
||
|
||
# Rule ids per slice. The held-out slice renames them so a policy that learned
|
||
# "9,xxx and R-STR-01" rather than the pattern is caught.
|
||
RULE_IDS = {
|
||
"main": {"STR": "R-STR-01", "VEL": "R-VEL-02", "CASH": "R-CASH-03", "WIRE": "R-WIRE-04", "DORM": "R-DORM-05"},
|
||
"held_out": {"STR": "TM-A1", "VEL": "TM-A2", "CASH": "TM-A3", "WIRE": "TM-A4", "DORM": "TM-A5"},
|
||
}
|
||
|
||
RULE_TEXT = {
|
||
"STR": "Three or more cash deposits just under the reporting threshold within ten days",
|
||
"VEL": "Funds received and moved out again within 48 hours",
|
||
"CASH": "Monthly cash deposits materially above the trailing six-month average",
|
||
"WIRE": "Outbound wires to a jurisdiction on the institution's monitored list",
|
||
"DORM": "Large credit to an account with no material activity for nine months",
|
||
}
|
||
|
||
# The typology the rule nominally points at — what a defensive filer would
|
||
# write. Diagnostic only; `typology` is never scored.
|
||
NOMINAL_TYPOLOGY = {"STR": "STRUCTURING", "VEL": "RAPID_MOVEMENT", "CASH": "STRUCTURING", "WIRE": "UNKNOWN", "DORM": "UNKNOWN"}
|
||
|
||
CURRENCIES = {
|
||
# threshold in minor units; scale multiplies every generated amount
|
||
"main": {"currency": "USD", "threshold": 1_000_000, "scale": 1},
|
||
"held_out": {"currency": "KRN", "threshold": 2_000_000, "scale": 2},
|
||
}
|
||
|
||
BENIGN_TEMPLATES = ("B1", "B2", "B3", "B4", "B5", "B6")
|
||
VISIBLE_TEMPLATES = ("S1", "S3", "S6")
|
||
OVERLAYS = ("funnel", "serial_closer", "doc_mismatch")
|
||
DOC_TEMPLATES = ("B2", "B3", "B6") # where a document is the explaining fact
|
||
|
||
TEMPLATE_FAMILY = {"B1": "CASH", "B2": "VEL", "B3": "VEL", "B4": "STR", "B5": "WIRE", "B6": "DORM", "S1": "STR", "S3": "VEL", "S6": "CASH"}
|
||
|
||
INBOUND = ("cash_in", "wire_in", "ach_in", "p2p_in")
|
||
|
||
_NAMES = Path(__file__).parent.parent / "names"
|
||
|
||
|
||
def _load(name: str):
|
||
return json.loads((_NAMES / name).read_text())
|
||
|
||
|
||
FIRST_NAMES: list[str] = _load("first_names.json")
|
||
SURNAMES: list[str] = _load("surnames.json")
|
||
WORDS: dict[str, list[str]] = _load("words.json")
|
||
JURISDICTIONS: dict[str, Any] = _load("jurisdictions.json")
|
||
|
||
BOILERPLATE = "Reviewed. Activity consistent with customer profile. No further action."
|
||
|
||
BENIGN_RATIONALE = {
|
||
"STR": "Closed: deposits are documented daily takings; amounts vary with trading days; within kyc.expected_monthly_cash.",
|
||
"VEL": "Closed: pass-through matches the settlement terms in the agreement on file; counterparties verified.",
|
||
"CASH": "Closed: seasonal peak consistent with kyc.expected_activity; volume within stated expectation.",
|
||
"WIRE": "Closed: destination and amount match the remittance declared at onboarding; within stated income.",
|
||
"DORM": "Closed: reactivation explained by a documented source of funds; document reviewed and retained.",
|
||
}
|
||
|
||
|
||
# ----------------------------------------------------------- utilities --
|
||
|
||
|
||
def canonical_json(value: Any) -> str:
|
||
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
||
|
||
|
||
def _round_to(cents: int, step: int) -> int:
|
||
return (cents // step) * step
|
||
|
||
|
||
class _Ledger:
|
||
"""Transactions are collected unordered, then ids are assigned by date.
|
||
|
||
A stable sort on the day ordinal, with insertion order breaking ties, is
|
||
what makes T-ids identical across the two implementations.
|
||
"""
|
||
|
||
def __init__(self) -> None:
|
||
self.rows: list[dict[str, Any]] = []
|
||
|
||
def add(self, day: int, channel: str, amount: int, branch: str | None = None, cp: str | None = None) -> int:
|
||
self.rows.append({"day": day, "seq": len(self.rows), "channel": channel, "amount": amount, "branch": branch, "cp": cp})
|
||
return len(self.rows) - 1
|
||
|
||
def finish(self) -> tuple[list[dict[str, Any]], dict[int, str]]:
|
||
ordered = sorted(self.rows, key=lambda r: (r["day"], r["seq"]))
|
||
id_of: dict[int, str] = {}
|
||
out = []
|
||
for i, row in enumerate(ordered):
|
||
tid = f"T-{i + 1}"
|
||
id_of[row["seq"]] = tid
|
||
out.append({
|
||
"id": tid,
|
||
"date": iso_date(row["day"]),
|
||
"month": iso_date(row["day"])[:7],
|
||
"channel": row["channel"],
|
||
"amount": row["amount"],
|
||
"branch": row["branch"],
|
||
"cp": row["cp"],
|
||
})
|
||
return out, id_of
|
||
|
||
|
||
class _Ctx:
|
||
"""Everything a template builder needs, plus the accumulating world."""
|
||
|
||
def __init__(self, seed: int, rng: XorShift32, slice_name: str) -> None:
|
||
self.seed = seed
|
||
self.rng = rng
|
||
self.slice = slice_name
|
||
cur = CURRENCIES[slice_name]
|
||
self.currency = cur["currency"]
|
||
self.threshold = cur["threshold"]
|
||
self.scale = cur["scale"]
|
||
self.rule_ids = RULE_IDS[slice_name]
|
||
self.home = JURISDICTIONS["home"][self.currency]
|
||
self.ledger = _Ledger()
|
||
self.cps: list[dict[str, Any]] = []
|
||
self.customer: dict[str, Any] = {}
|
||
self.months: list[tuple[int, int]] = []
|
||
self.fire_day = 0
|
||
self.fire_y = 0
|
||
self.fire_m = 0
|
||
self.documents: list[dict[str, Any]] = []
|
||
self.doc_party_cp: str | None = None # the CP a party-bearing document names
|
||
self.branches = rng.sample(WORDS["branches"], 2)
|
||
|
||
# money: `usd(n)` is n whole units of the slice currency, in minor units.
|
||
def usd(self, n: int) -> int:
|
||
return n * 100 * self.scale
|
||
|
||
def money(self, lo: int, hi: int, step: int = 10) -> int:
|
||
"""A random whole-unit amount in [lo, hi], rounded down to `step` units."""
|
||
return _round_to(self.usd(self.rng.between(lo, hi)), self.usd(step))
|
||
|
||
def set_fire(self, month_choices: list[int], day_lo: int = 12, day_hi: int = 27) -> None:
|
||
self.fire_y = 2026
|
||
self.fire_m = self.rng.pick(month_choices)
|
||
self.fire_day = days_from_civil(self.fire_y, self.fire_m, self.rng.between(day_lo, day_hi))
|
||
self.months = [add_months(self.fire_y, self.fire_m, -(WINDOW_MONTHS - 1 - i)) for i in range(WINDOW_MONTHS)]
|
||
|
||
def fire_dom(self) -> int:
|
||
return self.fire_day - days_from_civil(self.fire_y, self.fire_m, 1) + 1
|
||
|
||
def day_in(self, i: int, lo: int = 1, hi: int | None = None) -> int:
|
||
"""A day ordinal inside window month i. Nothing is ever dated on or after the fire date."""
|
||
y, m = self.months[i]
|
||
last = days_in_month(y, m) if hi is None else hi
|
||
if i == WINDOW_MONTHS - 1:
|
||
last = min(last, self.fire_dom() - 1)
|
||
lo = min(lo, last)
|
||
return days_from_civil(y, m, self.rng.between(lo, last))
|
||
|
||
def days_in(self, i: int, k: int, lo: int = 1, hi: int | None = None) -> list[int]:
|
||
"""k distinct days in month i, ascending."""
|
||
y, m = self.months[i]
|
||
last = days_in_month(y, m) if hi is None else hi
|
||
if i == WINDOW_MONTHS - 1:
|
||
last = min(last, self.fire_dom() - 1)
|
||
lo = min(lo, last)
|
||
first = days_from_civil(y, m, 1)
|
||
picks = self.rng.sample(list(range(lo, last + 1)), k)
|
||
return sorted(first + d - 1 for d in picks)
|
||
|
||
def person(self) -> str:
|
||
return f"{self.rng.pick(FIRST_NAMES)} {self.rng.pick(SURNAMES)}"
|
||
|
||
def business(self, noun: str | None = None) -> str:
|
||
adj = self.rng.pick(WORDS["adjectives"])
|
||
noun = noun or self.rng.pick(WORDS["nouns"])
|
||
return f"{adj} {noun} {self.rng.pick(WORDS['suffixes'])}"
|
||
|
||
def cp(self, name: str, kind: str, jurisdiction: str | None = None) -> str:
|
||
cid = f"CP-{len(self.cps) + 1}"
|
||
self.cps.append({"id": cid, "name": name, "type": kind, "jurisdiction": jurisdiction or self.home})
|
||
return cid
|
||
|
||
def doc(self, kind: str, description: str, party: str | None = None) -> str:
|
||
while True:
|
||
did = f"doc.D-{self.rng.hex4()}"
|
||
if all(d["id"] != did for d in self.documents):
|
||
break
|
||
self.documents.append({"id": did, "kind": kind, "description": description, "party": party})
|
||
return did
|
||
|
||
def add(self, day: int, channel: str, amount: int, branch: str | None = None, cp: str | None = None) -> int:
|
||
return self.ledger.add(day, channel, amount, branch, cp)
|
||
|
||
def branch(self) -> str:
|
||
return self.rng.pick(self.branches)
|
||
|
||
def max_month_cash(self) -> int:
|
||
"""The largest month of cash actually deposited so far — what the KYC expectation must cover."""
|
||
totals: dict[int, int] = {}
|
||
for row in self.ledger.rows:
|
||
if row["channel"] == "cash_in":
|
||
key = iso_date(row["day"])[:7]
|
||
idx = int(key[:4]) * 12 + int(key[5:])
|
||
totals[idx] = totals.get(idx, 0) + row["amount"]
|
||
return max(totals.values()) if totals else 0
|
||
|
||
|
||
def _kyc_common(ctx: _Ctx, customer_type: str) -> None:
|
||
"""The KYC draws every template makes, in the same order.
|
||
|
||
Prior-alert and prior-SAR counts are drawn HERE, before any template or
|
||
label-specific code runs, from one distribution. The serial-closer overlay
|
||
rewrites what the prior alerts say; it can never change how many there are,
|
||
which is the number the screen shows.
|
||
"""
|
||
rng = ctx.rng
|
||
ctx.customer = {
|
||
"id": f"C-{rng.between(1000, 9999)}",
|
||
"name": "",
|
||
"customer_type": customer_type,
|
||
"country": ctx.home,
|
||
"pep": rng.chance(3),
|
||
"prior_alerts": 0,
|
||
"prior_sars": 0,
|
||
"remittance_declared": "none declared",
|
||
}
|
||
roll = rng.below(100)
|
||
ctx.customer["prior_alerts"] = 0 if roll < 35 else 1 if roll < 55 else 2 if roll < 70 else 3 if roll < 88 else 4
|
||
ctx.customer["prior_sars"] = 1 if ctx.customer["prior_alerts"] >= 1 and rng.chance(8) else 0
|
||
opened_years = rng.between(2, 15)
|
||
ctx.customer["account_opened"] = iso_date(ctx.fire_day - opened_years * 365 - rng.between(0, 300))
|
||
ctx.customer["kyc_refreshed"] = iso_date(ctx.fire_day - rng.between(40, 500))
|
||
|
||
|
||
def _salary(ctx: _Ctx, monthly: int, employer: str) -> None:
|
||
for i in range(WINDOW_MONTHS):
|
||
amount = monthly + ctx.usd(ctx.rng.between(-60, 60))
|
||
ctx.add(ctx.day_in(i, 1, 5), "ach_in", amount, cp=employer)
|
||
|
||
|
||
def _small_cash_out(ctx: _Ctx, lo: int, hi: int, per_month_lo: int, per_month_hi: int) -> None:
|
||
for i in range(WINDOW_MONTHS):
|
||
n = ctx.rng.between(per_month_lo, per_month_hi)
|
||
for day in ctx.days_in(i, n):
|
||
ctx.add(day, "cash_out", ctx.money(lo, hi), branch=ctx.branch())
|
||
|
||
|
||
def _cash_deposits(ctx: _Ctx, i: int, total: int, n: int, lo_cap: int, hi_cap: int, days: list[int] | None = None) -> list[int]:
|
||
"""n cash deposits in month i summing roughly to `total`, each clamped."""
|
||
days = days if days is not None else ctx.days_in(i, n)
|
||
out = []
|
||
for day in days:
|
||
amount = total // n * ctx.rng.between(75, 125) // 100
|
||
amount = max(lo_cap, min(hi_cap, _round_to(amount, ctx.usd(10))))
|
||
out.append(ctx.add(day, "cash_in", amount, branch=ctx.branch()))
|
||
return out
|
||
|
||
|
||
# --------------------------------------------------------------- benign --
|
||
|
||
|
||
def _build_B1(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Seasonal cash business. CASH fires on the first peak month; the KYC file said it would."""
|
||
rng = ctx.rng
|
||
kind = rng.pick([
|
||
("landscaping services", "Landscaping", [4, 5, 6, 7, 8, 9], [4, 5], "April to September"),
|
||
("tax preparation", "Tax Services", [2, 3, 4], [3], "February to April"),
|
||
("pool maintenance", "Pool Care", [5, 6, 7, 8], [5, 6], "May to August"),
|
||
])
|
||
business_type, noun, peak, fire_choices, peak_text = kind
|
||
ctx.set_fire(fire_choices)
|
||
_kyc_common(ctx, "business")
|
||
ctx.customer["name"] = ctx.business(noun)
|
||
ctx.customer["business_type"] = business_type
|
||
off = ctx.money(3000, 7000, 100)
|
||
mult = rng.between(30, 40)
|
||
supplier = ctx.cp(ctx.business("Supply"), "business")
|
||
equipment = ctx.cp(ctx.business("Equipment"), "business")
|
||
for i, (y, m) in enumerate(ctx.months):
|
||
in_peak = m in peak
|
||
total = off * mult // 10 if in_peak else off
|
||
total = total * rng.between(85, 115) // 100
|
||
n = rng.between(8, 12) if in_peak else rng.between(3, 5)
|
||
_cash_deposits(ctx, i, total, n, ctx.usd(200), ctx.threshold * 8 // 10 - ctx.usd(100))
|
||
ctx.add(ctx.day_in(i, 20, 28), "ach_out", total * rng.between(20, 35) // 100, cp=supplier)
|
||
if m % 3 == 0:
|
||
ctx.add(ctx.day_in(i, 6, 12), "wire_out", ctx.money(2500, 9000, 100), cp=equipment)
|
||
expected = _round_to(ctx.max_month_cash() * rng.between(110, 130) // 100, ctx.usd(1000)) + ctx.usd(1000)
|
||
ctx.customer["expected_monthly_cash"] = expected
|
||
ctx.customer["expected_activity"] = f"Seasonal business; cash receipts peak {peak_text}."
|
||
ctx.customer["stated_income"] = _round_to(expected * 7, ctx.usd(1000))
|
||
ctx.doc("registration", f"Business registration — {ctx.customer['name']}")
|
||
ctx.doc("licence", f"Trade licence, {business_type}, renewed annually")
|
||
return {
|
||
"family": "CASH",
|
||
"triggering": "fire_month_cash",
|
||
"planted": [["kyc.expected_activity", "kyc.expected_monthly_cash"], ["kyc.expected_monthly_cash"]],
|
||
}
|
||
|
||
|
||
def _build_B4(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Cash restaurant. Three of the month's deposits land in the band; the file expected more cash than this."""
|
||
rng = ctx.rng
|
||
business_type, noun = rng.pick([("restaurant", "Bistro"), ("diner", "Diner"), ("pizzeria", "Kitchen"), ("cafe", "Grill")])
|
||
ctx.set_fire([3, 4, 5, 6], 14, 27)
|
||
_kyc_common(ctx, "business")
|
||
ctx.customer["name"] = ctx.business(noun)
|
||
ctx.customer["business_type"] = business_type
|
||
base = ctx.money(40000, 75000, 1000)
|
||
produce = ctx.cp(ctx.business("Produce"), "business")
|
||
foods = ctx.cp(ctx.business("Foods"), "business")
|
||
band_lo = ctx.threshold * 8 // 10
|
||
for i in range(WINDOW_MONTHS):
|
||
total = base * rng.between(90, 110) // 100
|
||
n = rng.between(11, 14)
|
||
if i == WINDOW_MONTHS - 1:
|
||
# Three window deposits in the band, on distinct days; the rest ordinary.
|
||
fire_dom = ctx.fire_dom()
|
||
window_days = ctx.days_in(i, 3, max(1, fire_dom - 10), fire_dom - 1)
|
||
for day in window_days:
|
||
ctx.add(day, "cash_in", ctx.money(band_lo // ctx.usd(1) + 100, ctx.threshold // ctx.usd(1) - 100), branch=ctx.branch())
|
||
n -= 3
|
||
_cash_deposits(ctx, i, total, n, ctx.usd(1500), band_lo - ctx.usd(100))
|
||
for day in ctx.days_in(i, 4, 1, 28):
|
||
ctx.add(day, "ach_out", total * rng.between(4, 7) // 100, cp=produce)
|
||
ctx.add(ctx.day_in(i, 1, 10), "ach_out", total * rng.between(10, 16) // 100, cp=foods)
|
||
expected = _round_to(ctx.max_month_cash() * rng.between(110, 130) // 100, ctx.usd(1000)) + ctx.usd(1000)
|
||
ctx.customer["expected_monthly_cash"] = expected
|
||
ctx.customer["expected_activity"] = "Cash-intensive food service; daily takings deposited most trading days."
|
||
ctx.customer["stated_income"] = _round_to(base * 12, ctx.usd(1000))
|
||
ctx.doc("registration", f"Business registration — {ctx.customer['name']}")
|
||
ctx.doc("licence", "Food service licence, renewed annually")
|
||
return {
|
||
"family": "STR",
|
||
"triggering": "band_window",
|
||
"planted": [["kyc.expected_monthly_cash"], ["kyc.expected_monthly_cash", "kyc.business_type"]],
|
||
}
|
||
|
||
|
||
def _build_B2(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Processor settlement. Wires in from a processor, contractors paid within two days; the agreement is on file."""
|
||
rng = ctx.rng
|
||
business_type, noun = rng.pick([("staffing agency", "Staffing"), ("freight brokerage", "Freight"), ("event production", "Events"), ("general contractor", "Contracting")])
|
||
ctx.set_fire([3, 4, 5, 6], 14, 27)
|
||
_kyc_common(ctx, "business")
|
||
ctx.customer["name"] = ctx.business(noun)
|
||
ctx.customer["business_type"] = business_type
|
||
processor_name = f"{rng.pick(WORDS['adjectives'])} Payments LLC"
|
||
processor = ctx.cp(processor_name, "processor")
|
||
ctx.doc_party_cp = processor
|
||
contractors = [ctx.cp(ctx.business(), "business") for _ in range(2)]
|
||
revenue = 0
|
||
window: list[int] = []
|
||
for i in range(WINDOW_MONTHS):
|
||
k = rng.between(3, 4)
|
||
days = ctx.days_in(i, k, 1, 24)
|
||
if i == WINDOW_MONTHS - 1:
|
||
# Guarantee one settlement inside the 14-day window with its payouts before the fire date.
|
||
fire_dom = ctx.fire_dom()
|
||
days = ctx.days_in(i, k - 1, 1, max(1, fire_dom - 8)) + [ctx.fire_day - rng.between(3, 6)]
|
||
for day in days:
|
||
amount = ctx.money(15000, 60000, 100)
|
||
revenue += amount
|
||
t_in = ctx.add(day, "wire_in", amount, cp=processor)
|
||
share = rng.between(70, 90)
|
||
first = amount * share // 100 * rng.between(50, 70) // 100
|
||
second = amount * share // 100 - first
|
||
t_a = ctx.add(day + 1, "wire_out", _round_to(first, ctx.usd(10)), cp=contractors[0])
|
||
t_b = ctx.add(day + rng.between(1, 2), "ach_out", _round_to(second, ctx.usd(10)), cp=contractors[1])
|
||
if i == WINDOW_MONTHS - 1 and day >= ctx.fire_day - 14:
|
||
window += [t_in, t_a, t_b]
|
||
ctx.customer["expected_monthly_cash"] = ctx.usd(0)
|
||
ctx.customer["expected_activity"] = "Card settlements from a payment processor; subcontractor payouts within days of settlement."
|
||
ctx.customer["stated_income"] = _round_to(revenue, ctx.usd(10000))
|
||
ctx.doc("registration", f"Business registration — {ctx.customer['name']}")
|
||
ctx.doc("processing_agreement", f"Merchant processing agreement between {ctx.customer['name']} and {processor_name}; settlement to this account on T+1.", party=processor_name)
|
||
return {"family": "VEL", "triggering": window, "planted": [["DOC"], ["DOC", "kyc.business_type"]]}
|
||
|
||
|
||
def _build_B3(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Property sale. One title-company wire in, moved to a brokerage; closing statement on file."""
|
||
rng = ctx.rng
|
||
ctx.set_fire([3, 4, 5, 6], 14, 27)
|
||
_kyc_common(ctx, "individual")
|
||
ctx.customer["name"] = ctx.person()
|
||
ctx.customer["occupation"] = rng.pick(["teacher", "software engineer", "pharmacist", "electrician", "accountant"])
|
||
salary = ctx.money(4000, 9000, 100)
|
||
employer = ctx.cp(ctx.business(), "business")
|
||
_salary(ctx, salary, employer)
|
||
_small_cash_out(ctx, 100, 400, 2, 4)
|
||
title_name = f"{rng.pick(SURNAMES)} Title & Escrow"
|
||
title = ctx.cp(title_name, "title_company")
|
||
ctx.doc_party_cp = title
|
||
brokerage = ctx.cp(f"{rng.pick(WORDS['adjectives'])} Securities", "brokerage")
|
||
proceeds = ctx.money(180000, 650000, 1000)
|
||
day_in = ctx.fire_day - rng.between(3, 6)
|
||
t_in = ctx.add(day_in, "wire_in", proceeds, cp=title)
|
||
t_out = ctx.add(day_in + rng.between(1, 2), "wire_out", _round_to(proceeds * rng.between(85, 100) // 100, ctx.usd(100)), cp=brokerage)
|
||
ctx.customer["expected_monthly_cash"] = ctx.usd(0)
|
||
ctx.customer["expected_activity"] = "Salaried individual; payroll deposits and routine spending."
|
||
ctx.customer["stated_income"] = _round_to(salary * 12, ctx.usd(1000))
|
||
ctx.doc("identity", "Government-issued identity document, verified at onboarding")
|
||
address = f"{rng.between(12, 980)} {rng.pick(WORDS['streets'])}"
|
||
ctx.doc("closing_statement", f"Closing statement — sale of {address}; net proceeds to seller disbursed by {title_name}.", party=title_name)
|
||
return {"family": "VEL", "triggering": [t_in, t_out], "planted": [["DOC"], ["DOC", "kyc.occupation"]]}
|
||
|
||
|
||
def _build_B5(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Family remittance. A monthly wire to one relative in a monitored jurisdiction, declared at onboarding."""
|
||
rng = ctx.rng
|
||
ctx.set_fire([3, 4, 5, 6])
|
||
_kyc_common(ctx, "individual")
|
||
surname = rng.pick(SURNAMES)
|
||
ctx.customer["name"] = f"{rng.pick(FIRST_NAMES)} {surname}"
|
||
ctx.customer["occupation"] = rng.pick(["registered nurse", "civil engineer", "teacher", "pharmacist"])
|
||
salary = ctx.money(4500, 8500, 100)
|
||
employer = ctx.cp(ctx.business(), "business")
|
||
_salary(ctx, salary, employer)
|
||
_small_cash_out(ctx, 100, 400, 2, 4)
|
||
jurisdiction = rng.pick(JURISDICTIONS["monitored"])
|
||
relation = rng.pick(["mother", "father", "brother", "sister"])
|
||
relative = ctx.cp(f"{rng.pick(FIRST_NAMES)} {surname}", "individual", jurisdiction)
|
||
monthly = ctx.money(800, 2500, 50)
|
||
wires = []
|
||
for i in range(WINDOW_MONTHS):
|
||
wires.append(ctx.add(ctx.day_in(i, 1, 7), "wire_out", monthly + ctx.usd(rng.between(-50, 50)), cp=relative))
|
||
ctx.customer["expected_monthly_cash"] = ctx.usd(0)
|
||
ctx.customer["expected_activity"] = "Salaried individual; payroll deposits and routine spending."
|
||
ctx.customer["remittance_declared"] = f"Monthly support to {relation} in {jurisdiction}, about {monthly // 100 // ctx.scale * ctx.scale:,} {ctx.currency} per month."
|
||
ctx.customer["stated_income"] = _round_to(salary * 12, ctx.usd(1000))
|
||
ctx.doc("identity", "Government-issued identity document, verified at onboarding")
|
||
ctx.doc("address", "Proof of address, utility statement")
|
||
return {"family": "WIRE", "triggering": wires[-3:], "planted": [["kyc.remittance_declared"], ["kyc.remittance_declared", "kyc.stated_income"]]}
|
||
|
||
|
||
def _build_B6(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Inheritance. A dormant account receives an estate distribution; the executor's letter is on file."""
|
||
rng = ctx.rng
|
||
ctx.set_fire([3, 4, 5, 6], 14, 27)
|
||
_kyc_common(ctx, "individual")
|
||
ctx.customer["name"] = ctx.person()
|
||
ctx.customer["occupation"] = "retired"
|
||
ctx.customer["account_opened"] = iso_date(ctx.fire_day - rng.between(8, 20) * 365 - rng.between(0, 300))
|
||
for i in range(WINDOW_MONTHS - 1):
|
||
if rng.chance(35):
|
||
ctx.add(ctx.day_in(i), "cash_out", ctx.money(40, 200), branch=ctx.branch())
|
||
firm = f"{rng.pick(SURNAMES)} & {rng.pick(SURNAMES)} LLP, client trust account"
|
||
trust = ctx.cp(firm, "law_firm_trust")
|
||
ctx.doc_party_cp = trust
|
||
brokerage = ctx.cp(f"{rng.pick(WORDS['adjectives'])} Securities", "brokerage")
|
||
amount = ctx.money(60000, 400000, 1000)
|
||
day_in = ctx.fire_day - rng.between(4, 7)
|
||
t_in = ctx.add(day_in, "wire_in", amount, cp=trust)
|
||
t_out = ctx.add(day_in + rng.between(1, 3), "wire_out", _round_to(amount * rng.between(50, 90) // 100, ctx.usd(100)), cp=brokerage)
|
||
ctx.customer["expected_monthly_cash"] = ctx.usd(0)
|
||
ctx.customer["expected_activity"] = "Retired; low activity expected."
|
||
ctx.customer["stated_income"] = ctx.money(20000, 40000, 1000)
|
||
ctx.doc("identity", "Government-issued identity document, verified at onboarding")
|
||
executor, decedent = ctx.person(), ctx.person()
|
||
ctx.doc("executor_letter", f"Letter from {executor}, executor, via {firm}: distribution from the estate of {decedent} to the named beneficiary.", party=firm)
|
||
return {"family": "DORM", "triggering": [t_in, t_out], "planted": [["DOC"]]}
|
||
|
||
|
||
# -------------------------------------------------------------- visible --
|
||
|
||
|
||
def _build_S1(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Structuring. Eight deposits just under the threshold in twelve days across three branches, on a modest income."""
|
||
rng = ctx.rng
|
||
ctx.set_fire([3, 4, 5, 6], 14, 27)
|
||
_kyc_common(ctx, "individual")
|
||
ctx.customer["name"] = ctx.person()
|
||
ctx.customer["occupation"] = rng.pick(["warehouse associate", "delivery driver", "retail supervisor", "line cook"])
|
||
ctx.branches = rng.sample(WORDS["branches"], 3)
|
||
salary = ctx.money(2600, 3600, 100)
|
||
employer = ctx.cp(ctx.business(), "business")
|
||
_salary(ctx, salary, employer)
|
||
_small_cash_out(ctx, 60, 300, 2, 4)
|
||
fire_dom = ctx.fire_dom()
|
||
days = ctx.days_in(WINDOW_MONTHS - 1, 8, max(1, fire_dom - 12), fire_dom - 2)
|
||
lo = ctx.threshold // ctx.usd(1) * 92 // 100
|
||
hi = ctx.threshold // ctx.usd(1) * 99 // 100
|
||
deposits = []
|
||
total = 0
|
||
for j, day in enumerate(days):
|
||
amount = ctx.money(lo, hi)
|
||
total += amount
|
||
deposits.append(ctx.add(day, "cash_in", amount, branch=ctx.branches[j % 3]))
|
||
holdings = ctx.cp(ctx.business("Holdings"), "business")
|
||
ctx.add(ctx.fire_day - 1, "wire_out", _round_to(total * rng.between(85, 95) // 100, ctx.usd(100)), cp=holdings)
|
||
ctx.customer["expected_monthly_cash"] = ctx.money(0, 500, 100)
|
||
ctx.customer["expected_activity"] = "Salaried individual; payroll deposits and routine spending."
|
||
ctx.customer["stated_income"] = _round_to(salary * 12 + ctx.usd(rng.between(0, 6000)), ctx.usd(1000))
|
||
ctx.doc("identity", "Government-issued identity document, verified at onboarding")
|
||
return {"family": "STR", "triggering": "band_window", "planted": [deposits], "typology": "STRUCTURING"}
|
||
|
||
|
||
def _build_S3(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Money mule. Four P2P credits from unrelated individuals, each wired to an exchange within a day."""
|
||
rng = ctx.rng
|
||
ctx.set_fire([3, 4, 5, 6], 16, 27)
|
||
_kyc_common(ctx, "individual")
|
||
ctx.customer["name"] = ctx.person()
|
||
ctx.customer["occupation"] = rng.pick(["student", "retired", "part-time cashier"])
|
||
stipend = ctx.money(900, 1800, 50)
|
||
source = ctx.cp(ctx.business(), "business")
|
||
_salary(ctx, stipend, source)
|
||
_small_cash_out(ctx, 40, 200, 2, 4)
|
||
exchange = ctx.cp(f"{rng.pick(WORDS['adjectives'])} Digital Assets", "exchange")
|
||
fire_dom = ctx.fire_dom()
|
||
days = ctx.days_in(WINDOW_MONTHS - 1, 4, max(1, fire_dom - 14), fire_dom - 2)
|
||
pairs = []
|
||
for day in days:
|
||
sender = ctx.cp(ctx.person(), "individual")
|
||
amount = ctx.money(2000, 4900, 10)
|
||
t_in = ctx.add(day, "p2p_in", amount, cp=sender)
|
||
t_out = ctx.add(day + rng.between(0, 1), "wire_out", _round_to(amount * rng.between(90, 97) // 100, ctx.usd(10)), cp=exchange)
|
||
pairs += [t_in, t_out]
|
||
ctx.customer["expected_monthly_cash"] = ctx.money(0, 300, 100)
|
||
ctx.customer["expected_activity"] = "Low-income individual; small regular credits and routine spending."
|
||
ctx.customer["stated_income"] = _round_to(stipend * 12, ctx.usd(1000))
|
||
ctx.doc("identity", "Government-issued identity document, verified at onboarding")
|
||
return {"family": "VEL", "triggering": pairs, "planted": [pairs], "typology": "MONEY_MULE"}
|
||
|
||
|
||
def _build_S6(ctx: _Ctx) -> dict[str, Any]:
|
||
"""Smurfing over time. Monthly cash triples across the year and no single deposit ever reaches the threshold."""
|
||
rng = ctx.rng
|
||
business_type, noun = rng.pick([("vending route", "Vending"), ("car wash", "Car Wash"), ("laundromat", "Laundry"), ("convenience store", "Market")])
|
||
ctx.set_fire([3, 4, 5, 6], 14, 27)
|
||
_kyc_common(ctx, "business")
|
||
ctx.customer["name"] = ctx.business(noun)
|
||
ctx.customer["business_type"] = business_type
|
||
c0 = ctx.money(9000, 14000, 500)
|
||
supplier = ctx.cp(ctx.business("Supply"), "business")
|
||
cap = ctx.threshold - ctx.usd(100)
|
||
for i in range(WINDOW_MONTHS):
|
||
total = c0 * (11 + 2 * i) // 11 * rng.between(97, 103) // 100
|
||
n = rng.between(6, 9)
|
||
_cash_deposits(ctx, i, total, n, ctx.usd(2000), cap)
|
||
ctx.add(ctx.day_in(i, 20, 28), "ach_out", total * rng.between(15, 25) // 100, cp=supplier)
|
||
ctx.customer["expected_monthly_cash"] = _round_to(c0 * 12 // 10, ctx.usd(500))
|
||
ctx.customer["expected_activity"] = "Cash-intensive retail; steady volume expected."
|
||
ctx.customer["stated_income"] = _round_to(c0 * 14, ctx.usd(1000))
|
||
ctx.doc("registration", f"Business registration — {ctx.customer['name']}")
|
||
return {"family": "CASH", "triggering": "fire_month_cash", "planted": "last_two_months", "typology": "STRUCTURING"}
|
||
|
||
|
||
BUILDERS = {"B1": _build_B1, "B2": _build_B2, "B3": _build_B3, "B4": _build_B4, "B5": _build_B5, "B6": _build_B6, "S1": _build_S1, "S3": _build_S3, "S6": _build_S6}
|
||
|
||
|
||
# ------------------------------------------------------------- assembly --
|
||
|
||
|
||
def _prior_alerts(ctx: _Ctx, family: str) -> list[dict[str, Any]]:
|
||
"""Benign prior alerts: mixed rules, specific rationales, a few days each."""
|
||
rng = ctx.rng
|
||
out: list[dict[str, Any]] = []
|
||
used: set[str] = set()
|
||
for j in range(ctx.customer["prior_alerts"]):
|
||
while True:
|
||
aid = f"A-{rng.between(1000, 4999)}"
|
||
if aid not in used:
|
||
used.add(aid)
|
||
break
|
||
fam = rng.pick(list(FAMILIES))
|
||
fired = ctx.fire_day - rng.between(30, 720)
|
||
escalated = j == 0 and ctx.customer["prior_sars"] >= 1
|
||
out.append({
|
||
"id": aid,
|
||
"rule": ctx.rule_ids[fam],
|
||
"fired": iso_date(fired),
|
||
"disposition": "escalated — report filed" if escalated else "closed",
|
||
"analyst": ctx.person(),
|
||
"rationale": "Escalated: pattern not explained by the file at the time." if escalated else BENIGN_RATIONALE[fam],
|
||
"elapsed_days": rng.between(2, 9),
|
||
})
|
||
out.sort(key=lambda a: a["fired"])
|
||
return out
|
||
|
||
|
||
def _assemble(ctx: _Ctx, template: str, spec: dict[str, Any]) -> dict[str, Any]:
|
||
transactions, id_of = ctx.ledger.finish()
|
||
family = spec["family"]
|
||
|
||
# Triggering set, from the rule where the rule is mechanical.
|
||
if spec["triggering"] == "band_window":
|
||
lo, hi = ctx.threshold * 8 // 10, ctx.threshold - 1
|
||
triggering = [t["id"] for t in transactions
|
||
if t["channel"] == "cash_in" and lo <= t["amount"] <= hi
|
||
and ctx.fire_day - 10 <= days_from_civil(*map(int, t["date"].split("-"))) <= ctx.fire_day - 1]
|
||
elif spec["triggering"] == "fire_month_cash":
|
||
fire_month = month_key(ctx.fire_y, ctx.fire_m)
|
||
triggering = [t["id"] for t in transactions if t["channel"] == "cash_in" and t["month"] == fire_month]
|
||
else:
|
||
triggering = sorted((id_of[s] for s in spec["triggering"]), key=lambda x: int(x[2:]))
|
||
|
||
# Counterparties: transactions and first-seen from the ledger, direction from the channel.
|
||
by_cp: dict[str, list[dict[str, Any]]] = {c["id"]: [] for c in ctx.cps}
|
||
for t in transactions:
|
||
if t["cp"]:
|
||
by_cp[t["cp"]].append(t)
|
||
counterparties = []
|
||
for c in ctx.cps:
|
||
rows = by_cp[c["id"]]
|
||
counterparties.append({
|
||
**c,
|
||
"first_seen": rows[0]["date"] if rows else iso_date(ctx.fire_day),
|
||
"other_alerted_accounts": 1 if ctx.rng.chance(15) else 0,
|
||
"transactions": [t["id"] for t in rows],
|
||
})
|
||
ranked = sorted(counterparties, key=lambda c: (-sum(t["amount"] for t in by_cp[c["id"]]), int(c["id"][3:])))
|
||
screen_cps = []
|
||
for c in ranked[:SCREEN_COUNTERPARTIES]:
|
||
rows = by_cp[c["id"]]
|
||
screen_cps.append({
|
||
"id": c["id"],
|
||
"direction": "in" if rows and rows[0]["channel"] in INBOUND else "out",
|
||
"count": len(rows),
|
||
"total": sum(t["amount"] for t in rows),
|
||
})
|
||
|
||
summary = []
|
||
for (y, m) in ctx.months:
|
||
key = month_key(y, m)
|
||
row = {"month": key, "cash_in": 0, "cash_out": 0, "wires_in": 0, "wires_out": 0, "ach_in": 0, "ach_out": 0, "count": 0}
|
||
for t in transactions:
|
||
if t["month"] != key:
|
||
continue
|
||
row["count"] += 1
|
||
ch = t["channel"]
|
||
col = {"cash_in": "cash_in", "cash_out": "cash_out", "wire_in": "wires_in", "wire_out": "wires_out",
|
||
"ach_in": "ach_in", "ach_out": "ach_out", "p2p_in": "wires_in", "p2p_out": "wires_out"}[ch]
|
||
row[col] += t["amount"]
|
||
summary.append(row)
|
||
|
||
# Planted citation sets. "DOC" is the template's explaining document.
|
||
explaining = next((d["id"] for d in ctx.documents if d["party"]), None)
|
||
if spec["planted"] == "last_two_months":
|
||
last_two = {month_key(*ctx.months[-1]), month_key(*ctx.months[-2])}
|
||
planted = [[t["id"] for t in transactions if t["channel"] == "cash_in" and t["month"] in last_two]]
|
||
else:
|
||
planted = []
|
||
for alt in spec["planted"]:
|
||
planted.append([
|
||
(explaining if x == "DOC" else id_of[x] if isinstance(x, int) else x) for x in alt
|
||
])
|
||
|
||
world = {
|
||
"seed": ctx.seed,
|
||
"held_out": ctx.slice == "held_out",
|
||
"currency": ctx.currency,
|
||
"threshold": ctx.threshold,
|
||
"screen_order": "kyc_first" if ctx.slice == "held_out" else "alert_first",
|
||
"customer": ctx.customer,
|
||
"months": [month_key(y, m) for (y, m) in ctx.months],
|
||
"transactions": transactions,
|
||
"counterparties": counterparties,
|
||
"screen_counterparties": screen_cps,
|
||
"alert": {
|
||
"id": f"A-{ctx.rng.between(5000, 9999)}",
|
||
"rule": ctx.rule_ids[family],
|
||
"family": family,
|
||
"rule_text": RULE_TEXT[family],
|
||
"fired": iso_date(ctx.fire_day),
|
||
"triggering": triggering,
|
||
},
|
||
"prior_alerts": _prior_alerts(ctx, family),
|
||
"documents": ctx.documents,
|
||
"summary": summary,
|
||
"template": template,
|
||
"overlay": None,
|
||
"label": "suspicious" if template.startswith("S") else "benign",
|
||
"typology": spec.get("typology"),
|
||
"planted": planted,
|
||
"doc_party_cp": ctx.doc_party_cp,
|
||
}
|
||
return world
|
||
|
||
|
||
# -------------------------------------------------------------- overlay --
|
||
|
||
|
||
def compatible_overlays(world: dict[str, Any]) -> list[str]:
|
||
"""Which hidden signals this benign world can carry. Depends on the screen, never on the label."""
|
||
kinds = ["funnel"]
|
||
if world["customer"]["prior_alerts"] >= 3 and world["customer"]["prior_sars"] == 0:
|
||
kinds.append("serial_closer")
|
||
if world["template"] in DOC_TEMPLATES:
|
||
kinds.append("doc_mismatch")
|
||
return kinds
|
||
|
||
|
||
def _overlay_urn(kinds: list[str]) -> list[str]:
|
||
"""funnel is always available, so the other two are drawn twice as often where they can be."""
|
||
urn: list[str] = []
|
||
for kind in kinds:
|
||
urn += [kind] * (1 if kind == "funnel" else 2)
|
||
return urn
|
||
|
||
|
||
def _outbound_target(world: dict[str, Any]) -> str:
|
||
"""The highest-volume outbound counterparty on the screen. Every benign template has one."""
|
||
for c in world["screen_counterparties"]:
|
||
if c["direction"] == "out":
|
||
return c["id"]
|
||
raise AssertionError(f"template {world['template']} has no outbound counterparty on screen")
|
||
|
||
|
||
def apply_overlay(world: dict[str, Any], kind: str, rng: XorShift32) -> None:
|
||
"""Overlay a suspicious signal onto lookup data only. Mutates `world`.
|
||
|
||
The screen — alert, triggering set, KYC block, summary, screen
|
||
counterparties — is not touched, and `tests/test_screen_leak.py` proves it.
|
||
"""
|
||
family = world["alert"]["family"]
|
||
triggering = world["alert"]["triggering"]
|
||
world["overlay"] = kind
|
||
world["label"] = "suspicious"
|
||
|
||
if kind == "funnel":
|
||
target = _outbound_target(world)
|
||
cp = next(c for c in world["counterparties"] if c["id"] == target)
|
||
cp["other_alerted_accounts"] = rng.between(5, 14)
|
||
world["typology"] = "FUNNEL_ACCOUNT"
|
||
world["planted"] = [[target] + cp["transactions"], list(cp["transactions"])]
|
||
|
||
elif kind == "serial_closer":
|
||
priors = world["prior_alerts"]
|
||
analysts = []
|
||
while len(analysts) < len(priors):
|
||
name = f"{rng.pick(FIRST_NAMES)} {rng.pick(SURNAMES)}"
|
||
if name not in analysts:
|
||
analysts.append(name)
|
||
fire_day = days_from_civil(*map(int, world["alert"]["fired"].split("-")))
|
||
days = sorted(rng.sample(list(range(fire_day - 330, fire_day - 30)), len(priors)))
|
||
for j, prior in enumerate(priors):
|
||
prior["rule"] = world["alert"]["rule"]
|
||
prior["fired"] = iso_date(days[j])
|
||
prior["disposition"] = "closed"
|
||
prior["analyst"] = analysts[j]
|
||
prior["rationale"] = BOILERPLATE
|
||
prior["elapsed_days"] = rng.between(0, 1)
|
||
world["typology"] = NOMINAL_TYPOLOGY[family]
|
||
ids = [p["id"] for p in priors]
|
||
world["planted"] = [ids + triggering, ids]
|
||
|
||
elif kind == "doc_mismatch":
|
||
target = world["doc_party_cp"]
|
||
cp = next(c for c in world["counterparties"] if c["id"] == target)
|
||
cp["name"] = f"{rng.pick(WORDS['adjectives'])} {rng.pick(['Holdings', 'Ventures', 'Trading'])} {rng.pick(WORDS['suffixes'])}"
|
||
doc = next(d["id"] for d in world["documents"] if d["party"])
|
||
inbound = [t["id"] for t in world["transactions"] if t["id"] in triggering and t["cp"] == target]
|
||
world["typology"] = NOMINAL_TYPOLOGY[family]
|
||
world["planted"] = [[doc, target] + inbound, [doc, target]]
|
||
else:
|
||
raise ValueError(kind)
|
||
|
||
|
||
# -------------------------------------------------------------- public --
|
||
|
||
|
||
def is_held_out(seed: int) -> bool:
|
||
return fnv1a32(str(seed)) % 8 == 7
|
||
|
||
|
||
def generate(seed: int, overlay: str | None = "auto") -> dict[str, Any]:
|
||
"""The world before reference hours. `overlay` is for tests:
|
||
"auto" follows the seed; None forces the benign twin; a kind forces it on."""
|
||
h = fnv1a32(str(seed))
|
||
slice_name = "held_out" if h % 8 == 7 else "main"
|
||
rng = XorShift32(h)
|
||
ctx = _Ctx(seed, rng, slice_name)
|
||
|
||
roll = rng.below(100)
|
||
tier = "benign" if roll < BENIGN_PCT else "visible" if roll < BENIGN_PCT + VISIBLE_PCT else "hidden"
|
||
if tier == "visible":
|
||
template = VISIBLE_TEMPLATES[rng.below(len(VISIBLE_TEMPLATES))]
|
||
else:
|
||
template = BENIGN_TEMPLATES[rng.below(len(BENIGN_TEMPLATES))]
|
||
|
||
spec = BUILDERS[template](ctx)
|
||
world = _assemble(ctx, template, spec)
|
||
world["tier"] = tier
|
||
|
||
if tier != "visible":
|
||
kinds = compatible_overlays(world)
|
||
if overlay == "auto":
|
||
if tier == "hidden":
|
||
apply_overlay(world, rng.pick(_overlay_urn(kinds)), rng)
|
||
elif overlay is not None:
|
||
apply_overlay(world, overlay, rng)
|
||
del world["doc_party_cp"]
|
||
return world
|
||
|
||
|
||
def world_for_seed(seed: int) -> dict[str, Any]:
|
||
"""The world with `reference_minutes` attached — part of the digest.
|
||
|
||
The reference is the cheapest shipped policy that reached the correct
|
||
disposition on this seed, found by running them through the engine. The
|
||
policies read only the engine's view, never this dict, so attaching their
|
||
result here is not circular; it is the same move as wordle's
|
||
`reference_depth`, made part of the world so the browser recomputes it
|
||
instead of trusting the recorded number.
|
||
"""
|
||
from .policies import reference_for # local import: policies -> engine -> generator
|
||
|
||
world = generate(seed)
|
||
minutes, policy = reference_for(world)
|
||
world["reference_minutes"] = minutes
|
||
world["reference_policy"] = policy
|
||
return world
|
||
|
||
|
||
SCREEN_KEYS = ("seed", "held_out", "currency", "threshold", "screen_order", "customer", "months",
|
||
"summary", "screen_counterparties", "alert")
|
||
|
||
|
||
def screen_of(world: dict[str, Any]) -> dict[str, Any]:
|
||
"""Exactly what turn 0 shows, including the triggering transactions in full."""
|
||
triggering = set(world["alert"]["triggering"])
|
||
screen = {k: world[k] for k in SCREEN_KEYS}
|
||
screen["triggering_transactions"] = [t for t in world["transactions"] if t["id"] in triggering]
|
||
return screen
|
||
|
||
|
||
def world_digest(seeds=range(4096)) -> str:
|
||
"""Digest (a): SHA-256 over the canonical world for each seed, newline-joined."""
|
||
digest = hashlib.sha256()
|
||
for seed in seeds:
|
||
digest.update(canonical_json(world_for_seed(seed)).encode())
|
||
digest.update(b"\n")
|
||
return digest.hexdigest()
|