1. What the Scenario Lab is — a practice kitchen, not the live market
Imagine you want to ask: what if the sun fails for three days? what if the grid tells a plant to cut back at noon? what if the market’s invoice is wrong by five rand? You cannot wait a real month, and you must not practise on the live books.
The Scenario Lab is the practice kitchen. You write a recipe (a scenario), press Run, and the platform cooks a whole market month in seconds — bids, prices, grid orders, meter readings, imbalance, wheeling settlement, and the month-end check against the market invoice. Then you compare two cooks, and you wash the dishes (Teardown) so the practice food does not sit on the live plates.
The screen lives at /org/sim. It is not
on the organisation landing page. It exists only in development and demo
builds. Production builds do not compile the route at all.
Follow along. Seat: any organisation user in a development or demo build (for example
trader_1@webill.net, passwordsupersecret). Path: after sign-in, open/org/simin the address bar. You should see the beaker heading Scenario Lab and a Scenarios strip of named chips.
Where this is taught. Strategy reading of a labelled simulated screen is V3 §17. How the harness itself clears prices and writes one set of meter truth is V3 §16. Demo setup, live meter ticks, and the Eskom / Munix apps stay in V21.
1.1 The reader’s problem
You need a safe place to:
- Rehearse a month of market life without waiting thirty calendar days.
- Change one thing (cloud, a grid order, a bad invoice line) and see what moved.
- Prove that two runs of the same recipe produce the same numbers — or, if they do not, that the platform changed, not the weather.
- Clean up every row the rehearsal created, while keeping a paper trail of what you did.
The Lab is that place. It is not a second market. It is not a forecast workbench. It is not a place to publish a price to the country.
1.2 What it is for — and what it is not for
| Use the Lab to | Do not use the Lab to |
|---|---|
| Ask a what-if against a written recipe | Claim a production System Marginal Price |
| Drill reconciliation on a declared bad invoice | Treat a ribbon-marked screen as live evidence |
| Watch the same clocks the live platform uses, sped up | Replace overnight jobs or the live meter writer |
| Compare two completed cooks of a recipe | Edit a recipe while it is still cooking |
Two other tools share the word simulation. Keep them apart:
| Tool | Question it answers | Where |
|---|---|---|
Scenario Lab (/org/sim) |
What if the market month ran under this weather, these grid orders, these invoice faults? | This volume |
| Strategy Simulations | What if I signed this contract into a book? | V3 §8 |
| Live meter ticks (S15) | Keep writing 20-minute readings on the wall clock after setup | V21 §6 |
1.3 How you should use it
You use the Lab as a controlled experiment.
- Start from a seeded recipe, or Clone one so the original stays untouched.
- Change one family of inputs — weather or grid orders or invoice faults or how jumpy residual prices are — not all four at once.
- Press Run scenario. Wait until every delivery day
appears in the Live day log and the run badge reads
COMPLETE(orFAILED). - Read the day log and the Compare strip. Follow any amber SIMULATED ribbon on other screens back here.
- Press Teardown when you are done looking. The practice rows go away. The run stays as a tombstone (the log and the headline numbers remain; the created data is gone).
flowchart LR
A[Pick or clone a scenario] --> B[Change one thing]
B --> C[Run scenario]
C --> D[Watch the day log]
D --> E{Complete?}
E -->|yes| F[Compare KPIs]
E -->|failed| G[Read the last day]
F --> H[Teardown]
G --> H
1.4 Who this volume is for
The primary seat is the platform operator in a demo world — the person who builds the dataset, presses Run, and must not confuse the result with production. Traders, IPP operators, and off-takers meet the Lab only when an amber SIMULATED ribbon appears on a Strategy, Bid Desk, Market Operator, or System Operator screen. That ribbon is a warning label, not a second product.
Data. Follow-along numbers in this volume come from
mix wb.setup --profile lab --reset --seed 42or a completedfull_demothat sealed stage S12 (Scenario Lab scripts). Typical sealed clock: as_of 2026-07-18, so the Lab’s own month is August 2026. If your demo was sealed on an older date and the Lab feels stale, roll it forward first (mix wb.setup.roll_forward— V21 §4).
2. Words you need — every idea before the buttons
This chapter names every idea the Lab uses. Later chapters assume these words. First use in this volume links to the glossary.
2.1 The kitchen metaphor, then the platform words
| Plain English | Platform word | One-sentence meaning |
|---|---|---|
| The recipe | Scenario | The written script of a month: dates, seed, weather, grid orders, invoice faults, who takes part |
| One cook of the recipe | SimRun | One execution: status, day-by-day diary, list of rows it created, headline numbers |
| The cook | Driver | The sped-up clock that walks each delivery day through the real daily jobs |
| The random-looking spice that is not random | Seed | An integer (demo uses 42) that makes “noise” repeatable |
| Scripted weather | Day factor | A multiplier on solar, wind, or load for one calendar day (default 1.00) |
| A grid order you wrote in advance | SO event | A System Operator instruction the driver issues on a scripted day |
| A deliberate invoice fault | MO injection | A declared error applied only to the Market Operator’s published file |
| The rest of the country | Residual market | Everyone not on this platform, modelled as a price-elastic pool |
| How jumpy that pool is | Elasticity k | Megawatt-hours the residual absorbs per 100% price move |
| The starting price shape | p_base | The hour-by-hour base price the residual bends around |
| The warning label | SIMULATED ribbon | Amber bar on any screen showing a live run’s data |
| The created rows | Artifact registry | The run’s list of every bid, meter row, settlement line it wrote |
| Washing the dishes | Teardown | Delete exactly those artifacts; keep the diary and headlines as a tombstone |
| “This cook used an older recipe” | Scenario drifted | The script was edited after this run was stored |
2.2 Abbreviations the Lab shows you
| Abbreviation | Stands for | In the Lab it means |
|---|---|---|
| SO | System Operator | The grid controller. Lab events pretend to be its orders. |
| MO | Market Operator | The market clerk. Lab injections pretend its invoice is wrong. |
| BRP | Balance Responsible Party | The organisation that must match what it scheduled to what the meters did. |
| DAM | Day-Ahead Market | Yesterday’s auction for today’s hours. The driver builds those bids at a fake 03:00 the day before. |
| IDM | Intra-Day Market | Today’s later auction, so you can correct the morning plan. The driver runs it at a fake 16:00. |
| SMP | System Marginal Price | The one price every cleared megawatt-hour settles at that hour. |
| TOU | Time of Use | Peak / standard / off-peak price bands. The mock p_base
is a TOU curve. |
| KPI | Key Performance Indicator | The small headline bundle on each run (days,
bids, margin, coverage, …). |
| S12 | Setup stage 12 | The setup step that plants the three seeded scenarios and their sample runs. |
| SAST | South Africa Standard Time | UTC+2. Gates and the fake clocks are SAST. |
| MW / MWh / kWh | Megawatt / megawatt-hour / kilowatt-hour | Power, then energy. 1 MWh = 1,000 kWh. |
| ISO date / ISO time | International date format | 2026-08-13 or 2026-08-13T10:00:00Z (the
Z means UTC). |
2.3 The two clocks
The Lab does not wait for real nights. It hands the same daily jobs a sim-clock: “pretend it is 03:00 on 12 August.” Recorded timestamps on the rows still say when the computer actually wrote them. Decision logic (gates, “is it too late to bid?”) reads the pretend clock.
| Clock | What it drives | Example |
|---|---|---|
| Sim-clock | Bid gates, which delivery day is “today”, when IDM runs | Driver says 03:00 SAST on D−1 |
| Wall clock | When the row was really inserted | Your laptop at 17:22 |
| Production Oban cron | Live overnight jobs — not the Lab | 01:00 SAST settlement runner |
| S15 live ticks | Continuous 20-minute meter writer | V21 §6 |
2.4 Why a seed makes “random” repeatable
Meter “forecast error” looks random. It is not. The Lab hashes
(seed, site, hour) into a number between −1 and 1, then
scales it by a small σ (sigma). Same seed, same site,
same hour → same wiggle. Change the seed and the wiggle pattern changes.
That is why the chip under each scenario name shows
seed 42: the seed is part of the experiment, not
decoration.
2.5 Who “participants” are
A scenario does not invent companies. It points at organisations already planted by setup:
- registration_ids — which BRP registration the market cycles drive
- book_ids — which strategy books (trader, off-taker, IPP) get the nightly forecast
- generator_site_ids / off_taker_site_ids — whose meters receive the written actuals
- noise_sigma — how large the seeded wiggle is (S12 uses 0.02, two percent)
If those lists are empty, the driver falls back to active pool memberships of the named books. A brand-new scenario from New starts with an empty participants map unless you cloned, or created it while another scenario was selected (then it inherits that scenario’s participants).
3. Opening the Lab — how you get there and what you see first
3.1 The two gates
Two locks sit in front of /org/sim. Both must be
open.
- Compile-time. The Elixir app is built with
Application.compile_env(:wb_wheeling, :sim_enabled)true. That is on indevandtest, off in production. When it is off, the route is not even in the router — there is nothing to bookmark. - Runtime.
WB.Sim.sim_enabled?/0must also pass. If you somehow reach the LiveView with the flag off, you are sent home to/.
There is no landing-page card. That is deliberate: a practice kitchen that is one mis-click from a live trader’s home page would be too easy to confuse with production.
Follow along. Development server (
mix phx.server) → sign in astrader_1@webill.net/supersecret→ type/org/simafter the host. You should see Scenario Lab 🧪 in the shell brand and WB.Sim in the page header.
If the page is missing, you are on a production build, the runtime flag is off, or you are not signed in as an organisation user. See §14.
3.2 The page chrome
The page is a single LiveView. It does not have tabs. Everything you need is stacked on one scroll.
The header title is Scenario Lab. The description
reads: Drive a scripted market month deterministically; compare and
tear down runs. The icon is the Heroicon beaker
(hero-beaker). The left shell has one item, also
Scenario Lab, pointing at /org/sim.
3.3 Deep links
Other screens can send you here with a run already open:
/org/sim?run_id=<the-run-uuid>
The Lab loads that run’s scenario, selects it, and fills the Live day log from that run’s diary. If the id is unknown, the page opens as a plain gallery — it does not error. The amber SIMULATED ribbon on Strategy, Bid Desk, Market Operator, and System Operator screens uses this link. Clicking the ribbon is how you “inspect in Lab.”
3.4 What you see before you pick a scenario
Until a chip is selected, only the Scenarios strip is interactive. The editor, the Runs list, Compare, and the day log stay hidden. That is why the first action is always: click a named chip.
After S12 you should see at least three chips:
| Chip text (name + seed) | What it is |
|---|---|
AUG-BASELINE seed 42 |
The full August reference month |
CODEX-S12-WARM seed 42 (or your setup
seed) |
The three-day warm window used to prove the Lab is alive |
CODEX-S12-ISOLATION seed 42 |
The one-day script used to prove teardown does not touch other desks |
Each chip shows the scenario name and, in smaller
type, seed {n}. The selected chip gets a thicker ink border
and a light grey fill.
You cannot change chips while a run task is in flight — the buttons disable. Finish or fail first.
4. The scenario list — chips and the three list actions
The Scenarios card is the gallery. Its heading is Scenarios. On the right sit three buttons. Under them sit one chip per saved recipe.
Scenarios strip lab · seed 42 · S12
| Chip 1 | Chip | Chip | Actions 2 |
|---|---|---|---|
| AUG-BASELINE seed 42 | CODEX-S12-WARM seed 42 | CODEX-S12-ISOLATION seed 42 | New · Clone · Delete |
- 1Chip — click to select. The selected chip is the recipe the editor and Runs panel talk about.
- 2Actions — New always; Clone and Delete only after a chip is selected.
4.1 Select
Click a chip. The Lab:
- Loads that scenario’s script into the editor.
- Subscribes to live messages for that scenario (so a running cook streams days as they finish).
- Lists every run that belongs to it, newest first.
- Clears the day log until you run again or open a deep link.
Nothing is saved by selecting. Select is look-up, not edit.
4.2 New
New creates a blank-ish recipe and selects it. The flash says New scenario created.
The new name is Scenario {unique-integer} — a throwaway
label so two people cannot collide. Change the interesting fields in the
editor afterwards; the name itself is not an on-screen rename field
(names are unique in the database).
What it copies depends on whether a chip was already selected:
| Field | No chip selected | A chip was selected |
|---|---|---|
| Name | Scenario {n} |
Scenario {n} |
| Seed | 42 | that scenario’s seed |
| Period | 1–1 Aug 2026 | that scenario’s period |
| Day factors | empty (every day is 1.00) | empty — weather is not copied |
| SO events | none | none |
| MO injections | none | none |
p_base source |
mock | that scenario’s source |
Elasticity k |
blank (∞) | that scenario’s k |
| Participants | empty | that scenario’s participants |
So New while AUG-BASELINE is selected
gives you the same books and the same market knobs, but a clean weather
and event slate. That is the safe way to start a fresh experiment that
still talks to the demo portfolio.
New is disabled when the selected scenario is not
mutable — a run is PENDING or RUNNING, or a
task is in flight.
4.3 Clone
Clone needs a selected chip. It copies
everything, including weather, events, and injections,
and names the copy {old name} copy {unique-integer}. The
flash says Scenario cloned with a new identity.
Clone is how you keep AUG-BASELINE as the reference.
Edit the clone. Leave the original alone so later comparisons still have
a known starting point.
Clone is disabled while the selected scenario is not mutable.
4.4 Delete
Delete removes the recipe, not a run. It is rose-coloured on purpose.
It stays disabled when:
- nothing is selected
- the scenario is not mutable (a cook is in flight)
- the scenario has any runs, including
TORN_DOWNtombstones
If you try to delete a recipe that still has history, the editor error reads: A scenario with historical run tombstones cannot be deleted. Tear those runs down if you like — teardown does not remove the tombstone row — so a scenario that has ever been run cannot be deleted from the screen. That is the audit rule: the recipe that produced a tombstone must remain nameable.
4.5 When the strip freezes
| You see | Why | What to do |
|---|---|---|
| Chips disabled | A run task is in flight | Wait for COMPLETE or FAILED |
| New / Clone / Delete faded | Scenario not mutable | Same — wait, or teardown is not the issue here |
| Delete faded, runs exist | History exists | Keep the recipe; start a clone if you need a disposable copy |
5. What a scenario contains — the script behind every chip
A scenario is pure data. It writes nothing by itself. The driver reads it and walks the month. Think of a sheet of paper titled “August, seed 42, cloudy mid-month, one noon curtailment, two invoice faults.”
5.1 The fields, in the order the editor shows them
| Field | What you are telling the cook | Where you edit it |
|---|---|---|
| Name | The chip label. Unique. Examples: AUG-BASELINE,
CODEX-S12-WARM |
Set at create / clone; not a form field |
| Seed | The repeatability key | Shown in the chip and the editor subtitle |
| Period start / end | Inclusive calendar window the driver walks, one delivery day at a time | Shown in the editor subtitle
(2026-08-01 → 2026-08-31) |
| Day factors | Sparse weather / load multipliers | §9 Day factors table |
| SO events | Scripted grid orders | §9 SO events list |
| MO injections | Scripted invoice faults | §9 MO error injections |
| p_base source | Where the starting price shape comes from: mock or
scenario |
Residual market panel |
| Elasticity k | How jumpy the residual market is. Blank = ∞ (flat mock) | Residual market panel |
| Participants | Which already-seeded registrations, books, and sites take part | Not a form — inherited from setup / clone / New-from-selected |
The editor subtitle always repeats the period, the seed, and
k (or ∞).
5.2 Sparse means “only the exceptions”
Day factors are a map of exceptions. A day you do not list is 1.00 for solar, wind, and load — a normal day. You do not fill thirty-one rows by hand unless you want thirty-one unusual days.
day_factors = {
2026-08-12: { solar: 0.35 },
2026-08-13: { solar: 0.35 },
2026-08-14: { solar: 0.35 }
}
Those three cloudy days are the original AUG-BASELINE
weather story. S12 then writes a richer set on top — §6 shows what you
actually see after setup.
5.3 The three seeded recipes, at a glance
After stage S12 the gallery is not empty. Setup plants three recipes and several sample runs so the Runs panel is already a teaching aid.
| Name | Period (typical sealed as_of 2026-07-18) | Why it exists |
|---|---|---|
| AUG-BASELINE | 1–31 Aug 2026 | The documentation month: weather, every event kind, every injection kind, comparison runs, a failed run, a torn-down run, then a deliberate edit so one run shows Scenario drifted |
| CODEX-S12-WARM | 10–12 Aug 2026 | A short window the verifier actually drives, so you can open Lab on a completed cook without waiting for thirty days |
| CODEX-S12-ISOLATION | 25 Aug 2026 (one day) | A throwaway script whose only job is to prove teardown deletes its rows and leaves every other desk unchanged |
Note. If you sealed on a different
as_of, the warm window is always the 10th–12th of the month after as_of, and isolation is thirteen days after the 12th. August dates below assume the usual July seal.
5.4 Sample runs that are not extra scenarios
S12 also stores runs under
AUG-BASELINE. They are cooks, not recipes.
| Sample run | Status you will see | What it is teaching |
|---|---|---|
| Comparison A | COMPLETE + Scenario drifted |
Two identical KPI bundles, then the script was edited — A no longer matches today’s paper |
| Comparison B | COMPLETE |
The twin of A, stored before the edit |
| Failed partial | FAILED |
A cook that wrote one day and then stopped on purpose |
| Tombstone | TORN_DOWN |
A cook whose artifacts are already gone |
CODEX-S12-WARM has a warm run the
verifier leaves COMPLETE with artifacts still attached —
that is the run a deep link such as /org/sim?run_id=…
usually opens.
CODEX-S12-ISOLATION may show torn-down isolation cooks;
their point is the empty desks around them, not a month of KPIs.
6. AUG-BASELINE — the full August month
AUG-BASELINE is the Lab’s reference recipe. The original
builder writes a simple August story (seed 42, cloudy 12–14, one
curtailment, two injections). Stage S12 then
enriches that same named row so the editor exercises
every control: more weather, every System Operator event kind, every
Market Operator injection kind, several books, and a handful of sample
runs.
What you see after a sealed lab or
full_demo is the enriched recipe. This
chapter describes that screen.
Follow along. Seat: organisation user. Path:
/org/sim→ chip AUG-BASELINE. You should see the subtitle2026-08-01 → 2026-08-31 · seed 42 · k 500and a day-factor grid with thirty-one date rows.
Data. lab / full_demo · seed 42 · stage S12 · as_of 2026-07-18 · last walked 2026-08-18.
6.1 Purpose
Use AUG-BASELINE when you want the whole
month in one script:
- a normal early August
- a cloudy spell in the second week
- a mid-month grid story
- a month-end invoice that is wrong on purpose
- enough sample runs that Compare and Scenario drifted are already visible
Do not edit this chip for a new experiment. Clone it first. S12 already edits it once after storing comparison A, which is why A’s row wears Scenario drifted. A second casual edit makes every historical cook harder to read.
6.2 Identity panel (the editor subtitle)
| Fact | Value after S12 |
|---|---|
| Name | AUG-BASELINE |
| Seed | 42 |
| Period | 2026-08-01 → 2026-08-31 (31 delivery days) |
p_base source |
Scenario curve (not the flat mock) |
Elasticity k |
500 MWh per 100% price move |
| Participants | The S08 active BRP registration; the S07 representative trader, off-taker, and IPP books; every generator and off-taker site those books touch |
| Noise σ | 0.02 (two percent seeded wiggle on written actuals) |
6.3 Day factors table — what each unusual day is saying
Unlisted days are 1.00 / 1.00 / 1.00. After S12 the exceptions are the warm window plus two extra cloudy days, and — after the drift edit — 20 August.
| Date | Solar | Wind | Load | Extra | Plain English |
|---|---|---|---|---|---|
| 2026-08-10 | 0.95 | 1.12 | 1.04 | σ 0.01 | Slightly dull sun, breezy, a little extra demand |
| 2026-08-11 | 0.48 | 0.87 | 1.10 | σ 0.02 | A dim solar day, softer wind, busier load |
| 2026-08-12 | 0.35 | 1.25 | 0.96 | σ 0.03 | Heavy cloud, strong wind, slightly quieter load |
| 2026-08-13 | 0.35 | 1.18 | 1.02 | — | Cloud continues; wind still up |
| 2026-08-14 | 0.35 | 0.91 | 1.07 | — | Cloud continues; wind eases |
| 2026-08-20 | 0.72 | 1.08 | 1.03 | — | Added after comparison A was stored — this is the drift |
Worked numbers for one hour of a 10 MWh solar baseline on 12 August:
- listed solar factor 0.35
- scripted availability
10 × 0.35 = 3.5 MWhbefore noise and curtailment - with σ 0.03 the seeded wiggle can move that by about
3.5 × 0.03 = 0.105 MWheither way - a noon curtailment (below) then caps what is allowed to land
Cells below 0.60 tint sky-blue; cells above 1.40 tint amber; other non-1.00 cells tint pale indigo. That is how the grid helps your eye find the story without reading every number.
6.4 SO events list — the six scripted grid stories
S12 writes one event of each kind the Lab (and the setup pack) know
about. Site names are the real demo site names, not the
old IPP-A label.
Typical window against as_of 2026-07-18 (generator = first participating IPP site; off-taker = first participating buyer site):
| Date | Kind | Who | Hours (SAST) | Cap | What it does |
|---|---|---|---|---|---|
| 2026-08-10 | CURTAILMENT |
generator | 10:00–13:00 | 8.0 MW | The plant may not put out more than 8 MW for those three hours |
| 2026-08-10 | OUTAGE_PLANNED |
generator | 14:00–16:00 | — | A booked outage — the plant is off the circuit on purpose |
| 2026-08-11 | OUTAGE_UNPLANNED |
off-taker | 08:00–10:00 | — | A surprise outage on the buyer side |
| 2026-08-11 | DISPATCH_CONSTRAINT |
generator | 16:00–18:00 | 4.5 MW | A tighter operating cap for the late afternoon |
| 2026-08-12 | RESERVE_ACTIVATION |
generator | 17:00–19:00 | 3.0 MW | Reserve is called — the plant is held for the system |
| 2026-08-12 | SYSTEM_CONDITION |
registration | whole day | — | A high-stress banner (stress: HIGH) on the BRP, not a
site cap |
Each list row reads:
{date} · {site} · {kind} · {start}:00–{end}:00.
Edit and Remove sit on the right.
Severity on these seeded rows is WARNING (the isolation
failed run uses CRITICAL — that is a different chip).
The add form under the list is how you would append a seventh event. Leave it alone on this chip; clone first.
6.5 MO error injections list — four declared invoice faults
Each injection names the reconciliation outcome it must
produce. The list shows
{type} · {when} → {outcome} in bold.
| Type | When | Value | Charge | Reconciliation must say |
|---|---|---|---|---|
republish_price |
2026-08-10T10:00:00Z | +R 5 | — | VARIANCE / PRICE — the invoice priced the hour R 5 above the schedule you already stored |
meter_bias |
2026-08-11 | +3.3% | — | VARIANCE / METER — the MO’s metered quantity is 3.3% away from yours |
drop_line |
2026-08-12T10:00:00Z | — | ENERGY | MISSING_MO — you have an energy line; the MO file does not |
phantom_line |
2026-08-12T15:30:00Z | R 275.00 | ENERGY | MISSING_INTERNAL — the MO bills R 275 you have no record of |
No injections would mean a 100% matched month, because the simulated MO otherwise echoes your own truth. These four exist so the reconciler’s whole vocabulary is exercisable on one recipe.
6.6 Residual market panel on this chip
The sparkline is the deterministic TOU preview: R 700 / R 980
/ R 1,400 per MWh (night / day / evening peak 17:00–20:00).
AUG-BASELINE uses Scenario curve plus
k = 500.
Worked price impact (the same arithmetic V3 §16 teaches):
If the hour’s p_base is R 980 and you sell 4.71
MWh into a residual with k = 500:
- the residual must absorb 4.71 MWh
- the price moves by
4.71 ÷ 500 = 0.942% - SMP =
980 × (1 − 0.00942) = R 970.77 - versus the flat mock
4.71 × 980 = R 4,615.80, you earn4.71 × 970.77 = R 4,572.33— R 43.47 less
That R 43 is the lesson: your own volume moves the price.
k = 500 is deliberately “thin enough to feel.”
6.7 Runs list you should already see
Do not be surprised that Run scenario is not the first cook. S12 pre-seeded several.
| What the row looks like | Meaning |
|---|---|
Green COMPLETE · 31 days or
1 days · a KPI map · maybe Scenario
drifted |
A finished cook. Comparison A/B store
margin_r: 12138.50 and
coverage_pct: 100.0 |
Red FAILED · a short day count · error in
the KPI map |
The injected failure after the first durable day |
Grey TORN_DOWN · Teardown hidden |
Artifacts already removed; diary kept |
Compare (latest vs previous) appears as soon as two runs exist. On this chip it is already showing two KPI blobs side by side.
The Live day log stays on “No completed days.” until
you start a new cook or open a ?run_id= link. Pre-seeded
runs have diaries; a deep link is how you reopen one.
6.8 Actions that are safe on this chip
| Action | Safe? | Why |
|---|---|---|
| Select / read every panel | Yes | Looking does not mutate |
| Run scenario | Yes, if no cook is in flight | Starts a new cook; does not overwrite old tombstones |
Teardown on a COMPLETE / FAILED row |
Yes | Removes that cook’s artifacts only |
| Clone | Yes — preferred | Your experiment gets a new identity |
| Edit factors / events / injections / market | Possible, but do not on the reference | You will drift every stored cook |
| Delete | No | History exists; the button stays disabled |
6.9 How this scenario should be used
- Read it as the picture of a full month.
- Clone it to
AUG-BASELINE copy …before changing weather. - Run the clone, then Compare against a completed cook of the original (or against the clone’s own previous cook).
- Teardown the clone’s cooks when you are finished looking.
- Leave the original chip’s sample runs as the gallery lesson unless you are deliberately cleaning a private database.
Claim scripts
Scenario curtailment events can carry a claim script with a lodgement delay, provisional percentage and final energy factor. The simulation clock runs the production claim lifecycle through the Mock channel and keeps it scoped to that run’s instructions. An explicitly supplied operator quantity is a simulated input; absent weather or meters are not manufactured. Uncompensated claims close without a payment path. Teardown removes the run’s claim records while retaining immutable billing artefacts. The S08/S11 gallery separately demonstrates draft, lodged, provisionally paid, reconciled, disputed and closed states, plus an escalated draft.
Plant feedback during a curtailed afternoon
The Driver uses the same System Operator instruction windows for
device setpoints and meter actuals. Existing mock feeds emit capped
power, setpoint and curtailment mode before, during and after the
instruction; a site without a feed remains forecast-only. The day log
records feed IDs and trace hashes under
plant_telemetry.
After running a curtailed day, open the participating plant’s Your plant live page and choose its trading date. Compare the amber curtailment band, setpoint changes, meter-backed trace status and the claim’s evidence reference. The two-hour arithmetic example is 16,350 + 16,690 kWh potential less 8,010 + 7,990 kWh metered = 17,040 kWh shortfall; at R0.65/kWh that is R11,076. Actual scenario traces depend on the PPA method and available curves and meters, so an incomplete trace stays PARTIAL.
Teardown tombstones the Lab run and removes its mutable financial rows. Immutable plant traces, device batches and the instructions needed by those traces remain as provenance; a teardown never rewrites downloaded evidence.
7. CODEX-S12-WARM — the three-day warm run
CODEX-S12-WARM is the short, living proof that the Lab
works. Setup does not wait for you to press Run on a thirty-one-day
script. It plants a three-day window, drives it, and leaves a
COMPLETE cook with artifacts still attached so a deep
link can open a finished diary.
Follow along. Path:
/org/sim→ CODEX-S12-WARM. You should see a three-day period in the subtitle (typically2026-08-10 → 2026-08-12 · seed 42 · k 500) and a much shorter day-factor grid thanAUG-BASELINE.
Data. lab / full_demo · seed 42 · stage S12 · warm window = 10th–12th of the month after as_of.
7.1 Purpose
Use the warm chip when you want to:
- Open a finished cook immediately
(
/org/sim?run_id=from the SIMULATED ribbon or from the setup pack’s printed path) - Practise reading a day log that has only three delivery days
- See the same kinds of weather, events, and injections as the big month, without scrolling thirty-one factor rows
It is not a second reference month. It is the tour-size version of the same story.
7.2 Identity panel
| Fact | Typical sealed value |
|---|---|
| Name | CODEX-S12-WARM |
| Seed | The setup seed (42) |
| Period | 2026-08-10 → 2026-08-12 |
p_base source |
Scenario curve |
Elasticity k |
500.000 |
| Participants | The same registration / books / sites as
AUG-BASELINE |
The window is computed, not hardcoded: start = first day of
(as_of’s next month) plus nine days. A July seal → August
10–12. A June seal → July 10–12. That is why the mix task
wb.setup.roll_forward exists — so Lab scripts stay on “this
month” instead of a fossil August after you have lived past it.
7.3 Day factors table
Only the three warm dates are stored (S12 takes those keys from the richer map).
| Date | Solar | Wind | Load | σ | Plain English |
|---|---|---|---|---|---|
| 10th | 0.95 | 1.12 | 1.04 | 0.01 | Almost-normal sun, extra wind, slightly busy |
| 11th | 0.48 | 0.87 | 1.10 | 0.02 | Dim sun, softer wind, busier load |
| 12th | 0.35 | 1.25 | 0.96 | 0.03 | Heavy cloud, strong wind, quieter load |
The grid therefore has three date rows, not thirty-one. Double-click a cell to edit (range 0.00–2.00) only if you have cloned, or if you accept that you will drift the warm run.
Bulk fill on this chip can paint all three days at once — From / To must stay inside 10–12 August.
7.4 SO events list
S12 keeps only events whose date is one of the three warm dates:
| Date | Kind | Hours | What you are seeing |
|---|---|---|---|
| 10th | CURTAILMENT on the generator |
10:00–13:00 at 8.0 MW | A noon cap |
| 10th | OUTAGE_PLANNED on the generator |
14:00–16:00 | A booked afternoon outage |
| 11th | OUTAGE_UNPLANNED on the off-taker |
08:00–10:00 | A morning surprise on the buyer |
| 11th | DISPATCH_CONSTRAINT on the generator |
16:00–18:00 at 4.5 MW | A late-afternoon tighter cap |
| 12th | RESERVE_ACTIVATION on the generator |
17:00–19:00 at 3.0 MW | Reserve called at peak |
| 12th | SYSTEM_CONDITION on the registration |
(no hour window) | High stress banner |
So the warm chip’s event list is the same six rows
as AUG-BASELINE’s first three days — not a different story,
a cropped one.
7.5 MO error injections list
Injections are copied in full from the canonical set — they are not cropped by date. You will still see all four:
republish_priceon the 10th 10:00 UTC → VARIANCE / PRICEmeter_biason the 11th → VARIANCE / METERdrop_lineon the 12th 10:00 UTC → MISSING_MOphantom_lineon the 12th 15:30 UTC → MISSING_INTERNAL
That is intentional. Month-end reconciliation still needs a file that can be wrong in every classified way, even when the driven window is three days.
7.6 Residual market panel
Same as the big month: Scenario curve, k = 500, the 700
/ 980 / 1400 sparkline. Saving a new k here does
not change AUG-BASELINE; they are separate
rows.
7.7 Runs, Compare, and the day log
The warm run is the one setup leaves COMPLETE with artifacts still owned. Opening the chip lists that cook:
- green
COMPLETE 3 days(or however many the driver logged)- a KPI map including
days,bids,imbalance_positions, and ascenario_hash - Teardown enabled (it is not
PENDING/RUNNING)
If you tear it down, the SIMULATED ribbon on other screens that
pointed at this run will clear, and the setup pack’s printed
/org/sim?run_id=… link will open a tombstone diary instead
of live artifacts. Prefer cloning and running the clone if you want a
private cook.
Compare appears once a second cook exists (for example after you press Run scenario yourself).
Live day log fills from a deep link or from a cook
you start while watching. Each streamed row is one ISO date plus the
inspect of that day’s entry (dam, cleared,
so_events, idm, imbalance,
actuals, wheeling_settlement).
7.8 Actions
| Action | On this chip |
|---|---|
| Read panels | The intended first use |
| Run scenario | Starts another three-day cook; the editor locks while it runs |
| Teardown the warm run | Allowed — you will lose the “already complete” gallery |
| Clone | Best way to experiment with the three-day weather |
| Delete | Disabled while any run (including a tombstone) exists |
7.9 How this scenario should be used
Treat it as the five-minute tour: select it, read
the three-day grid, read the six events, read the four injections, open
the warm run’s diary from the ribbon or ?run_id=, then go
back to AUG-BASELINE or a clone for a real experiment.
8. CODEX-S12-ISOLATION — proving teardown is safe
CODEX-S12-ISOLATION is not a weather story. It is a
safety drill. Setup creates a one-day recipe, writes a
couple of dummy cooks, tears them down, and checks that every other desk
— Strategy allocations, Bid Desk rows, statements, ordinary meter
history — still checksums the same.
You open this chip to understand Teardown, not to study cloudy days.
Follow along. Path:
/org/sim→ CODEX-S12-ISOLATION. You should see a one-day period (typically2026-08-25 → 2026-08-25 · seed 42) and empty day-factor / event / injection panels.
Data. lab / full_demo · seed 42 · stage S12 · isolation day = warm-window end plus 13 days (12 Aug + 13 = 25 Aug).
8.1 Purpose
The fear teardown must kill: “if I wash the practice dishes, do I also throw away last month’s real statement?”
Isolation answers no, by construction:
- Create a tiny recipe that owns a few rows (a System Operator
instruction tagged
CODEX-S12-ISOLATION-COMPLETEor…-FAILED). - Checksum every “desk” table excluding rows this Lab run registered.
- Teardown the isolation cooks.
- Checksum again. The numbers must match.
- Confirm the warm run and the
failed
AUG-BASELINErun still exist with their artifacts. - Confirm you cannot hang another run’s artifact id on a neighbour run (cross-claim is blocked).
If any of that failed, S12 would not have sealed.
8.2 Identity panel
| Fact | Typical sealed value |
|---|---|
| Name | CODEX-S12-ISOLATION |
| Seed | 42 |
| Period | 2026-08-25 → 2026-08-25 (one delivery day) |
| Day factors | empty — every factor is 1.00 |
| SO events | empty in the script (the isolation runs ingest a
SYSTEM_CONDITION as an artifact, they do not store it on
the recipe) |
| MO injections | empty |
p_base source |
mock (the default; this chip is not a price experiment) |
Elasticity k |
blank / ∞ |
| Participants | The same registration and books; site lists may be omitted |
The editor subtitle therefore looks almost empty: one date, seed 42,
k ∞.
8.3 Day factors table
One date row. All three cells read 1.00 and stay white (the “normal” tint). There is nothing to bulk-fill unless you are inventing a new drill.
8.4 SO events list
The list shows No scripted events. The add form is there, unused.
The isolation runs still created a
SYSTEM_CONDITION instruction each (external refs
CODEX-S12-ISOLATION-COMPLETE and
CODEX-S12-ISOLATION-FAILED) and registered those ids on the
cook. After a successful S12 those instructions have already been torn
down. You will not find them on the live SO desk. That absence is the
point.
8.5 MO error injections list
No deliberate reconciliation differences. Isolation
is not a reconciliation drill. AUG-BASELINE and the warm
chip own that lesson.
8.6 Residual market panel
Default mock curve, blank k. Changing k
here does not help you understand teardown. Leave it.
8.7 Runs list — tombstones on purpose
After S12 the isolation cooks have been torn down (and teardown was
pressed a second time to prove it is repeat-safe). You
should see grey TORN_DOWN rows:
- KPI map includes
isolation: trueandterminal: COMPLETEorFAILED - day log has a single date with
phase: isolation - Teardown is hidden (status is already
TORN_DOWN) - Compare may show two tombstone KPI blobs
The warm run on the other chip must still be
COMPLETE. If tearing down isolation had eaten the warm
artifacts, S12 would have failed warm_run_preserved?.
8.8 Actions
| Action | On this chip |
|---|---|
| Read the empty panels | Yes — they teach “this recipe is a harness, not a month” |
| Run scenario | Possible, but you are no longer in the sealed drill |
| Teardown | Only if you started a new cook |
| Clone / Delete | Clone is fine; Delete stays disabled while tombstones exist |
8.9 How this scenario should be used
Read it once so Teardown is not a superstition. Then leave it alone.
Everyday what-ifs belong on a clone of
AUG-BASELINE or on CODEX-S12-WARM,
not on the isolation chip.
9. The editor — four panels, every field and action
Once a chip is selected, the scenario editor
(#scenario-editor) opens. The heading is the scenario name.
The subtitle is
{start} → {end} · seed {n} · k {value-or-∞}.
A rose error banner (#scenario-editor-error) appears
only when a save fails — for example a factor outside 0.0–2.0, or an
edit while a cook is running: Scenario edits are disabled while its
run is active.
The four panels sit in a two-by-two grid on a wide screen, stacked on a narrow one:
| Panel | Asks | Must not be used to claim |
|---|---|---|
| Day factors | What if the sun / wind / load is different on these days? | that real weather changed |
| Residual market | How jumpy is the rest of the country? | an approved SMP |
| SO events | What if the grid issues this order? | that the System Operator really issued it |
| MO error injections | Can reconciliation name this fault? | that a live MO file arrived wrong |
Careful. While a run is
PENDINGorRUNNING, every editor control disables. That stops the recipe changing under the cook’s hands.
9.1 Day factors — the table
Heading: Day factors. Helper text: Double-click a cell to edit. Range 0.0–2.0; unlisted values are 1.0.
The grid (#day-factor-grid) has four columns:
Day, SOLAR, WIND,
LOAD. One row per calendar date in the period.
| Column | Meaning |
|---|---|
| Day | ISO date (2026-08-12) |
| SOLAR | Multiplier on solar-shaped generation that day |
| WIND | Multiplier on wind-shaped generation |
| LOAD | Multiplier on consumption |
A cell at 1.00 is a normal day. 0.35 means “about a third of the usual profile.” 2.00 is the allowed ceiling — twice the usual. 0.00 is allowed and means “none of that resource that day.”
Tints:
| Tint | When |
|---|---|
| White | exactly 1.00 |
| Pale indigo | not 1.00, but between 0.60 and 1.40 |
| Sky blue | below 0.60 (a real shortage) |
| Amber | above 1.40 (a real surplus) |
To edit one cell: double-click it. A small number field and Save appear. Enter a value, press Save. Flash: Factor saved. The table does not show a separate Cancel on the cell; click another control or press the page’s cancel-factor path by starting a different edit.
Bulk fill (#bulk-factor-form) paints a
date range in one go:
| Field | What to enter |
|---|---|
| From | First ISO date in range (must sit inside the period) |
| To | Last ISO date (not before From) |
| Factor | solar, wind, or load |
| Value | 0.00–2.00 |
| Bulk fill | Writes that value onto every day in the range |
Flash: Factors filled across N day(s). Use this for “make the second week cloudy” instead of eighteen double-clicks.
Worked fill: From 2026-08-12, To
2026-08-14, Factor solar, Value
0.35 writes the original three-day cloud. Conservation of
meaning: wind and load on those days stay whatever they were; only solar
changes.
9.2 Residual market — the price knobs
Heading: Residual market.
The p_base preview is a 24-bar sparkline, one bar
per hour. Hover a bar to read hour {h}: R {price}/MWh.
Heights:
| Hours (SAST) | Preview price | Bar |
|---|---|---|
| 00:00–06:59 and 21:00–23:59 | R 700 / MWh | short |
| 07:00–16:59 | R 980 / MWh | medium |
| 17:00–20:59 | R 1,400 / MWh | tall |
That is the deterministic TOU curve, not a live stack. Caption under it: p_base preview: deterministic TOU curve (700 / 980 / 1400 R/MWh).
The form (#market-form):
| Field | Options / range | Meaning |
|---|---|---|
| p_base source | Mock curve / Scenario curve | Mock = the TOU picture. Scenario = the scenario is allowed to supply the shape (S12 sets this). |
| Elasticity k (MWh) | blank, or a non-negative decimal with up to 3 places | Blank persists as ∞ — the flat mock, no price
impact. 500 is the demo’s thin market. |
| k slider | 0–2000 | Convenience; it writes the same field. Moving the slider live-syncs the number box. |
| Save market | — | Persists source + k. Flash: Market parameters saved. |
k is “how many megawatt-hours the rest of the country
will absorb (or release) when the price moves 100%.” Small
k = a thin market = your volume moves the price a lot. Huge
k = an ocean = your volume barely shows. ∞ = the old mock:
the price never moves.
You cannot type a fourth decimal place. The Lab stores the decimal exactly as entered.
9.3 SO events — the list and the add form
Heading: SO events. Helper: date · site · kind · affected hours.
The list (#scenario-events) is one row per scripted
order. Empty state: No scripted events.
Each row shows
{date} · {site_ref} · {kind} · {start}:00–{end}:00 and two
actions:
| Action | What it does |
|---|---|
| Edit | Loads that row into the form. The submit button flips from Add event to Save event. |
| Remove | Drops that row. Flash: Event removed. (via the editor save path) |
The form (#event-form) fields:
| Field | Rule |
|---|---|
| Date | Must fall inside the scenario period |
| Site | A non-empty site label (demo names, or the old IPP-A
style tag) |
| Kind | One of CURTAILMENT, OUTAGE_PLANNED,
OUTAGE_UNPLANNED |
| Start hour | Integer 0–23 |
| End hour | Integer 1–24, and strictly after start |
| MW cap | Required and ≥ 0 when Kind is CURTAILMENT; ignored for
the outage kinds in the Lab form |
The Lab form’s kind menu is those three. S12’s setup
pack can also store DISPATCH_CONSTRAINT,
RESERVE_ACTIVATION, and SYSTEM_CONDITION on
the recipe — you will see those rows on
AUG-BASELINE and the warm chip, and you can Remove them,
but the on-screen add menu does not offer them. That is honest: the
gallery shows every kind the verifier planted; the live form authors the
three kinds the editor validates.
As you type hours, event preview updates:
- Enter an hour window to preview the affected delivery hours.
- Preview: N affected delivery hour(s).
- For curtailment: Curtailment preview: N affected hour(s) at a {cap} MW cap.
Example: start 11, end 13, kind CURTAILMENT, cap 8 →
Curtailment preview: 2 affected hour(s) at a 8 MW cap. Those
two hours are 11:00 and 12:00 — end is exclusive in ordinary speech (the
label prints 11:00–13:00).
9.4 MO error injections — the list and the add form
Heading: MO error injections.
The list (#scenario-injections) shows
{type} · {period_start} → **{recon outcome}**. Empty state:
No deliberate reconciliation differences.
Remove drops a row. There is no Edit — change means remove and add again.
The form (#injection-form):
| Field | Used by | Meaning |
|---|---|---|
| Injection | all | republish_price, meter_bias,
drop_line, phantom_line |
| Date / ISO time | all | A date (2026-08-14) or a UTC timestamp
(2026-08-13T10:00:00Z) inside the period |
| Value (+R / % / amount) | price, bias, phantom | delta_r for a republish; pct for bias
(0.033 = 3.3%); amount_r for a phantom line |
| Charge type | drop and phantom | ENERGY or IMBALANCE |
| Add injection | — | Appends the row. Flash: Injection saved. |
| Type | What the simulated MO does wrong | Reconciliation label you must see |
|---|---|---|
republish_price |
Prices an hour at SMP + Δ | VARIANCE / PRICE |
meter_bias |
Shifts the metered quantity by a percent | VARIANCE / METER |
drop_line |
Omits a line you have internally | MISSING_MO |
phantom_line |
Adds a line you do not have | MISSING_INTERNAL |
Injections touch only the MO’s published file. Internal truth — your bids, your meters, your ledger — stays clean. That is how you can drill “the invoice is wrong” without lying to yourself about what happened.
9.5 Your own scenarios share this editor
A New or Clone recipe uses the same
four panels. There is no second screen. What changes is the
content: a new recipe starts with empty weather and events; a
clone starts as a carbon copy. Walk the four panels on the clone exactly
as you walked them on AUG-BASELINE.
10. Runs, Compare, the day log, and Teardown
Under the editor sits the Runs card. This is the report side of the Lab. There is no PDF download and no separate “Reports” menu. The reports are on this card: the run list, the KPI blobs, the Compare strip, and the Live day log.
10.1 The Run scenario button
Top right: Run scenario (play icon). It is disabled while the scenario is not mutable.
Pressing it starts a supervised task that calls
WB.Sim.Driver.run/2. Flash: Simulation started. If
nothing is selected: Select a scenario first. If a task is
already in flight: A simulation is already running. The driver
itself also refuses a second cook while any run is active anywhere
(:simulation_already_running).
The editor locks. Chips lock. You watch the day log instead.
PENDING and RUNNING pills are amber. COMPLETE is green. FAILED is rose. TORN_DOWN is grey. Teardown is hidden on TORN_DOWN and disabled on PENDING / RUNNING.
10.2 The run list
Empty state: No runs yet — press Run scenario.
Each row, newest first, shows:
| Piece | What it is |
|---|---|
| Status pill | PENDING / RUNNING / COMPLETE
/ FAILED / TORN_DOWN |
{n} days |
How many dates are already in the day log
(map_size) |
| Scenario drifted (amber) | The recipe’s updated_at is after this run was
created. Today’s paper is not the paper this cook used. |
| KPI blob | A monospaced inspect of the kpis map |
| Teardown | Shown unless status is already TORN_DOWN |
Above the list, if any run exists, the SIMULATED ribbon for the newest run may appear — the same amber warning other screens use.
10.3 What the KPI blob is saying
A driver-finished cook typically stores:
| Key | Meaning |
|---|---|
days |
How many delivery days were logged |
bids |
How many bid rows the participating registrations own |
imbalance_positions |
How many imbalance position rows those registrations own |
scenario_snapshot |
A frozen copy of the recipe at finish time (seed, period, factors, events, injections, market, participants) |
scenario_hash |
SHA-256 of that snapshot — a short fingerprint for “did the paper change?” |
S12’s comparison A/B additionally store a teaching bundle:
| Key | Seeded value | Meaning on that gallery row |
|---|---|---|
days |
31 | “Treat this as a full month” (the stored cook itself logged one comparison day) |
margin_r |
12138.50 |
Headline margin in rand, identical on A and B |
coverage_pct |
100.0 | Full coverage in the teaching bundle |
scenario_snapshot |
A’s snapshot from before the 20 Aug edit | Why A can show Scenario drifted after S12 edits 20 August |
A failed cook stores error (the reason string) plus
whatever snapshot it had. An isolation cook stores
isolation: true and terminal: COMPLETE or
FAILED.
These blobs are the on-screen report. Read them as headlines, then open the day log for the diary.
10.4 Compare (latest vs previous)
When at least two runs exist, a strip appears: Compare (latest vs previous). Two monospaced KPI blobs sit side by side — newest cook on the left, the one before it on the right.
There is no chart and no colouring of diffs. You read the two maps with your eyes. Because a scenario is deterministic, the same paper + the same seed must produce the same headlines. A difference means:
- you changed the paper (factors, events, injections,
k, period, seed, participants), or - the platform changed, or
- one side is a teaching fixture (A/B) and the other is a real driver cook — do not treat those as a regression.
If a row wears Scenario drifted, stop calling it a current baseline. Clone, run two fresh cooks, and Compare those.
10.5 Live day log — the diary
Heading: Live day log. Right side:
{n} completed.
The stream (#sim-days) appends one card per finished
delivery day:
2026-08-12 %{ "dam" => …, "cleared" => …, "so_events" => 2, "idm" => …,
"imbalance" => …, "actuals" => …, "wheeling_settlement" => … }
| Key in the entry | What that day did |
|---|---|
dam |
Day-ahead build + submit summary |
cleared |
What the elastic engine accepted |
so_events |
How many scripted grid orders were issued that day |
idm |
Intraday cycle summary |
imbalance |
Yesterday-shaped imbalance summary for the day |
actuals |
The one-truth meter write (baseline × factors × noise − curtailment) |
wheeling_settlement |
How many settlement periods closed, with energy and artifact ids |
Empty state: No completed days. A deep link fills this from the opened run’s stored diary. A live cook streams days as the driver finishes them.
S12’s comparison cooks store a shorter teaching entry
(phase: comparison, margin_r: 12138.50).
Isolation stores phase: isolation. A failed partial stores
an actuals / written note and then stops.
This diary survives teardown. That is why a grey
TORN_DOWN row is still worth opening from
?run_id=: you can read what happened after the created rows
are gone.
10.6 Teardown
Teardown deletes exactly the rows in that run’s artifact registry — bids, bid lines, imbalance positions, SO instructions, settlement periods, line items, allocation runs, and so on — and reports the count.
Flash: Torn down — N row(s) deleted.
Rules:
- You cannot teardown
PENDINGorRUNNING. Flash: A running simulation cannot be torn down. - Teardown does not delete the run row. Status
becomes
TORN_DOWN. The day log and KPIs stay. The artifact map is emptied. - Pressing Teardown again on a tombstone is repeat-safe (isolation proves this). You do not double-delete live desks.
- Teardown does not delete the scenario chip.
- Other runs’ artifacts stay. Isolation proves a neighbour’s warm cook survives.
After teardown, any screen that was showing those rows drops the SIMULATED ribbon (nothing live to label). Treat remaining ribbon-marked screens as still owned by some other cook.
11. What happens when you press Run — one day, then the next
This is the workflow the Driver walks. You do not press these steps yourself. You press Run scenario once. The day log is how you watch.
Where this is taught in depth. Elastic clearing, the actuals formula, and injection arithmetic live in V3 §16. This chapter is the Lab-operator walk: what order, what you see, what “done” means.
11.1 Before the first delivery day
- The Lab starts a
SimRun(PENDING). The runtime guard must still be on — a run cannot begin in production. - The driver marks it
RUNNING. - Unless told to skip, it runs the nightly strategy for each participating book over a one-month horizon (allocation runs, BESS intervals, risk snapshots). Those rows go into the artifact registry.
- Then it walks
period_start…period_end, one calendar date at a time.
Only one driver may mutate the simulated Market Operator / System Operator gateways at a time. A second Run is refused until the first finishes or fails.
11.2 One delivery day
For date D the driver pretends two clocks:
| Pretend clock | When | What job |
|---|---|---|
| 03:00 SAST on D−1 | The day before | Build and submit DAM bids. If that pretend clock is after the gate (often 10:00), the bid expires with a gate-miss — the same rule as the live desk. |
| 16:00 SAST on D | The afternoon | Run the IDM cycle so the morning plan can be corrected |
Between those, in order:
flowchart TB
A[DAM build + submit at fake 03:00 D-1] --> B[Elastic clear + write accepted volumes]
B --> C[Issue today's scripted SO events]
C --> D[IDM cycle at fake 16:00]
D --> E[Write one-truth meter actuals]
E --> F[Imbalance for the day]
F --> G[Wheeling settlement for participating PPAs]
G --> H[Append the day-log card and stream it]
Actuals are the one physical truth both legs read:
actual(site, hour) = baseline × day_factor × (1 + σ × noise(seed, site, hour))
− curtailed(site, hour)
- baseline — what the forecast said the site should do
- day_factor — the cell you typed (solar / wind / load)
- noise — the seed’s repeatable wiggle
- curtailed — energy the scripted SO order took off, floored at zero
The generator writes three 20-minute meter rows per hour. The last row eats the rounding remainder so the three always sum to the hour. Wheeling settlement and market imbalance both read those rows. They cannot disagree about what physically happened.
If any leg raises, the driver stops. Remaining days
are not walked. The run finishes FAILED with the error in
its KPI map. Earlier days stay in the log. Artifacts already registered
stay until you Teardown.
11.3 After the last day
- The simulated MO publishes the month’s settlement file, with injections applied.
- Reconciliation runs against that file.
- Those rows join the artifact registry.
- The run finishes
COMPLETEwith the KPI bundle in §10.3.
A 31-day AUG-BASELINE cook therefore does thirty-one
day-log cards, then one month-end file. A 3-day warm cook does three
cards, then the same kind of month-end (injections on the 10th–12th
still sit on that file).
11.4 What you should do while it runs
| Do | Do not |
|---|---|
Watch #sim-days grow |
Edit factors “just for the next day” — the form is locked, and even if it were not, you would be changing the paper mid-cook |
Wait for COMPLETE or FAILED |
Start a second Run |
On FAILED, read the last card and the
error KPI |
Assume later dates happened |
On COMPLETE, Compare, then follow ribbons |
Read a ribbon-marked Bid Desk as a live position |
11.5 After it finishes
- Read Compare if you have a previous cook of the same paper.
- Open Strategy / Bid Desk / MO / SO only to see labelled artifacts, then come back.
- Teardown when the lesson is over so the next person does not inherit your practice rows.
Organization controls drill
From an organization seat, choose Controls drill to
add the controls_drill script to the picker. S12 also seeds
this script for its active participant. It needs two distinct active
organization members. Choose Run controls drill to arm
the organization switch at 02:55 SAST on the day before delivery and run
the 03:00 DAM cycle for every active registration. The run checks that
submissions do not increase, then uses the second member’s approval to
disarm into MANUAL within the exercise.
The exercise rolls back its temporary policy, stop and approval changes. It keeps a dated DRILL event with the registrations and independent checker, and the run displays zero submissions and each blocked outcome. The current data model permits one registration per organization; the script selects the whole organization scope. A failed drill is shown as a failed run. S17’s separate month fixture records a corrupt draft refusal on day 2 and a gate miss on day 4; the annual controls export includes those incidents and the drill.
12. Worked example — one cloud, one cap, one bad price
This walk clones AUG-BASELINE, changes
only 12 August’s solar factor, runs the clone, and
reads the reports. The arithmetic is small enough to redo on paper.
Data. lab · seed 42 · stage S12 · as_of 2026-07-18 · clone of
AUG-BASELINE· last walked 2026-08-18.
Follow along. Seat:
trader_1@webill.net/supersecret. Path:/org/sim→ AUG-BASELINE → Clone → Day factors → 2026-08-12 SOLAR. You should see a new chip namedAUG-BASELINE copy {n}.
12.1 Inputs
| Input | Baseline chip | Clone (the experiment) |
|---|---|---|
| Name | AUG-BASELINE |
AUG-BASELINE copy {n} |
| Seed | 42 | 42 (inherited) |
| Period | 1–31 Aug 2026 | 1–31 Aug 2026 |
| Solar on 2026-08-12 | 0.35 (already cloudy) | 0.70 |
| Wind / load that day | 1.25 / 0.96 | unchanged |
k |
500 | 500 |
| SO events | the six S12 events | unchanged |
| MO injections | the four S12 injections | unchanged |
We change one cell. Everything else is the conservation control: if Compare later moves on 18 August’s curtailment story, something else is wrong — we did not touch that day.
12.2 The solar arithmetic on 12 August
Take a solar site whose forecast baseline at 12:00 is 10 MWh.
| Step | Formula | Baseline (0.35) | Clone (0.70) |
|---|---|---|---|
| R1 listed factor | cell | 0.35 | 0.70 |
| R2 scripted availability | 10 × factor |
3.50 MWh | 7.00 MWh |
| R3 ignore noise for the lesson | σ is 0.03 on this date; we treat the centre | 3.50 | 7.00 |
| R4 noon curtailment at 8 MW | cap is 8 MWh/h; both 3.50 and 7.00 sit under 8 | 3.50 | 7.00 |
Conservation check on the uncapped story: the clone should
show twice the solar of the baseline on that hour
(0.70 ÷ 0.35 = 2). The 8 MW cap does not bind either
number, so the ratio survives into actuals.
If the baseline had been 16.4 MWh (the V3 §16 curtailment example) the cap would bind:
| Unconstrained | After 8 MW cap | |
|---|---|---|
| factor 0.35 | 16.4 × 0.35 = 5.74 |
5.74 (under the cap) |
| factor 1.00 | 16.4 | 8.00 |
| factor 0.70 | 16.4 × 0.70 = 11.48 |
8.00 (cap binds) |
So: a solar-factor experiment only shows through when the plant is
not already pinned by a cap. On 12 August AUG-BASELINE
already has a 10:00–13:00 8 MW curtailment. Pick an hour
outside 10:00–13:00 (for example 08:00) if you want the
0.35 → 0.70 doubling to be visible in the day-log actuals
summary.
12.3 Steps on the screen
- Select AUG-BASELINE. Confirm the subtitle
k 500. - Press Clone. Select the new chip.
- In Day factors, find
2026-08-12/ SOLAR. Double-click. Change0.35to0.70. Save. Flash: Factor saved. The cell tints pale indigo (0.70 is between 0.60 and 1.40). - Leave Residual market, SO events, and MO injections untouched.
- Press Run scenario. Wait until Live day
log shows 31 cards and the newest run pill is green
COMPLETE. - Read Compare (latest vs previous) — if this is the
clone’s first cook there is no previous clone cook yet. Run a second
time without further edits. Compare must now show the
same
scenario_hashand the samedays/ bid counts. That is the determinism check. - To see weather impact, Compare a completed cook of the
unedited clone (or a fresh clone left at 0.35) against
the 0.70 cook. The hashes must differ. The day-log card
for
2026-08-12is whereactualsshould move;2026-08-18should not.
12.4 What the reports should look like
| Report | What you expect |
|---|---|
| Run list | Two COMPLETE rows on the clone if you ran twice;
neither should say Scenario drifted if you did not edit
after the first cook |
KPI days |
31 |
KPI scenario_hash |
Identical across the two 0.70 cooks; different from a 0.35 cook |
| Compare | Two maps; your eye checks days, bids,
hash |
| Day log 2026-08-12 | so_events still 2 (curtailment + planned outage).
actuals energy higher than the 0.35 cook on solar-shaped
hours outside the cap window |
| Day log 2026-08-18 | Unchanged story versus a 0.35 cook — you did not touch that date |
| Ribbon on Bid Desk / Strategy | Amber, naming this run, until Teardown |
12.5 Teardown
Press Teardown on the clone’s cooks when you have
the numbers. Flash names the deleted row count. The clone chip remains.
AUG-BASELINE and CODEX-S12-WARM remain.
Isolation’s checksum lesson still holds: you washed this cook’s
dishes.
This is the pattern to expect on your own page: one change, two cooks, one compare, one teardown.
13. How the Lab sits beside the rest of the platform
The Lab is one room. It borrows every other room’s jobs. This chapter says who owns what, so you do not re-learn settlement here or treat a ribbon as a live book.
13.1 The SIMULATED ribbon on other screens
While a cook that is not TORN_DOWN owns artifacts, any
screen showing those rows wears a slim amber bar:
SIMULATED — run #… (name, seed) · inspect in Lab
The bar is a deep link back to
/org/sim?run_id=…. It can appear on Strategy, Bid Desk,
Market Operator, and System Operator screens. Treat every number on a
ribbon-marked page as demo-contaminated until you teardown that run.
Where this is taught. The ribbon as a Strategy reading skill is V3 §17.
13.2 Not the Strategy Simulations workbench
Strategy → Simulations answers “what if I signed this contract?” One hypothetical member, one engine run, on a book. It does not walk DAM, IDM, SO, imbalance, or month-end reconciliation.
The Lab answers “what if the market month ran under this paper?” Same English word, opposite scale. See V3 §8.
13.3 Not the overnight jobs
| Mode | Clock | Owner |
|---|---|---|
| Scenario Lab Driver | Accelerated sim-clock | This volume |
| Production Oban cron | Real SAST | V21 §3 · V1 |
| S15 live meters | Wall clock, +20 min virtual ticks | V21 §6 |
The Lab calls the same cycle functions the cron would call. It does not replace cron. Do not stop Oban “because the Lab is running” unless you are doing a destructive setup reset — and then stop Lab drivers as well (V21 §4).
13.4 Settlement, market, and SO meaning do not change
A cook writes real-shaped rows: settlement periods, bid lines, SO instructions. Their meaning is still owned elsewhere:
| If you want to understand… | Read |
|---|---|
| DRAFT / PRELIMINARY / FINAL, statements, the ledger | V8 |
| Bid Desk, gates, imbalance, reconciliation classes | V9 |
| How the Market Operator console shows a day | V11 |
| How a System Operator instruction is issued and acked | V12 |
| Demo rebuilds, roll-forward, Eskom / Munix | V21 |
The Lab only answers: under this paper, those rooms were exercised, and here is the diary.
13.5 Roll-forward and a stale August
Manual examples often cite a sealed clock such as
full_demo · seed 42 · as_of 2026-07-18. That date is the
manifest clock, not “today.” If your local demo is
weeks old, Lab scripts and Strategy snapshots sit on last month’s
calendar.
# Stop Phoenix, Oban peers, Lab drivers, and live ticks first.
mix wb.setup.roll_forwardThat rebuilds the prior sealed profile onto today’s UTC
date (or --as-of). It is destructive. After it,
S12’s warm window and isolation day move with the new month. Details: V21 §4.
14. Common questions
Why is there no Scenario Lab card on the home page?
On purpose. The route is/org/sim, development and demo only. A landing card would invite a live seat to author a recipe. Type the path, or follow a SIMULATED ribbon.Why does
/org/simsend me home?
The runtimesim_enabledflag is off, or you are on a build that never compiled the route. Use a development or demo server, not production.Why are New / Clone / the editor faded?
A cook isPENDINGorRUNNING, or a task is still in flight. Wait forCOMPLETEorFAILED. You cannot edit the paper mid-cook.Why can’t I Delete
AUG-BASELINE?
It has runs, including tombstones. The recipe that produced history must stay nameable. Clone instead.What does Scenario drifted mean?
Someone saved the recipe after that cook was stored. The diary is still true of the old paper. Do not use that row as a current baseline. S12 does this on purpose to comparison A.Why does Compare show two identical maps?
Either you ran the same paper twice (determinism — good) or you are looking at S12’s teaching pair A/B (margin_r12,138.50 both sides). A real weather change must change thescenario_hash.Why is the day log empty when the list already has COMPLETE rows?
The live stream starts empty until you Run again or open?run_id=. Pre-seeded cooks have diaries; deep-link to see them.Why did my run fail after a few days?
A leg raised and the driver stopped. Read the last day-log card and theerrorKPI. Remaining dates did not run. Teardown when you have finished reading. S12’s failed gallery row is an injected example of this shape.Can I teardown a running cook?
No. Flash: A running simulation cannot be torn down. Wait for a terminal status.Does Teardown delete the scenario?
No. It deletes that cook’s registered rows and greys the run. The chip stays.Will Teardown delete last month’s live statement?
Not if isolation still holds. Teardown deletes registered artifacts of that run only. Ordinary history before the Lab’s writer window is excluded from the drill. See §8.Why is a Bid Desk / MO / SO screen wearing an amber bar?
A Lab cook still owns those rows. Inspect in Lab, then Teardown when the exercise is over. Do not quote that screen as production.Is this the same as Strategy → Simulations?
No. That workbench is “what if I signed this contract?” The Lab is “what if the market month ran under this paper?” §1.2.Why are some event kinds on the list but not in the add menu?
S12 plantsDISPATCH_CONSTRAINT,RESERVE_ACTIVATION, andSYSTEM_CONDITIONfor the verifier. The live form authorsCURTAILMENTand the two outage kinds. You can Remove a planted row; you add the three form kinds.I changed solar on the 12th and the noon hour barely moved.
Check the 10:00–13:00 8 MW curtailment. A cap that is already binding hides a factor change. Compare an hour outside that window (§12.2).The dates are last month.
Your seal is old. Roll forward before authoring (§13.5).Password?
Demo seats usesupersecret. See V21 §4.
15. Terms used in this volume
First-use definitions live in the hub glossary. This list is the volume’s own index — every Lab-specific word, with the chapter that taught it.
| Term | Taught in |
|---|---|
| Scenario Lab | §1 |
| Scenario (sim) | §2 · §5 |
| SimRun | §2 · §10 |
| Driver | §11 |
| Seed / seeded noise | §2.4 |
| Day factor | §5.2 · §9.1 |
| Elasticity k | §6.6 · §9.2 |
| p_base | §9.2 |
| Residual market | §2.1 |
| SO instruction / event | §9.3 |
| MO injection | §9.4 |
| AUG-BASELINE | §6 |
| CODEX-S12-WARM | §7 |
| CODEX-S12-ISOLATION | §8 |
| Artifact registry | §10.6 |
| Teardown / tombstone | §8 · §10.6 |
| Scenario drifted | §10.2 |
| SIMULATED ribbon | §13.1 |
| Simulation harness (WB.Sim) | V3 §16 |
| DAM / IDM / SMP / BRP / MO / SO | §2.2 |
16. Reference card — the Lab on one page
Screen: Scenario Lab (/org/sim) —
development and demo builds only. Header: WB.Sim ·
beaker · Drive a scripted market month deterministically; compare
and tear down runs.
16.1 The three seeded chips (typical as_of 2026-07-18)
| Chip | Period | Use it for |
|---|---|---|
AUG-BASELINE seed 42 |
1–31 Aug | The full month. Clone before editing. |
CODEX-S12-WARM seed 42 |
10–12 Aug | The three-day tour and the living COMPLETE cook. |
CODEX-S12-ISOLATION seed 42 |
25 Aug | Understanding Teardown. Do not experiment here. |
16.2 List actions
| Button | Needs | Does |
|---|---|---|
| New | Mutable | Creates Scenario {n}. Inherits market + participants
from the selected chip; does not copy weather or
events. |
| Clone | A selected, mutable chip | Carbon copy named {name} copy {n}. |
| Delete | Selected, mutable, zero runs | Removes the recipe. History blocks it. |
| Chip click | No task in flight | Selects the recipe; loads editor + runs. |
16.3 Editor panels
| Panel | Actions | Empty state |
|---|---|---|
| Day factors | Double-click cell · Save · Bulk fill | Every cell 1.00 |
| Residual market | p_base source · k box · k slider · Save market | Mock + k ∞ |
| SO events | Edit · Remove · Add event | No scripted events. |
| MO injections | Remove · Add injection | No deliberate reconciliation differences. |
Factor range 0.00–2.00. Event hours start 0–23, end
1–24, end > start. Injection types: republish_price →
VARIANCE/PRICE; meter_bias → VARIANCE/METER;
drop_line → MISSING_MO; phantom_line →
MISSING_INTERNAL.
16.4 Runs and reports
| Control | Does |
|---|---|
| Run scenario | Starts the driver. Locks the editor. Streams the day log. |
| Status pills | PENDING → RUNNING → COMPLETE or FAILED → TORN_DOWN |
| Scenario drifted | Paper changed after this cook. |
| KPI blob | Headlines (days, bids, hash, …). This is
the report. |
| Compare | Latest vs previous KPI maps. |
| Live day log | One card per finished delivery day. Survives teardown. |
| Teardown | Deletes registered artifacts; keeps diary + KPIs. Not on a running cook. |
16.5 Safe daily path
- Open
/org/simin a demo build. - Clone
AUG-BASELINE(or tourCODEX-S12-WARMfirst). - Change one family of inputs.
- Run. Wait for COMPLETE.
- Compare. Follow ribbons only as labelled artefacts.
- Teardown.
16.6 Demo walkthrough
Follow along.
mix phx.server· org seat · passwordsupersecret.
/org/sim— three chips.CODEX-S12-WARM— three-day grid, six events, four injections.AUG-BASELINE— thirty-one rows, comparison cooks, one Scenario drifted.CODEX-S12-ISOLATION— empty panels,TORN_DOWNrows.- Clone baseline → change one solar cell → Run → Compare → Teardown.
Where next. Ribbon reading: V3 §17. Harness arithmetic: V3 §16. Setup and live ticks: V21.
17. Stochastic mode
Route: /org/sim ·
Panel: #stochastic-mode · development and
demo builds only.
The Lab already replays a scripted month once. Stochastic mode asks the same scenario N times against a chosen book, with the three named shocks (tariff hike, municipal surcharge, curtailment-day count) exposed as inputs, and shows the spread of margin instead of a single replay.
No new route. The Lab is one page and this is a mode within it. The
existing guards hold: the route is compiled behind
:sim_enabled, the LiveView runtime-guards on
WB.Sim.sim_enabled?/0, and a stochastic run is unreachable
when the sim is disabled.
17.1 A rehearsal, not the book’s risk position
A stochastic run evaluates and displays. It does
not persist a RiskDistributionSnapshot
against the book. That row is the book’s real risk position, written by
the nightly worker at 02:45. A practice kitchen must not overwrite the
live number.
Keep the result on the page (#stochastic-spread). If a
run is to be kept, it belongs in the Lab’s own artifact registry so
WB.Sim.teardown/1 can delete it exactly — never in the
strategy risk tables.
The same scenario, seed and N render the same spread twice. Sampling
is a pure hash of
(seed, path_index, dimension, coordinate), the same
discipline as a Lab month replay.
17.2 The form
#stochastic-form after you select a scenario:
| Field | Meaning |
|---|---|
| Book | One of the scenario’s participant books |
| Paths (N) | How many tomorrows. Default on the form is 50; the nightly worker uses 500 |
| Seed | Determinism seed. Defaults to the scenario’s seed |
| Tariff hike % | Fixed shock, or leave blank for none |
| Municipal surcharge % | Fixed shock, or leave blank for none |
| Curtailment days | Fixed count of curtailed delivery days, or blank |
[Run N times] samples, evaluates, and fills
#stochastic-spread with VaR-95, VaR-99, expected
shortfall-95 and mean margin. Nothing is written to the book.
17.3 How this sits beside a live month
Use Run scenario when you want the scripted month, the day log, and teardown. Use Run N times when you want the distribution of the book’s economics under that script’s horizon, with optional shocks. They do not substitute for each other. The nightly VaR on the Dashboard is the estate’s real number; this panel is the rehearsal.
Follow along. Seat: any seeded user (password
supersecret). Open/org/sim, selectAUG-BASELINE, run N = 8 with seed 42 twice, and confirm the two spreads match. Confirm the book’sRiskDistributionSnapshotrow (if any) is unchanged.
