1. What this module is, and why it exists
The three seat volumes now own the screens. This chapter keeps the shared engine picture — pools, runs, allocation lines — so every seat can point at one story.
| Seat | Home volume |
|---|---|
| IPP operator | V4 · IPP |
| Off-taker | V5 · OffTaker |
| Trader | V6 · Trader |
The trader problem below is the original worked example of that engine. The trader screens are taught in V6.
1.1 The trader's problem
An energy trader in the South African wheeling market sits between generators and consumers:
- You buy energy from IPPs (independent power producers — solar farms, wind farms, firm/dispatchable blocks) under PPAs, at contracted c/kWh prices.
- You sell that energy to off-takers (factories, malls, municipalities) and to other traders, under sell-side PPAs, at higher c/kWh prices.
- You pay wheeling charges to move the energy across the grid, and you lose a few percent of every MWh to distribution losses on the way.
- Your profit is the spread: sell price − buy price − wheeling, hour by hour.
The catch is that none of this is flat. Solar produces at midday and nothing at night. Your factory load runs 24/7; your mall load peaks in the daytime; a firm evening block must be served no matter what. Time-of-use tariffs make an evening kWh worth far more than a midday one. So the questions that decide whether you make money are inherently hourly:
- Which supply serves which demand, in each of the ~8,760 hours of the year?
- How much supply goes unsold (spill — cost with no revenue)?
- How much demand goes unmet (shortfall — expensive grid top-ups or penalties)?
- What margin does each hour actually earn, and how much of it evaporates if generation comes in below forecast?
- How much flexible energy should you nominate for next month, when nominating too little means expensive grid top-ups and too much means paid-for energy that spills?
- If you own a battery, when should it charge and when should it discharge, given that every stored kWh loses a little to efficiency and wears the warranty?
Doing this on a spreadsheet is where portfolios go to die. This module does it with a deterministic engine and puts the answers on eight screens.
1.2 What the module does
You group your contracts and sites into pools (supply on one side, demand on the other), and the engine allocates every forecast hour of energy from supply to demand, pricing each flow with the same conventions the settlement system uses. Every result is stored as tagged, auditable allocation lines — so coverage, margin, spill and shortfall are never estimates; they are sums over hourly lines you can drill into. On top of the engine sit the decision screens summarised below.
Auditable allocation lines
An allocation line is one immutable row for one SAST hour: which member of which pool supplied which member of which pool, how much energy was sent and delivered, the buy / sell / wheeling legs in c/kWh, the Rand margin, and which rules produced it. Headline KPIs on Overview and the Dashboard are simply sums (or ratios) over those rows — open a run's Allocations tab (§7) and you can take any number apart.
Four common kinds (illustrative values, same conventions as settlement):
| Kind | Plain meaning | Tiny example |
|---|---|---|
ALLOCATION |
Energy that actually flowed from a supply member to a demand member | 12h · Solar · IPP-A → Industrial · Factory · 16 710 sent · 16 142 delivered · buy 65.0 · sell 120.0 · whl 15.0 · **R 6 088** · merit_order
— midday solar wheeled to a factory; margin is (sell − buy − wheeling) ×
delivered kWh |
SPILL |
Supply nobody bought that hour — you still paid for it | 02h · Wind · PPA-W · 3 000 unsold · spill value 10.0c · sunk cost **−R 2 160**
— night wind with no offtake; recovery (if any) never fully offsets the
buy cost |
SHORTFALL |
Demand nobody supplied — firm buyers then take grid top-up | 19h · Firm · Evening Buyer · 2 000 unserved · top-up 225.0c · **−R 1 300**
— evening firm gap filled at Eskom rates; the loss shows as negative
margin |
BESS_CHARGE /
BESS_DISCHARGE |
Battery movement (only when the book dispatched storage) | Charge at 12h from free solar spill (margin
0 — it only positions); discharge at
19h against the firm gap, with buy = stored marginal cost
and sell = discharge value — the arbitrage is the line's margin |
Because every line is tagged, "coverage is 94%" means these hours and members were covered; "spill cost R 2 160" means this exact unsold row. Re-running creates a new run; old lines stay for audit (§1.3).
Decision screens on top of the engine
- Simulation workbench (§8) — safe what-if: run the book as today (baseline) and again with a pretend buy/sell candidate and terms (variant). Compare deltas (margin, spill, shortfall, concentration) without touching live membership, forecasts or nominations; promote only when you like the story.
- Nomination workbench (§9) — for flexible supply members, propose an hour-by-hour monthly declaration using asymmetric under-/over-nomination costs (newsvendor / critical quantile), show the full rationale, let you override any hour, then submit into settlement only after you approve.
- BESS dispatch view (§10) — hour-by-hour charge/discharge schedule against the book's real spill and shortfall, subject to SoC, power, efficiency, cycle-cap and minimum spread; every move (and every profitable-looking pair it declined) is recorded with a rationale.
- Risk dashboard (§11) — drillable panels for portfolio margin, risk (P50 vs P90), prices, concentration, limits/alerts, battery tiles and nomination performance — all refreshed from the latest snapshot so you diagnose what and where, not just that something is wrong.
Off-taker and IPP seats reuse the same engine with buy- or sell-shaped screens (§12–§14); the trader path above is the fullest tour of what "Strategy" means.
1.3 Design principles you will feel in the UI
A run is one complete execution of the allocation
engine: given a scenario, a calendar month and a forecast grade (P50 or
P90), the engine walks every SAST hour, matches supply to demand under
the book's rules, and writes the resulting allocation
lines (§1.2). The run is
then frozen — coverage, margin, spill and shortfall are simply sums over
those lines. You create runs from the Book tab’s
Forecast mode (§5),
open any one in §7
to inspect the proof, and treat the newest non-superseded
COMPLETE run for a scenario / grade / period as the current
answer for that case. Four deliberate decisions shape how runs (and the
screens around them) behave:
Runs are immutable. Once an allocation run completes, its lines never change. Re-running the same scenario and period creates a new run and marks the old one superseded (kept, greyed out). Why: audit trail. You can always answer "what did the book say last Tuesday?", and a settlement dispute can be defended with the exact run that informed a decision.
Every number is a sum over tagged lines. Each line records which member of which pool supplied which member of which pool, in which hour, at which prices, and which rules produced it. Why: when a KPI looks wrong, you drill down to the exact PPA pair and hour instead of arguing about a rollup. The same line-by-line convention reconciles with settlement.
Every run pins its inputs with a hash. The run header shows an inputs hash — a SHA-256 fingerprint of everything the run consumed (members, prices, rules, forecasts, period — and the battery's dispatch parameters). Same hash ⇒ byte-identical results. Why: reproducibility. "Why did run X say margin was R 100/MWh?" — "Here is its hash; re-run with those inputs and you get the same lines."
Scenarios never mix. Every run is labelled by the kind of input it consumed — not by a cosmetic tag. The four labels mean:
Label What the run does Why it is labelled that way FORECASTAllocates the book's rolling supply-forecast shapes against demand profiles for the chosen month and grade It is the operating plan — "what we expect to happen" — and is the default current position on Overview NOMINATEDRe-runs the engine using the submitted nomination rows as the declared volumes, not the free forecast It answers "what did we say we would deliver?" so you can compare declared position to the forecast plan ACTUALAllocates using settled meter data for the period (when that data exists) It is labelled for realised performance — outcome review after the fact, not forward planning SIMULATIONAllocates against hypothetical inputs (a candidate PPA, a tweaked shape, an isolated what-if) The name marks it as non-live: it never supersedes your forecast or nominated current, so a sandbox case cannot contaminate the book's real position A simulation never replaces your forecast; a later nominated run does not overwrite the forecast current simply because it is newer. Filter and compare within one scenario (§5.3).
The operating-plan forecast itself has an explicit source order. A human
:MANUALupload still wins; a weather-driven:MODELseries sits below that and above the synthesised per-technology shape. Hours the model covers take dated values; hours beyond the horizon keep the shape. The operator-facing account of where those model numbers come from lives in V4 §17.
The same philosophy extends to nomination and batteries: every nomination the optimiser proposes carries its full rationale (the scenarios it considered, the asymmetric costs, the quantile it aimed for, and what the naive alternatives would have cost), and every hour the battery moves — and every profitable-looking hour it deliberately did not — is recorded and shown. You are never asked to trust a number you cannot take apart.
1.4 Concepts to have in hand before you start
| Concept | What it means here | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Hourly grain, SAST | The engine works in hours. All hours you see on screen are SAST (South Africa Standard Time, UTC+2, no daylight saving) — the timezone of meters and tariffs. | |||||||||||||||
| TOU (time-of-use) | Tariff periods — peak / standard / off-peak — set by the SAST calendar. Contract prices can carry per-period multipliers, so the same PPA prices an evening kWh higher than a midday one. | |||||||||||||||
| P50 / P90 | Forecast grades. P50 is the median ("expected") generation; P90 is the conservative case you are 90% confident of reaching at least. P90 ≤ P50 always. The gap between the two is where your risk number comes from (§11.3). Worked example. If P50 is 10,000 MWh, then in a typical year you expect to wheel 10 GWh. If P90 is 9,200 MWh, then you can confidently commit to wheeling at least 9.2 GWh in almost every year. Risk Gap (MWh) = P50 − P90 Risk Gap (%) = (P50 − P90) ÷ P50 × 100 |
|||||||||||||||
| DLF (distribution loss factor) | Physics: energy injected at the generator loses a few percent in the network. A DLF of 0.966 means 1,000 kWh sent becomes 966 kWh delivered. You buy the sent energy and sell the delivered energy — the engine, like settlement, keeps both numbers on every line. | |||||||||||||||
| Wheeling | The c/kWh network charge for moving energy from source to sink, charged on delivered energy. It comes straight out of your margin. | |||||||||||||||
| Spill | Supply nobody bought in an hour. You still paid for it (take-or-pay especially), so it is sunk cost unless the member has a spill value (e.g. a recovery price).
|
|||||||||||||||
| Shortfall | Demand nobody supplied in an hour. Firm demand must then be topped up from the grid at the member's Eskom top-up price — usually your most expensive energy.
Shortfall pricing options:
|
|||||||||||||||
| Nomination | A monthly declaration of how much a flexible supply member will deliver, hour by hour. Settlement holds you to it: under-deliver and you buy the gap at grid rates; over-nominate and you pay for energy that spills. Getting the number right is what the Nominations workbench (§9) is for. Nomination workflow:
|
|||||||||||||||
| SoC (state of charge) | How full a battery is, in kWh (or % of usable capacity). A battery's whole value is moving energy through time — charge cheap, discharge dear — subject to its SoC window, power limits, efficiency losses and warranty cycle cap (§10). SoC (%) = (Remaining Capacity ÷ Full Charge Capacity) × 100 Example. If a 10 MWh BESS has degraded to 9 MWh and currently holds 4.5 MWh:
|
2. Getting in — open the book behind a trader pool
The trader seat’s home for Trader pools — opening the book is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Trader pools — opening the book: v6-trader.html#2-getting-in.
3. Overview — your 30-second morning decision
The trader seat’s home for Trader Overview is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Trader Overview: v6-trader.html#3-overview–your-landing-page.
4. Book · Rules — how the book is wired
The trader seat’s home for Book · Rules is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Book · Rules: v6-trader.html#4-pools–build-your-portfolio-structure.
5. Book · Forecast — what the engine will do
The trader seat’s home for Book · Forecast is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Book · Forecast: v6-trader.html#5-runs–execute-and-browse-the-engine.
5A. Book · Outcomes — what actually settled
The trader seat’s home for Book · Outcomes is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Book · Outcomes: v6-trader.html#5a-settled-book–procured-vs-sold.
5B. Settled Chains — end-to-end path health
The trader seat’s home for Settled Chains is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Settled Chains: v6-trader.html#5b-settled-chains–end-to-end-path-health.
6. How the engine allocates (what a run actually does)
The trader seat’s home for How the engine allocates is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. How the engine allocates: v6-trader.html#6-how-the-engine-allocates-what-a-run-actually-does.
7. Run detail — the engine made visible
The trader seat’s home for Run detail is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Run detail: v6-trader.html#7-run-detail–the-engine-made-visible.
8. The Desk — sell the long, buy the short (what if I signed this?)
The trader seat’s home for The Desk is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. The Desk: v6-trader.html#8-simulations–the-workbench-what-if-i-signed-this.
9. Nominations — the workbench (how much should I commit?)
The trader seat’s home for Nominations is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Nominations: v6-trader.html#9-nominations–the-workbench-how-much-should-i-commit.
10. BESS — dispatch the battery
The trader seat’s home for BESS dispatch is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. BESS dispatch: V6 §11. The full
/strategy/besspage is V6 §11.11.
11. Dashboard — the story of the pool
The trader seat’s home for Trader Dashboard is now V6 · Trader.
V3 keeps the shared strategy story: how three seats share one engine (§12), the off-taker pointer (§13), and the IPP pointer (§14).
Where this is taught. Trader Dashboard: v6-trader.html#11-dashboard–margin–risk.
12. Beyond the trader — off-taker and IPP books
12.1 One engine, three seats at the table
Every wheeling deal has (at least) three parties, and until Phase 3A only one of them had these tools:
- The trader buys from many, sells to many, and lives on the spread. Sections 2–11.
- The off-taker (a factory, a mall, a mine) buys energy it was going to buy anyway — the only question is from whom, and at what blend. Its money question is not margin but saving: every wheeled kWh that displaces a grid kWh saves the difference between the grid tariff and the wheeled energy's true landed cost.
- The IPP (a solar farm, a wind farm) generates a fixed, weather-shaped product and must sell it. Its money question is the revenue gap: every unsold kWh realises R 0 against a real levelised cost, so the game is filling the gap at the best achievable price — without signing firm promises the plant physically cannot keep.
Each of the other two seats, IPP Site and Off-taker Site, is given its own book — same engine, same immutable runs, same tagged lines, same hashes, same nightly cycle — with the inputs and KPIs re-expressed in that seat's language. Everything you learned in V6 §§1–12 transfers: pools, members, merit order, spill/shortfall, snapshots, limits. What changes is what the pools contain and which numbers the screens lead with. The seat homes are V4 · IPP, V5 · OffTaker and V6 · Trader.
| Trader book | Off-taker book | IPP book | |
|---|---|---|---|
| Keyed to | a trader pool | an off-taker site | a generator site |
| Direction | buys and sells | buy-only | sell-only |
| Headline KPI | Margin (R) | Bill saving (R) | Realised R/MWh |
| Tabs | 8 (incl. Book) | 3 (Overview · Procurement · Dashboard; se | ttled Sources is Overview's Settled view) IPP strategy tabs (Overview · Sales · Allocation · Dashboard) |
| "Bad" looks like | spill + shortfall | over-contracting, grey energy | unsold energy, one dominant buyer |
12.2 Getting in — the site directories
Off-taker and IPP books are entered from the site, not from a pool list:
- Open OffTaker Sites
(
/org/off-taker-sites) or IPP Sites (/org/ipp-sites;/org/generator-sitesforwards there) in the sidebar. - Every site is a card on the landing (V2 §3.1, §4.1) with Overview as its first chip (the old row’s Strategy button, same id).
- First click seeds the book silently — Own Load · Wheeled Supply · Virtual Supply · Grid for an off-taker, Own Generation · Contracted Buyers · Virtual Buyers for a generator (below) — and lands you on its Overview. The confirm modal of earlier releases is gone: a site without a book shows a dashed not wired card whose one line is the setup prompt.
- Every later click goes straight in. The card carries what the old “mini KPI chip” promised and more: an off-taker site shows demand served, contract price against the grid and the month’s estimated saving; a generator site shows sold share, realised against contracted price and revenue at P50.
The same organisation guard applies as everywhere else: a foreign or
unknown site id bounces you back to the directory. Routes, for
bookmarking:
…/off-taker-sites/<site id>/strategy[/procurement|/allocations|/dashboard]
and
…/generator-sites/<site id>/strategy[/sales|/hierarchy|/forecast|/allocations|/dashboard].
Off-taker Sources is /strategy/allocations
(V5
§5); trader Book is /strategy/book (V6 §6A);
trader Chains is /strategy/chains (V6
§6B). Off-taker sites: V5 §2. Trader pools: V6 §2. IPP sites: V4 §3.
12.3 What the off-taker seed builds
Four pools, purpose-built for a buyer:
| Pool | Side · type | Priority | What it holds |
|---|---|---|---|
| Own Load | Demand · 🏢 own load | 10 | The site itself — its consumption profile is the demand every run serves. Coverage target 75%. |
| Wheeled Supply | Supply · 📄 contracted | 10 | The site's executed physical PPAs — real wheeled energy, placed automatically. |
| Virtual Supply | Supply · ☁ virtual | 20 | Virtual PPA claims (financial green cover). |
| Grid | Supply · ⚡ grid | 100 | The grid backstop (below). Always last in the merit order. |
Two things here are new to the engine, and both are worth understanding:
The grid backstop member. The Grid pool holds one synthetic member with infinite capacity — it can serve any hour, any volume, and it never spills. Its price is not a contract price: it is the site's retail grid tariff, resolved per TOU period from the same settlement tariff table that computes your settled savings (so forecast and settlement can never disagree about what the grid "would have cost"). For the seeded Megaflex category that is peak 210 · standard 128 · off-peak 95 c/kWh, escalated by the book's grid-escalation assumption for each future calendar year. Because it sits at priority 100 with an unbeatable capacity but a beatable price, the allocator uses it exactly the way reality does: every hour your PPAs can't (or shouldn't) cover falls to the grid — visibly, priced, on its own tagged lines.
Book assumptions (the ⚙ params). The book carries
the buyer's planning assumptions, editable on the Overview: grid
escalation (default 12%/yr — the number NERSA trajectories
argue about), spill credit (default 40 c/kWh — what
over-contracted energy recovers when it's banked or credited back), the
Eskom tariff category (default megaflex)
and the default wheeling adder (15 c/kWh). Changing
them changes the next run's inputs hash — assumption changes are
auditable like everything else.
12.4 The landed-cost model — where "saving" comes from
Saving is grid-door cost minus wheeled-door cost at your meter. Losses and wheeling sit inside landed cost; a cheap sticker price is the wrong comparison. Over-contract (buying wheeled energy the site did not use) can wipe the saving.
The full model — two doors, the 85 → 103 → 25 c worked example, over-contract, and how to read Bill saving — lives with the off-taker seat.
Go to V5 §8 · The landed-cost model.
12.5 What the IPP seed builds
Three pools, purpose-built for a seller:
| Pool | Side · type | What it holds |
|---|---|---|
| Own Generation | Supply · ⚡ own generation | The plant itself, at its P50 shape. Its "buy price" is zero — the plant doesn't buy its own energy; its cost basis is the LCOE held on the book. |
| Contracted Buyers | Demand · 👥 buyers | The plant's executed physical PPAs — who has committed to take the energy. |
| Virtual Buyers | Demand · 👥 virtual buyers | Virtual PPA claims (strike-price revenue). |
A freshly seeded IPP book with no buyers runs at 100% unsold — and that is not an error state. Unsold generation appears as spill lines (with no purchase cost — you can't lose money buying your own sunshine), and that hour-by-hour unsold profile is the sales prospectus: it is exactly the shape the Sales workbench (V4 §6) scores buyers against.
The IPP book's ⚙ params: LCOE (default 48 c/kWh — the levelised cost every realised price is judged against) and replacement cost (default 210 c/kWh — what buying replacement energy costs when a firm promise can't be met from generation; it prices the firm-viability check, V4 §6.8).
12.6 The kind KPIs — and their risk limits
Each book kind computes its own KPI family on every run, snapshots it nightly, and can carry risk limits (§11.6) on any of them:
| Off-taker KPI | Meaning |
|---|---|
SAVINGS_R |
Bill saving for the period, net of over-contracting. |
SAVINGS_PCT |
Saving ÷ the grid-only baseline bill. |
GREEN_COVERAGE_PCT |
Non-grid delivered energy ÷ consumption. |
OVERCONTRACT_EXPOSURE_R |
Landed cost lost on unconsumed wheeled energy (net of spill credit). |
EFFECTIVE_COST_R_PER_MWH |
What the blended supply actually costs per MWh consumed. |
| IPP KPI | Meaning |
|---|---|
REALISED_R_PER_MWH |
Revenue ÷ total generation (unsold hours count at zero) — the honest number. |
EFFECTIVE_PRICE_CKWH |
Achieved sale price on the energy that did sell. |
UNSOLD_PCT |
Generation with no buyer ÷ total generation. |
CONTRACTED_RATIO_PCT |
Share of generation under contract. |
REVENUE_HHI |
Buyer concentration (1.0 = one buyer holds all your revenue). |
A typical off-taker limit: Green coverage %, direction below, warn 30 — the sustainability floor turns into an amber alert the night coverage slips. A typical IPP limit: Unsold %, direction above, warn 20.
12.7 What did not change
The trader book is untouched — provably. The engine changes are additive (member-level flags and closures; the allocator itself never branches on book kind), and the M5 gate required a trader run's inputs hash and KPIs to be byte-identical before and after the change. Your trader screens, numbers, hashes and history are exactly what they were.
Data. full_demo · seed 42 · stage S09 (settlement matrix) · last re-derived 2026-07-18 against the full_demo manifest.
Follow along. Seat:
trader_1@webill.net(passwordsupersecret). Start in T1 Renewables Pool Strategy, then use the relevant Site directory to enter the off-taker or IPP book discussed here.
13. The off-taker screens — pay less, and greener
An off-taker is a factory, a mall or a mine that already buys electricity every hour. The only question is from whom, and at what blend. Its money question is not margin but saving.
V3 keeps the shared engine story here. The full off-taker seat — sites, Operations, Performance, Allocation, Procurement, Dashboard and landed cost — lives in a separate book so that depth has room to breathe.
Go to V5 · OffTaker. That volume covers:
| Topic | Where in V5 |
|---|---|
| What this is, and why it exists | §1 |
| Off-taker sites (directory and workspace) | §2 |
| Operations — meter history and imports | §3 |
| Performance — savings-first | §4 |
| Allocation — settled energy | §5 |
| Procurement Desk | §6 |
| Coverage rationale | §6.1 |
| Buy-side Dashboard | §7 |
| Landed-cost model | §8 |
Why a separate volume? Strategy (this book) teaches how the three seats share one engine. Off-taker work is a buy-side programme of equal weight to the IPP volume: sites, savings, procurement and the dashboard. That programme owns V5.
The seeded off-taker book (Own Load · Wheeled Supply · Virtual Supply · Grid) stays in this volume at §12.3.
14. The IPP screens — sell the gap
Independent Power Producers run plants that generate whenever the weather allows — but energy with no buyer earns R 0. The commercial job is to sell the gap: every useful hour that is still unsold. Strategy books for IPP sites surface that revenue gap (realised vs achieved price, unsold %, buyer concentration) and give you workbenches to test sales, TOU counters and firm blocks before you negotiate.
V3 keeps the shared engine story here. Trader screens live in V6; off-taker screens live in V5. The full IPP operator volume — screens and the allocation / forecast / HAF programme — lives in a separate book so that depth has room to breathe without drowning the strategy flow.
Go to V4 · IPP. That volume covers:
| Topic | Where in V4 |
|---|---|
| Fleet story (every owned plant) | §2 |
| Sites and the IPP strategy area | §3 · §3.1 |
| Revenue-gap Overview (realised, achieved, unsold, HHI) | §4 |
| HAF capacity screen (SCE / AES widget) | §5 |
| Sales workbench (fit, simulate, promote) | §6 |
| TOU pricing and firm viability | §6.7 · §6.8 |
| Plant live (feed, samples, consent) | §11 |
| Sell-side Dashboard panels | §12 |
| Allocation hierarchy, settlement trace, monthly forecast, contracting guard | §7–§9 · §13 |
Why a separate volume? Strategy (this book) teaches how trader and off-taker books run — pools, allocation runs, procurement, simulation. IPP work adds a second stack of equal weight: who gets energy when the plant is short (hierarchy), what next month can still sell (forecast / AES / SCE), and how bankable that answer is (HAF). That programme is too deep to nest under a single strategy chapter, so it owns V4.
IPP book seeding and the three pools (Own Generation · Contracted Buyers · Virtual Buyers) stay in this volume at §12.5. The demo walkthrough still seats an IPP book in §21, then hands off to V4 for the deep screens.
15. Market operations — bridge to V7
Chapters that used to live here — the SAWEM / BRP market seat, the Bid Desk and market screens, Phase 3C reconciliation / System Operator / price intelligence, and the Phase 3C screen walkthrough — have moved to V9 · Market Operations so market topics have one home (PLAN decision D4).
Book-level strategy continues in the chapters after this bridge. Strategy interpretation of market outcomes (MARKET panel, CE, netting economics on the book) stays in this volume; clocks, screens and operator seats live in V9.
| Topic | Where |
|---|---|
| SAWEM primer & clocks | V9 §1 |
| Registration & compliance | V9 §3 |
| Bid Desk & ladders | V9 §4 |
| IDM | V9 §5 |
| Imbalance | V9 §6 |
| Network zones | V12 §2 |
| SO instructions | V12 §3.2 |
| Reconciliation & disputes | V9 §8 |
| Notices & market rules | V9 §9 |
| MO console | V11 §12 |
| SO console | V12 §4 · V12 §5 · V12 §6 |
| Worked delivery day | V9 §12 |
Where this is taught. Full market cycle: V9 · Market Operations. The simulation harness that exercises a market month in-process remains in this volume (§16–§17).
16. The simulation harness — a market month in seconds
19.1 The problem: five things the mock could never test
Everything from §15 to §18 runs, in the prototype, against a mock market operator: deterministic, instant, and deliberately dumb — the SMP is a fixed time-of-use curve (700/980/1400), the MO's settlement file always echoes your own records, and SO events are hand-built test fixtures. That was exactly right for building the platform's side of each interface. But five things can never be tested against a mock, because they all involve the market reacting:
| What you want to test | Why the mock can't | What the harness does |
|---|---|---|
| Price impact — does my volume move the price? | The mock's SMP ignores your bids entirely. | Bids clear against a price-elastic residual market (§19.3). |
| Reconciliation catching real errors | The mock's settlement file mirrors your own data — it can never mismatch. | Error injections make the MO wrong on purpose, in controlled ways (§19.6). |
| SO events at scale | Fixtures are hand-built one at a time. | Events are scripted per scenario day (§19.4). |
| Two settlements, one truth | Nothing generated a consistent set of metered actuals for both the wheeling settlement and the market imbalance. | One actuals generator writes the meter rows both legs read (§19.5). |
| A month, end to end | The real cycles hang off real cron clocks — a month takes a month. | A simulated clock drives the same cycle functions day by day, in seconds (§19.7). |
Phase 3D adds a simulation harness: a lightweight,
in-process Market Operator and System Operator (WB.Sim)
that sit behind the same connection points the mock uses, plus
a scripted scenario, a single-truth actuals
generator, and a driver that replays a whole
market month deterministically. The result is that the entire documented
arc — strategy runs → nominations → bids → clearing → actuals →
imbalance → settlement → month-end reconciliation — becomes something
you can run, watch, compare and tear down, from one screen.
One naming caution before anything else: this is not the §8 Simulations workbench. That screen answers "what if I signed this contract?" — one hypothetical member, one engine run. The Phase-3D harness simulates the market itself — every party, every cycle, a full month. Same word, very different tool.
19.2 The rules that keep it safe
Four hard rules, each worth knowing because each answers a "but what if…":
- The mock is the limit case, not a casualty. The
elastic clearing engine has a dial — the residual-market elasticity
k. Setk = ∞(the default when no scenario is running) and it degenerates to exactly the flat mock: SMP pinned to the TOU curve, the same acceptance rule. Nothing built in Phases 3B–3C is invalidated; the mock is simply the harness with the realism turned off. - A simulation can never touch production. Simulation
runs are environment guarded: a run refuses to start
anywhere the
simflag isn't enabled (development and demo builds only — the flag is absent in production). The Scenario Lab route doesn't even exist in a production build. Belt and braces. - Sim data is never mistakable for real data. Every row a run creates is recorded in the run's artifact registry, and any screen showing a live run's data wears a slim amber SIMULATED ribbon (§20.2) naming the run, its scenario and its seed.
- Every run cleans up after itself, exactly.
Teardowndeletes precisely the rows the registry recorded — bids, positions, meter rows, recon lines, strategy runs — and leaves the database at a zero row-count difference. The run itself survives as a tombstone: its day log and headline KPIs are kept, its data is gone.
And the standing invariants hold: WB.Brp and
WB.Strategy are untouched (the harness plugs in behind the
existing gateway seams), and everything is
deterministic — a scenario's seed fully
determines its month; no wall-clock, no unseeded randomness, anywhere in
the engines.
19.3 A market that reacts — the elastic clearing engine (worked)
The single most important new behaviour: your bids move the
price. The harness models everyone not on the platform
— the rest of the market — as a residual market: a
linear, price-elastic curve around the hour's base price
p_base (the familiar TOU curve). Its one parameter is the
elasticity k — how many MWh the residual
market absorbs (or releases) per 100% price move:
residual_net_supply(p) = k × (p − p_base) ÷ p_base
Sell into the market and the residual must absorb your volume — which only happens at a lower price. Buy, and you pull the price up. The clearing engine finds the single price where everything balances.
Worked example — your offer moves the price (
p_base980,k500). The Phase-3C netted position offers 4.71 MWh at hour 12. The residual market must absorb +4.71 MWh, so the price falls by4.71 ÷ 500 = 0.942%:SMP = 980 × (1 − 4.71/500) = R 970.77 (all rungs still clear → fully accepted) revenue = 4.71 × 970.77 = R 4 572.33Versus the flat mock's
4.71 × 980 = R 4 615.80— R 43.47 less. That R 43 is the lesson: volume has price impact, and the bigger you are in an hour, the more your own selling depresses what you sell at. Add a second trader offering the same 4.71 and the move deepens:980 × (1 − 9.42/500) =R 961.54. Buys push the other way — the hour-19 short of 6.0 MWh clears at1 400 × (1 + 6.0/500) =R 1 416.80: covering your gap made the gap dearer.
Two refinements complete the picture. The marginal line
clears pro-rata: if your rung is priced exactly at the
clearing price, you get the fraction the market can absorb — a 10 MWh
offer at R 965 (p_base 980) is taken for 7.653 MWh,
because that's precisely what the residual absorbs at 965. And
k = ∞ is the off switch: an infinitely
elastic residual absorbs anything at p_base, which is the
flat mock — so every worked number from §15 still reproduces when
no scenario is live.
19.4 The script and the run — Scenario and SimRun
A Scenario is the complete script of a market month, stored as data:
| Field | What it scripts | AUG-BASELINE's value (§19.8) |
|---|---|---|
seed |
Every random-looking number in the run (noise, ids). Same seed ⇒ same month, byte for byte. | 42 |
period |
The simulated window. | 1–31 Aug |
day factors |
Per-day generation/load multipliers — a sparse map over a default of 1.0. This is how you script weather. | 12–14 Aug: solar 0.35 (a cloudy spell) |
events |
SO instructions to issue, per day — the same fields a real instruction carries. | 18 Aug: curtail IPP-A to 8 MW, 11:00–13:00 |
injections |
Deliberate MO errors (§19.6), each declared with the recon outcome it must produce. | a +R 5 price republication; a 3.3% meter bias |
market |
p_base source + the
elasticity k. |
the TOU curve; k = 500 |
participants |
Which registrations/books the run drives — references to already-seeded entities. | the demo BRP + its book |
A SimRun is one execution of a scenario: its status
(PENDING → RUNNING → COMPLETE / FAILED, then
TORN_DOWN), a day log (per day: what each
cycle did — bids built, cleared, SO events, imbalance), the
artifact registry (every row created, per resource, for
exact teardown) and the run's KPIs. Runs accumulate
under a scenario, so run #3 can be compared against run #2 — and because
the scenario is deterministic, any difference between two runs of the
same script is a real change in the platform, which is exactly
what makes the harness a regression net.
19.5 One scenario, one truth — the actuals generator (worked)
The subtlest Phase-3D idea. The platform has two settlement legs — the wheeling settlement (physical, per PPA) and the market imbalance (per BRP hour) — and both are supposed to describe the same physical electricity. Until now nothing guaranteed that: tests fed each leg its own numbers. The harness generates one set of metered actuals and lets both legs read it:
actual(site, hour) = baseline(site, hour) × day factor × (1 + σ · noise(seed, site, hour))
− curtailed(site, hour)
- baseline — the site's forecast shape (what it should have done);
- day factor — the scenario's scripted weather (0.35 on a cloudy day);
- noise — a deterministic hash of
(seed, site, hour)in [−1, 1), scaled by the scenario's σ (default 0 — exact); this is what forecast error is made of; - curtailed — energy the SO ordered off, subtracted last (floored at zero).
The result is written as three 20-minute meter rows per hour (the platform's native meter granularity), TOU-bucketed, with the last row absorbing any rounding remainder so the three always sum to the hourly value exactly.
Worked example — the §17.4 curtailment becomes metered truth. IPP-A's baseline for 11:00 and 12:00 is 16 400 and 16 710 kWh. The SO caps it at 8 MW, removing 8 400 and 8 710 kWh. The generator writes actuals of exactly 8 000 kWh per hour — six 20-minute rows that conserve to the kWh — and both the wheeling settlement and the imbalance calculation read those same six rows. They cannot disagree about what happened, because there is only one record of what happened. The only permitted disagreement is the next section's.
19.6 Making the MO wrong on purpose — error injections (worked)
Phase 3C built a reconciliation engine (§17.3) — and then could only ever feed it files that matched, because the mock MO echoes your own records. The harness fixes that with injections: declared, controlled corruptions applied to the MO's outputs only — the internal truth is never touched. Each injection type maps to exactly one recon classification, so the reconciler's whole taxonomy is exercisable on demand:
| Injection | What the MO does wrong | What reconciliation must say |
|---|---|---|
republish_price |
The settlement file prices an hour at
SMP + Δ — but the schedule you pulled earlier said
SMP. |
VARIANCE / PRICE |
meter_bias |
The MO's metered quantity differs from yours by a scripted % on a day's imbalance lines. | VARIANCE / METER |
drop_line |
A line you have internally is missing from the MO file. | MISSING_MO |
phantom_line |
The MO bills a line you have no record of. | MISSING_INTERNAL |
No injections ⇒ the MO file mirrors the internal truth by construction ⇒ 100% matched — which is itself an assertion worth having.
Worked example — a price republication, end to end. In AUG-BASELINE the hour-12 offer on 13 Aug clears (elastically) at R 966.85. The scenario injects
republish_price +5on that hour, so the month-end MO file prices it at R 971.85 while the platform's internal expectation still says 966.85. The reconciliation flags exactly one energy line, classification VARIANCE, hint PRICE — the §17.3 machinery catching a genuinely wrong invoice for the first time, rather than being told the answer.
19.7 The driver — a month in seconds
The driver is the sim's clock. For each day of the scenario window it calls the same cycle functions the real cron clocks call (§15.6, §17.9) — nothing is re-implemented — but hands them a simulated clock instead of the wall clock:
- Build + submit the day's DAM bids at a simulated 03:00 (D−1) — against the real gate: build past 10:00 and the bid expires with a gate-miss alert, exactly as §15.6 warns. (The mock era could never actually exercise that path; the sim clock makes the gate real.)
- Clear through the elastic engine (§19.3); accepted volumes and SMPs write back to the bid lines, PubSub fills any open Bid Desk.
- Issue the day's scripted SO events (§19.4) — the same instruction rows the real feed would write.
- Re-bid intraday at a simulated 16:00 (the IDM cycle, gates and all).
- Settle yesterday's imbalance.
…each day appending its results to the run's day log. After the last day: the MO publishes the month's settlement file (with the injections), reconciliation runs, every created row is registered, and the run closes with its KPIs. A month of market life, in seconds, deterministic to the cent.
One honest scoping note: only decision logic reads the simulated clock (gates, freshness); record timestamps stay real — so the audit trail of a sim run is still truthful about when it actually executed.
19.8 AUG-BASELINE — the documentation, executable
Every phase of this manual pinned its worked numbers in tests. Phase 3D adds the capstone: AUG-BASELINE, a reference scenario (seed 42, 1–31 Aug, the cloudy spell, the day-18 curtailment, both injections) whose single driver run demonstrates the whole arc — and a test that asserts it. One run proves, end to end: the elastic clearing (hour-12 clears at 966.85, not the mock's 980 — your own 6.71 MWh moved it); the curtailment arriving as a real instruction; the injected price error surviving into the MO file (971.85) for reconciliation to catch; determinism (running it twice gives identical KPIs and prices); and teardown to zero.
The per-phase byte-numbers (Phase-1 allocations, Phase-2 nomination quantities and BESS schedules, the §17.3 recon variances at their exact Rand) remain pinned where they live — each phase's own test suite. AUG-BASELINE is the integration tripwire: if any part of the chain drifts, the one run that exercises all of it goes red.
Data. full_demo · seed 42 · scenario
AUG-BASELINE· last re-derived 2026-07-18 against the full_demo manifest.Follow along. In a demo-enabled build, seat:
trader_1@webill.net(passwordsupersecret). Path:/org/sim→AUG-BASELINE. You should see the seed and run provenance before inspecting simulated artifacts.
17. The simulation screens — the Scenario Lab and the SIMULATED ribbon
You sometimes need to rehearse a whole market month without confusing the rehearsal for production. The Scenario Lab is that controlled environment: it drives the normal cycle under a simulated clock and labels every resulting screen so a test result is never mistaken for a live position.
Data. full_demo · seed 42 · simulation fixture
AUG-BASELINE· last re-derived 2026-07-18. The reference run is deterministic for its seed.
20.1
The Scenario Lab (/org/sim — development and demo builds
only)
The Lab is intentionally not a Strategy landing-page card. In enabled
development or demo builds, open /org/sim directly. In
production the route does not exist; where the simulation flag is
disabled you are returned to the landing page. This separation protects
live operational data from accidental scenario authoring.
The Lab is a launcher and inspection surface, not the primary tutorial for scenario operations. V14 owns the operating mechanics, scenario conventions and safe use. Here you only need to recognise its controls when a Strategy, BESS or market screen carries simulated context.
| Area | What you see | What it is for |
|---|---|---|
| Scenario chips | Name and seed for stored scenarios | Select the script to inspect or run |
| Script editor | Period, seed, elasticity and day-factor grid | Define controlled daily variation |
| Events panels | SO curtailments and MO injections | Script operational and price shocks |
| Run card | PENDING, RUNNING, COMPLETE or
FAILED |
See the asynchronous driver state |
| Runs and compare | Newest-first runs and KPI comparison | Detect changed platform behaviour |
| Teardown | Artifact count and retained tombstone | Remove created data without losing evidence |
The seed is part of the experiment. A change between two runs of the
same scenario and seed is evidence of a platform or input change, not
ordinary randomness. Clone AUG-BASELINE rather than editing
it when you need a new experiment, so the reference case remains
available.
20.2 What a scenario script controls
The script supplies the conditions the ordinary clocks will encounter. It does not create a parallel allocation, bidding or reconciliation implementation.
| Script element | Example effect | Result you can inspect |
|---|---|---|
| Day factor, 0.0–2.0 | Raises or lowers a day’s profile | Allocation and forecast shape |
| SO event | Curtailment at a delivery interval | Dispatch, allocator and excused imbalance |
| MO injection | External market-file perturbation | Reconciliation variance classification |
Elasticity / p_base |
Price-response input and source | Market clearing and exact price context |
While a run is active, authoring controls are disabled. This prevents a script from changing underneath the driver. Editing after completion is allowed but does not rewrite history: earlier rows receive Scenario drifted so you can see that today’s script no longer exactly describes their original input.
20.3 Run, inspect and tear down
Select Run scenario to start the supervised
asynchronous driver. The run card moves from PENDING to
RUNNING; completed delivery days stream into the log in
order. A failed day creates a FAILED tombstone with its
earlier rows and created artifacts still inspectable until teardown. A
second run cannot start while one is active.
| Outcome | Meaning | What you do |
|---|---|---|
COMPLETE |
All scripted delivery days finished | Compare KPIs or follow a labelled screen |
FAILED |
A day did not complete | Inspect the log before tearing down |
TORN_DOWN |
Artifacts were removed | Use retained log and KPI tombstone only |
| Scenario drifted | Script changed since this run | Do not call it a current baseline |
Teardown deletes exactly the registered artifacts for that run and reports the count. It retains the run row, log and KPIs as an audit tombstone. Teardown is why you can rehearse aggressively without leaving ambiguous simulated records in operational views.
20.4 The SIMULATED ribbon — sim data is never incognito
Any screen showing artifacts owned by a live simulation run displays the slim amber ribbon:
🟠 SIMULATED — run #019f5200 (AUG-BASELINE, seed 42)
It identifies the owning run from the artifact registry — not merely that a simulation was run recently. The ribbon disappears when the run is torn down. Selecting it deep-links to the Lab with the run, scenario and delivery-window context selected.
| Ribbon state | Meaning | Safe interpretation |
|---|---|---|
| Present with run and seed | Data belongs to the named live simulation | Explore freely; do not operate it as live |
| Present on a dashboard or allocation | A displayed KPI or line is simulated | Compare only with the named scenario context |
| Absent | No live sim artifact owns this view | Use normal production interpretation |
| Gone after teardown | Simulated artifacts were removed | Refer to the Lab tombstone for evidence |
The ribbon can appear on the Lab, Bid Desk, Imbalance, Reconciliation, BESS, allocation detail, trader/IPP/off-taker dashboards and both operator consoles. It is a provenance marker, not a warning that the screen is malfunctioning.
20.5 Console behaviour while a scenario is live
The Market Operator and System Operator consoles use their normal controls; in demo or development they are re-pointed to the live scenario instead of gaining a second “simulation” button.
- Clear day uses the elastic engine while a scenario is live, so participant price impact is visible. Without a live run it retains the ordinary flat mock.
- An SO curtailment issued during a run has the same downstream fields and effects as a scripted curtailment: allocator lines and excused imbalance.
- Publish & reconcile applies live scenario injections, making the perturbed external file visibly differ from internal truth.
Follow along. Seat:
trader_1@webill.net(passwordsupersecret). In a demo-enabled build, open/org/sim, selectAUG-BASELINE(seed 42) and inspect the run list. You should see scenario context before you start a run; on any produced operational view, confirm the amber SIMULATED ribbon.
For the full scenario-authoring and operating procedure, continue at V14. The next chapter places this simulated cycle alongside the real trader, market and settlement clocks.
18. The rhythms — how it all fits together
Your book is not managed by one screen or one deadline. Forecasts, nominations, market gates and reconciliation each arrive on their own clock. This chapter gives you the trader operating rhythm: what becomes visible, what you need to decide, and where to go next.
Platform clock home. Step-by-step process and data-driven examples for settlement, strategy, market day and the IPP programme: V1 · The Four Rhythms. This chapter stays the trader decision map on that same clock.
Data. full_demo · seed 42 · stage S09 (settlement matrix) · last re-derived 2026-07-18 against the full_demo manifest. Times are SAST.
18.1 The daily and market clock
The dial is a locator, not a substitute for market-rule timing. It shows the events that change the questions on your trader book.
- 02:30 Nightly strategy re-forecast
- 03:00 D−1 DAM bid build and submission
- 06:30 D+1 Imbalance priced into snapshot
Where this is taught. Market gate rules and market operating mechanics belong to V9; operational runners and scenario controls belong to V21. This section only connects their clocks to the Strategy decisions you make.
18.2 The monthly strip
| When | Clock event | Strategy consequence |
|---|---|---|
| Every night, 02:30 | P50/P90 forecast and snapshot | Overview receives new risk evidence |
| Morning | Trader review | You triage exceptions and assign work |
| 20th, 05:00 | Next-month nomination proposal | You review before the external deadline |
| D−1, 03:00–10:00 | DAM build and gate | Registered books need a fresh position |
| D−1, 12:00 | Cleared schedule arrives | Bid Desk can compare declared and cleared |
| Delivery day, each 30 min | IDM monitor | Drift is compared against the cost of carrying it, not measured against a threshold |
| Delivery day, five fixed gates | Intraday auctions | 18:00 D−1, then 00:00, 06:00, 12:00, 18:00 (V9 §5.1) |
| D+1, 06:30 | Imbalance pricing | Market KPIs return to the snapshot |
| Daily, 12:30 | Price forecast version | Market view uses refreshed quantiles |
| 3rd, 07:00 | Monthly reconciliation | Variances and disputes need review |
| On demand | Scenario run | Rehearsal replays the same cycle safely |
18.3 Rhythm 1 — nightly re-forecast
Every book — trader, off-taker and IPP — receives a rolling 12-month P50 run, P90 stress pass, Margin@Risk calculation, per-pool breakdown and limit checks. An active battery is dispatched as part of the run. Re-running the same day updates that day’s snapshot rather than creating a duplicate. The same belt, explained tray-by-tray with worked numbers, is §3.5 (Run Forecast Now is the on-demand twin).
| What you see | What you do |
|---|---|
| Fresh Overview timestamp and KPI cards | Use it as today’s decision starting point |
| Directory mini-KPI chip refresh | Open the affected pool, not every pool |
18.4 Rhythm 2 — monthly nomination proposal
On the 20th at 05:00, the nomination worker prepares a
PROPOSED next-month plan for nominated members. It never
alters plans already approved or submitted.
| What you see | What you do |
|---|---|
| Proposed plan and rationale | Review overrides, approve and submit before deadline |
| Existing approved/submitted plan | Preserve it; only revise through the defined workflow |
18.5 Rhythm 3 — morning exception check
Open Overview. Green means nothing crossed configured limits; amber and red mean you have an exception to locate.
| What you see | What you do |
|---|---|
| Amber/red KPI or next-best action | Open Dashboard for hours and pools |
| Battery-cycle or nomination-tolerance hint | Go to BESS or Nominations respectively |
18.6 Rhythm 4 — repair portfolio structure
Pool membership, rules and assignment determine what the engine can allocate. Change them on Pools; the amber Forecast is behind this board banner (§4.6) reminds you to press Run Forecast Now before trusting Overview or Dashboard again.
| What you see | What you do |
|---|---|
| Unassigned executed contract | Assign it to the correct pool |
| Forecast is behind this board | Run Forecast Now (stay until the spinner clears) |
| Coverage shape problem | Change rules/members, then confirm with a new run |
| Archived an active pool | Run Forecast Now — the board fingerprint changed |
18.7 Rhythm 5 — improve the commercial book
When structure cannot solve the gap, use Simulations. Select a candidate whose shape fits, test terms against baseline and promote only an explainable result.
| What you see | What you do |
|---|---|
| High spill | Test daytime offtake |
| Persistent shortfall | Test generation or firm cover |
| Strong coherent deltas | Promote to a proposal, then assign executed contract |
18.8 Rhythm 6 — audit a number
Every headline result can be traced to an immutable run detail with source-to-destination lines, frozen BESS schedule where applicable and a reproducibility hash.
| What you see | What you do |
|---|---|
| KPI requires explanation | Open its newest current run |
| A changed result | Compare it with the superseded earlier run |
18.9 Rhythm 7 — operate the other seats
The same engine rhythm uses different questions. Off-takers start from savings and coverage; IPPs start from realised revenue and unsold energy.
| What you see | What you do |
|---|---|
| Off-taker exception | Use its savings-first Overview and Procurement workbench |
| IPP exception | Use its revenue-gap Overview and Sales workbench |
18.10 Rhythm 8 — meet market clocks
For registered books, the fresh strategy position feeds the D−1 DAM build at 03:00, with a 10:00 gate. The cleared schedule arrives at 12:00; the IDM monitor checks drift every 30 minutes on delivery day; D+1 imbalance pricing merges market KPIs into the snapshot.
| What you see | What you do |
|---|---|
| Gate miss or stale position | Resolve before the relevant V6 gate closes |
| Imbalance-limit exception | Investigate market outcome and book shape |
18.11 Rhythm 9 — close the market loop
The SO poller ingests grid instructions every 15 minutes, the price forecast versions daily at 12:30, and the reconciler joins last month’s internal and MO values on the 3rd at 07:00.
| What you see | What you do |
|---|---|
| Curtailment or system-condition change | Read its effect on allocation and imbalance |
| Reconciliation variance | Review the line and progress any dispute |
18.12 Rhythm 10 — rehearse without contaminating live work
The simulation harness adds no separate business clock. Its Scenario Lab replays rhythms 1–9 under a simulated clock, day by day, including the consequences of a late bid build.
| What you see | What you do |
|---|---|
| SIMULATED ribbon | Treat the data as rehearsal context only |
| Completed deterministic scenario | Compare results to expose platform changes |
Follow along. Seat:
trader_1@webill.net(passwordsupersecret). Start at T1 Renewables Pool → Strategy → Overview each morning. When a card is amber or red, trace it to Dashboard or Runs; when it is structural, use Pools; when it is commercial, use Simulations; before a deadline, open Nominations or the relevant V6 market desk.
The rhythm is deliberately closed: a snapshot detects, a specialist screen changes or explains, an immutable run proves, and the next snapshot confirms.
Next. Platform-wide cadence with worked examples: V1. Ops cards if a job fails: V21 §3.
19. Common questions
Why is my firm supply spilling? Working as intended: FIRM-pool supply is reserved for firm demand (§6.1) and priced high, so in hours when firm obligations are already met and cheaper supply covers open demand, the firm block goes unsold. Watch ToP utilisation (§11.3) — persistent low utilisation means the firm cover is oversized for the obligations it backs. (It is also exactly the pattern a battery monetises — §10.)
Why does a green arrow sit next to a negative
number? Delta colouring is directional, not sign-based (§8.3): −2.1 pp
of spill is an improvement, so it's green.
Why did my margin change when I changed nothing? The nightly cycle re-runs a rolling 12-month window, so each night one month falls off the front and a new one joins the back; prices also escalate as contract anniversaries pass. Compare the two runs' inputs hashes — if they differ, the inputs differ, and the Audit tabs will show you where.
Why is coverage below target even though I have plenty of MW? Coverage is hourly, not average. 100 MW of solar cannot cover a 20 MW evening load. Look at the run detail chart (§7.3) for the hours where the demand line clears the stack, and the Shortfall tab for the members affected — then let the workbench find a shape that fills them.
Can a simulation mess up my forecast?
No. Simulations run under the SIMULATION scenario, never
supersede anything, and their hypothetical member is never saved (§8, §1.3).
Do I have to wait for the nightly run after editing pools? No — Run Forecast Now (§3.5) performs the identical cycle on demand. On Pools, the amber Forecast is behind this board banner (§4.6) appears after member / rule / archive changes and clears when the run finishes; stay on the page while the spinner overlay is up.
Where do the coverage / spill numbers come from on + New pool? Choosing an archetype Type (Solar, Industrial, …) prefills the same limits as Seed default pools — supply max spill %, or demand coverage target/min/max and max shortfall, plus purpose and priority. A green hint under Type shows the suggestion; edit any field to override. Custom and Edit pool do not invent numbers. Full field reference: §4.1.1–§4.1.3; suggestion behaviour: §4.1.4.
Why is + member greyed out on Pools? Trader pool membership from the board is contract assignment: the button stays inactive until you have an unassigned SIGNED/ACTIVE physical PPA (§4.3, §4.5). Prospect sites are not addable here — conclude the deal first, then assign it.
What happens when I remove a member from a pool? The membership is destroyed and the executed PPA returns to the Unassigned contracts strip so you can place it elsewhere. Allocation history keeps its kWh/prices; press Run Forecast Now so Overview matches the new board (§4.3, §4.6).
Why does Rules only offer some rule types? The picker is side-filtered: supply pools see PRIORITY, PRO_RATA, MW_CAP, NOMINATION, SHAPE_MATCH; demand pools see PRIORITY, PRO_RATA, MW_CAP, PRICE_OPTIMISE. Types already on the pool are hidden until you delete them (§4.4).
Where do spill / shortfall top-up numbers come from? Book rates on Pools sets book-wide defaults. Assigning a contract prefills spill / shortfall from those defaults when set, otherwise from the PPA. Clear a member field to inherit the book default at run time; an explicit member value stays an override when book rates change (§4.3).
Why does the optimiser nominate above the expected
residual? Because your mistakes are priced asymmetrically (§9.2):
being short costs the top-up premium (Cu = 115c in the
worked example), being long only the spill loss (Co = 55c).
When short hurts ~2× more, the cost-minimising point sits at the 67.6th
percentile, not the 50th. If your Cu and Co
were equal, the optimiser would nominate the median — the tilt
is exactly as large as your cost asymmetry.
Can the monthly worker or a re-optimise overwrite the plan I
approved? No. Approved and submitted plans are locked; both the
worker (§9.7)
and [Re-optimise all] (§9.3) skip them. Only your own explicit
re-submission (§9.5) replaces a
submitted month — and that marks the new rows revised.
Why doesn't my battery dispatch? Four usual causes,
in order of likelihood: (1) the battery isn't an active member
of an active supply pool — configuring the asset alone isn't
enough (§10.3); (2) the book
gives it no valued outlet — no firm shortfall and no
priced unmet demand means every discharge value is zero; (3) every
candidate pair's spread is below the minimum — check
the rejected-pairs strip, then reconsider min_spread or the
degradation cost; (4) nobody has re-run — dispatch enters on the next
run, not on save.
Why is the rejected-pairs strip empty? Usually because your charge energy is free: zero-rated spill clears almost any spread threshold, so nothing gets held. The strip earns its keep when charging costs real money (surplus PPA energy, grid) — that's when the 16.1-vs-20 style rejections (§10.2) appear.
Why does the Nomination Performance panel say "awaiting
settled actuals"? Its three metrics compare what you
declared with what actually happened — and "actually
happened" is the settled (ACTUAL) backcast, which arrives
with a later milestone (§11.8). The nomination
decision tooling (§9) is
fully live; the scoring of past decisions lights up with the
backcast.
Why is my off-taker book's Bill Saving negative? You are over-contracted: the loss on wheeled energy your site didn't consume (paid at landed cost, credited back at only the spill credit — V5 §8) exceeds the gross saving on the energy it did. Check the Over-contract card, then open the coverage rationale (V5 §6.1) — it will recommend smaller hourly volumes, typically around the 30th percentile of each hour's load, not the average.
Why does the coverage optimiser tell me to contract
less than my expected load? The mirror of the
nomination question above: your mistakes are priced asymmetrically, but
the other way around. Over-buying costs
landed − spill credit (63c in the worked example);
under-buying only forgoes grid − landed (25c). When being
long hurts 2.5× more, the cost-minimising point is the 28.4th percentile
— deliberately under the median, with the grid backstop covering the top
of the distribution at retail rates.
Why does my off-taker Overview show an amber "Missing grid rates" banner? The site's Eskom tariff category has no in-force KWH rates loaded, so the grid backstop — and therefore every saving — cannot be priced (V5 §4.6). Load the category's retail rates (the demo seeds Megaflex 210/128/95); the banner clears on the next visit.
My IPP's achieved price looks fine — why is "vs LCOE" red? Achieved is earned only on the energy that sold; realised divides revenue by all generation, unsold hours included (V4 §4). At 60% sold, a 65 c/kWh achieved price realises just 390 R/MWh — below a 480 R/MWh LCOE. The fix is on the Sales desk: fill the unsold profile, even at prices below your current achieved.
Why does the firm-viability card decline a block the buyer is
offering a premium for? Because your plant can't physically
generate in those hours: for a solar plant and an evening block,
P(short) = 1.0 (the card’s Chance the plant falls
short reads 100 %) and the expected margin is
price − replacement — negative for any realistic premium
(V4 §6.8). If the card shows
🔋 Needs storage, the deal works with a
battery whose marginal cost it displays — sign the block and the battery
together, or neither.
Why do the TOU-multiplier numbers on my screen differ from a counterparty's spreadsheet? Almost always because their spreadsheet assumes a different split of your generation across peak/standard/off-peak hours. The tab (V4 §6.7) weights each multiplier by your plant's actual hourly shape under the engine's SAST TOU calendar — the same calendar every allocation run uses — so the effective price is consistent with how your energy would actually settle. Insist on shape-weighted numbers in the negotiation.
Why did my offer go in at R 10/MWh — isn't that giving the energy away? No, because SAWEM clears at a uniform price: everything that clears trades at the hour's SMP, not at your bid (§15.5). The R 10 rung is firm take-or-pay spill whose alternative is earning zero — its rung price is a reservation price ("anything beats nothing, plus my safety margin"), and at an SMP of R 980 it earns R 980 like everyone else. The ladder protects you from selling below value; the auction sets what you actually receive.
Why can't I edit a price on the Bid Desk? Deliberate. Every bid line carries provenance back to a strategy run and its opportunity costs (§16.2); a hand-typed price would be a number nobody can defend later. If a price looks wrong, the book is wrong — fix the member's spill value or top-up, re-run, [Rebuild from latest run] — and the new price arrives with its explanation attached.
Why does my bid say EXPIRED? The build finished after the DAM gate (10:00 the day before delivery). A late bid is never submitted — it is expired and logged as a gate miss, which the MARKET panel counts and a critical alert reports (§15.6). If it keeps happening, the run the 03:00 build waits on is finishing too late.
Why is my new registration not bidding? It is PENDING — only ACTIVE registrations may submit, and activation is the Market Operator's button, not yours (§16.5; in the demo, sign in as the MO and press [Activate]). Also check a book is actually attached (§16.1): a registration with no books has nothing to bid.
Why did I pay imbalance in a month that looks fine on average? Imbalance is settled per hour, not per month (§15.7). Over-delivering every morning does not offset under-delivering one evening — the morning surplus earns the (low) surplus price while the evening shortfall pays the (high) short price. The Imbalance screen's per-hour table shows exactly which hours cost you; the attribution drawer names the member whose forecast error drove each one.
Why does the IDM tab show no re-bids on a day I know the forecast moved? The rule is no longer a megawatt-hour threshold. Since the SAWEM intraday market landed, each drifting hour is a comparison: re-declaring your own capacity down is charged flat at the Intra-Day Price, while carrying the deviation settles at your own offered price if an instruction is expected, or at the market price with no penalty at all if it is inside your Metering Accuracy Band. The desk recommends whichever is cheaper and shows its reason (V9 §5.5). Two things still stop a re-bid outright: the hour’s auction gate has closed, or the drift is inside the band. A drift discovered too late shows up in the next morning’s imbalance instead — which is the economics the R 1 800 worked example is about.
Why doesn't my dashboard show the MARKET panel? The book's registration is missing or not ACTIVE (§16.4). The panel is deliberately absent rather than empty for unregistered books — market KPIs on a book that never bids would be noise.
Why is the SMP always exactly 700/980/1400? You are on the prototype market operator, which prices deterministically off the TOU calendar so every demo and test reproduces exactly (§15.5). A real MO integration replaces the price feed, not the screens — the platform side (bids, schedules, imbalance, provenance) is what this phase built.
Why doesn't the price ribbon change my bid prices? By design, and permanently (§17.2 Rule 1). Your bid prices are reservation prices — what the energy is worth to you — and SAWEM's uniform-price auction pays the SMP regardless of your bid, so pricing off a forecast gains nothing and risks not clearing. The forecast changes positions (hold vs sign, netting, battery reserve), never rungs; a regression test fails the build if a forecast row moves one.
Why is an hour on the Imbalance view marked "excused" with no charge? The SO instructed you off (or down) in that hour, and instructed deviation is not your fault: the deviation splits into instructed and uninstructed parts and only the uninstructed part is charged (§17.4). Hover the pill for the counterfactual — what the excused energy would have cost you before Phase 3C.
Who pays when the SO curtails my generator? Whatever
the PPA's curtailment clause says — that is the
treatment on the instruction window (§17.4).
DEEMED_ALWAYS / BUYER_RISK: the buyer pays for
the deemed (ready-but-undelivered) energy in full; SHARED:
half; SELLER_RISK: the generator absorbs it. The run's KPIs
keep deemed energy (gross, treatment-independent) and
curtailment cost (what your book bore) separate so the two
never get conflated.
Why did the Reconciliation view flag a line whose variance is
only R 33.55? Because this registration's tolerance is set
tighter than the R 50 default (§17.3 — tolerance is per
registration, max(R 50, 0.1%) if you never touch it). And
small systematic variances are exactly what a tight tolerance is for: a
wrong clearing price applied to one hour was probably applied to
many.
The month is 98.9% matched — why do I still care about two lines? Because match rate is a count of lines, not a weight of money, and offsetting errors are never netted (§17.3). Each flagged line has its own root cause — a PRICE hint points at the MO's price feed, a METER hint at a meter-data mismatch — and either cause, left undisputed, repeats next month.
What actually happens when I press [Dispute]? The line is marked DISPUTED and a dispute case is created in DRAFT with an auto-assembled evidence pack — references to the line, its period, charge type, variance and hint (§17.3). Nothing is sent yet: [Mark submitted] sends it to the MO, whose console shows it in a dispute inbox (§18.6); their Resolve / Write-off flows back to your kanban. You never author evidence by hand — the artefacts already exist.
Why does the netting badge say 8.71 → 4.71 — where did my volume go? Nowhere profitable was lost (§17.6): your registration balances a long book and a short book in the same hour, so the short leg is served internally by the long leg's surplus and only the net 4.71 goes to market. Revenue is identical to trading both legs at SMP; what disappears is the short leg's execution risk (R 3 700/hour in the worked example) and 4.00 MW of open-position consumption.
The CE card says "sign bilateral" even though P50 is above
the offer — is it broken? No — that is the point of the CE (§17.7). P50 is a coin
flip; the offer is a certainty. At your book's λ the certainty
equivalent P50 − λ(P50 − P10) is below the offer,
so the fixed price wins for a desk with your risk appetite. Drag the λ
slider left (risk-neutral) and watch the verdict flip back — the flip
point is your appetite, made explicit.
Why does the demo's price ribbon show the same number three times (980/980/980)? The prototype MO's clearing price is a deterministic TOU curve, so 28 days of its history has zero spread and P10 = P50 = P90 (§17.5). The quantile machinery is the same one a real, noisy price history feeds — the worked examples' 450/980/1320 spread shows what that looks like.
Why is there no system-condition banner on my Bid Desk? The SO hasn't declared one for your registration. In the demo, sign in to the SO console (§18.5) and set the selector to HIGH — the banner appears on the next Bid Desk load, red, for every ACTIVE registration.
Why don't I see BESS market bars or a reserve band? They appear only for the nightly BRP path with an ACTIVE registration and a complete applicable P25/P75 forecast (§17.8). The bar and interval provenance name the pinned forecast; without either input the battery intentionally retains the byte-identical Phase-2 schedule.
Is the Scenario Lab the same as the Simulations screen? No — same word, different tools (§19.1). The §8 Simulations workbench asks "what if I signed this one contract?" — one hypothetical member, one engine run, deltas against your baseline. The §19 simulation harness simulates the market itself — every cycle, every party, a scripted month — to test and demonstrate the whole platform end to end.
Why did the sim clear my hour at 966.85 when the SMP table
says 980? Because in the sim, your bids move the price
(§19.3).
980 is the base price; your 6.71 MWh of selling pushed it down by
6.71 ÷ 500 = 1.34%. The flat 980 you're used to is the
special case k = ∞ — a market so deep your volume doesn't
register — which is what the mock always assumed and what the sim
reverts to when no scenario is running.
Can a simulation run pollute my real data? Three defences say no (§19.2): a run refuses to start outside development/demo builds (in production the Lab's route doesn't even exist); everything a run creates is tracked in its artifact registry and wears the SIMULATED ribbon on screen; and teardown deletes exactly those rows, verified to a zero row-count difference. What survives teardown is only the tombstone — the run's log and KPIs, as a record that it happened.
Why can't I find the Scenario Lab from the landing
page? By design — it is a developer/demo tool, not a user seat,
so it gets no card. Navigate to /org/sim directly (§20.1).
If that bounces you to the landing page, you are on a build where the
sim is disabled.
Why do two runs of the same scenario give exactly the same numbers? Determinism is the point (§19.4): a scenario's seed fixes the noise, the clock is simulated, and no engine touches the wall clock or an unseeded random. Identical runs are what make the compare strip meaningful — if run #3 differs from run #2 on the same script, the platform changed, and that difference is a finding, not noise.
Why did a bid expire during a sim run? Because gate enforcement in the sim is real (§19.7): the driver builds bids at a simulated 03:00 and the DAM gate closes at 10:00 the day before delivery — script the build later than the gate and the bid expires with the same critical gate-miss alert the live desk raises. The mock era could never actually test that; the sim clock makes it a demonstrable failure mode.
The MO console is showing a SIMULATED banner — is something wrong? No — a simulation run is live, and the console is currently fronting the simulated market (§20.3). Its [Clear day] clears through the elastic engine while that's true. The banner disappears when the run completes and is torn down; with no live run the console behaves exactly as §16.5 describes.
20. Terms used in this volume
The master glossary lives on the hub. These are the strategy terms this volume relies on most — follow each link for the full definition.
- Book
- Chain reconciliation
- Unexplained (kWh)
- Pool
- Allocation run
- Line kinds
- Nomination / Hour template
- BESS / SoC
- Margin@Risk
- BRP (market seat — see §15)
- SimRun / Scenario Lab
- Strike price / CfD (taught in V7 §9; this volume only bridges)
- Matched fraction (Virtual Supply workbench — money lives on the proposal, V7 §9)
Full glossary. Hub → Glossary.
21. The demo walkthrough — see everything with seeded data
Thread part 3: plan the cast’s books — T1 Renewables Pool, OffTaker C, and the IPP screens — against the same seeded entities contracted in V2.
Every screen in this manual can be exercised against seeded demo data — useful for training, screenshots, and kicking the tyres without touching real contracts.
24.1 Set it up
From the project root, against your development environment:
Demo setup command
mix wb.setup --profile full_demo --reset --as-of 2026-07-14 --seed 42
The command is deliberately reset-first: it removes
the complete prior application dataset, preserves the setup audit chain,
and deterministically reconstitutes the full S00–S14 demonstration. Run
it again after a schema change or whenever the demo world has drifted.
Every application account is recreated with password
supersecret; the command returns a sealed manifest only
after its historical, financial, route, component, chart, ownership, and
job barriers pass.
What it builds:
- IPP 2 / IPP 3 — seven generator sites (solar, wind… and one battery, below),
- Trader 1 / Trader 2 — three trader pools each, every one with a strategy book, the eight seeded strategy pools, PPA-backed supply members placed by technology, two board risk limits (coverage below 70/60, MaR above 25/40), and a completed current-month forecast run + snapshot — so all the Phase-1 screens render with data,
- OffTaker C — one off-taker site (George Eskom Plant, a manufacturing load),
- Phase-3A market plumbing — the Megaflex
retail rates (peak 210 / standard 128 / off-peak 95 c/kWh, the
§12.3 numbers) so grid
backstops can be priced, and one wheeled
OFFTAKER_IPPPPA: George Eskom Plant buys 0.5 MW as-generated at 85 c/kWh from Tshwane Solar Park — deliberately sized to the factory's load so the solar is consumed rather than over-contracted, - two Phase-3A books, each with a current-month run +
snapshot:
- an off-taker book on George Eskom Plant — its four pools (§12.3), the wheeled PPA placed as green supply (with the 40 c/kWh spill credit), the grid backstop live at the Megaflex rates. Expect roughly: green coverage ≈ 47%, a positive monthly saving in the tens of thousands of Rand, over-contract exposure 0, and an effective cost well under the grid-only baseline,
- an IPP book on Tshwane Solar Park — its three pools (§12.5), with the George PPA as its one (small) contracted buyer. Expect the raw revenue-gap position: unsold ≈ 98%, achieved 85 c/kWh on what sells, realised only ~14 R/MWh across the plant — the V4 §4 lesson, live.
The first trader book gets the full Phase-2 treatment (Trader 1's first pool, T1 Renewables Pool):
- a firm demand member — the off-taker site joins the 💵 Trader Sales pool with an Eskom top-up of 210 c/kWh, creating the evening shortfall the battery and the nominations exist to serve;
- a battery — the Rosslyn BESS site (5 MW / 20 MWh usable, all §10.1 defaults) with its dispatch asset, as a member of the 🔒 Firm pool;
- a nominated member — the book's first PPA-backed supply member is flagged nominated, and a PROPOSED nomination plan for the current month is built;
- a fresh dispatched run + snapshot, so the BESS metrics and both new dashboard panels are live immediately.
…and the Phase-3B market treatment (the same first trader book):
- an ACTIVE BRP registration
(
BRP-T1-DEMO, role Trader, safety margin R 10) attached to the T1 Renewables book, plus a market-rules row (gate 10:00, floor −R 1 000, cap R 32 000) and themo@webill.netconsole user; - a dedicated forecast run spanning yesterday → tomorrow, so the net position has lines on both demo days regardless of month boundary;
- yesterday, played end-to-end: DAM bid built and submitted → cleared by the mock MO → schedule recorded → imbalance settled with the actuals deliberately set to 80% of schedule (a 20% under-delivery), so the Imbalance screen has real short hours with real charges and the book's snapshot carries the market KPIs;
- tomorrow, bid and cleared: a submitted DAM bid with accepted volumes and clearing prices already on it, so the Bid Desk opens populated.
The same ACTIVE registration also carries the physical-PPA seller's IPP book for the M10 scenario. When more than one registered book has a position, the D−1 cycle persists one REGISTRATION-scope net bid and writes the accepted SMP attribution back to each book; a one-book registration keeps its original BOOK-scope behavior.
…and the Phase-3C closing-loop treatment (same registration), so every §17–§18 surface renders on first visit:
- a price forecast for both markets, built by the
real trailing-quantile refresh over the mock MO's price history
(remember the §17.5
caveat: the mock's prices are a flat TOU curve, so the demo's quantiles
have no spread — the ribbon reads
980/980/980at noon; the machinery is the real one); - complete current-month P25/P75 market lanes and a 2.5 MWh / R 1 900 reserve request, so the cloudy BESS day is dispatched through the guarded nightly market path rather than a display-only fixture;
- the §3.1 curtailment instruction — IPP-A capped at 8 MW, 11:00–13:00 SAST tomorrow, carrying its effect figures (17.11 MWh / R 11 121.50 deemed / R 12 413.50 excused) — so the SO feed and the SO console's issued list open populated;
- the §2.1 reconciled month — last month, 2 209 lines · 98.9% matched · −R 8 420, with the two flagged lines (A: ENERGY +R 33.55, hint PRICE; B: IMBALANCE −R 314.50, hint METER) and one open dispute on line B, already SUBMITTED — so the Reconciliation view has a worklist and the MO console's dispute inbox has a case waiting;
- the
so@webill.netconsole user (passwordsupersecret) behind the fifth landing card.
…and the Phase-3D simulation treatment. Development
builds ship with the sim flag enabled
(config :wb_wheeling, :sim_enabled, true in
config/dev.exs — nothing to set up), the operator consoles
re-pointed at the simulators, and the seed registers one ready-to-run
scenario:
- AUG-BASELINE (§19.8), wired to the demo's BRP registration and T1 Renewables plus its physical-PPA seller's IPP book: seed 42, 1–31 Aug, the 12–14 Aug cloudy spell (solar 0.35), the 18 Aug IPP-A curtailment (8 MW, 11:00–13:00), the two MO injections, elasticity k = 500 — so the Scenario Lab opens with a member-bearing worked example of §19.4. Re-running the seed refreshes this scenario and its forecast/reserve fixtures without duplication.
No new user is needed: the Lab lives at /org/sim with no
login card (it is a developer/demo surface, not a seat).
24.2 The guided tour — trader (Phases 1–2)
Sign in as Trader 1 (from the landing page), then:
- Trader Pools → T1 Renewables Pool → ⚖ Strategy. The Overview loads with live KPI cards; the tab bar includes Book and Chains beside Book · Forecast.
- Book (§5A). Pick a settled month — warehouse badge, buy/sell legs, headroom.
- Book · Outcomes → Chains (§5B). Same month — verdict strip, pick a chain, read the rail, volume bridge (Unexplained), money, legs, book check. Toggle Unbalanced only if the list is long.
- Nominations. The nominated member is listed with a
PROPOSED plan — monthly total, E[cost], the saving vs
P50. Click the plan row, pick the Weekday tab, and click an evening cell
(e.g. 19h): the rationale panel opens with the five residual scenarios,
Cu/Co,q*and the three expected costs. Try the full loop from §9.6 — override a cell, Approve, Submit — and note the row-count confirmation. (Everything here is demo data; submitting affects only the demo book.) - BESS. The Rosslyn BESS day view: KPI strip, the mirrored charge/discharge chart with the SoC line, the schedule table with its rationale strings, and the rejected-pairs strip (likely "None…" — the demo's charge energy is free spill, §22). Open ⚙ Asset config, raise Min spread to 40, save, Re-run dispatch, and watch the schedule thin out — then put it back.
- Book · Forecast → the latest FORECAST run → the battery
fold. The same day view, frozen inside the run — and in the
Allocations tab, the
BESS_CHARGE/BESS_DISCHARGElines taggedbess_dispatch, with the stored marginal cost on the discharge lines' buy column. - Dashboard. Panels ⑥ (the four battery tiles) and ⑦ (awaiting settled actuals — expected, see §11.8). Add a BESS cycles/day limit (above, warn 1.2, breach 1.5) via ⚙ Edit limits, press Run Forecast Now on the Overview, and see it evaluate.
- Overview. With the book's spill and coverage as seeded, expect a breach or two in the cards and a populated next-best-actions list — the morning-check experience of §3, end to end.
24.3 The guided tour — off-taker (Phase 3A)
Sign in as OffTaker C, then:
- Off-taker Sites in the sidebar. The George Eskom Plant row's Strategy button already shows its saving chip (the demo seeded the book and ran it) — click it to go straight into the Overview. (On a site without a book you would first get the confirm modal listing the four pools — try it later by adding a second site.)
- Overview. The use vs pay hero is live — a positive Saving vs all-grid, Green around 47%, Over-contract at zero (the PPA is sized to the load). The supply mix shows the wheeled solar band displacing the grid band through the daylight hours, and the escalation chart draws the 12%/yr grid line pulling away from the flat landed line — the §13.1.1–§13.1.3 worked numbers on screen. No amber banner: the demo loaded the Megaflex rates (§13.1.6). Open ⚙ Assumptions, change grid escalation to 8, save, Run Forecast Now, and watch the year-5 divergence flatten — then put it back (§13.1.5).
- Procurement → Coverage Rationale tool. The per-hour
newsvendor table:
Cu/Co,q*around 28%, recommended volumes sitting below the median load — the §13.3 asymmetry, on your data. - Procurement Desk. The need band prices the
uncovered block as Option 0 · stay on the grid;
candidates are listed fit-first with coverage, route and risk chips.
Select one, keep the default terms, [Simulate
Purchase], and read the assessment against the grid — Δ
saving/yr with the year-1 vs year-5 pair. [Build
Proposal] opens a private
OFFTAKER_IPPdraft with suggestions (nothing is sent — demo data; build freely). - Dashboard. The five buy-side panels; add a Green coverage % limit (below, warn 30) via the trader-style limits flow and re-run to see it evaluate.
24.4 The guided tour — IPP (Phase 3A)
Sign in as IPP 2, then:
- IPP Sites in the sidebar. Tshwane Solar Park's Strategy button shows its realised-price chip — click through to the Overview.
- Overview. Realised vs Achieved side by side — the V4 §4 lesson at its most extreme: achieved 85 c/kWh on the sliver that sells to the George PPA, realised ~14 R/MWh across the plant, unsold ≈ 98%. The unsold-profile chart shows the daytime mountain one small wheeled buyer barely dents, and next-best-actions point at the gap. ⚙ LCOE / params holds the 48 c/kWh LCOE and 210 c/kWh replacement assumptions.
- Sales → TOU pricing. Type base 65 and read the ranked table — for this solar shape the flat set beats both multiplier counters (the V4 §6.7 worked example, computed live from the plant's real hourly shape).
- Sales → Firm viability. At the default 140 c/kWh evening block the card shows ❌ Decline (expected margin −70 c/kWh, chance the plant falls short 100 %) with "No active battery on this site." The needs-storage flip (🔋, +51.1 c/kWh) appears when a battery marginal is available — V4 §6.8.
- Sales → Workbench. Off-taker buyers ranked by fit
against the unsold profile; simulate a sale, read Δ realised / Δ unsold
/ Δ HHI, and [Promote] — a seller-initiated
OFFTAKER_IPPdraft proposal. - Dashboard. Revenue / Risk / Utilisation / Price / Alerts — the contracted-ratio gauge against its 85% reference makes the revenue gap impossible to miss (V4 §12).
24.5 The guided tour — the market seat (Phase 3B)
Sign in as Trader 1 again, then:
- BRP / Market in the sidebar. The registration
console shows
BRP-T1-DEMOwith its ACTIVE badge, the Trader role chip, the R 10 safety margin, and T1 Renewables in the books-balanced table with its last-bid date. The compliance block lists any ≥10 MW generator sites without cover — cross-check against the "No BRP" chips on the IPP Sites directory. - Bid Desk. The date defaults to
tomorrow, which the demo has already bid and cleared:
the toolbar's gate clock is counting down (or shows
Due now if you open it after 10:00), the net-position chart
shows the book's long daytime hours, and the ladder table has accepted
volumes and the clearing SMP per hour. Click any row —
it expands to its provenance: the run id and the components (firm spill
at 0 c, wind at 40 c…) whose §15.5 arithmetic produced
the rung prices. If the book has an
OPEN_POSITION_MWlimit, the clip strip shows what it clamped — add one (breach 3) on the Dashboard's limits editor, [Rebuild from latest run], and watch a 6.71 MWh hour get cut to 3.00 with the clamp logged. - Rebuild → Submit, by hand. Pick a date a few days out (no bid exists yet): the desk shows the empty state, [Rebuild from latest run] builds a DRAFT (the ladder appears with no results), and [Submit] — with its opportunity-cost-anchor confirm — turns it SUBMITTED with an MO reference. That is the 03:00 cycle, done manually.
- Imbalance. The date defaults to yesterday, which the demo settled with a 20% under-delivery: the scheduled line sits above the actual line all day, every hour's row shows its short MWh and its charge, and the toolbar totals the month. Click an hour — the attribution drawer opens and names the members whose forecast error drove the gap, largest first.
- Trader Pools → T1 Renewables Pool → ⚖ Strategy → Dashboard. The ⑨ MARKET (BRP) panel is live: DAM uplift MTD with its recovered-spill / avoided-top-up decomposition, acceptance rate, imbalance cost MTD, bid count, gate misses (0). Add an imbalance-% limit (above, warn 5, breach 10) and the panel wears the severity badge.
24.6 The guided tour — the Market Operator (the other window)
The two-sided loop needs two browser windows:
- Window 1 — Trader 1's Bid Desk, on a fresh future date: build and Submit a bid (tour §24.5 step 3). Leave the desk open.
- Window 2 — from the landing page choose the fourth
card, Market Operator (
mo@webill.net/supersecret). The Market Desk shows the market-rules panel (gate, floor, cap), every registration with its status, and the bid board — your window-1 bid is on it, SUBMITTED, with the same gate clock. - [Clear day] on the Market Desk — then look back at window 1: the Bid Desk's accepted and result columns have filled in, without a reload. That is the 12:00 schedule fetch, compressed to a button.
- Try the lifecycle gate: register a second organisation as a BRP (§16.1 — it starts PENDING), attach a book, and try to submit — refused. On the Market Desk, [Activate] it, and the next submit goes through: D2's licensing step, made tangible.
- [Publish new version…] on the rules panel (change the cap, say). The next bid you build on any desk pins the new version — rules are data, and every bid records which version it was built under.
24.7 The guided tour — the closing loop, BRP side (Phase 3C)
Sign in as Trader 1 again, then:
- BRP / Market → Reconciliation. The seeded month is
on screen: the summary header reads 2 209 lines · 98.9% · −R 8
420, and the flagged-lines table shows the §17.3 pair — Line
A (ENERGY,
6 575.80vs6 609.35, +R 33.55, hint PRICE) and Line B (IMBALANCE,−2 793.50vs−3 108.00, −R 314.50, hint METER). Expand a row to read the re-derivation — both sides' quantity × price, the evidence for the hint. Press [Accept] on Line A (a R 33.55 fight is not worth an afternoon) and watch its resolution log. Line B is already DISPUTED — its case sits in the kanban as SUBMITTED, waiting for the MO (you will answer it yourself in tour §24.8 step 4). - BRP / Market → System Operator. The instruction inbox holds the seeded §3.1 curtailment: IPP-A · 11:00–13:00 tomorrow, kind badge, and the effects line — 17.11 MWh curtailed · deemed R 11 121.50 · imbalance excused R 12 413.50. Press [Acknowledge] — the stamp appears here and, as you'll see in the next tour, on the SO console's issued list too.
- Imbalance. The per-hour table now carries the Instructed | Uninstructed split (§18.4). The demo's settled day was a plain 20% under-delivery with no SO cover, so instructed reads 0 and the full deviation is charged — exactly right. The green "excused" pill (with its "would otherwise cost…" tooltip) appears on hours an SO window covered, once a curtailed day settles.
- Bid Desk. Above the net-position chart sits the
price ribbon — on demo data it reads
P10 980 · P50 980 · P90 980, the flat-history caveat of §17.5 made visible (a real price history spreads the three apart). Below the chart, the CE card compares an offer to the band; with zero spread the CE equals P50 at every λ, so the verdict sits at Marginal and the slider doesn't move it — drag it anyway to see the mechanics, then reread the §17.7 worked numbers to see what a spready history does. The netting badge appears only when the registration balances more than one book (attach the IPP book on the Registration screen to see thegross → netcollapse); the system-condition banner appears in the next tour.
24.8 The guided tour — the System Operator console and the operator loops
Two windows again — the BRP in one, an operator in the other:
- Window 2 — from the landing page choose the
fifth card, System Operator (
so@webill.net/supersecret). The Dispatch Desk opens with the seeded curtailment already on the issued list — wearing Acknowledged by BRP ✓ if you did tour §24.7 step 2 — and in the active-constraints table while its window is open. - Issue one yourself. The form comes pre-filled with the §3.1 shape (CURTAILMENT, 11:00–13:00, cap 8 MW, deemed 65 c/kWh). Pick the T1 book as the target and watch the effect preview compute live from that book's own forecast — curtailed MWh and deemed R, before you commit. Press [Issue instruction], then check window 1's SO feed: the new card is there with the same figures the preview showed — identical by construction, because the console stored its estimate on the instruction (§18.5).
- Set the system condition to HIGH on the selector, then open window 1's Bid Desk: the red banner is across the top. Set it back to LOW and it turns quiet again — the SO's context signal, end to end.
- Answer the dispute. Open a third window (or reuse
window 2) as the Market Operator (
/mo). The dispute inbox shows the seeded case — R 314.50, with its evidence line. Press [Resolve], then look at the Trader's Reconciliation kanban: the card has moved to RESOLVED. Dispute raised on one side, answered on the other, no e-mail in between. - [Publish & reconcile] on the MO's settlement panel — pick the registration and the current month. The console publishes a settlement file and runs the same join the monthly clock runs, flashing 100.0% matched (the console's file mirrors the platform's internal expectation — §18.6 — so it reconciles clean; the seeded month is where the interesting variances live). Note the Reconciliation view shows the newest run, so do this step last if you want the 98.9% month on screen for a demo audience.
24.9 The guided tour — the Scenario Lab (Phase 3D)
No login card for this one — with the dev server running, navigate
straight to /org/sim (any browser window;
on a build without the sim it bounces to the landing page, §19.2):
- Select AUG-BASELINE. The script panel is §19.4 on
screen: the header reads 1 Aug → 31 Aug · seed 42 · k 500; the
day-factor column shows only the three scripted days
(
2026-08-12/13/14: solar 0.35— everything unlisted is 1.0); the events column shows the 18 Aug curtailment of IPP-A; and each injection is annotated with the recon outcome it must produce (republish_price → VARIANCE / PRICE,meter_bias → VARIANCE / METER) — read it as a promise the run will keep. - [▶ Run scenario]. The run starts in the background: watch each delivery day appear in the ordered log while the controls are disabled. It ends COMPLETE with 31 days and its KPI map (day, bid and imbalance-position counts for the demo books). A simulated August just happened: thirty-one D−1 bid builds against the real 10:00 gate, thirty-one elastic clearings, the cloudy spell, the curtailment, the month-end settlement file with its injected errors, and the reconciliation that caught them. If an execution fails, its FAILED row retains the earlier day log and artifacts for inspection; tear it down only after inspecting the failure.
- Run it again. The compare strip puts the two runs side by side — and they are identical. That is §19.4's determinism doing its job: same script, same seed, same month, to the cent. (If they ever differ after a code change, that difference is a regression finding — this screen is the tripwire.)
- See what it wrote. The run's rows are ordinary platform rows written through ordinary flows — follow the amber ribbon from the Lab into the matching window on the Bid Desk, BESS, Imbalance or Reconciliation view. The BESS page shows the cloudy market decision and reserve band, the Bid Desk shows the registration netting stack, the SO feed holds the day-18 curtailment, and Reconciliation shows the injected variances. Edit a factor after either run and its historical row receives a Scenario drifted badge without rewriting its log/KPIs.
- [Teardown]. The flash reports exactly how many run-owned rows were deleted; the row stays as a grey TORN_DOWN tombstone with its log and KPIs. The registry is exact: the long-lived seeded Bid Desk, BESS and reconciliation fixtures remain in place after teardown, and repeating teardown is safe.
The SIMULATED ribbon is the provenance bridge for this tour: it is visible wherever a live run owns the displayed data and returns you to the matching Lab run/window.
Data. full_demo · seed 42 · stage S09 (settlement matrix) · last re-derived 2026-07-18 against the full_demo manifest · M8 gate stamp commit
925037a.Follow along. Seat:
trader_1@webill.net(passwordsupersecret). Begin at T1 Renewables Pool → Strategy and use this chapter’s order to keep each demo screen attached to its evidence.
Previously in the thread → V7 §7 · physical deal / V7 §8 · virtual deal. Next in the thread → V4 §3.1 · IPP strategy area (then V4 §7 · hierarchy). Market day → V9 §12.
Screens covered: StrategyOverviewLive,
PoolsLive, AllocationsLive,
AllocationDetailLive, TraderBookLive,
TraderChainsLive, SimulationsLive,
NominationsLive, BessLive,
RiskDashboardLive (trader — Phases 1 & 2),
OfftakerOverviewLive, ProcurementLive,
OfftakerAllocationsLive,
OfftakerDashboardLive, IppOverviewLive,
SalesLive, IppAllocationsLive,
IppDashboardLive (off-taker & IPP — Phase 3A + settled
Sources / Allocations), RegistrationLive,
BidDeskLive, ImbalanceLive, the MARKET
dashboard panel and Mo.MarketDeskLive (the market seat —
Phase 3B), ReconciliationLive, SoFeedLive, the
Bid Desk / Imbalance extensions, So.DispatchDeskLive and
the MO console's settlement and dispute panels (the closing loop — Phase
3C), and Org.Sim.ScenarioLabLive with the cross-cutting
SIMULATED ribbon (the simulation harness — Phase 3D). Gates passed: M2
2026-07-05, M3 2026-07-06, M4 2026-07-10, M5 2026-07-10, M6 2026-07-10,
M7 2026-07-11, M8 2026-07-11, M9 2026-07-14 and M10 2026-07-14 — the
documented arc through Phase 5 is complete and self-verifying. The
settled (ACTUAL) backcast, nomination-performance scoring,
measured forecast-error scenarios and meter-derived load scenarios are
covered in this manual. M10 discharges the recorded BESS, full
sim-month, Lab authoring, netted-bids, provenance,
reconciliation-heatmap and cross-console-update integrations while
retaining their production guards and exact ownership
boundaries.
22. From sensitivity to distribution
Route: …/strategy/dashboard ·
Tab: Dashboard · Counterparty
exposure: on the Desk (§8.10;
…/strategy/exposure forwards there)
Margin-at-Risk is a sensitivity: run the book at P50, run it again at P90 supply, report the difference. That answers "what if the wind disappoints" with one adverse case and no probability. Value-at-Risk answers "how often does a loss at least this bad show up" by sampling five hundred tomorrows and reading the tail of that list.
This chapter is the distribution behind the tiles in §11.3. Nothing here replaces MaR. The two numbers sit beside each other because they answer different questions.
22.1 What is being sampled
Not the market’s own clearing. Prices arrive as an assumption drawn
from the trailing quantile envelope the platform already publishes
(PriceForecast P10/P50/P90). Volume errors arrive from the
empirical ForecastErrorProfile cells. Three named shocks —
tariff hike, municipal surcharge, curtailment-day count — can be fixed
or sampled. Every random number is a pure hash of
(seed, path_index, dimension, coordinate). There is no wall
clock and no :rand. The same seed on the same book on the
same date replays byte-identically.
The nightly worker (WB.Strategy.Workers.RunStochastic)
runs at 02:45 UTC, after the 02:30 strategy pass (it
needs that pass’s inputs and RiskSnapshot) and before the
03:00 day-ahead bid build. A VaR published at 11:00 describes a position
the desk already committed at 10:00. N defaults to 500 and is
configurable per book (book.params["stochastic_n"]) so a
book that outgrows the window is throttled by a data change.
The fleet sampler uses the same deterministic draw machinery in a multi-site mode. Every member shares one market-price path, while site volume errors use separate site coordinates and the fleet’s effective correlation matrix. Shocks are shared because a tariff change or curtailment scenario is a portfolio event. The ordinary single-book sampler and its default volume dimension are unchanged; a fleet of one reproduces the corresponding dimensioned site sample exactly.
22.2 The two confidences and the two horizons
VaR is loss relative to expected:
VaR = mean(margins) − quantile(margins, 1 − c).
| Tile | Confidence | Horizon | What it is |
|---|---|---|---|
| VaR-95 1d | 95% | One day | How much expected margin you lose at the 5th percentile of sampled tomorrows |
| VaR-99 1d | 99% | One day | The far tail of the same day |
| VaR-95 1m | 95% | One month | A separately sampled month — not √T of the 1-day number |
| VaR-99 1m | 99% | One month | The far tail of that month |
Expected shortfall is the mean of the tail including
the VaR observation — "when it is worse than VaR, how much worse." The
1-day snapshot stores ES-95; the 1-month snapshot stores ES-99. Both
live on RiskDistributionSnapshot, beside
RiskSnapshot, not inside it. Writing the distribution
leaves the MaR row byte-identical.
22.3 The fan chart
#dashboard-var-fan draws the stored quantiles (P5
through P95) as a banded line on the existing chart family. It is the
picture of the distribution the tiles summarise. A missing or stale
distribution is labelled; the fan is not drawn from the last row the
page can find.
22.4 A breach alerts. It does not gate.
Q6 is open. The platform publishes both confidences at both horizons and enforces limit rows on VaR-95 / 1-day daily and VaR-99 / 1-month monthly — alert-only. No code path blocks a bid submission on a VaR (or counterparty) breach.
The reason is operational, not theoretical. A VaR breach at 09:00 with a day-ahead gate at 10:00 would stop the desk bidding at all. An unbid position falls into imbalance, which can cost more than the risk the gate was guarding. An unproven model does not get a veto over the bid desk.
gate_bidding? on the limit row defaults false and is
read nowhere. Answering Q6 later is a data change plus one call site,
with a failing test (no_bid_gate_test.exs) to prove it took
effect. The bid path keeps selecting :OPEN_POSITION_MW by
name.
22.5 Counterparty exposure
Where: Desk → Counterparty exposure on the
latest snapshot (#exposure-page, §8.10). The old
…/strategy/exposure route forwards there.
If this party stopped performing tomorrow, how much would it cost us? Open position grouped by counterparty, valued at P90 if we are short (replacement is expensive) and P10 if we are long (leftover energy is cheap). Site-only memberships that cannot be attributed are listed as Unattributed, not dropped.
This is the book’s view of bilateral counterparties. It is not market credit cover — that remains the Market Operator’s Required Credit Cover under Market Code §15.10(8).
Limit rows are effective-dated (effective_from required,
effective_to optional). The row in force on the snapshot
date is the newest whose window covers that date. A user of organisation
A reaching organisation B’s page gets a 404, not a
403.
Follow along. Seat:
trader_1@webill.net(passwordsupersecret). Path: T1 Renewables Pool → Strategy → Dashboard → read MaR and the VaR tiles → Desk → Counterparty exposure. A VaR or counterparty finding shows in the existing alert feed. It does not stop a bid.
