recon_gen.common.l2.auto_scenario
Auto-derive a ScenarioPlant covering every L1 invariant from an L2 instance.
Companion to common.l2.seed — that module owns the typed plant
primitives + emit_seed machinery; this module knows how to walk an
arbitrary L2 instance and pick representative entities so an
integrator can run recon-gen demo seed-l2 myorg.yaml and get
a working seed without authoring scenarios in Python.
Heuristics (deterministic, sorted by stable keys at every choice point):
TemplateInstance: materialize 2 synthetic instances under the first
AccountTemplate(sorted by role name). Synthetic ids arecust-001/cust-002, namesCustomer 1/Customer 2. Persona-blind by construction.DriftPlant: pick the first 2-leg Rail (sorted by name) whose destination_role matches the template, AND has at least one external-scope Account whose role matches the source side. Use that external Account as the counter.
OverdraftPlant: needs only a TemplateInstance — no rail. Plant on the second customer.
LimitBreachPlant: first
LimitSchedule(sorted by parent_role + transfer_type) whose transfer_type matches some outbound 2-leg Rail (source = template role, destination = external role). Plant amount = cap × 1.5 to guarantee breach.StuckPendingPlant: first Rail (sorted by name) with
max_pending_ageset.StuckUnbundledPlant: first Rail with
max_unbundled_ageset. Validator R8 guarantees such a rail is bundled by some aggregating rail, so the resulting Posted leg surfaces in<prefix>_stuck_unbundled.SupersessionPlant: first single-leg Rail or any Rail with a customer-side leg.
Plants that can’t be derived (e.g., no LimitSchedule declared, no
2-leg inbound rail) are omitted from the returned ScenarioPlant.
The CLI surface logs a one-line warning per omission so the
integrator knows what’s missing from their YAML for full coverage.
The auto-scenario deliberately does NOT try to produce byte-identical
output to the curated default_ar_scenario — the two are different
contracts. default_ar_scenario is the hash-locked canonical AR
fixture; this module produces a reasonable starting demo for ANY L2.
Functions
|
Layer a single broken-Rail spike on top of an existing scenario (R.3.c). |
|
Tune Investigation fanout plants for visibility (R.3.d). |
|
Walk |
|
Replicate per-kind plants across the window for visibility (R.3.b). |
|
Return a copy of |
|
The account_ids this template will materialize during seed. |
Classes
|
Describes which plants the auto-scenario emitted vs. omitted. |
- class recon_gen.common.l2.auto_scenario.AutoScenarioReport(scenario, omitted)[source]
Bases:
objectDescribes which plants the auto-scenario emitted vs. omitted.
The CLI prints this so the integrator knows what’s missing from their YAML for full L1 coverage.
- Parameters:
scenario (ScenarioPlant)
omitted (tuple[tuple[str, str], ...])
- omitted: tuple[tuple[str, str], ...]
- scenario: ScenarioPlant
- recon_gen.common.l2.auto_scenario.add_broken_rail_plants(base, instance, *, broken_count=15)[source]
Layer a single broken-Rail spike on top of an existing scenario (R.3.c).
Picks one Rail with
max_pending_ageset + plantsbroken_countstuck_pending entries on it across the window. L1 Exceptions KPI then has a magnitude that matters; the L2 Exceptions sheet’s bar chart shows the broken Rail spike immediately.Picker rule: deterministic — sorted by rail name, the FIRST rail with max_pending_age set. Different from
default_scenario_for’s pending_rail picker by intent — the broken rail is a separate concept; using the same picker would just stack plants on the existing stuck_pending row.No-op when no max_pending_age-eligible rail exists OR the picked rail’s role doesn’t resolve to any materialized account.
- Return type:
- Parameters:
base (ScenarioPlant)
instance (L2Instance)
broken_count (int)
- recon_gen.common.l2.auto_scenario.boost_inv_fanout_plants(base, *, amount_multiplier=5, extra_recipient_count=0)[source]
Tune Investigation fanout plants for visibility (R.3.d).
The Phase R baseline puts ~600 customer-ACH transfers per day into the system at median ~$665 per transfer. The default
InvFanoutPlant.amount_per_transfer = $500from the auto-scenario sits BELOW the baseline median — its cluster is structurally visible (12 senders → 1 recipient) but per-transfer amounts don’t stand out.This helper bumps each inv_fanout plant’s amount by
amount_multiplier(5× default → $2,500 per transfer) so the cluster’s aggregate inflow (~$30,000 across 12 senders in one day) stands out clearly on the Recipient Fanout sheet’s Sankey + the Volume Anomalies sheet’s z-score band.Optional
extra_recipient_count: synthesize N extra fanout plants targeting different recipients (cycles through the existing template instances) so multiple clusters appear on the dashboards. Out of scope for the first land — defaults to 0.- Return type:
- Parameters:
base (ScenarioPlant)
amount_multiplier (int)
extra_recipient_count (int)
- recon_gen.common.l2.auto_scenario.default_scenario_for(instance, *, today=None, mode='l1_invariants', per_rail_firings=3)[source]
Walk
instanceand return an auto-derivedScenarioPlant.Modes (M.4.2):
l1_invariants(default) — only L1 SHOULD-violation plants (drift, overdraft, limit-breach, stuck-pending, stuck-unbundled, supersession, transfer-template). The legacy / pre-M.4.2 shape; L2 Flow Tracing surfaces dead for any rail not picked.broad— onlyRailFiringPlantrows: every declared rail whose role(s) resolve to a materialized account firesper_rail_firingstimes across stratified days. No L1 invariant plants. Useful for visual verification of the L2 surface in isolation.l1_plus_broad— both layers. The harness (M.4.1.b) uses this so Playwright can assert both planted SHOULD violations AND planted-rail visibility on the same deploy.
See module docstring for per-plant heuristics. Returns the scenario plus a report of any plant kinds that couldn’t be materialized from this instance (e.g., no
LimitScheduledeclared → no LimitBreachPlant).- Return type:
- Parameters:
instance (L2Instance)
today (date | None)
mode (Literal['l1_invariants', 'broad', 'l1_plus_broad'])
per_rail_firings (int)
- recon_gen.common.l2.auto_scenario.densify_scenario(base, *, factor=5, day_stride=7)[source]
Replicate per-kind plants across the window for visibility (R.3.b).
The R.2 baseline puts ~60k legs per L2 instance into the window; a single drift / overdraft / etc plant gets lost in the noise. This helper takes a base
ScenarioPlant(typically fromdefault_scenario_for) and replicates each plant kind by varyingdays_agoso each kind shows N rows on the dashboards instead of 1.For stuck-pending / stuck-unbundled, the days_ago stride keeps every replica well past the rail’s max_*_age cap so all replicas surface. For drift / overdraft / breach / supersession, the stride spreads them across the window for visual diversity.
inv_fanout_plantsandtransfer_template_plantsare NOT replicated — the fanout already plants N senders per recipient (its own density), and TransferTemplate plants already produce 3 firings per template (the Complete / Orphan / Required-met cases).- Return type:
- Parameters:
base (ScenarioPlant)
factor (int)
day_stride (int)
- recon_gen.common.l2.auto_scenario.filter_scenario_plants(base, kinds)[source]
Return a copy of
basekeeping only the requested L1 plant kinds.The data-shaping panel’s plant-toggle checkboxes (X.4.h.2) write a subset of the
PlantKindenum intocfg.test_generator.plants; this is the projection that consumes that subset. Per SPEC’s “Data-shaping model / plants” section:Noneor empty tuple ⇒ “all kinds” (today’s behavior), so the absent-block case stays byte-identical to the locked seeds.Only the 6 L1-invariant plant kinds in
PlantKindare gated: drift / overdraft / limit_breach / stuck_pending / stuck_unbundled / supersession. The other plant tuples onScenarioPlant—failed_transaction_plants(X.1.i — separate Failed-status fixture),transfer_template_plants,rail_firing_plants,inv_fanout_plants— are L2-shape / Investigation fixtures, not L1 SHOULD-violations, and pass through unchanged. Same withtemplate_instances(the customer materialization, needed by every plant kind that references customer-side accounts) andtoday(the reference date).- Return type:
- Parameters:
base (ScenarioPlant)
kinds (tuple[str, ...] | None)
- recon_gen.common.l2.auto_scenario.template_instance_ids(template)[source]
The account_ids this template will materialize during seed.
Public surface for the validator (U7 — collision with singleton Account.id) so the validator never replicates the rendering rule. Resolves the same template-rendering path the seed uses inside
_materialize_instances— change one, the other follows.- Return type:
tuple[str,...]- Parameters:
template (AccountTemplate)