Cost figures: system cost and avg energy cost cover fuel, carbon, start-up, new-build capital, fixed O&M for the existing fleet, REIPPPP payments and grid reinforcement; rooftop PV is excluded. Load shedding is costed separately at R9.53/kWh, Eskom’s cost-of-loadshedding figure, and shown beneath system cost when it occurs. Replacement cost instead prices every MWh at full lifecycle cost, as if the mix were built new today. Neither is a tariff. Methodology in the README.
Run the full model
network-aware MIP · HiGHS · runs in your browserHourly dispatch
modelledHourly dispatch chart: generation by carrier for a representative 168-hour week. Tabular summary below.
Annual electricity mix
modelledLoad-shedding risk
modelled60 outage-yearsNational engineBars: probability the year reaches at least stage S, across 60 simulated years.
Carbon budget
modelled · vs South Africa’s NDCGeneration capacity build rates
capacity expansion · least-cost schedule to 2030Least-cost build schedule – how much of each technology, in which year, and in regional mode where. Uses each coal unit’s retirement date, technology costs and build-rate limits.
Set build limits per technology – GW added per year
What happens if the grid build lags? – cost of delay
Everything above assumes the Transmission Development Plan lands on schedule. NTCSA itself notes the first five years carry high certainty and beyond 2030 is uncertain – and 52% of the mapped projects sit in Concept, Pre-Concept or generic Planned rather than Execution. This slips those and re-solves.
Provincial electricity mix
modelledGrid queue pressure
SAPVIA & NTCSA GCCA · pipeline vs headroomPermitted but not built
environmental approvalsLevelised cost comparison
sourced constants · see About · R/kWh · full lifecycle build costThe no-gas frontier
modelled · worst of twelve weather yearsPrice-setting technology by hour
modelledHourly shadow price for a representative 168-hour week, each bar coloured by the technology setting the price in that hour.
Capture price forecast
modelled · PPA value to 2030Battery revenue benchmark
modelled · lithium-ion 4h · what a BESS would earnBattery revenue split
modelled · lithium-ion 4h · arbitrage vs ancillary vs capacityCapture rate projections
modelled · cannibalisation · wind vs solarBattery revenue projections
modelled · revenue per MWWhere a battery earns most
modelled · lithium-ion 4h · nodal · needs full model runCarbon capture & storage retrofits for the coal fleet
policy test · changes every outputModel assumptions & caveats
What this is: a national-level digital twin of the South African power system. Hourly demand, wind and solar profiles come from 2025 Eskom data. Fleet parameters – EAF, OCGT load factor, rooftop PV, structural demand level – are calibrated to Eskom’s weekly system status reports: EAF ≈ 68 % (calendar year to date, Week 32), OCGT load factor ≈ 1.3 %, 8.6 GW behind-the-meter rooftop PV (NTCSA estimates 9.1 GW at June 2026, of which 0.49 GW is ground-mounted wheeled plant this model counts as utility supply), post-2023 structural demand decline. Transmission and distribution losses are not modelled separately, and should not be: the demand series is Eskom’s transmission-level demand – what generators must send out – and is therefore already gross of downstream losses. Our grid demand of 206 TWh against Eskom’s 183 TWh of billed sales implies about 11 %, which sits on Eskom’s reported 9.1 % T&D loss figure. Adding a loss factor on top would double-count it and push coal roughly 11 % above its benchmark, having started 2 % below. Output was calibrated against Ember's metered generation (12 months to May 2026) on 16 Aug 2026, which found the model overstating non-fossil generation by about 8 percentage points. Koeberg's capacity factor was corrected from a hardcoded 0.90 to 0.75, and turbine/panel availability availability derates were briefly added to wind and utility PV and then removed as a double count: the national profiles are Eskom’s own metered hourly output (Data Portal ESK19243), so availability, losses and real curtailment are already inside the per-unit series, and the regional profiles are Renewables.ninja, whose PV already carries a 10 % system loss. The wind normalisation was corrected again on 6 September 2026. Dividing metered energy by any FLAT nameplate understates the per-unit series while the fleet is growing, and the 4,044 MW figure derived in August was an end-of-period estimate against a year whose mean installed capacity was 3,642 MW – understating wind by 11 % and solar by 21 %. Each hour is now divided by that hour’s measured installed capacity, from Eskom’s ESK19679. Compared fleet-normalised, the model’s wind capacity factor lands within 1 % of Eskom’s measurement; compared as energy it reads about 20 % above Ember, which is fleet size and weather year rather than model error. A separate audit found rooftop PV running at 21.2 % capacity factor against utility PV’s 22.1 %, which is physically wrong: a rooftop fleet takes whatever orientation the roof has, is shaded, is never cleaned, does not track, and sits in Gauteng and the Western Cape rather than the Northern Cape. Its derate moved from 0.94 to 0.78, giving ~17.6 %. Note also that the 9.1 GW rooftop figure is itself an inference – NTCSA derives it from residual load on sunny versus cloudy days and counts only systems under 100 kWp – and analysts have questioned whether it runs high. The remaining gap is deliberate rather than tuned away: Ember reports metered output, already net of the network curtailment Eskom applies to Cape wind, and it does not count privately wheeled plant or fully capture behind-the-meter rooftop. Calibrating the derates to close that gap would bake curtailment into a technical assumption and then double-count it whenever a scenario curtails. The validation panel shows the four carriers against Ember so the difference stays visible.
Price formation: coal sets the marginal price in almost every hour of the default scenario, so the shadow price is nearly flat (roughly R715–760) and storage has little to arbitrage. That is a real property of a coal-dominated single-node system, not a bug – but it means battery revenue, capture-price spreads and the value of flexibility are all understated at default settings and only become meaningful once enough wind and solar are added to push coal off the margin. The model now carries an explicit operating reserve (N-1 contingency plus load- and VRE-following terms, all on sliders). At today’s fleet it rarely binds – South Africa has roughly 7 GW of fast-start peakers and storage against a reserve need near 1.3 GW – so it is not what keeps prices flat; coal simply sets the price in every hour. It bites once coal is tight: at EAF 52 % with 12 % demand growth, switching reserve on moves diesel from 1,843 to 2,031 running hours. Unit-level forced outages are now modelled too: availability comes from a two-state Markov process over the 85 individual coal units rather than a flat derate, so several units can be out at once and the bad hours are genuinely bad. The EAF slider still sets the annual average; what changes is the shape. It matters most where it should – at EAF 55 % the model sheds for 207 hours instead of 3, and diesel runs 1,402 hours instead of 415. A fixed seed keeps the headline scenario reproducible; the risk panel varies it. Part-load heat rate is now modelled: a coal unit burns more fuel per MWh the further it is backed off, so emissions follow fuel burned rather than energy sold. The penalty averages 1.5 % today and 3.6 % with 20 GW of extra solar, because solar is what pushes coal to part load. Ignoring it would overstate the CO₂ saving from that solar by about 4 %. Still simplified: unit trips are drawn independently, where real ones cluster (common-mode failures, coal quality, a stressed fleet), and intra-regional network limits below the ten-region corridor level remain out of scope – though new build from the sliders is now sited against GCCA connection headroom rather than simply following the existing fleet, so capacity no longer piles into the Northern Cape and Hydra Central, which have had zero solar headroom since GCCA 2025. Beyond about 19.9 GW of new solar the model reports that the scenario has outrun national connection headroom instead of quietly absorbing it.
Demand response is modelled as two separate products, because they behave differently. Interruptible load – contracted industrial customers Eskom can drop, historically the smelters – defaults to 1,200 MW because it genuinely exists, is called before any shedding, and is reported apart from unserved energy: a managed reduction under contract is not a blackout. It is not free, and the compensation is booked into system cost. Shiftable load – water heating, pumping, irrigation and EV charging moved within the day, strictly energy-neutral – defaults to ZERO, because at meaningful scale it does not yet exist here. It is a scenario lever rather than a description of today, and is worth testing alongside a high-solar build since it soaks up midday output.
Virtual power plants are modelled separately again, because they aggregate behind-the-meter assets the utility does not own – rooftop solar, household batteries, and above all controllable electric geysers. This is live policy rather than theory: Cape Town has an RMI-supported feasibility study for a municipal VPP, and eThekwini’s Project Smart Solar with Plentify, funded by AFD and the EU, is connecting residential PV, batteries and geysers into a city-wide VPP. The controllable pool is sized at 4 GW – residential is about 17 % of consumption and the geyser 40–50 % of a household bill, averaging ~1.8 GW, but geyser load is what makes the morning and evening peaks, so it contributes far more than its average into them. Enrolment defaults to zero. The model shows clear diminishing returns: the evening peak falls about 2 GW by 50 % enrolment and then stops falling and simply moves to the early hours, which is what happens in any system that shifts load heavily.
Carbon cap: the build optimiser can be run subject to an emissions ceiling rather than only reporting against the NDC, which is how the IRP poses the question. The answer is informative: the least-cost plan already lands under about 100 Mt, so meeting the sector’s 140 Mt share costs nothing extra. Tighten to 80 Mt and the plan costs roughly R25bn more, with a marginal abatement cost near R1,500/tCO₂ – far above today’s R46/t effective carbon price, which is the point: a price at that level will not on its own deliver a cap that tight.
The model’s own winter peak runs higher than Eskom’s reported evening peak (~27 GW) because it is driven by the 2025 demand series and includes storage charging – the System adequacy and validation panels show the gap rather than hiding it.
How dispatch works: merit order – rooftop PV nets off demand → wind / utility PV / CSP → nuclear, hydro, Cahora Bassa imports → coal → pumped storage & batteries → gas CCGT → diesel OCGT → unserved energy. One stage of load shedding ≈ 1 000 MW of unserved demand.
Unit commitment: coal is committed at individual unit level – 85 units across 31 stations – in 4-hour blocks, respecting each unit’s minimum stable level (0.50–0.65 of capacity, fleet-weighted 0.563), minimum up and down times, and ramp limits. This is what produces the “Buffalo curve”: coal that cannot drop far enough at midday to make room for solar while remaining available for the morning and evening peaks. A fully committed fleet has a floor of roughly 23 GW that cannot move. The heuristic is benchmarked against the full MIP optimiser and lands within about 1 % of optimal for today’s system, widening as new solar is added.
| Coal | R546/MWh fuel (Eskom FY2025 primary energy) · +R80 VOM · 1.04 tCO₂/MWh · 42 GW installed · real per-unit retirement dates to 2051 |
| Diesel OCGT | R6 100/MWh fuel · +R70 VOM · 0.78 tCO₂/MWh · 3.4 GW |
| Gas CCGT | R1 968/MWh fuel · +R35 VOM · 0.37 tCO₂/MWh · LNG-fired. FY2026 JKM reference: $18.50/MMBtu delivered × R16.21/USD ÷ 52% efficiency. Carbon added separately. Use the Gas running cost slider for spot ($23 ≈ R2 450) or pre-Hormuz consensus ($12 ≈ R1 280) |
| Imports (Cahora Bassa) | R550/MWh · 1 150 MW @ 85 % |
| Variable O&M | Applied, and shown above per source. Thermal only: wind, solar and battery carry their O&M as fixed R/kW-yr instead, which is the standard convention rather than an omission. Anchored to NREL ATB figures. Hydro and imports get no adder \u2013 hydro has no fuel so its R30/MWh already is consumables, and the import price is contracted and embeds the supplier\u2019s own O&M. |
| Existing fleet | Wind 4.6 GW (REIPPPP 4.0 + wheeled 0.47 + Eskom Sere 0.1) · Utility PV 3.27 GW (grid-contracted 2.78 + wheeled 0.49) · Hybrid 0.34 GW (RMIPPPP, contracted dispatchable 05:00–21:30; modelled hourly as 05:00–22:00) · Rooftop 8.6 GW · CSP 0.6 GW · Batteries 0.8 GW · Pumped storage 2.9 GW |
| New-build capex | Wind R21 000 · PV R12 000 · Rooftop R17 000 · Battery (4h) R10 500 · CCGT R18 000 /kW. Declining per BNEF to 2035 (solar −30 %, storage −25 %, onshore wind −23 %); CCGT rises, having hit a record high in 2025 |
| Network | 439 real Eskom transmission lines · 185 substations with verified coordinates and kV ratings (NTCSA shapefile, OSM, Eskom GPS, DBSA RFP 008 register) · GCCA 2025 connection headroom by region · 11 Renewable Energy Development Zones (GN 114 / Gazette 41445 2018 and GN 142/144/145 / Gazette 44191 2021), held as centre points and equivalent-area radii rather than gazetted polygons. Per-substation capability bands shown in the Grid connection tab are a GridTwin estimate derived from that regional figure plus network topology – they are not published by Eskom or NTCSA. |
| Planned build | 221 projects from NTCSA’s Transmission Development Plan 2025–2034, all nine provinces, each with its published commissioning year and delivery phase |
| Renewables vs Non-fossil | “Renewables” = wind + utility PV + rooftop PV + CSP + hydro. “Non-fossil” adds nuclear and (mostly-hydro) imports. |
Risk and variability: the risk panel dispatches 60 synthetic years, each drawing both an outage path and a real weather year. Coal availability follows a mean-corrected daily AR(1) process around the EAF slider, calibrated to the multi-week swings in Eskom’s reported unplanned outages; weather is drawn from ten real years (2014–2023, Renewables.ninja/MERRA-2 at capacity-weighted REIPPPP plant locations) and cycled so each year is used equally. The two are sampled independently, since unplanned outages track plant condition rather than the weather. This matters most in high-renewable scenarios: varying coal availability alone told you nothing once the coal fleet had retired. Separately, Show wind & solar extremes re-solves the scenario against 10 real weather years (2014–2023, Renewables.ninja / MERRA-2 at capacity-weighted REIPPPP plant locations). South Africa has the most variable solar resource in Africa, and 2022 delivered 17 % less wind energy than 2023 from an identical fleet – so a single-year run can materially over- or under-state both adequacy risk and curtailment.
Capacity expansion: the build-schedule optimiser is a linear programme solved in-browser with HiGHS. It minimises discounted system cost over 2026–2030 using representative days rather than all 8 760 hours, so it sizes the build rather than proving it – verify any resulting mix with Run the full model. In regional mode it also decides where, subject to per-region GCCA headroom, real corridor limits, and a per-region annual build cap of 45% of the national rate (without that last constraint the optimiser concentrated an entire national year of wind into a single province, roughly eleven times what any South African province has sustained).
What it is not: the instant model has no network constraints (the MIP does, across 10 regions) and no operating-reserve co-optimisation. Costs exclude existing-fleet capex, wires and retail. It answers bulk adequacy, not distribution reliability, where most outages start. Everything runs in your browser, except the rooftop tool's address and roof lookups, which go to Google. The TDP is a published plan, not an investment commitment. For decision-grade work use PyPSA-RSA – this app is the intuition layer in front of it.
How the model compares to Eskom’s published figures
How the model compares to other published models
How the model works
Two engines answer two different questions. The instant engine re-dispatches the whole system in about half a second and treats South Africa as a single node – fast enough to move a slider and watch the result, but blind to where power physically flows. The full model is a mixed-integer unit-commitment solve across the ten GCCA supply areas, run on 52 representative days with a 2% optimality gap, or over a full year if you ask for it. Both use the same fleet, the same profiles and the same costs, so where they disagree the disagreement is about the network, not the inputs.
Unit commitment is modelled per station rather than per technology: minimum stable levels, minimum up and down times, and start-up costs, with per-unit parameters taken from PyPSA-RSA. Coal carries a part-load heat rate penalty, so backing a unit off raises its fuel burn per MWh and its emissions with it. Storage dispatches on opportunity cost rather than fuel cost, and each technology keeps its own state of charge and round-trip efficiency. Solver is HiGHS, compiled to WebAssembly and running in your browser – nothing is sent to a server.
Corridors between supply areas are represented by a DC power flow, not a transport model. Susceptances are derived from the real line geometry in NTCSA’s published transmission shapefiles: length is computed from the routes, and reactance is assigned from voltage class using standard overhead-line values, which is the method PyPSA and PyPSA-Eur use. Parallel circuits add susceptance. Series compensation is applied to the long high-voltage lines, because Eskom has compensated its backbone since 1975 and ignoring it makes those lines look electrically longer than they are; the thresholds were tested for sensitivity rather than assumed. The HVDC circuits to Cahora Bassa are excluded from the power flow entirely and treated as a fixed import, since HVDC flow is set by converter controls rather than by impedance.
The distinction from a transport model matters: a transport model will route power around a constraint in a way the physics will not allow, which flatters every congestion result. Reactance here is assigned rather than measured, and conductor bundling and per-scheme compensation settings are not public, so this is good enough to show that flows follow physics rather than economics – not a substitute for a load-flow study.
Every price here is a shadow price: the marginal cost of serving the next MWh in that hour. South Africa has no wholesale market yet, so there is nothing to compare against – these are what the system would pay if dispatch were priced rather than administered. When SAWEM publishes, they become testable. The market is designed to clear to a single national price, so a single-node price is the correct basis rather than a simplification; the locational signal lands on curtailment and network charges instead.
Hourly wind and solar profiles are per supply area across twelve weather years, 2014–2025, so a scenario can be tested against a bad wind year rather than an average one. Wind comes from NASA MERRA-2 via Renewables.ninja, sampled at up to twelve sites per region chosen from environmental authorisations and gazetted development zones, then calibrated so the national mean matches Eskom’s metered output. Solar is PVGIS satellite data at 5 km. Demand is Eskom’s measured 2025 hourly profile, domestic only, scaled by the growth slider.
Why the sampling matters. These profiles used one point per region until September 2026. A single point in a province with a coast, an escarpment and a dry interior is not a province: the national fleet ran out of wind thirteen times more often than the country does, and KwaZulu-Natal’s capacity factor was understated by twelve percentage points. Anything drawn from the earlier version, here or elsewhere, should be re-checked against this one.
Every change runs a test suite before it ships: per-carrier reconciliation against Eskom’s published outturn, capacity factors checked separately so that energy and CF errors cannot cancel and hide each other, hour-by-hour energy balance invariants across eleven scenarios, cross-panel consistency so two panels cannot report different numbers for the same thing, and a sweep asserting that every control still moves something. The two panels above show the reconciliations themselves.
The parameter tables – per-corridor susceptances, the compensation settings, the assembled regional capacity and queue data – are not published. They are the part that took the work, and they are licensed CC BY-NC-ND. The method above is not secret and was never the hard part; if you are building something similar, the NTCSA shapefiles and the GCCA headroom tables are the public pieces worth having. Get in touch if it would help to compare notes.