recon_gen.common.l2.trainer_timeline
compute_plant_timeline — project planted exceptions onto a per-day
timeline (X.4.h.6.a).
The Studio data-shaping panel renders this as a vertical column —
one row per day in the plant window, annotated with which exception
kinds hit that day. The trainer can scan top-to-bottom to learn how
each plant lands across time, click a day to jump the end_date
knob there, and re-deploy.
Pure projection: walks the same ScenarioPlant the deploy pipeline
emits (build_default_scenario from auto_scenario.py), reads
plant.days_ago + scenario.today, and bins each plant onto
today - timedelta(days=days_ago). No new generator logic — this
is a read-only view of what the pipeline already plants.
Scope-aware: when tg.scope == "uncovered_rails" the deploy
pipeline emits NO plants (just baseline fill), so this returns an
empty timeline. "full" and "exceptions_only" both emit the
same plant set, so they share the same timeline output.
Severability: pure Python, no DB import, no async. Same posture as
trainer.py’s plants_per_node. The Studio route that wraps
this calls it at request time and renders to HTML.
Functions
|
Walk the auto-scenario for |
|
Aggregate count per |
Classes
|
One planted exception, projected onto its emit date. |
|
A row in the rendered timeline — date + the plants that hit it. |
- class recon_gen.common.l2.trainer_timeline.PlantHit(kind, account_id, rail_name, amount)[source]
Bases:
objectOne planted exception, projected onto its emit date.
kindmatches one of the 6PlantKindvalues the trainer panel’s plant-toggle filters on.rail_nameis None for overdraft (no rail involved — overdraft is an account-balance state, not a rail-bound transfer).amountis the canonical money magnitude — for supersession, the corrected_amount.- Parameters:
kind (Literal['drift', 'overdraft', 'limit_breach', 'stuck_pending', 'stuck_unbundled', 'supersession'])
account_id (str)
rail_name (str | None)
amount (Decimal | None)
- account_id: str
- amount: Decimal | None
- kind: Literal['drift', 'overdraft', 'limit_breach', 'stuck_pending', 'stuck_unbundled', 'supersession']
- rail_name: str | None
- class recon_gen.common.l2.trainer_timeline.TimelineDay(day, hits)[source]
Bases:
objectA row in the rendered timeline — date + the plants that hit it.
- Parameters:
day (date)
hits (tuple[PlantHit, ...])
- day: date
- recon_gen.common.l2.trainer_timeline.compute_plant_timeline(instance, tg)[source]
Walk the auto-scenario for
instance+tgand return oneTimelineDayper distinct plant date, sorted oldest → newest.Days with zero plants are omitted — the operator scans the timeline for “what landed when”, not “every calendar day”. The window is determined by the plant set itself (typically 7 days back from
tg.end_date).When
tg.scope == "uncovered_rails"the deploy pipeline emits NO plants (only baseline fill), so this returns an empty list. The trainer’s UI can then render a “no plants in this scope” hint.Threads
tg.plants(None or empty = all kinds; non-empty = subset filter) through the samefilter_scenario_plantschain the deploy pipeline uses, so the timeline reflects exactly what the nextDeploy changeswill land.- Return type:
list[TimelineDay]- Parameters:
instance (L2Instance)
tg (TestGeneratorConfig)
- recon_gen.common.l2.trainer_timeline.hits_by_kind(timeline)[source]
Aggregate count per
PlantKindacross the whole timeline.Helper for the timeline header — surfaces “12 drift, 2 overdraft, 1 supersession” so the operator gets a one-line summary before scrolling the per-day rows. Returns kinds with zero count omitted.
- Return type:
Mapping[Literal['drift','overdraft','limit_breach','stuck_pending','stuck_unbundled','supersession'],int]- Parameters:
timeline (Sequence[TimelineDay])