← Manual home

V21 · Operations & Administration

Eskom and Munix reference data, the background-process catalog, demo setup, live meters, and troubleshooting. Scenario Lab is V12.

1. The Eskom app

Your settlement can only be as current as the tariff and network reference data behind it. The Eskom seat is where you publish those inputs; it is not where you negotiate or settle a contract.

Follow along. Seat: eskom@webill.net (Eskom; password supersecret). Path: sign in → Tariffs. You should see: a tariff table with charge counts, Add New Tariff, and a per-row Permutate action.

Where this is taught. The meanings of tariff, rate, DLF, TLF, and UoS are owned by V2 §8. Their settlement application is owned by V8 §4.

1.1 Know the boundary before you edit

The landing route points at Virtual Contracts. /eskom/virtual-contracts is the VWA register — registered Virtual Wheeling accounts, enrolled meters, the month’s refund obligation, and 45-day aging. It is taught in V17 §9. /eskom/wheeling-contracts is still a placeholder. Create and sign a per-contract agreement in V7, then read its monetary result in V8.

Eskom reference data feeds a site tariff and then settlement; illustrative operational flow.
Eskom seatpublishes reference data
→maintains
Tariff + lossesrates, DLF/TLF, UoS
→prices
Settlement linecontract period result

1.2 Build a tariff in the order the screen expects

Add New Tariff creates a named shell. Open that tariff’s Charges screen to add a Consumption Charge or Service Charge. Each form asks for Name, Index, Frequency, Season, TOU, Denomination, and Status; complete the commercial sheet before activating it. Consumption indices are energy based, such as KWH or MWH. Service lines use their own index basis, such as POD or FLAT, and must not be substituted for an energy rate.

Screen Path You maintain Expected UI
Tariffs /eskom/tariffs tariff shells and permutations table, drill-down actions
Charges /eskom/tariffs/:id/charges consumption/service charge definitions two add actions and a charge list
Rates /eskom/tariffs/:id/rates Season · TOU · Description values inline value field and Save

Permutate creates missing HIGH/LOW × PEAK/STANDARD/OFF rate shells for eligible charges. It is deliberately disabled once every charge has already been permutated or activated: disabled means there is nothing left to generate, not that the page failed. A charge marked INACTIVE remains reference data but does not price settlement.

Careful. Permutate produces empty rate positions, not approved prices. Enter the published value in each inline row and press Save before marking a charge ACTIVE.

1.3 Worked example — roll the tariff forward safely

Data. full_demo · seed 42 · stage S02 (tariffs and losses) · illustrative 2026/27 rate sheet.

Input Value
New tariff Megaflex 2026/27
Consumption line Energy, KWH, monthly, HIGH/PEAK
Service line Network access, POD, monthly, FLAT
Energy rate 120.0c/kWh
Service rate R 450.00/POD
  1. On Tariffs, choose Add New Tariff and save Megaflex 2026/27.
  2. Open Charges and add the energy line. Set its denomination to cents, its index to KWH, its frequency to monthly, then leave it INACTIVE while you complete the rate grid.
  3. Add the service line using POD and FLAT. It remains a separate charge: R1: 16,142 kWh × 120.0c ÷ 100 = R 19,370.40 energy; R2: 1 POD × R 450.00 = R 450.00 service.
  4. Return to the tariff table and choose Permutate. Open Rates, enter each approved Season/TOU value, and use the row’s inline Save.
  5. Activate only the charge lines whose full grid is approved. Check the site tariff selection afterward; settlement reads published data, never invents a missing rate.

Rates sample view

Season 1 TOU 2 Description Value 3
HIGH PEAK Energy 120.0c · Save
  1. 1Season selects the published season.
  2. 2TOU identifies the interval band.
  3. 3Value is not live until the inline save succeeds.

What you see on screen is a saved rate row and an ACTIVE charge; this is the pattern to expect on your own page. The resulting settlement line belongs in V8 §7, not in this editor.

1.4 Maintain losses and UoS separately

Open Wheeling Charges at /eskom/loss-factors. The screen has distinct tables and distinct add actions: Add DLF, Add TLF, Add UoS, and Add Levy. Enter the field set shown by each form—DLF by voltage tier, TLF by zone and distance band, UoS by category and charge definition, non-bypassable levies by electrification-subsidy or regulatory-levy category and a c/kWh rate—rather than copying one factor into another table. The levy table’s Non-bypassable column is fixed Yes; it is not a checkbox.

Reference table Operational question it answers
DLF What distribution factor applies at this voltage tier?
TLF What transmission factor applies for this zone/distance band?
UoS What network-use charge applies to this category?
Non-bypassable levies What national levy (electrification subsidy / NERSA regulatory levy) applies on this date?

Follow along. Seat: eskom@webill.net (Eskom). Path: Wheeling Charges → choose Add DLF, Add TLF, Add UoS, or Add Levy. You should see: a form for that specific reference type and its row in the matching table after save.

Do not diagnose a final statement by changing historical reference rows. Trace the period and its status first: settlement moves DRAFT → PRELIMINARY → FINAL, and a FINAL result needs its explicit correction path.

Where this is taught. Statuses and controlled re-settlement are taught in V8 §11. The municipal mirror is §2.

1.5 Your publication check before settlement

Before a new billing period, inspect one active charge from the tariff list through its rate grid and one network row through the loss-factor screen. Confirm the denomination is compatible with the number you entered: cents per kWh and rand per POD are not interchangeable. Record the published source and effective operational decision outside this screen according to your governance process.

Check Screen evidence
Tariff name and charge visible in the tariff/charge table
Seasonal rate saved Season, TOU, Description, and value row
Charge availability Status is ACTIVE only when ready
Network reference DLF, TLF, UoS, or a non-bypassable levy exists in its own table

If the expected price on a settlement differs, trace the contract’s selected tariff, interval classification, and status before changing a rate. That sequence tells you whether the issue is configuration, a period correction, or a misunderstanding of the ledger.

1.6 The operator billing portal

/operator is a new persona — not /eskom and not /so. Path: landing page → Eskom door → sidebar Billing (a POST, not a bookmark). The issuer chip is Eskom’s legal name. Search the issued register by month, entity, and organization. Month run approves and re-issues. Received remittances are the supply-only BRP hat.

B13. Doors are the interim identity story. Production Eskom users will need accounts; this path is what ships until that decision.

B14. Leaving transition (issuing_from) is an MO/ops act, not a switch on Settings. Until that month, off-takers see the amount in V15 §13 and still receive the bill from the utility. The first issued month is the month on or after issuing_from.

Fuel prices

/eskom/fuel-prices shows effective-dated fuel prices in R/GJ, their basis and source link. Missing fuels remain missing; references older than 120 days are marked stale. To record a new price, close the old series window and create a new sourced row. Overlapping windows in the same fuel/source series are refused. The seeded April 2026 gas price remains secondary-sourced pending the filed NERSA note. The diesel derivation uses a rounded R/GJ price, so its reconstructed R/MWh differs slightly from the original expenditure quotient.

For each quarterly gas update, obtain the filed NERSA/Sasol price notice, verify its effective date and units, close the prior same-source window, and enter the new R/GJ row with the document reference. Do not silently replace the April 2026 secondary source with an assumed official value. For the annual diesel reference, retain the Eskom expenditure and generation figures and the heat-rate basis used to derive R/GJ; document the rounding. Reopen a thermal plant’s dispatch card to check the newly pinned row. The register page is read-only; changes use the reference-data actions.

2. The Munix app

Your sites can appear eligible on a map yet still lack a municipal tariff or charge configuration. Munix is where the municipality publishes its participation state and its local reference data; your session is scoped to that municipality from the moment you sign in.

Follow along. Seat: <normalised municipality name>@webill.net (Munix; password supersecret). Path: sign in → Configuration. You should see: the municipal status chip, boundary/map information, participation facts, and wheeling charge controls when eligible.

Where this is taught. The map, site eligibility, and municipality object are taught in V2 §9. This chapter teaches the municipality’s publishing screens.

2.1 Set the operational status first

Configuration is /munix/configuration; /munix redirects there. Choose the chip that accurately describes current participation: Wheeling, Pilot, Partial, or None. This is a published operational fact, not a draft workflow. Org-facing maps and eligibility banners reflect it when those pages next load; there is no separate publish button.

Status chip What you should communicate operationally
Wheeling municipal wheeling configuration may be maintained
Pilot limited rollout; confirm eligibility in the owning V1 flow
Partial only some capability/coverage is represented
None no wheeling participation is published

The Embedded wheeling tariff and Monthly Admin Fee controls appear only while the status is Wheeling. Enter the values that the municipality has approved, then choose Save. Changing a map boundary or seeing a map does not create a fee; the charge configuration is its own state.

Municipal configuration reading path — status exposes wheeling-charge configuration only for Wheeling.
NONE / PILOT / PARTIAL
→set published status
WHEELING
→configure
Embedded tariff + monthly fee

2.2 Worked example — turn a participating municipality into usable data

Data. full_demo · seed 42 · stage S02 (tariffs and municipal reference data) · illustrative municipal configuration.

Input Value
Published status Wheeling
Embedded wheeling tariff 15.0c/kWh
Monthly Admin Fee R 850.00
Delivered interval 16,142 kWh
  1. Open Configuration and verify the current chip before changing it. Select Wheeling only when the municipality is genuinely available for wheeling.
  2. The wheeling-charge area now appears. Enter 15.0c/kWh for Embedded wheeling tariff and R 850.00 for Monthly Admin Fee, then press Save.
  3. The illustrative monthly network amount is R1: 16,142 kWh × 15.0c ÷ 100 = R 2,421.30; R2: R 2,421.30 + R 850.00 = R 3,271.30.
  4. Re-open Configuration and confirm the saved values. Then open the V1 map in a separate session: the status is what map and eligibility surfaces consume, while the arithmetic itself is applied by settlement.

What you see on screen is the Wheeling chip plus populated, saved charge fields. This is the pattern to expect on your own page; do not expect an issued statement here.

Where this is taught. The monetary line and its ledger destination are taught in V8 §7 and V8 §8.

2.3 Maintain municipal tariffs without confusing ownership

At /munix/tariffs, you follow the same data sequence as Eskom: add a tariff, add charge definitions, Permutate missing seasonal/TOU positions, fill inline rates, save, then activate ready charges. A municipal tariff is available only to sites in that municipality. It does not replace national loss-factor maintenance, which remains the Eskom seat’s job.

Route Shared implementation Meaning for you
/munix/tariffs/:id/charges Eskom.ChargesLive with :munix municipal charge editor
/munix/tariffs/:id/rates Eskom.RatesLive with :munix municipal rate editor

Follow along. Seat: municipality email (Munix). Path: Tariffs → choose a tariff → Charges → Rates. You should see: the same charge/rate editing pattern as Eskom, inside the Munix shell and scoped to your municipality.

Use Permutate before you fill the grid. It is disabled when all eligible charges have already been permutated or activated, which is an expected completed state. A saved INACTIVE charge remains unavailable to pricing until you activate it.

2.4 Know what is deliberately absent

/munix/wheeling-contracts and /munix/wheeling-charges are placeholders. They are not alternate contract or charge registers. The former does not list agreements; the latter is not the Eskom DLF/TLF/UoS editor.

Careful. “Wheeling” status and the map colour answer whether the municipality is published as participating. They do not prove that a particular site has a valid tariff, a contract, readings, or a FINAL settlement.

Where this is taught. National tariff and loss-factor reference data is in §1; agreements are in V7; settlement proceeds DRAFT → PRELIMINARY → FINAL in V8 §11.

2.5 Your hand-off check

Before telling an organisation that municipal wheeling is ready, make the separate facts visible. Configuration must show the intended status; a Wheeling municipality must have saved charge values where the tariff is required; and the organisation must still complete its site and agreement work. These facts are related, but none is a substitute for another.

Question Seat that answers it
Is the municipality participating? Munix Configuration
What local tariff and fee apply? Munix Tariffs / charge configuration
Is this site eligible and mapped? organisation screens in V1
Is there an active agreement? deal room in V3
What was actually priced? settlement in V4

Careful. Do not use a placeholder route as evidence that an agreement or a charge has been registered. The current placeholders are intentionally honest about the feature boundary.

2.4 Municipal billing

Sidebar Billing POSTs to /operator/session with this municipality already in the session. The issuer chip is that municipality’s legal name, never Eskom’s. The rest of the walk — register, month run, received remittances, settings — is V15 §13. B13/B14 apply here as on the Eskom door: doors are interim; transition flip stays with MO/ops.

3. Background-process catalog

Every scheduled job, enqueue-driven worker, AshOban trigger, long-running sim process and operator mix task — what it does, when it runs, what you see, and what to do if it fails. Feature depth lives in the linked volume; this chapter is the operations index.

How to learn the clock. Narrative, step-by-step process and worked examples for the four rhythms: V1 · The Four Rhythms. Orientation preview: V0 §6. This catalog keeps failure modes, queues and retry behaviour.

Counts at the pinned config/config.exs. Oban cron entries and distinct workers: see COVERAGE.md Matrix 3. The complete run list is V0 §6; this chapter is the operations index (failure modes, queues, retries). Reconcile against COVERAGE when cron changes.

Schedules below are SAST as configured. Gate times for market workers still come from MarketRules (V9 §1).

3.1 The 24-hour dial

Daily / frequent jobs (SAST) — settlement gold · strategy purple · market blue · forecast teal · lifecycle red
00061218
  • 00:05 Archive SO instructions
  • 01:00 Daily settlement runner
  • 02:30 RunNightly (books + BESS inputs)
  • 02:00 Weather cycle · 06/09/12/15 nowcast drift
  • 03:00 Submit DAM bids · simulated-market DAM
  • 06:30 Compute imbalance (D+1)
  • 07:20 / 07:50 CAD Trust observer / publisher
  • 12:00 Fetch DAM schedule
  • 12:30 Refresh SMP price forecast
  • */15 Poll SO · 04 / 10 / 16 / 22 IDM monitor
  • hourly Expire stale proposals (AshOban)

3.2 The month strip

Day-of-month jobs (SAST)

3.3 Cron cards

FetchRates — reference FX publications

The market_data queue runs at 10:45 and 14:00 SAST on weekdays, with a fallback pass at 17:30 SAST. An existing primary publication makes the live fetch a no-op. A fallback pass uses the source policy only when the primary set for that date is absent. Calendar absence and provider refusals end the job; transient failures have at most three attempts with 30-second and two-minute waits before the remaining attempts.

Mocks remain the configured default. Live fetching requires explicit provider configuration and an effective WB.Fx.SourcePolicy row. Every publication retains publisher, channel, publication time and retrieval time. A Friday response to a Sunday request is refused. mix wb.fx.backfill --from YYYY-MM-DD --to YYYY-MM-DD requests SARB history through Frankfurter in chunks of at most 90 days; it refuses while the mock is configured.

Live valuation is not armed by this release. The desk must resolve PD-F8-1 before go-live: a trading-day SARB rate is published after the SAPP day-ahead gate. The existing conversion path keeps its exact-date rule; later P13M0 policy integration must be verified before using the new policy for live valuations.

Each card: what / when / what you see / if it fails / taught in. The complete 65-line crontab (every configured run) is V0 §6; counts in COVERAGE.md Matrix 3.

SettlementRunner — DAILY · WEEKLY · MONTHLY · QUARTERLY

When 0 1 * * * · 0 2 * * 1 · 0 3 1 * * · 0 3 1 */3 *
What Fans out period settlement for the given frequency
You see New/updated settlement periods and pipeline jobs; statements after finalisation
If it fails Oban settlement queue retries; check period status stays DRAFT/PRELIMINARY; re-run from Contract Settlement when meters are ready
Taught in V8 §2 · V8 §11

ChainReconciliationRunner

When 0 4 1 * * (1st, after monthly settle)
What Cross-PPA chain check (volumes / trader margin)
You see Chain recon results on trader/settlement views
If it fails Queue retry; investigate broken chain links before forcing a re-settle
Taught in V8 §10

LevyConservationRunner

When 10 4 1 * * (1st, after chain reconciliation)
What Proves every in-force non-bypassable levy produced a settlement line on the prior month
You see A failed Oban job when a period is missing a mandatory levy; billing preflight refuses the same month
If it fails The job raises (unlike chain reconciliation, which only logs). Inspect the named period and category, then re-settle or add the missing NetworkRate
Taught in V8 §4.2.1 · V8 §10

RunBackcast

When 0 4 4 * * (4th)
What Settled strategy actuals — backcast prior month after recon
You see Updated strategy snapshots / backcast runs on books
If it fails strategy queue; confirm prior month settlement closed
Taught in V3 (settled actuals / scoring)

RunNightly

When 30 2 * * *
What Refresh every book’s strategy forecast; resolve BRP BESS market inputs first
You see Fresh FORECAST runs, MARKET panel inputs ready for DAM
If it fails Book stuck on stale run — check brp/strategy queues
Taught in V3 · V9 §1 · V9 §12

ProposeNominations

When 0 5 20 * * (20th)
What Propose next month’s nominations for nominated memberships
You see Nomination plans move toward PROPOSED on Nominations screens
If it fails Manual propose from the book; check membership flags
Taught in V3 §9

SubmitDamBids

When 0 3 * * *, while the actual D−1 gate is derived from MarketRules
What Builds and submits day-ahead bids for tomorrow’s delivery
You see Bid Desk rows advance; a gate-miss is visible when the run is too late
If it fails Check the brp queue and MarketRules before retrying; do not submit a stale market day blindly
Taught in V9 §4

FetchSchedule

When 0 12 * * *
What Pulls the accepted DAM schedule after clearing
You see Schedule values attached to the cleared bid/day
If it fails Confirm the bid reached its gate and inspect the brp queue; an empty schedule is evidence, not a zero schedule
Taught in V9 §4

ComputeImbalance

When 30 6 * * * for D+1
What Prices yesterday’s actual-versus-scheduled deviations
You see Imbalance hours and their calculated exposure
If it fails Verify actual readings and the accepted schedule first, then retry on the brp queue
Taught in V9 §6

IdmMonitor

When 0 4,10,16,22 * * * (the five §10(1) Code gates)
What Watches delivery-day drift and prepares intraday response
You see IDM monitoring state and re-bid candidates
If it fails Inspect the brp queue and current market clock; do not assume a missed monitor is a DAM failure
Taught in V9 §5

PollSo

When */15 * * * *
What Ingests System Operator dispatch, curtailment, and outage instructions
You see Active SO instruction cards and affected delivery windows
If it fails Inspect the feed and brp queue; preserve the source evidence before a re-bid
Taught in V12 §3.2

ArchiveSoInstructions

When 5 0 * * *
What Archives completed SO windows while retaining searchable history
You see Active inboxes lose completed windows; date search still finds them
If it fails The inbox may be noisy but dispatch history is not deleted; correct the job on brp and retry
Taught in V12 §3.2

RefreshPriceForecast

When 30 12 * * *
What Refreshes the trailing-quantile SMP forecast after DAM information
You see A later forecast version feeding the market/strategy view
If it fails Check input freshness and the brp queue before treating an old forecast as a market price
Taught in V9 §9

ReconcileMonth

When 0 7 3 * *
What Compares the internal expectation with the MO settlement file
You see A reconciliation worklist and variances
If it fails Confirm the MO file arrived, then investigate variance inputs rather than overwriting them
Taught in V9 §8

MonthlyForecastRunner

When 0 4 5 * * (5th)
What Fan-out one ForecastRunner per active IPP for the target month
You see New forecast versions on IPP Forecast screens
If it fails forecast queue; confirm IPP has an ACTIVE physical PPA
Taught in V4 (monthly forecast)

FetchWeatherCycle · FetchWeatherDrift

When 0 2 * * * (cycle) · 0 6,9,12,15 * * * (drift)
What One weather pull per generator site (72 h horizon, jittered) an hour before DAM; four later nowcast issues on the same :forecast queue
You see Forecast / committed / nowcast bands on the IPP strategy dashboard; Bid Desk provenance names provider and model-run
If it fails Site shows stale or no forecast — synthetic shape, never a silent zero night; check the forecast queue and the mock adapter
Taught in V0 §11 · V3

CertificateRunner · Cadt.SyncRunner · Cadt.PublishRunner

When 45 4 1 * * (accrual) · 20 7 * * * (observer) · 50 7 * * * (publisher)
What Accrue RECs from FINAL settlement; CAD Trust observer sync; testnet publisher until the mandate
You see New certificate batches on the Desk; observer rows on /operator/cadt
If it fails Accrual cannot be invoiced at 05:00 the same morning — a human must confirm first; CADT jobs move no money
Taught in V19 · §9

Billing and insight (1st of month)

When 0 2 aggregates · 30 4 statements · 0 5 issuer · 30 5 org billing · 0 6 facts · 30 6 planner (all on the 1st)
What Settlement documents, invoices, organisation billing, insight facts, opted-in planner
You see New statements then invoices; insight facts only after invoices exist
If it fails Do not re-run the issuer before statements exist; facts before invoices are empty by design
Taught in V15 · V16 · V8

DamCadence · IdmCadence · DeliveryCadence · SettlementCadence · TrustInterest

When MO operating cadence in config/config.exs (gate close 08:00, unconstrained, constrained, prices, publish; five IDM auctions; metering 00:15/00:30; balancing 15:00–15:30 D+1…D+7; weekday statements/cover; 1 Sep calendar; 02:00 reassertion; 22:15 trust interest)
What The Market Operator’s own clock — not the BRP’s bid workers
You see Cadence ledger at /mo/cadence; schedules and prices after publish
If it fails Inspect /mo/cadence before re-running a step; publishing is a separate human action on Clear Day
Taught in V11 · V0 §6

DisputeClock · ApplicationClock · MecBreachScan · RunStochastic · RunFleet

When 15 5 * * * · 30 5 * * * · 5 0 * * * · 45 2 * * * · 15 3 * * *
What Bilateral dispute reminder then lapse; BRP application clock; prior-day generator MEC scan; site stochastic VaR after RunNightly; fleet aggregation after site results
You see Dispute / application board already correct when the desk opens; site VaR before 03:00 DAM; the four-chapter fleet story and correlated VaR on IPP Sites → Fleet
If it fails Check settlement / brp / strategy queues; a missed VaR is not a reason to skip the bid
Taught in V8 · V9 · V3 §22 · V4 §2

3.4 Enqueue-driven workers and setup helper

These five are Oban workers but do not have an independent cron line. Their “when” is the event that enqueues them. BackfillMeterHistory is included because operators encounter it during S05, but it is a setup helper—not an Oban.Worker.

PipelineRunner

When SettlementRunner fans it out for an allocation set
What Resolves, matches, prices, adjusts, and writes one settlement pipeline
You see Period progress and updated DRAFT/PRELIMINARY result data
If it fails Inspect meter completeness and settlement; rerun the controlled settlement path, never edit a FINAL period
Taught in V8 §2 · V8 §11

StatementRunner

When Settlement finalisation enqueues statement production
What Produces party-facing statements from the final settlement result
You see Statement records/versioned views after the period is FINAL
If it fails Verify finalisation and the settlement queue; a missing statement is not evidence that money was not settled
Taught in V8 §9

ForecastRunner

When MonthlyForecastRunner fans out, or an operator explicitly enqueues it
What Creates one IPP monthly forecast version
You see A fresh forecast on the IPP forecast screen
If it fails Check forecast, active physical PPA eligibility, and source period before retrying
Taught in V4 §8

RunAllocation

When Manual or legacy strategy shell requests it; nightly work normally uses RunNightly
What Executes the requested allocation path for a strategy run
You see A strategy run record and its allocation result
If it fails Check the strategy queue and inputs; avoid attributing it to the S15 meter loop
Taught in V3 §5

IngestReadingWorker

When Each S15 simulator tick enqueues it
What Persists the reading produced by the live-meter simulator
You see New interval/device readings after the tick
If it fails Check default/the worker’s queue and stop live before mutating setup; do not use S05 history as a repair
Taught in §6 · V2 §7

BackfillMeterHistory

When S05 during mix wb.setup
What Builds deterministic historical meter rows and deliberate missing-read fixtures
You see Roughly 12 months of historical intervals, including gallery gaps
If it fails Resume the same setup manifest and inspect setup; it is not a cron retry and not an Oban worker
Taught in §4 · V2 §7

3.5 AshOban triggers (4)

Trigger Cadence What Taught in
PhysicalPpa :activate daily 0 0 * * * SIGNED → ACTIVE when start_date ≤ today V7
PhysicalPpa :suspend_unsigned daily 0 1 * * * AGREED past 10-day sign window → SUSPENDED V7 §5.1
VirtualPpa :suspend_unsigned daily 0 1 * * * Same for virtual AGREED deals V7 §5.1
Proposal :expire_stale hourly PENDING + past expires_at → EXPIRED V7 · §7

3.6 Long-running sim surfaces (3)

Process Kind Cadence Taught in
WB.Sim.LiveMeter.Simulator GenServer (started by mix wb.setup --profile live, not in app children) Virtual +20 min / tick; wall default 20 min §6
WB.Sim.Driver Callable module (Task under Sim.TaskSupervisor from Lab) Day-by-day sim-clock month in seconds §5
WB.Sim.Market.DamSubmitter GenServer (app child when :simulated_market_submitter is true — dev only) Boot tick, then daily 03:00 SAST; no-op if the S16 overlay is absent §4.7

Setup and test pin two further transports so those runs never reach a network: :sapp_client → WB.Sim.SappSim and :fx_provider → WB.Fx.Provider.Mock. A missing published rate blocks rather than degrades — an operator seeing blocked Regional-desk actions should look at the FX register first. Live fetching requires explicit configuration and source policy; live valuation remains gated on PD-F8-1 and the later conversion integration. Taught in V18.

3.7 Mix tasks (5)

Task What Taught in
mix wb.setup Consolidated demo/acceptance control plane (--include-market appends S16) §4
mix setup.market Foundation plus the fifty-participant SAWEM overlay (profile market) §4.7
mix setup.market_month A whole Billing Period traded through that overlay (S17); --rebuild resets first §4.8
mix wb.forecast_backfill Historical IPP monthly forecasts --from/--to V4
mix wb.replay_hierarchy_diff Re-settle one IPP month through hierarchy; print matched/surplus report V4 · V8 §5

Queues to watch: default, proposals, setup, contracts, settlement, strategy, brp, forecast. FAQ for stuck jobs: §7.

3.9 The Market Operator’s Clear Day panel

Clearing a Trading Day is not a background job. It is four steps on the MO console, in order, with a human between the third and the fourth.

Step What it does
1. Run unconstrained Solves the market’s own answer: cheapest way to meet the submitted demand, ignoring the network. This is the run that sets the price.
2. Apply SO constraints Attaches the System Operator’s security limits — corridor capacities, zonal export caps.
3. Run constrained Solves again with those limits. This is the run the system is dispatched from.
4. Publish Makes both schedules and all five prices the thing every participant settles against.

Publishing is separate from solving, and that is the point of the panel. The obligation is to publish by 14:00 on the day before delivery. An operator who has just re-run a day needs to look at it before it becomes everybody’s settlement basis — so Publish is its own button, behind its own confirmation, and a solved run sits in SOLVED until somebody presses it.

Solving again does not edit a published run. It creates a new one, and publishing that one supersedes the old one, which stays readable. That is what makes “why did my statement change” answerable months later.

Three things on the panel are worth reading rather than skimming:

Requires the HiGHS solver on the host (brew install highs, or your platform’s package). Unit commitment with minimum run and down times cannot be solved correctly by any greedy method, so an absent binary is a deployment fault rather than a reason to fall back — the panel says so instead of returning a schedule that quietly ignores commitment.

The public price board at /prices needs no login at all: the market price set is published information, and a register only participants can read is not published.

Hydrology fetch runs Tuesdays at 01:30 on the forecast queue. The generation bridge runs monthly at 04:50 on settlement, after certificate accrual, proposing fuel activity from FINAL generation. Neither job submits a tax declaration or arms a live provider.

Trading-control workers

Worker Schedule (SAST) Evidence
WB.Brp.Workers.ProposeEscalation 09:30 daily; checks the effective gate-minus-30-minute window One persisted escalation per registration and trading date; telemetry, critical log and refresh notification; never submits
WB.Org.Workers.ExpireApprovals Every 15 minutes Pending requests past their expiry become EXPIRED; retries are idempotent
WB.Brp.Workers.SelfAssessmentSnapshot 1 January at 06:00 Previous calendar year’s organisation-scoped pack, with a deterministic content hash

Forecast evidence workers

The scheduler uses UTC. ScoreSkill runs Monday 04:30 UTC (06:30 SAST), creating one job per weather site and prior Monday week. Re-running identical inputs reuses the evidence version. BuildSoPack runs daily 07:30 UTC (09:30 SAST) for the next seven delivery days; BuildSoRolling runs hourly at :40 for six hours from the next full hour. Both retain category and data refusals as records. These workers build evidence and never push it to a live operator. The pack transport resolves to Mock unless explicitly configured otherwise.

Data fabric workers

FreshnessSweep runs every fifteen minutes; AttemptRetention at 01:10 UTC preserves ninety days and the latest attempt. DeadLetterSweep runs at 00:20 UTC. DeliverWebhook is event-driven on the events queue (concurrency five) with eight attempts per schedule. These jobs do not alter the monthly settlement-chain order.

DispatchPending runs each minute to recover committed outbox rows when an enclosing transaction did not forward its Ash notifications. A rolled-back source transaction leaves no delivery.

Plant telemetry workers

UTC schedule / trigger Worker Operational result
Every five minutes Telemetry.Workers.IngestMirrorDrop Process the configured plant-owned historian inbox; archive each file and its result
Minutes 1, 6, …, 56 Telemetry.Workers.FreshnessSweep Record stale/recovered transitions and update the feed registry
Minutes 2, 17, 32, 47 Telemetry.Workers.IntradayMecScan Provisional warnings from good device power; no billing
Instruction end + 15 minutes / curtailment-off event Telemetry.Workers.BuildCurtailmentTrace Freeze samples and method inputs; retry missing meter coverage hourly for up to 48 hours

These four workers share telemetry concurrency five. Vendor pull workers are not installed: P11M2 remains trigger-gated. Compression applies to raw telemetry older than 30 days on a capable Timescale build; raw retention is 400 days. The hourly aggregate is retained. Monitor the feed registry and Oban for refusals and retries.

4. Demo & acceptance data

How the seeded world is built. This chapter states the procedures, profiles and safety rails in plain language. Copy-paste commands live on the control-plane tasks (mix wb.setup, mix setup.demo, mix setup.market).

4.1 Safety rails

Before any mutating setup command:

  1. Stop Phoenix, Oban peers, IEx, Scenario Lab drivers and the live-meter runtime. Call WB.Setup.stop_live() if a live tick loop is still up.
  2. Use a local, disposable database only. The control plane refuses MIX_ENV=prod.
  3. Every data profile requires --reset — an explicit acknowledgement that teardown will destroy application data in scope.
  4. Prefer an explicit --as-of YYYY-MM-DD and --seed (default 42) so demos reproduce. Budget ~2 GB free and ten+ uninterrupted minutes for a full build.

4.2 Profiles

Profile Stages Duration
foundation S00–S03 < 1 min
full_demo S00–S14 + S18 3–6 min; ~209 s verified
acceptance S00–S14 environment-dependent
market S00–S03 + S16 ~1–2 min
market_month S00–S03 + S16 + S17 ~10–25 min
lab scoped S12 2–4 min
stress scoped S03 environment-dependent
live S15 (guarded) continuous

Pass --include-market on foundation, full_demo or acceptance to append S16 after that profile’s last stage. The dedicated market profile already includes S16. See §4.7.

S18 · Regional desk is part of full_demo: a sponsored SAPP participation profile, a small FPM + DAM book, a cross-border path, and a shadowed month with a classified variance, all against SappSim and the fixture rate sets. The sponsor is labelled illustrative. Setup pins :sapp_client and :fx_provider to in-process mocks — those runs never reach a network. The trader-seat volume is V18.

market_month is the longest deterministic profile there is: it runs the fifty participants through every Trading Day of a month — day-ahead clearing, prices, publication, metering, allocation, balancing, statements — and then bills the month. See §4.8.

4.3 Stage graph S00–S17

Setup stages — S00 teardown/lock through S14 seal; S15 live is not a deterministic rebuild; S16–S17 are the simulated-market overlay and its traded month. Read left → right, then down.
S00Preflight
S01Orgs
S02Tariffs
S03Sites
S04Deals
S05Meters
S06–07Strategy
S08Market
S09–11Settle
S12–13Lab / UI
S14Seal
S15Live
S16SIM market
S17Trading month
Stage Seals
S00 Preflight, lock, teardown, zero residue, manifest. Also clears local written-agreement PDFs under priv/agreements/generated/ (dev/test); production S3 objects are out of teardown scope
S01 Localities, orgs, users, Eskom/MO/SO seats
S02 Tariffs, losses, UoS, municipal wheeling ref
S03 Sites, pools, devices, accreditation variants
S04 Proposal / contract lifecycle and executed written PPA PDFs for every dual-signed physical contract (GenerateAgreement → ExecutedAgreement on local disk in Dev/Test)
S05 ~12-mo meter history + missing-read gallery
S06–S07 Strategy books, runs, noms, BESS, sims
S08 BRP registrations, DAM/IDM, SO, clock replay
S09–S11 Settlement matrix (re-seals agreement PDFs for any new dual-signed physical PPAs), statements, chain recon / disputes
S12–S13 Scenario Lab scripts; UI status gallery
S14 Exhaustive verify → COMPLETED
S15 Live ticks (needs matching S14 as_of/seed)
S16 Fifty simulated SAWEM participants (15 IPP / 15 off-taker / 20 trader), MO+SO registration, initial DAM. Overlay — not part of default full_demo
S17 A whole Billing Period traded through the S16 overlay: every Trading Day cleared, priced, published, metered, allocated, balanced twice and statemented, then the month billed and paid (§4.8)

Written agreement PDFs in setup

Dual-signed physical PPAs (SIGNED, ACTIVE, TERMINATED in the gallery) get a long-form PDF during setup — the same pipeline as live signing (V5 §6.3).

Step What happens
S00 teardown Truncates executed_agreements with the rest of application data and rm -rfs priv/agreements/generated/ so orphan PDFs cannot survive a rebuild
S04 After the proposal/contract gallery is built, setup drains the agreements Oban queue and asserts every dual-signed physical PPA has a GENERATED PDF (local file://… URI in Dev/Test)
S09 Settlement’s contract matrix may create more dual-signed physicals; the same seal runs again so those PDFs exist before settlement stories
Rebuild Procedure B (mix wb.setup --profile full_demo --reset …) therefore always recreates the PDF set from the signed snapshots

Download path after setup: Contracts → open a SIGNED/ACTIVE physical → executed-agreement download (/org/agreements/:id). Thread example: PPPA-202607-138209764 (IPP2 Atlantis Wind → OffTaker C) once that gallery ref is present.

4.4 The procedures

A — recreate the database, then set up once. Use this only when you intend to discard migrations’ application data and setup audit history. Do not use mix ecto.reset followed by full_demo: seeds.exs competes with the setup control plane.

mix ecto.drop
mix ecto.create
mix ecto.migrate
mix wb.setup --profile full_demo --reset --seed 42

B — teardown and rebuild while retaining the database shell. This keeps extensions, migrations, and setup_audit, then reconstructs the deterministic world.

mix wb.setup --profile full_demo --reset --seed 42

C — tear down once, then advance deliberately. Copy the manifest ID that S00 prints and resume that exact manifest at each next stage; this lets you inspect the boundary where a pack changes the data.

mix wb.setup --profile full_demo --reset --through S00 --seed 42
mix wb.setup --resume MANIFEST_ID --through S01
mix wb.setup --resume MANIFEST_ID --through S14

D — roll an already-sealed demo forward to today. When a prior full_demo was sealed on an older business clock (for example as_of 2026-07-18) and you want Strategy Overview, Scenario Lab and market artifacts to sit on today’s UTC date without inventing a new seed, use:

mix wb.setup.roll_forward
# equivalent:
mix setup.roll_forward

Options: --as-of YYYY-MM-DD (default: today UTC), --seed N (default: inherit the prior completed run’s seed, else 42), --profile full_demo|acceptance|lab, --dry-run, --force (rebuild even when as_of is already current). This is still destructive — it stops the live-meter runtime and runs the same reset-first rebuild as Procedure B with the resolved clock/seed. Stop Phoenix, Oban peers and Scenario Lab drivers first. The Demo Dashboard’s Update demo to today runs this same procedure in the background (§8.2).

Expected success: manifest status: "COMPLETED", current_stage: "S14", teardown_complete: true, no failure.

4.5 Credentials pointer

Every demo user password is supersecret. Seats include eskom@webill.net, mo@webill.net, so@webill.net, org seats *_@webill.net, municipality = normalised name + @webill.net.

4.6 Recovery playbook

If a setup stops, leave the failed world intact long enough to identify the manifest and failed stage. Read recent setup_audit records, then correlate the manifest with jobs on the setup queue. Resume the same manifest ID after correcting the cause; starting another destructive profile over a half-built world destroys the useful evidence and can leave mixed fixtures.

Symptom Safe response Do not do
A stage reports failure inspect stage output, fix the pack/input, resume its manifest start a second --reset
Setup queue is delayed inspect oban_jobs and its error/retry state hand-mark the stage complete
Credentials do not work correct S01 and rebuild patch password hashes
Live process is running call WB.Setup.stop_live() before a mutating run overlap S15 and reset
Full demo is too broad rebuild with foundation or lab for the stated goal delete selected fixture rows

Careful. --reset is an acknowledgement that the profile tears down data in scope. The guard against MIX_ENV=prod, the live-overlap refusal, explicit seed/as-of values, and the manifest audit are safety rails—not inconveniences to bypass.

Lab profile for Scenario Lab drills: §5. Live profile for S15: §6. Simulated market overlay (S16): §4.7. A traded month (S17): §4.8.

4.7 Simulated SAWEM market

The ordinary demo cast already has a handful of BRPs (S03 parties, S08 BRP-T1-DEMO and the status gallery). The simulated market is an optional overlay of fifty extra participants that sit beside that cast, register with both the Market Operator and the System Operator, and submit a day-ahead series every day.

It is not part of default full_demo. S14 still seals the ordinary world first; S16 appends afterwards so existing counts stay valid.

Who is in the overlay

Kind Count Technologies / categories Org / BRP prefix
IPP 15 Solar (4), Wind (4), Hydro (3), Coal (2), Gas (2) — 1 300 MW of export capacity SIM IPP … / BRP-SIM-IPP-…
Large off-taker 15 Mines (5), factories (5), malls (5) — 497 MW average draw, 90 MW down to 8 MW SIM Mine/Factory/Mall … / BRP-SIM-OT-…
Trader 20 One book per participant, zones Z1–Z9 cycling SIM Trader … / BRP-SIM-TRD-…

The off-takers carry industrial-scale load on purpose. A cast averaging a couple of megawatts each against 1 300 MW of generation would clear at the price floor in every hour of every month and settle to almost nothing; at 497 MW it peaks around 620 MW against roughly 840 MW of available supply on a winter evening, so the market has a marginal unit in it and the System Marginal Price moves.

Every organisation name starts with SIM, every BRP code with BRP-SIM-, and every meter is tagged SIM-MARKET. Sign-in emails are sim_ipp_solar_01@webill.net, sim_ot_mine_01@webill.net, sim_trader_01@webill.net, and so on. The password is still supersecret. Overlay generator meters (including coal MTR-NERSA-SIM-GEN-12 / 13 and gas 14 / 15) have no wheeling interval history until you Generate Reads on the site — that button uses the same technology shapes as the rest of the platform (coal baseload, gas morning and evening peak). SAWEM market intervals from S17 are a different table and do not fill that explorer.

Each participant is:

  1. an organisation + user (S01-style credentials);
  2. a site or trader pool (generator / off-taker / pool);
  3. an ACTIVE BRP registration with the Market Operator (GENERATOR / CONSUMER / TRADER, Annexure 1 criterion, MPID assigned);
  4. a Trading Unit (and Point of Delivery where the site’s GPS falls inside the provisional network model) with the System Operator via the same onboarding path the wizards use. A trader registers a Trading Unit with no Point of Delivery of its own: §12.3(1) makes it the counterparty at the IPP’s and the off-taker’s meters, and inventing a meter for it would double-count energy that already has two owners;
  5. a 168-period §9.3 DAM series for tomorrow, shaped by technology or load (solar is daylight-weighted; coal is baseload; malls peak in business hours).

How to seed it

Dedicated rebuild (foundation + overlay, reset-first) — same shape as mix setup.demo:

mix setup.market
# equivalent:
mix wb.setup --profile market --reset --seed 42

Add the overlay to an existing profile without changing that profile’s own stages:

mix setup --include-market
mix setup.demo --include-market
mix wb.setup --profile full_demo --reset --include-market --seed 42

mix setup --include-market sets WB_SETUP_INCLUDE_MARKET=1 so priv/repo/seeds.exs appends S16 after foundation.

From the Demo Dashboard (/demo, §8): the Simulated SAWEM market card shows whether the overlay is present. Include simulated market seeds S16 in the background without teardown — the ordinary demo world stays. Use this when foundation or full_demo is already sealed and you only want the fifty participants.

WB.Setup.seed_simulated_market/1 is the same overlay write the button calls.

Daily DAM submissions

WB.Sim.Market.DamSubmitter is a GenServer started with the application when :simulated_market_submitter is true (dev only; off in test and prod). On boot it submits tomorrow’s series if the overlay is present and any unit is still missing one, then sleeps until the next 03:00 SAST — the same window as WB.Brp.Workers.SubmitDamBids. A missing overlay is a no-op: foundation-only boots stay quiet until mix setup.market or Include simulated market lands the cast.

Capacity follows a technology (or load) shape so the fifty participants do not all declare a flat MCR. A unit that already has an operative series for the Trading Day is skipped.

The Demo Dashboard’s market card reports participant counts, the next Trading Day, whether that day’s DAM is in, and whether the submitter is alive — and its daily checks can backfill Trading Days the 03:00 window slept through (§8.4).

S16 itself posts no meter readings — it declares tomorrow, it does not measure yesterday. Metering arrives with S17, which submits 20-minute coverage as a simulated Network Operator for every metered BRP-SIM-… Point of Delivery; the design it follows is docs/references/market/spec/no-meter-readings.md. Do not point the wheeling live-meter runtime at these participants — that runtime writes ts_readings for active PPA devices and is a parallel world.

S16 also seeds two months of synthetic market invoices so /org/market-accounts is not empty before anything has traded. Where S17 then trades one of those months for real, it cancels the placeholders and raises its own documents — see §4.8.

Data. market · seed 42 · stage S16 · fifty BRP-SIM-… parties beside the ordinary S03/S08 cast. Daily writer: §3.6.

4.8 A month of market trading

S16 gives you a market that can be looked at: fifty registered participants and tomorrow’s day-ahead series. S17 gives you a market that has run — every Trading Day of a chosen Billing Period taken through the Market Operator’s own chain, and the month closed with statements, invoices, payments and a billing board.

Everything on this page is written by WB.Sawem.Cycles — the same functions the scheduled jobs call, given an explicit clock. There is no second implementation of the market in the seed. A step that would miss in production misses here, on the cadence ledger, with its clause.

What a Trading Day of the seeded month does

Clock (SAST) Step Callable
D−1 09:00 Participants submit their 168-period §9.3 series, with production or consumption curves Submission.Intake.put/2
D−1 10:00 The gate closes close_dam_gate/2
D−1 10:05 Whoever missed it is rolled over under §9.3(2) generate_rolled_over_submissions/2
D−1 11:00 Unconstrained clear clear_unconstrained/2
D−1 11:30 Constrained clear clear_constrained/2
D−1 12:00 Prices and the §9.8–9.9 settlement lines calculate_prices/2
D−1 14:00 §9.10 schedule reports published publish_schedule_reports/2
D 11:00 The Intra-Day auction, iteration 3 clear_idm/3
D+1 06:00 The Network Operator’s 20-minute metering batch ingest_metering/2
D+1 06:30 Provisional allocation allocate_metering/2
D+1 08:00 Preliminary balancing run_balancing/3
D+1 17:00 Indicative settlement statements issue_indicative_statements/2
D+7 06:30 Verified allocation allocate_metering/2
D+7 08:00 Official balancing run_balancing/3

Provisional then verified is not busywork. §12.2(2) gives seven days to verify metering and §13.6(1) keeps balancing preliminary until D+7, so a seed that only ran the official pass would leave the desk nothing to compare against.

What month end does

Clock Step
BP+5WD 12:00 Initial statements, one per party, one line per settlement kind per Settlement Day
the same instant Invoices and self-billing invoices, with their §15.3.4(2) clocks a day apart
BP+5WD The monthly §15.1(1)(d) Market Operator Charge, into the ring-fenced MO Charge Account
invoice due date Participants pay by EFT — three business days
self-billing due date The Market Operator pays out — four business days
BP+6WD Credit cover reassessed for every party
BP+5WD Mo.Settlement.Month.run/3 persisted for the billing board
BP+1M The timetabled settlement rerun

Collection comes before distribution because the Code puts it there (§15.3.4(2)(d),(e)), which is what lets a Shortfall reach the creditors in the same cycle rather than the next one.

Where the Trading Clearing Account has collected less than the month owes — which is what a defaulter does to it — every creditor is paid its share, not the first few in full and the rest nothing. §15.3.4(11) allocates a bad debt across the creditors in proportion to what each was owed, and the cash side follows.

Money in the seeded month

WB.Brp.Registration opens every party’s own bank account with the standard R3 million float when the registration is created. A month’s energy bill for the largest off-taker in the cast is around R43 million, so before the first Trading Day the seed tops each account up to a R250 million working balance and posts R25 million of it as credit cover. Both are labelled as notional funds from the participant’s own business, never as income from the market — the same honesty rule the opening balance itself follows.

Without the top-up every participant is an insufficient-funds refusal, and a month in which nobody could pay teaches nothing about §15.3.4: the ladder is only interesting when the participants who can pay do.

The monthly §15.1(1)(d) Market Operator Charge is R18 500 per party and lands in the ring-fenced MO Charge Account, which §15.3.4(6) forbids from ever funding a shortfall or a cascade.

The days that are not ordinary

A month of thirty-one identical days proves the pipe and teaches nothing, so the seed places each interesting failure deliberately:

Day of month What happens, and where you see it
4, 18 Three units miss the 10:00 gate; §9.3(2) rolls their previous series forward. /mo/cadence
7, 21 A zonal export limit is declared, so the constrained run diverges from the unconstrained one and §9.8.2 pays for it. Needs HiGHS installed; without it the day runs as an ordinary one
9, 23 The System Operator instructs four units over three evening hours; §13.4 / §13.9 legs follow. /mo/balancing
11 Metering arrives :ESTIMATED and never verifies, so the official balancing run refuses with its clause rather than settling on estimates. /mo/metering, /mo/balancing
every 3rd An Intra-Day auction clears iteration 3 against lodged redeclarations. /mo/bidding/idm

Month end adds two more: the largest debtor pays 60 % of its invoice and cures its Credit Call inside the window; the smallest pays nothing and does not, so its Shortfall becomes Unsecured Bad Debt and the §15.3.4(11) cascade allocates it across the creditors. One month, both branches of the ladder.

How to seed it

From the Demo Dashboard (/demo, §8), with the overlay present: the Market trading card offers the last six complete Billing Periods. Pick one and press Populate trading month — or press Trade the last 3 months and let it take the incomplete ones oldest-first. It runs in the background, and the progress bars are read from the rows the seed has written, not from a counter in the page, so they survive a reload and a second browser.

From the command line:

# Add a month to an overlay that is already seeded — no teardown.
mix setup.market_month --month 2026-07

# Rebuild foundation + the fifty participants + the month, reset-first.
mix setup.market_month --month 2026-07 --rebuild

# A three-day smoke run.
mix setup.market_month --month 2026-07 --days 3

--month defaults to the last complete calendar month. WB.Setup.seed_trading_month/1 is the same write the button calls.

Roughly what it writes

For a 31-day month and the fifty-party cast:

Artefact Rows
§9.3 submissions, 168 periods each 50 × 31
Published clearing runs, with prices and settlement lines 31
Native 20-minute meter readings 24 metered points × 72 × 31
Hourly rollups and Actual Energy, at two vintages 24 × 24 × 31 × 2
Balancing runs, preliminary and official 62
Indicative settlement statements 50 × 31
Initial statements, invoices and self-billing invoices 50 + 50
Cadence rows ~14 × 31

Ten to twenty-five minutes on a laptop is normal.

Six meters that are not read, and why

Thirty of the fifty participants own a physical Point of Delivery: fifteen IPP meters and fifteen off-taker meters, each paired with a trader as counterparty so §12.3(1)’s two-BRP rule is satisfied. Six of those thirty sit at locations the provisional network model PROV-2026-07 does not cover, so they have no Network Zone — and §3.3(4)(a) makes jurisdiction a property of the zone.

The seed does not invent a zone for them. It does not submit readings it knows would be refused, and it does not give the affected units an Actual Energy of zero either, because a unit settled at exactly its schedule reads as a clean day rather than as one that was never measured. Those units appear on the Market Operator’s own unplaced-sites board (/mo/zones/unplaced) and in the run’s unplaced_points. The other twenty-four are metered, allocated and balanced.

The five traders with no wheeling book this month (BRP-SIM-TRD-16 … 20) are in the same position for the same reason: no meter, no balancing position — and still a full month of declarations, cleared schedules, settlement lines, statements, documents and accounts.

Running it twice

Idempotent, and resumable by construction. A Trading Day that has already published a clearing run, an official balancing run and an indicative statement for every party is recovered from the record rather than re-traded: the month gets its lines back from the published run and the balancing legs, and nothing is written twice. An interrupted month is finished by running the same command again.

§15.3.4 issues a statement once, so a repair run does not re-issue one. A revision is §15.3.5’s instrument, not a second issue of the same figures.

Where to look afterwards

Screen What the month put there
/mo/bidding?date=… The day’s chalkboard: submissions, SMP curve, energy account netting to R0.00
/mo/bidding/month?month=2026-07 The billing board: statements, documents, the cascade, the cash position
/mo/cadence?date=… Every callable that ran, missed or refused, with its clause
/mo/metering?date=… Allocation status, vintages, the exception inbox
/mo/balancing?date=…&stage=OFFICIAL Imbalance volumes, both stacks, both prices
/mo/accounts The three §15.2(2) market accounts, designations, movements, credit cover
/mo/billing Invoices and self-billing invoices with their PDFs
/org/market-accounts The same month from a participant’s own seat

Data. market_month · seed 42 · stages S00–S03 + S16 + S17 · the month named by --month, default the last complete one. Verified by test/wb/setup_acceptance/trading_month_test.exs (three days, gated on WB_SETUP_ACCEPTANCE=1).

5. Scenario Lab — pointer to V12

The Scenario Lab (/org/sim, development and demo builds only) is taught in V14 · Scenario Lab.

That volume covers purpose, the three seeded recipes (AUG-BASELINE, CODEX-S12-WARM, CODEX-S12-ISOLATION), every editor panel, Runs, Compare, the day log, Teardown, and the worked clone-and-run.

This chapter stays so older #5-scenario-lab links still land, and so the operations catalog can point at a neighbour instead of re-teaching the kitchen.

If you need… Go to
What the Lab is and how to use it V14 §1
Each seeded scenario V14 §6 · §7 · §8
Panels, actions, lists, reports V14 §9 · §10
Live meter ticks after seal §6
Demo rebuild / roll-forward §4
Strategy ribbon reading V3 §17

Gating (unchanged). The route compiles only when Application.compile_env(:wb_wheeling, :sim_enabled) is true (dev / test, off in production). At runtime WB.Sim.sim_enabled?/0 must also pass or you are redirected home.

Revised read

S12 also runs a Revised read demonstration against its participant meters: one FINAL interval and one DRAFT interval receive a substituted revision. The FINAL period keeps its settlement and gains a locked-period finding; the DRAFT period queues a re-cut. Original readings remain available. Repeating the script skips an already revised demonstration interval. If either state has no eligible interval, the S12 result explicitly reports no_eligible_interval for that state.

6. Live simulation mode

S15 turns on a continuous live-meter writer after a sealed S14 world. It is not another deterministic rebuild of history.

6.1 S05 history vs S15 live

S05 (seeded history) S15 (live)
When During full_demo / acceptance setup After S14, via --profile live
What Deterministic interval rows (~12 months) Ongoing 20-minute ticks
Reset Part of --reset teardown No --reset on the live profile
Writer Setup pack / BackfillMeterHistory WB.Sim.LiveMeter.Simulator GenServer
Clock sealed historical interval series wall cadence with +20-minute virtual advance
Purpose reproducible examples and acceptance evidence observe ongoing ingestion after the seal
Safe stop teardown rebuilds it WB.Setup.stop_live() before any mutation

Settlement and strategy treat live ticks like any other meter data once ingested — late cumulative reads still preserve energy (V2 §7).

S05 is therefore not a slow version of S15. S05 creates a fixed, inspectable past as part of a profile; S15 writes a moving present after that past has been sealed. Do not attempt to repair live drift by rerunning S05 over it.

6.2 Start live only from a matching seal

  1. Complete a sealed S14 with known --as-of and --seed.
  2. Stop conflicting processes (WB.Setup.stop_live() if needed).
  3. Start live with the matching --as-of and --seed. The live profile is continuous and accepts no --reset.
mix wb.setup --profile live --seed 42 --as-of YYYY-MM-DD
  1. The Simulator starts under WB.Sim.LiveMeter.Supervisor (not in the default Application children). Each tick enqueues IngestReadingWorker (§3).

The Demo Dashboard’s Catch up metering now (§8.4) runs this same profile against the sealed run’s own clock and seed — it is a button on the procedure above, not a different path. Update demo to today is the clock rebuild (§4.4), not this live-meter start.

Default wall cadence: 20 minutes per tick (virtual clock advances +20 minutes). Catch-up can run faster when behind.

Follow along. Seat: an organisation user after a sealed S14 world. Path: open a device or interval explorer, then observe after one live tick. You should see: a newly ingested reading rather than a rebuilt history.

6.3 Worked observation — one tick becomes a reading

Data. full_demo · seed 42 · S14 sealed, then S15 live · illustrative 20-minute tick.

Input Value
Starting virtual time 10:00 SAST
Tick cadence 20 wall minutes
Next virtual time 10:20 SAST
Writer WB.Sim.LiveMeter.Simulator
Ingestion job IngestReadingWorker
  1. Start S15 against the matching sealed profile. The GenServer creates the next simulated meter observation at the tick.
  2. At one cadence, the virtual clock moves R1: 10:00 + 20 minutes = 10:20 SAST; it enqueues IngestReadingWorker.
  3. The worker persists the reading. Open the device/interval explorer and verify the new timestamp rather than expecting a modified S05 record.
  4. A daily settlement run can subsequently include eligible fresh intervals. It will not overwrite a FINAL period merely because another reading arrived.

What you see on screen is a later interval row after ingestion. This is the pattern to expect on your own page; the simulator itself is not a settlement approval action.

6.4 What you watch

6.5 Stop before any destructive work

Call WB.Setup.stop_live() before teardown, second setup, or leaving the machine. Setup refuses to overlap live ticks with a mutating profile run — that refusal is intentional (§7).

Credentials / safety still apply (§4). Never point live at a shared or production database.

Where this is taught. Meter and interval interpretation is taught in V2 §7. The ingestion worker appears in §3; controlled settlement status is taught in V8 §11.

6.6 A safe live-session checklist

Treat a live session as an observation window. Start from one sealed S14 manifest, keep its as-of date and seed with your notes, and tell other operators that S15 is writing intervals. When you are finished, stop the writer before opening a Scenario Lab drill or rebuilding fixtures.

Before start During observation Before teardown
matching S14, seed, as-of new interval timestamps and ingestion jobs WB.Setup.stop_live() completed
local disposable database no concurrent mutating setup no active ticks remain
no stale simulator process distinguish S15 from Lab Driver resume/rebuild only after stop

If an interval does not appear, inspect IngestReadingWorker and the live supervisor before manually adding readings. Manual repair hides the very ingestion behavior that S15 was meant to demonstrate.

Plant telemetry companion

S15 starts LiveTelemetry.Simulator beside the meter simulator. It advances a virtual five-minute clock and submits deterministic Mock device payloads through the production normaliser and plausibility engine. Ten-minute feeds emit only on their own boundaries. It starts only with the guarded live profile; an ordinary application boot does not start it. The mock badge remains visible, and no vendor HTTP call is made.

7. Troubleshooting & FAQ

When a screen looks wrong, identify whether the source is configuration, readings, lifecycle, or a background worker before changing data. This chapter gives the first safe check and points to the section that owns the mechanism. If this summary conflicts with the owning volume, follow the owner.

Settlements and statements

  1. Why is my settlement still DRAFT?
    DRAFT is a working result while the pipeline resolves data and can be re-run safely. As data becomes usable it can be PRELIMINARY; FINAL is the controlled close, so the normal runner does not silently clobber it. Check the period’s readings and pipeline status before you retry.
    → V8 §11

  2. Why can’t I re-settle a FINAL period from the nightly job?
    FINAL is an operator-protected statement of the period, not an automatic cache. The runner may update DRAFT or PRELIMINARY work but requires the explicit correction/re-settlement path for FINAL. Identify the source correction first; changing a tariff or meter row without that trace is not a safe remedy.
    → V8 §11

  3. Why is a statement not issued?
    StatementRunner is enqueued by settlement finalisation, so a non-FINAL period has no normal statement to issue. Confirm the period status and inspect the settlement queue before assuming a money calculation failed. A failed statement job needs its job evidence and retry path; it does not authorize editing ledger rows.
    → V8 §9 · §3.4

  4. Why is the result empty after a re-run?
    The empty-run guard prevents a partial-meter tail from wiping a previously useful result. Treat the empty output as a data-completeness signal, then inspect the interval window and allocation inputs. Once the readings land, use the owned re-settlement route rather than fabricating zero values.
    → V8 §11

  5. Why do IPP hours say provisional?
    Provisional means the calculation has incomplete or late meter evidence, not that the platform has lost energy. Late cumulative reads preserve the quantity and allow a controlled re-settlement. Compare the interval coverage first, then read the final report after data is complete.
    → V4 §8 · V8 §3

Deals and lifecycle

  1. Why did my proposal expire?
    The hourly AshOban :expire_stale trigger moves a PENDING proposal past expires_at to EXPIRED. That is a lifecycle transition, not a deleted negotiation. Renew only when its product and max_renewals allow it; otherwise create the appropriate new deal flow.
    → V7 · §3.5

  2. Why is my contract still SIGNED after its start date?
    The daily PhysicalPpa :suspend_unsigned trigger (and the virtual twin) moves AGREED deals past the 10-day sign window to SUSPENDED; revival is a mutual Request / Accept on Negotiations (V7 §5.1). The daily PhysicalPpa activation trigger moves SIGNED to ACTIVE when start_date ≤ today. Confirm the date and the trigger/queue evidence before changing contract data. If the lifecycle is valid but execution failed, follow the deal owner’s recovery route rather than backdating settlement.
    → V7 · §3.5

Meters, jobs, and live data

  1. Why is an interval missing or red on the gallery device?
    CODEX-DEMO-GALLERY-MISSING-READ deliberately contains a gap so you can recognize incomplete coverage. It is expected fixture data, not a reason to repair the demo database. The resolution rules preserve energy when a later cumulative read arrives.
    → V2 §7

  2. Why is an Oban queue stuck?
    First identify the worker and its owning queue—such as settlement, brp, strategy, forecast, or setup—then inspect its error and retry state. A stuck job usually reflects an unmet input or external boundary, not an invitation to create a duplicate job. Use the worker card to verify what it should have produced before retrying.
    → §3

  3. Why did the IPP monthly forecast not fan out?
    MonthlyForecastRunner runs at 04:00 on the fifth and fans out a ForecastRunner per eligible active IPP. Check the active physical PPA and the forecast queue before expecting a report. A missing fan-out is not corrected by manually copying another IPP’s forecast.
    → §3.3 · V4 §8

  4. Why are live ticks colliding with setup?
    A mutating profile and S15 would write incompatible assumptions into the same world, so setup refuses the overlap. Stop the simulator with WB.Setup.stop_live(), verify it stopped, then resume or rebuild from §4. Do not force a reset through the guard.
    → §4 · §6

Market and Scenario Lab

  1. Why is a bid EXPIRED or showing a gate-miss?
    The bid was built after the D−1 gate determined by MarketRules; 10:00 is a typical gate, not a substitute for configured rules. Check the gate, worker timing, and bid source before attempting another submission. A retry after delivery has moved on is not a valid bid.
    → V9 §4

  2. Why is the SIMULATED ribbon visible on MO or SO screens?
    A Scenario Lab run still owns those artifacts, and its status is not TORN_DOWN. Open /org/sim, identify the run by its name and seed, then inspect or tear it down when the exercise is complete. Do not read a ribbon-marked screen as production-like operational evidence.
    → V14 · §5 · V3 §17

  3. Why does a Lab comparison show a drifted badge?
    The two runs no longer share comparable provenance or resulting trajectory: confirm scenario clone, seed, period, and edited controls in the day log. A baseline comparison should isolate one intended change, such as a day factor, rather than blend market, SO, and MO injections. Clone a clean baseline and rerun if you cannot explain the divergence.
    → V14 §10

Setup and seats

  1. Why did setup fail mid-stage?
    Inspect the failed stage and its setup queue evidence, then resume the same manifest ID after correcting the cause. A second --reset on a half-built world removes the audit trail that explains the failure. Resume that manifest; do not start a second reset.
    → §4

  2. Why is my password wrong after a rebuild?
    Demo seats use supersecret; Eskom is eskom@webill.net, and a municipal account is its normalised municipality name plus @webill.net. If the expected seat cannot sign in, correct the S01 pack and rebuild. Do not hand-edit password hashes, because that breaks reproducibility.
    → §4

  3. Why can’t I find Scenario Lab on the landing page?
    The Lab requires compile-time and runtime sim_enabled gates. Production builds omit it, and a disabled runtime redirects the route home. Use a development/demo build and the lab/full-demo setup profile rather than attempting to expose the route manually.
    → V14 · §5

Eskom and Munix

  1. Why is the municipality map the wrong colour?
    The Munix Configuration wheeling-status chip drives the V1 map legend. Confirm whether the published state should be Wheeling, Pilot, Partial, or None, save it in the municipality seat, then reload the organisation map. A colour is not proof that a tariff or PPA exists.
    → §2 · V2 §9

  2. Why does Munix say Wheeling but the map still seems unusable?
    The status publishes municipal participation, while the map presents that fact to organisation users. It does not create an Embedded wheeling tariff, Monthly Admin Fee, valid site configuration, or agreement. Check the saved Munix charge controls and then trace the V1 site/deal path.
    → §2 · V2 §9

  3. Why is Permutate disabled or doing nothing?
    Every eligible charge has already been permutated or activated, so there are no missing season/TOU shells to create. Open Charges and Rates to confirm the grid and saved values; the action never invents a price. Activate only after the approved grid is complete.
    → §1

The health probe fails, but the home page opens

Open /health/ready and read checks. A 503 identifies a critical dependency failure; /health/live only proves that the endpoint answers. Restore the named dependency or apply pending migrations. A missing HiGHS binary or stale weather produces degraded instead. An unconfigured /metrics returns 404 by design; configure the bearer token on the web listener and send it in the Authorization header.

8. The demo dashboard — /demo

One room for the whole demo estate. Everything §4 builds — the full demo world, the simulated SAWEM market, traded Billing Periods — is seeded, watched and kept up to date from this page, and the inventory of what was built is listed on it. The three demo tools the landing page used to host live here now.

8.1 Opening it

From the landing page (/), the Demo tools strip shows the full demo’s seal state in one line; Open Demo Dashboard opens the room in its own window, so it can sit beside the console you are demonstrating. The page exists only where demo tools are enabled (:sim_enabled — dev and demo builds); on any other build the route is compiled out and a stray visit lands back on /. No seat is required — like the Scenario Lab (§5), it is a harness, not an organisation login.

Follow along. Any dev/demo build. Path: / → Open Demo Dashboard. You should see: the world’s identity in the header (as-of date, seed, today), eight stat cards, four operation cards, the last three Billing Periods, the daily checks, and the tables of what was built.

8.2 The operations

Four cards. Every mutating button states in its confirmation exactly what it will do; every run happens in the background and the page stays usable.

Card What it runs Notes
Full demo data full_demo (S00–S14) — Seed full demo when the world has never been sealed. When it is sealed on an older clock, Update demo to today runs §4.4 Procedure D (WB.Setup.roll_forward/1) in the background: same seed, as_of becomes today, derived strategy/settlement data rebuilds. The button shows Updating… and stays disabled until the new seal. Resets application tables; several minutes. The simulated market and traded months are not rebuilt — use those cards (or Set up everything) for the whole estate. Catch up metering now (§8.4) is the non-destructive meter fill.
Simulated SAWEM market S16 — the fifty-participant overlay of §4.7, without teardown Also links to the Scenario Lab
Market trading S17 — §4.8, for the picked Billing Period or Trade the window months — the two closed months settled, the current month traded through today and left unsettled Requires the overlay; a part-seeded month resumes rather than duplicating
Set up everything The whole estate from scratch, in one sequence: teardown, the full demo with the market overlay in the same run (S00–S14 + S16), the two closed Billing Periods settled, the current month traded through today, then a day-ahead backfill so every participant has submissions through tomorrow’s gate Nothing is skipped — after the teardown nothing is built. The console equivalent is printed under the button

8.3 Live progress

Progress is read from the rows the seeds write — the setup manifest, the published clearing runs, the statements — so it survives a reload, a second browser and a restarted server. Setup transitions additionally ring a PubSub doorbell (WB.Setup.PubSub) so every open dashboard refetches the moment a stage completes or a Trading Day lands, with a slow poll as the fallback while anything is running. If the page and the rows ever seem to disagree, believe the rows and reload.

8.4 Daily start-up checks

The estate has two daily obligations: the 20-minute meters must reach now, and every simulated participant must hold tomorrow’s day-ahead series. The Daily start-up checks panel owns both.

8.5 What was built

The stat cards and tables are the §4.2 world, counted and named: organizations with their BRP registrations and holdings (simulated participants badged sim), generator sites by technology and connection, off-taker sites by category, trader pools, and every physical and virtual PPA as a contract leg (who sells to whom — site, pool, organisation) beside its network side (Eskom, or the municipality each end sits in). Document counts split wheeling billing (tax invoices, credit notes) from market documents (invoices, self-billing, debit notes), and carbon certificates are summed from issued batches.

8.6 Safety rails

The §4.1 rails all hold here: dev/demo builds only, production refused below the page by the setup control plane, destructive actions confirmed in plain words, one setup at a time (a second Set up everything is refused while one runs), and the page itself writes nothing — every mutation goes through the same WB.Setup functions the mix tasks call.

8.7 Market phase before a demo

The phase shown on this dashboard is the register value, not a build flag. Changing it is a publish on Market Operator → Rules (/mo/rules) — open, review, confirm — the same ceremony as moving the price cap. It is not a deployment.

Before a sales demo, open /demo and / and /mo/rules and check that the three labels agree with each other and with what you are about to say in the room. A rebuilt demo estate seeds Phase 1 explicitly (WB.Setup.Packs.Market.Registrations, WB.Setup.Packs.Market.Simulated). Walkthrough: V9 §18.

Where next. Procedures and recovery: §4. The overlay’s cast: §4.7. What a traded month writes: §4.8. Watching live ticks: §6. What-if experiments on the sealed world: the Scenario Lab, V14.

Trading-control status

The daily status report includes armed registrations, PROPOSE drafts awaiting submission, individual check refusals in the last 24 hours, and approvals approaching expiry. Counts come from persisted rows. A refusal count counts check results, so one bid refused by two checks contributes two. Review the Controls tab for the affected bid and the Approvals inbox for decisions.

9. Registry connector operations

The carbon registry seam (WB.Carbon.Registry.Connector) is Mock in every committed environment. Live Verra and Gold Standard adapters exist; going live is a configuration change, not a code change.

Credentials

Environment variable names only — never values in config/*.exs:

Adapter Key Base URL
Verra VERRA_REGISTRY_API_KEY VERRA_REGISTRY_BASE_URL
Gold Standard CorTenX GOLD_STANDARD_API_KEY GOLD_STANDARD_BASE_URL

Keys belong in KMS (spec §17), not in the repo and not in a shell history that gets pasted into a ticket.

Fixture capture

  1. Point the adapter at a sandbox (or a live account you control).
  2. Run each of the five callbacks once.
  3. Sanitise: redact account identifiers and personal data; keep field names and shape.
  4. Commit under test/support/fixtures/registry_connectors/<adapter>/ with the capture date and API version in that folder’s README.
  5. CI replays the files through the real Req decode path with :carbon_registry_forbid_network set.

When a registry changes its API, re-capture. Production already stores every live response as an artifact, so drift shows up in data, not in guesswork.

Go-live checklist

  1. Conformance suite green (verra_conformance_test.exs, gold_standard_conformance_test.exs).
  2. Config map selects the live adapter for that registry only.
  3. Credentials present at runtime; Mock remains the default for every other registry.

COAS manual procedure

COAS has no public API in v1. Operators complete each stage by uploading the document (hashed through Artifact) and capturing the reference:

Stage What to upload Reference
LoA applied Application pack DEE/COAS application id
LoA granted Extended Letter of Approval LoA number
Cancellation requested Origin-registry cancellation request Origin reference
Cancelled Cancellation certificate (purpose wording §2.4) Cancellation certificate id
COAS listed Listing confirmation COAS ownership-repository id
COAS transferred Transfer confirmation (when it happens) Transfer id
Retired SARS Carbon Offset Certificate RetirementClaim.coas_reference

A stage cannot complete without its artifact. A stage cannot skip its predecessor. After Cancelled, international-side actions refuse.

CAD Trust observer (/operator/cadt)

The CADT node is infrastructure beside the app, never an Elixir dependency. Hosting is invisible to code. CI and the demo estate run the Mock exclusively (:cadt_forbid_network in test).

/operator/cadt is the observer console: per-registry sync health (roots, last synced, :STALE with the recorded error) and the suspect ledger (:OPEN first, resolution trail on the resolved). Observation reads; this screen never holds keys and never writes to the node.

A genuine double-count hit blocks listing and retirement until an operator resolves it with an evidence artifact. The Certificate Desk shows the same incident as a blocking banner.

Demo step S22 seeds one colliding serial block against the Mock’s planted fixture, runs the sweep, and resolves the suspect so a fresh mix setup.demo always shows a found-and-resolved trail.

CAD Trust node — deployment, mirror, keys

The Connectivity Deck’s sizing for a CADT node plus Chia: quad-core, 4–8 GB RAM, ~500 GB disk, Ubuntu preferred. Optionally run CADT’s mirror database (MySQL/Postgres) as the analytics-friendly read path. The app talks to either through WB.Cadt.Client; swapping Mock for NodeApi (or a later mirror adapter) is configuration.

Testnet is the default. No committed config names mainnet. Publishing is the same software with a Chia wallet key (24-word mnemonic in real key management — it signs data-layer writes inside the node) plus a CADT API key that merely guards the node’s API. The API key reaches the adapter from CADT_API_KEY / CADT_BASE_URL at runtime. The adapter refuses publishing with :missing_api_key rather than defaulting.

The key ceremony itself is an ops runbook outside this repository. Record who performed it, when, and which KMS slot holds the material. Never paste a mnemonic, API key, or example secret into tickets, fixtures, or this manual.

10. Platform health and metrics

The Demo dashboard’s daily checks include Platform health, with a link to the complete readiness report. /health and /health/ready work on both listeners without a market token. ok and degraded return 200; a critical failure returns 503. /health/live always returns 200 when the endpoint can answer, so it is the process liveness probe.

The eight checks cover the database, pending migrations, TimescaleDB, Oban, the Oban leader, HiGHS, weather freshness and cluster peers. Each has a two-second deadline. An optional fault (such as a missing solver or stale weather) is degraded; stopped Oban, a failed database, pending migrations or missing TimescaleDB fail readiness. Mock feeds identify themselves as mock. These checks make no external provider request.

PromEx records web/API request latency, LiveView activity, database queries, jobs, VM usage, publication and gate-miss counts, API authentication/idempotency/rate-limit outcomes, budgets, feed/FX ages, leadership and readiness. Clearing metric names are registered for the later background-clearing milestone; they do not yet have emitters.

/metrics is available only on the web listener. It returns 404 until operations supplies METRICS_BEARER_TOKEN, then requires a matching bearer token (401 otherwise). Restrict the load balancer path to the Prometheus host and scrape each instance every 15 seconds. Grafana dashboard upload needs its configured host and credential; the application does not upload dashboards at boot. Never put tokens into links or the manual.

Honeybadger uses its environment key and the wb_wheeling app name. Moving a key out of a file does not rotate it: the operator’s dated rotation and revoked-key check live in docs/references/ops/DEPLOY-EC2.md. Stage 1’s rotation is still pending.

11. Feeds and delivered events

The operator’s problem

Webill does not invent every number it shows. Meters arrive from files and devices. Weather arrives from a forecast vendor. Carbon registries answer when you ask about a certificate. The market gateways send bids out and bring results back. A buyer may ask you to knock on their own server when an invoice is issued.

If any of those pathways goes quiet, the screens still look finished — they just show yesterday. The operator’s problem is therefore simple: name every pathway, know how often it should speak, and see when it last told the truth.

A named pipe, then the platform word

Think of the platform as a house with labelled pipes. One pipe carries 20-minute meter buckets. Another carries hourly weather. A third carries an outbound “invoice issued” knock to a URL you typed. You do not pour hourly rain into the meter pipe, and you do not judge a weekly registry check by a 20-minute clock.

That labelled pipe is a feed. The label on the pipe is the family. How often a reading is supposed to arrive is the grain (for example 20 minutes). How the pipe is allowed to run — live, pretend, or only when a person uploads — is the mode. The house rule for “this is still current” is the policy. Each try to use the pipe is an attempt. What you see on the register — fresh, stale, never, refusing — is the status, derived from those facts. It is not a mood.

A 20-minute meter feed does not redefine an hourly weather forecast. Grain belongs to the feed.

11.0 Words you need before the buttons

Plain English Platform word One-sentence meaning
A labelled pipe in or out of the house Feed One named pathway: provider, family, grain, mode, and a policy
The kind of thing the pipe carries Family METER, TELEMETRY, WEATHER, PRICE, FX, REGISTRY, BILL, MARKET, or EVENT
How big each bucket is Grain The interval of one reading, in minutes (20 min, 60 min, or — when the feed is not interval-shaped)
How often we bother the pipe Cadence Seconds between scheduled tries (a weather pull every 21,600 seconds is six hours)
In or out Direction INBOUND (data arrives) or OUTBOUND (we send)
Live, pretend, or by hand Mode LIVE, MOCK, or MANUAL
The house rule for “still current” Policy An effective-dated freshness window, optional quota, and a source note
One try to use the pipe Attempt A timestamped OK, REFUSED, TRANSIENT, or OUTAGE row
Whether the pipe is telling the truth now Status NEVER, FRESH, STALE, REFUSING — or the mode badges MOCK / MANUAL
“Please knock on my door when X happens” Webhook An HTTPS callback you register for a topic
The post that could not be delivered Dead letter A delivery that exhausted retries or was abandoned; it waits for a human Retry
The one-time door key whsec_ secret A signing secret shown once; it is never put in the payload

Abbreviations this chapter uses

Abbreviation Stands for Here it means
HTTPS Hypertext Transfer Protocol Secure A web address that starts with https:// — the only callback the form accepts
URL Uniform Resource Locator The address of that callback
HTTP Hypertext Transfer Protocol The numbered reply from the callback (200 means success; 410 means gone)
FX Foreign exchange Currency rates used by regional trading
CSV Comma-separated values A spreadsheet-shaped text file (meter imports and plant historian mirrors)
AMI Advanced Metering Infrastructure A municipal smart-meter export format
MV90 A common meter-export format The Eskom interval-file shape the import adapter reads
MRV Monitoring, reporting and verification Carbon-project device readings — not settlement meters
CAD Trust / CADT Climate Action Data Trust An observer feed for carbon registry facts
COAS A South African carbon-offset registry path Tracked by hand (MANUAL); there is no live pull
SAPP Southern African Power Pool Cross-border market client and hydrology
SO System Operator The grid controller; forecast packs and curtailment claims go out on MARKET feeds
MO Market Operator The market clerk; the organisation talks to it on market:mo_gateway
BRP Balance Responsible Party The organisation that must match what it scheduled to what the meters did
SAST South Africa Standard Time UTC+2. Screen times you care about are SAST; some night jobs are catalogued in UTC
UTC Coordinated Universal Time The scheduler’s clock in §3
Oban The job runner Background work: the fifteen-minute sweep, webhook delivery, retention
SSE Server-Sent Events The Market API’s live event stream — a different pipe from these webhooks

Where this is taught. Night-job times live in §3. Plant-owner screens live in V4 §11 and V2 §11. Signed Market API subscribe/retry endpoints live in V13 §13.

How a reading becomes a status

flowchart TB
  A[Something arrives or is sent on a named feed] --> B[Record one attempt: OK, REFUSED, TRANSIENT, or OUTAGE]
  B --> C[Update last attempt and last success]
  C --> D[Apply today's policy]
  D --> E[Derive NEVER, FRESH, STALE, or REFUSING]
  E --> F[Fifteen-minute sweep shows the new badge and files a finding if it just went stale or refusing]
  1. A worker, an upload, or a plant push uses a feed key (for example weather:mock or plant:{site}:{provider}:{start-date}).
  2. The platform writes an attempt with an outcome.
  3. If the outcome is OK and the clock is newer than the last success, last success moves forward.
  4. Status is then derived — it is not stored as a separate opinion.
  5. Every fifteen minutes FreshnessSweep re-seeds the catalog, recomputes every badge, broadcasts the change, and files a quality finding when a live feed has just become STALE or REFUSING.

Attempt history is kept for ninety days, and each feed always keeps its latest attempt even after that window. That job is AttemptRetention (01:10 UTC) — see §3.

Mode is not status

MOCK and MANUAL are modes. They never pretend to be live freshness.

Mode Everyday picture What the badge means
LIVE The real postman Status is derived from policy and attempts: NEVER, FRESH, STALE, or REFUSING
MOCK A practice pipe in the basement Always shows MOCK, even when idle. Demo weather and the live-meter simulator sit here
MANUAL You walk the letter to the counter Always shows MANUAL. Meter file imports and bill uploads sit here — nothing is late until a person acts

A live feed without a policy is already STALE, reason no_policy. A missing rule is itself a stale condition. — on grain or last success means unknown / never succeeded, not a third clock.

Live-feed status — derived from last success, latest attempt, and today’s policy. MOCK and MANUAL stay on their mode badges and do not walk this rail.
NEVER
→first OK attempt
FRESH
→success older than the policy window
STALE
Also from a later REFUSED attempt: REFUSING

NEVER — no successful attempt yet. FRESH — last success still inside the policy window. STALE — window expired, or there is no policy. REFUSING — the latest attempt is REFUSED and is newer than the last success. A later OK returns the feed to FRESH.

Status Plain meaning How you get there
NEVER This live pipe has never delivered a success last_success_at is empty
FRESH The last success is still inside the policy window A clock no older than freshness_window_seconds after last success
STALE We should have heard by now — or nobody wrote a rule Window expired, or no policy row
REFUSING The far side said no, after we had once succeeded Latest attempt outcome is REFUSED and is newer than last success
MOCK Practice data Mode is MOCK — status is not computed
MANUAL A person must act Mode is MANUAL — status is not computed

Default policies (source note GD6 interim / F12 platform defaults, effective from 1 January 2020):

Policies are effective-dated. A new row with a later effective_from replaces the old rule without rewriting history.

Each feed family

The catalog has nine families. Eight have named pathways on the operations register today. PRICE is reserved: the enum exists; no seeded row uses it yet.

Family Everyday picture Direction Typical grain Who uses it
METER The household electricity meter In 20 min Settlement and the live simulator. Not the plant “now” panel
TELEMETRY A plant’s own instruments, or a river gauge In 5–10 min on a plant; — on hydrology / carbon MRV Owner plant screen; SAPP hydrology; carbon MRV. Not settlement
WEATHER The outdoor thermometer / forecast In 60 min Strategy and IPP forecasts
PRICE A market-price ticker — — Reserved; no registered pathway
FX The bureau-de-change board In — (daily cadence) Regional trading
REGISTRY The carbon certificate card catalogue In — Carbon desk; CAD Trust observer; COAS by hand
BILL A PDF or CSV you upload In — Billing ingest
MARKET The clerk’s counter at the market In or out 60 min on the gateways MO / SO / SAPP clients; SO forecast pack; curtailment claim
EVENT “Knock on my door when X happens” Out — Signed webhooks

METER. Interval energy used to settle contracts. Imports are MANUAL (you drop a file). The live simulator and device ingest are scheduled. Setup backfill is MOCK. These rows are the settlement truth. A plant telemetry sample never replaces them.

TELEMETRY. Device evidence: power, charge, wind speed, hydrology, carbon MRV. Three shapes exist:

  1. Platform rows — telemetry:hydrology (SAPP) and telemetry:carbon_mrv.
  2. Per-plant rows — created when an owner registers a feed (plant:{site-id}:{provider}:{start-date}). See §11.2.
  3. Not this family — carbon inverter project telemetry on a methodology pack, and 20-minute settlement meters.

WEATHER. Hourly forecasts. Demo rows resolve to the Mock provider until weather is armed. Open-Meteo, the Open-Meteo ensemble, and Solcast are named so a later go-live is a configuration change, not a new pipe.

PRICE. No key, no row, no policy. Do not invent a ticker here.

FX. Daily foreign-exchange rates. Demo uses fx:mock.

REGISTRY. Verra, Gold Standard, the mock connector, CAD Trust, and COAS. COAS is MANUAL — a person tracks it. Live Verra / Gold Standard adapters exist; going live is taught in §9.

BILL. bill:manual_upload — you bring a bill. Nothing is stale because a person did not upload today.

MARKET. How the organisation talks to the Market Operator, the System Operator, and SAPP, plus two outbound duties: the hourly Grid Code forecast pack and a curtailment claim. Gateways are 60-minute grain with a 15-minute cadence (900 seconds).

EVENT. One outbound row, events:webhook_out. Every signed delivery records an attempt on this feed. The human workflow is §11.1.

The named pathways on the register

These keys are seeded by WB.Feeds.Registrations. The Pathway column shows the provider (today that string is the key) and the resolved module.

Key Family Mode (typical demo) Grain Cadence What it is
weather:mock WEATHER MOCK 60 min 6 h Practice forecast
weather:open_meteo WEATHER MOCK until armed 60 min 6 h Open-Meteo
weather:open_meteo_ensemble WEATHER MOCK until armed 60 min 6 h Ensemble spread
weather:solcast WEATHER MOCK until armed 60 min 6 h Solcast (quota 500/day)
registry:verra REGISTRY MOCK until armed — — Verra connector
registry:gold_standard REGISTRY MOCK until armed — — Gold Standard
registry:coas REGISTRY MANUAL — — Hand-tracked COAS
registry:mock REGISTRY MOCK — — Practice registry
registry:cadt REGISTRY from adapter — daily CAD Trust observer
meter:import:canonical METER MANUAL 20 min — Canonical interval import
meter:import:eskom_mv90 METER MANUAL 20 min — Eskom MV90 export
meter:import:municipal_ami METER MANUAL 20 min — Municipal AMI CSV
meter:import:mock METER MANUAL 20 min — Practice import
meter:setup_backfill METER MOCK 20 min — Demo history writer
meter:live_simulator METER MOCK 20 min 20 min S15 live ticks
meter:device_ingest METER LIVE / ingest 20 min 20 min Device → time series
telemetry:hydrology TELEMETRY from adapter — daily SAPP hydrology
telemetry:carbon_mrv TELEMETRY from adapter — daily Carbon MRV
fx:mock FX MOCK — daily Practice FX
market:mo_gateway MARKET from adapter 60 min 15 min Market Operator client
market:so_gateway MARKET from adapter 60 min 15 min System Operator client
market:sapp_client MARKET from adapter 60 min — SAPP client
market:so_forecast_submission MARKET outbound 60 min — Grid Code forecast pack
market:curtailment_claim MARKET outbound — — Curtailment claim channel
bill:manual_upload BILL MANUAL — — Bill ingest
events:webhook_out EVENT outbound — — Signed webhook deliveries
plant:{site}:{provider}:{from} TELEMETRY MOCK or LIVE cadence ÷ 60 as registered One owner’s plant feed

Timezone: most rows require an explicit UTC offset on timestamps (utc_offset_required). Import adapters may declare their own rule.

Data. Catalog from WB.Feeds.Registrations.rows/0 as of manual 3.20.2. Demo modules resolve to Mock until a provider is armed. Last checked 2026-09-07.

How to use the Feed registry (/ops/feeds)

This is the operations estate, not an organisation login. It exists only where demo tools are compiled (:sim_enabled — dev and demo builds). On any other build the route is not there; a stray visit is sent back to /. No seat is required. There is no menu chip on the Demo dashboard today — you type the address.

Follow along. Any dev/demo build. Path: open /ops/feeds. You should see: heading Feed registry, a Family or status filter, a table (#feed-rows), and a Delivered events → link to /ops/events.

Column What it shows What you do with it
Pathway Provider (the key) on the first line; family and resolved module underneath Confirm which pipe and which code is plugged in
Mode / status Amber mode chip (LIVE / MOCK / MANUAL) plus the derived state; a “since” line when the state has a start MOCK/MANUAL: ignore freshness. LIVE: act on NEVER / STALE / REFUSING
Grain 20 min, 60 min, or — — is “not an interval feed”, not an error
Last success Timestamp or — — on a LIVE row is NEVER
Policy Freshness {n}s · quota {n or unmetered} · {source note}, or No policy No policy on a LIVE row is already STALE

Filter (#feed-filter, label Family or status): All, each family, or MOCK / MANUAL / STALE / FRESH / NEVER / REFUSING. The filter is exact. WEATHER does not show plant telemetry.

The table live-updates when the sweep broadcasts a status change. You do not create feeds on this page. You read them. Creating a plant feed happens on the owner’s plant screen. Creating a webhook happens on Configuration or the Market API page.

Done looks like: every LIVE row you care about is FRESH, MOCK rows still say MOCK, MANUAL rows are idle unless someone should have uploaded, and no LIVE row says No policy.

Empty / refusal. An empty table after a filter means nothing matches — the pipes are still there. Production has no /ops/feeds at all.

How to use Data feeds on Configuration

The Feeds & webhooks tab of the Organization room (/org/configuration/feeds, V2 §2.6) stacks two cards in #organization-data-fabric:

Each feed row gives a plain name with its key under it (for example Meter devices, meter:device_ingest), the kind of data, a status chip and the interval (20 min, or — when the feed has none). The chip translates the status:

Chip Status
Receiving FRESH, with the time of the last good reading
Stale STALE, with the time it went stale
No data yet NEVER
Refused REFUSING
Upload on demand MANUAL
Simulated MOCK

The same strip is attached to the Market API page (/org/brp/api) when a participant registration is selected. There the webhook scope is the party, not the organisation. Pick a registration first or Create webhook refuses with No participant registration selected.

Follow along. Seat: offtaker_c@webill.net (Organization; password supersecret). Path: sign in → Configuration → scroll to Your feeds. You should see: shared weather/meter lines and any org-owned plant feeds, each with mode and status.

11.1 Webhooks and dead letters

A webhook is a promise: “when this kind of thing happens, knock on this HTTPS address and sign the knock so I know it is you.” The signed knock is a delivery. If the far door never opens, the letter is not thrown away — it becomes a dead letter and waits for a person.

That is the outbound EVENT feed events:webhook_out.

What they are for

Think of a doorbell on your office, not a letterbox on ours. You do not come here to fetch every invoice. You hang a bell on your finance system (or your ERP, or a chat room that can take HTTPS). When Webill issues an invoice, we press that bell. Your system opens a payable without anyone staring at this screen.

A webhook is therefore for telling another system you already run. It is not a feed into Webill. We do not invent the address — you paste an HTTPS URL you control. The Webhooks card on Configuration says so under its title.

The kind of news you asked for is the topic (for example invoice.issued). One webhook is one URL plus the topics you tick. A URL holds one webhook; to change what it hears, edit its topics rather than adding a second row.

A worked example — invoice.issued

OffTaker C buys wheeled energy. When the seller’s bill is issued, C’s finance clerk should not have to open Configuration and hunt. Their accounts-payable system should already have a line: document number, amount, due date.

  1. On Organization → Feeds & webhooks, under Webhooks, C pastes https://finance.offtaker-c.example/webill/hooks and leaves the default topic invoice.issued ticked.
  2. They press Create webhook, press Copy beside the whsec_ secret, paste it into the finance app (so it can check the signature), then press I’ve stored it.
  3. Later, a billing document for C is issued. Webill POSTs a small signed JSON body { type, timestamp, data } with document_id and number.
  4. Finance answers 200. Recent deliveries shows invoice.issued with a Delivered chip and 1 attempt. The EVENT feed records an OK attempt.
  5. If finance is down, we retry (see below). After a dead letter, C presses Retry once the server is back.
flowchart LR
  A[Seller issues C's invoice] --> B[Webhook invoice.issued]
  B --> C[POST to C's HTTPS finance URL]
  C --> D[Finance opens a payable]
  D --> E[Finance answers 200]
  E --> F[Recent deliveries shows Delivered]

Follow along. Seat: offtaker_c@webill.net (Organization; password supersecret). Path: Organization → Feeds & webhooks → Webhooks. You should see: the help line under the title, invoice.issued ticked, and Create webhook. Demo/dev can use the in-process receiver instead of a real finance host (§11.1 local receiver).

Data. The knock for this topic is wired: when a billing document is issued, both the issuer organisation and the recipient organisation are notified (WB.Events.Notifier). The table below says which topics knock, and where.

Each topic and why you would use it

Every topic name is listed here. Why subscribe is the job you would give your other system. Knocks from Your webhooks is what is built and wired today: only four topics are published into this doorbell.

Money and paper

Topic What happened Why you would subscribe Knocks from Your webhooks
invoice.issued A bill was issued to or by your organisation Open a payable / receivable in finance; start a payment run Yes — issuer and recipient
credit_note.issued A credit note was issued Reverse or reduce that payable / receivable Yes — issuer and recipient
settlement_period.final A physical PPA’s settlement period was finalised Lock the month in your ledger; stop treating it as draft Yes — seller and buyer organisations on that PPA
statement.issued A market settlement statement was published Pull the statement into your books Form only
document.received A document arrived for you File it; start a review Form only (API poll may already show it)
bill.updated An existing bill changed Refresh the payable; do not pay the old figure Form only

The market day

Topic What happened Why you would subscribe Knocks from Your webhooks
gate.closing A bid gate is about to close Page the trader; freeze last edits Form only
gate.extended That gate was given extra time Unlock the desk; you still have a window Form only
submission.rolled_over Your bid was carried to the next day Confirm you are still in; do not re-type a live bid Form only (API poll may already show it)
schedule.published The market published your schedule Drive the plant / book to that shape Form only (API poll may already show it)
prices.published Day-ahead prices were published Reprice positions; update a public board Form only (API poll may already show it)
idm.cleared The intra-day market cleared Apply the afternoon correction Form only (API poll may already show it)
balancing.prices.published Imbalance prices were published Estimate the balancing bill Form only (API poll may already show it)
dispatch.instruction The System Operator instructed your unit Move output; log the instruction in ops Yes — the party’s registration (use /org/brp/api, not Configuration)

Credit cover and notices

Topic What happened Why you would subscribe Knocks from Your webhooks
credit_cover.increase_notice You were asked to post more cover Start a treasury transfer Form only (API poll may already show it)
credit_cover.warning You are near the warning limit Alert treasury before a call Form only (API poll may already show it)
credit_call.made A credit call was made Treat it as urgent cash Form only
notice.published The Market Operator published a notice Put it on the trading desk Form only (API poll may already show it)
query.resolved A data query was resolved Close the dispute file Form only

Standing data

Topic What happened Why you would subscribe Knocks from Your webhooks
registry.counterparty_change A party’s registry facts changed Refresh KYC / standing data Form only (API poll may already show it)

dispatch.instruction is scoped to a party (your market registration). Create it on /org/brp/api after you select a registration. The other three wired topics are scoped to the organisation and belong on Configuration.

Create a webhook

On Configuration (organisation scope) or /org/brp/api (party scope):

  1. Enter an HTTPS callback URL. An http:// address, or one with a user name or password in it, is refused at the field.
  2. Tick one or more Topics. None ticked is refused at the field.
  3. Press Create webhook (#create-webhook).
  4. Copy the whsec_… secret from #webhook-secret-once immediately (Copy puts it on your clipboard). Press I’ve stored it and it is gone. Storage keeps only an encrypted copy. The secret is never included in the JSON body we POST.

The topic list is the table in §11.1 topics. The form default on Configuration is invoice.issued, because that is the everyday finance doorbell and it is wired today.

A URL that already has a webhook is refused (This URL already has a webhook. Change its topics in the list below.). If that webhook was deleted, the message says to restore it instead. If secrets were never provisioned, Create webhook flashes Webhook secrets have not been provisioned for this environment.

Each webhook is a row: endpoint, topic chips, a status chip (Active, Paused, Paused after failures or Deleted) and the last delivery. The row’s round buttons:

Button What it does Asks first?
Edit topics Opens the row with the topic checkboxes; Save topics keeps them No
Pause deliveries / Resume deliveries Stops new deliveries, or starts them again and clears the failure count No — undo is one click
Rotate secret Mints a new whsec_ and shows it once, like Create Yes: the row explains the 24-hour overlap
Delete webhook Stops deliveries at once. The row stays as Deleted, with its delivery history Yes
Restore webhook Brings a deleted webhook back as Active No

After a rotation, deliveries are signed with both the new and the old secret for 24 hours, so the far side can switch without missing a notice (Market API rule — V13 §13). The row says Old secret valid until … while that window is open. Rotating again inside the 24 hours ends the older secret at once. A topic a webhook holds but can never receive at its scope shows as an amber chip marked (not sent here).

What “done” looks like on the far side

We POST JSON to your URL with Standard Webhooks headers. A reply in 200–299 completes the delivery (DELIVERED). Timeouts, 408 / 425 / 429, server errors (500+), and network failures are TRANSIENT — we retry. Any other reply, including 410 Gone, is REFUSED — no retry; the delivery is dead at once.

flowchart TB
  A[You create a webhook and copy the whsec_ secret] --> B[An event of that topic happens]
  B --> C[We POST a signed JSON body to your HTTPS URL]
  C --> D{What did the door say?}
  D -->|200-299| E[DELIVERED — consecutive failures reset]
  D -->|timeout, 408, 425, 429, 500+| F[RETRYING — wait, then try again]
  D -->|other, including 410| G[DEAD at once]
  F --> H{Eight attempts used up?}
  H -->|no| C
  H -->|yes| I[DEAD — a dead letter]
  I --> J[Five dead letters in a row pause the subscription]

Retry gaps (then a small deterministic jitter, about ±10 %):

5 seconds → 5 minutes → 30 minutes → 2 hours → 5 hours → 10 hours → 24 hours.

Eight unsuccessful attempts exhaust a schedule. A retry left waiting more than 72 hours is swept to dead (DeadLetterSweep, 00:20 UTC) with the error Retry schedule abandoned for 72 hours.

Pause stops new events. In-flight deliveries keep their schedule (a paused subscription snoozes 300 seconds and tries again). Five consecutive dead letters pause automatically. Resume is always a human click — the toggle also clears the consecutive-failure counter. DISABLED kills in-flight work as dead (Subscription disabled).

The operations page (/ops/events)

Same gate as the Feed registry: :sim_enabled, no seat, type the URL or follow Delivered events →.

Follow along. Any dev/demo build. Path: /ops/feeds → Delivered events →. You should see: Subscriptions, Delivery log and dead letters, and an empty Attempt timeline until you press Timeline.

Block What you see What you can press
Subscriptions Callback URL; scope_kind · status · topics Pause / Resume (#toggle-{id})
Delivery log Event id, status (PENDING / RETRYING / DELIVERED / DEAD), attempt count, last HTTP / error Timeline on any row; Retry only when status is DEAD
Attempt timeline Attempt number, time, outcome, HTTP or — Fills when you pick Timeline; — means no HTTP response (timeout / network)

Retry re-enqueues the dead letter and keeps the old attempt rows. You get a new schedule; you do not rewrite history.

On Configuration, Recent deliveries lists the latest 20 deliveries across your webhooks: topic, endpoint, a status chip (Queued, Retrying, Delivered or Failed), attempts and time. A Failed row has Send again, which is the same Retry.

flowchart LR
  A[Delivery is DEAD] --> B[You press Retry]
  B --> C[New attempt schedule starts]
  C --> D[Previous Timeline rows stay]

Local receiver and the production key

Dev, demo and test deliver to an in-process receiver (WB.Events.Sink). It checks the signature on the exact bytes. Host failing-sink.example answers 503 on purpose so you can watch retries and dead letters without a real outage.

Production reads EVENTS_SECRET_KEY from deployment secrets (a base64-encoded 32-byte key). Provision it before anyone creates a subscription, and keep it across restarts. Losing it makes stored whsec_ blobs unreadable — you cannot verify or rotate until a new key is in place, and old secrets stay dark.

Careful. Copy whsec_ when you see it. Do not put EVENTS_SECRET_KEY or any whsec_ in this manual, in a ticket, or in a screenshot.

Where this is taught. How a machine verifies webhook-signature, the 20 KB payload cap, and /subscriptions/:id/deliveries/:delivery_id/retry are V13 §13. This chapter owns the human screens and the dead-letter story.

11.2 Plant telemetry feeds

A plant feed is a TELEMETRY row for one generator site, one provider, and one effective start date. Registering it on the owner’s plant screen creates the operations identity plant:{site-id}:{provider}:{from} and stores a hashed draft data schedule. Signing that schedule is a separate commercial act.

Kind What you are pointing at Mode
mock Generated demonstration readings MOCK
push_api The plant’s collector POSTs to the platform LIVE
gateway_mirror A file dropped in the operator inbox (historian CSV) LIVE
vendor_cloud Huawei / Sungrow / SMA / SolarEdge Reserved — cannot be activated; the milestone is not opened

A commercial vendor row still needs a credential reference even while unarmed. Kind must match provider for mock / push / mirror.

Successful batches record accepted and rejected counts. The raw payload artifact is the bytes that arrived. Sample revisions keep a correction’s provenance — they never silently rewrite the first truth. Stale and recovered transitions update the feed’s operational status (the plant freshness sweep every five minutes, distinct from the registry’s fifteen-minute sweep).

These feeds are not settlement meters and not carbon inverter MRV. The plant screen links the feed, the draft schedule, and the immutable curtailment traces.

flowchart TB
  A[Owner registers mock, push, or mirror] --> B[TELEMETRY row appears on /ops/feeds and Your feeds]
  B --> C[Collector pushes or the inbox sweep reads a file]
  C --> D[Accepted and rejected counts; raw bytes stored]
  D --> E[Plant now panel can show a fresh sample]
  E --> F[Settlement still reads the 20-minute METER rows]

Follow along. Seat: ipp_2@webill.net (Organization; password supersecret). Path: Atlantis Wind → Strategy → Plant (or Dashboard Plant now → Open plant). You should see: path, latest observation time, and a mock badge on demonstration readings.

Where this is taught. How the owner registers, consents, and reads the panel: V4 §11 and V2 §11. The push API and mirror CSV schema: V13 §17. This chapter owns the registry row and the “not a settlement meter” boundary.

11.3 What runs while you sleep

You do not press these. They keep the pipes honest.

When (UTC) Worker What you notice
Every 15 minutes Feeds.Workers.FreshnessSweep Badges refresh; a new STALE / REFUSING finding if the change just happened
01:10 Feeds.Workers.AttemptRetention Attempts older than 90 days drop; the latest attempt on each feed stays
00:20 Events.Workers.DeadLetterSweep Retries abandoned for 72 hours become DEAD
On each event Events.Workers.DeliverWebhook Up to eight signed POSTs on the events queue
Every minute Events.Workers.DispatchPending Recovers a committed outbox row if the original transaction did not notify

A rolled-back source transaction leaves no delivery. These jobs do not change the monthly settlement-chain order.

Where this is taught. The full clock, including the plant telemetry workers (mirror inbox, plant freshness, MEC scan, traces), is §3.