Solved national example · Part 4 of 6

BESS siting in India: where a 5 GW four-hour fleet actually earns

A worked national siting study: rank Indian states by nodal marginal-price spread and evening ramp, place 5 GW / 4 h, re-solve, and test the modelled revenue against real IEX day-ahead prices.

For
Grid planners, IPP and utility strategy teams, storage investors and policy analysts
Time
60 minutes plus paired solves
Updated
4 Aug 2026
Before and after comparison for a national 5 GW four-hour BESS siting study, showing peak, price-spread, average-price and revenue tiles
A national siting result has to separate what the system gains from what an owner earns; on this run the two point in opposite directions. · Hosted on Cloudinary.

Direct answer

Rank every bus on the price spread and ramp severity the nodal solve actually produced, place capacity on the top-ranked buses in proportion to score, re-solve the clone on identical settings, and then check the resulting revenue against a market benchmark before calling it revenue — on this network the model puts 98.4% of a 5 GW fleet into the northern load pocket for delivery reasons, not arbitrage.

Solved result first

What the completed analysis must expose

Open the technical text / video script →

This is the national counterpart to the 30 MW single-project screen in ‘Small BESS project tutorial’: same workflow, but the question is which nodes get capacity rather than what one site does. The production demo below is a 168-hour India state-resolution case solved on 2 August 2026 with PyPSA + HiGHS (build b87889a6, base result 927c1e87, 7.73 s). It is a method benchmark, not a forecast or a market quote. The decisive move is running the same week twice — once on merit-order and once nodally — because the difference between them is the constraint the siting answer is built on.

Worked production demo — same 168-hour week, same fleet, solved with and without the network; values change when the model or inputs change
ResultMerit-orderPyPSA nodalDecision reading
Energy served36,258.6 GWh35,827.4 GWh431.2 GWh the network could not deliver
System cost₹14.7 billion₹26.1 billionObjective value only; compare across matched horizons
Emissions10.6 MtCO₂19.5 MtCO₂Constrained delivery forces dirtier local generation
Curtailment0 GWh355.3 GWhRenewable output spilled where it cannot be moved
Unserved energy0 GWh492.7 GWhDemand the network physically failed to reach
Average price₹404.3/MWh₹24,830.9/MWhNodal average carries scarcity pricing; not a tariff

Analytical method

  1. 01State the decision as a siting question with a capacity, a duration and a number of sites, and name the metric that would falsify it.
  2. 02Solve the base case on both engines: merit-order to see the fleet, nodal to see the network, and treat the difference as the thing you are actually siting against.
  3. 03Rank buses on the price spread and ramp severity the solve produced, and read the ranking's shape before trusting its order.
  4. 04Place capacity in a copy-on-write clone, re-solve on identical settings, and compare physical and commercial metrics separately.
  5. 05Reconcile modelled revenue against a market benchmark for the same window, and attribute the gap before quoting either number.
  6. 06Re-run at a second duration and a second fleet size, because duration and scale move system value and owner revenue in opposite directions.

Who should interrogate what

Transmission planner

Is this a storage problem or a wires problem?

Compare curtailment and unserved energy between the merit-order and nodal solves; if the gap is large, storage is competing with a line, and the ranking is telling you where the line is missing.

Storage investor

What would this asset actually be paid?

Ignore the workflow's revenue tile until you have priced the same dispatch at a real market series; the modelled figure is a system value derived from LP duals, not a merchant revenue.

Procurement or policy lead

Two hours or four, and how many tranches?

Read the duration and fleet-size sensitivities together: the system barely notices the extra two hours, the owner earns a third more from them, and the fourth tranche does not earn what the first did.

DISCOM or SLDC planner

Does this help my evening peak?

Check the p95 net-load delta, not the price-spread delta; a cost-optimised battery with no peak term can flatten prices while raising the coincident peak.

Reviewer or agent

Can I reproduce and challenge this?

Take the base and study result IDs, the solve settings and the run manifest hash, re-run the pair, and confirm each configuration returns a different result key before accepting any sensitivity.

Before you start

  • A network whose buses are the unit you are willing to site at — states here, substations if you need a yard-level answer.
  • An untouched base scenario that solves on the nodal engine, not merit-order.
  • A market price series for the same calendar window, used to sanity-check modelled revenue rather than to drive the solve.

What you will have

You will produce a ranked site list with capacities, paired base and study result IDs, a before/after KPI comparison, a duration and fleet-size sensitivity, and an explicit reconciliation between modelled system value and market-priced merchant revenue.

Step-by-step

Complete the workflow

01

Frame the siting decision before you model

Decide whether you are siting for arbitrage or for delivery, because the two answers sit in different places and are paid by different mechanisms.

Modeler workspace showing the India National demo-week network selected in a named project before any scenario is edited
Fix the decision, the counterfactual and the pass or fail metric before touching the model, because a siting question and a revenue question need different evidence. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Write the question as one sentence with a capacity, a duration and a site count — here, 5,000 MW at 4 h across 5 sites.
  2. 2.List the arbitrage hypothesis (buy cheap midday, sell dear at the evening peak) and the delivery hypothesis (serve load the network cannot reach) separately.
  3. 3.Name the metric that would falsify each: a flat price spread kills arbitrage, zero unserved energy kills delivery.

Expected evidence

  • A one-sentence decision with an explicit counterfactual and two competing hypotheses to test rather than one to confirm.

Worked calculation

Energy capacity = 5,000 MW × 4 h = 20,000 MWh

Trust check

A siting study answers where, not whether. It excludes capex, fixed O&M, degradation, augmentation, financing, taxes and contracted revenue.

02

Load a network whose buses are the unit you can site at

Load the catalog dataset whose bus granularity matches the decision; a state-resolution model answers which state, never which yard.

Modeler data catalog dialog listing the India grid datasets with their bus, generator, load and line counts and entitlement tier
The catalog states what you are about to load before you load it, so scope is a deliberate choice rather than something discovered halfway through a study. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open the data catalog and read each dataset's bus, generator, load and line counts before loading.
  2. 2.Load ‘India National — demo week (states)’ into a named project, which deep-copies it as a private, editable model.
  3. 3.Confirm the horizon is 168 contiguous hourly snapshots.

Expected evidence

  • A private model with 33 state buses and an untouched base scenario, and a catalog original that is unchanged.

Trust check

Catalog datasets are read-only sources. Loading copies them into your project, so nothing you do downstream can alter the shared original.

03

Confirm the geography and fleet you actually loaded

Look at the network on the map before solving, because a siting result only means something relative to the topology that produced it.

Modeler map showing the loaded India state network on a satellite basemap with generators coloured by carrier over the Atlas substation reference layer
Confirm the geography and fleet you actually loaded, because a siting answer is only meaningful relative to the network that produced it. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open the Map tab and colour generators by carrier.
  2. 2.Check the inter-state corridors are present — 33 lines here, one per bus pair modelled as a transfer limit.
  3. 3.Note which regions carry dense generation and which carry dense load.

Expected evidence

  • A recognisable India network with generation clusters and inter-state corridors visible before any scenario edit.

Trust check

Corridor limits are transfer-capacity assumptions, not a circuit-by-circuit seasonal rating register. Treat congestion results as planning-grade.

04

Audit the component counts and find the gaps

Read the tables before the first solve; the counts are the model, whatever the catalog card promised.

Modeler Tables view with GridAI closed listing 33 buses, 201 generators, 46 storage units, 33 lines and 33 loads for the loaded India network
Read the component counts before the first solve: 46 storage units are existing hydro and pumped storage, and the absence of batteries is what makes the later before and after comparison meaningful. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open Tables and record buses, generators, storage units, lines and loads.
  2. 2.List the carriers present and, more importantly, the ones absent.
  3. 3.Confirm no batteries exist in the base case so the later comparison is meaningful.

Expected evidence

  • 346 components: 33 buses, 201 generators, 46 storage units, 33 lines, 33 loads, 8 carriers.
  • The 46 storage units are existing hydro and pumped storage; there are no batteries in the base.
Fleet actually loaded, by nameplate capacity (MW)
CarrierCapacityNote
solar230,678Drives the midday price collapse
coal223,498Committable; sets the marginal price most hours
hydro51,876Includes reservoir units modelled as storage
gas20,122Peaking and balancing
nuclear8,780Must-run baseload
lignite6,620Mine-mouth thermal
diesel470Emergency only; dispatched 0 GWh this week

Trust check

This dataset carries no wind carrier. India has roughly 50 GW of wind, much of it in Tamil Nadu, Gujarat and Karnataka, and it is the resource that most shapes the evening ramp — so this run understates the case for storage in the windy southern and western states.

05

Solve once with no network — the copper-plate read

Merit-order stacks the fleet by marginal cost and meets demand with no transmission in the model at all.

Modeler Analytics view on the merit-order engine reporting 36,258.6 GWh served, zero curtailment, zero unserved energy and an average price of 404.3 rupees per MWh
Merit-order stacks the fleet with no network in it, so it reports a comfortable week and cannot locate a constraint anywhere on the map. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open Analytics and select the merit-order engine.
  2. 2.Record energy served, system cost, emissions, curtailment, unserved energy and average price.
  3. 3.Resist concluding anything from this run.

Expected evidence

  • 36,258.6 GWh served, ₹14.7 bn cost, 10.6 MtCO₂, 0 GWh curtailed, 0 GWh unserved, ₹404.3/MWh average.

Trust check

Zero curtailment and zero unserved energy here are a property of the formulation, not a finding about India. A model with no network cannot locate a constraint.

06

Solve the same week with the network enforced

Re-solve as a nodal linear program with inter-state transfer limits binding; same fleet, same demand, same hours.

Modeler Analytics view on the PyPSA nodal engine reporting 35,827.4 GWh served, 355.3 GWh curtailed, 492.7 GWh unserved and an average price of 24,830.9 rupees per MWh
The same week solved with inter-state transfer limits enforced spills 355.3 GWh in one place while failing to serve 492.7 GWh in another, which is the entire reason siting is a question. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Switch the engine to PyPSA (nodal) and re-solve.
  2. 2.Record the same six KPIs and the base result ID.
  3. 3.Difference the two runs line by line.

Expected evidence

  • 35,827.4 GWh served, ₹26.1 bn cost, 19.5 MtCO₂, 355.3 GWh curtailed, 492.7 GWh unserved, ₹24,830.9/MWh average, solved in 7.73 s.
  • Cost up 78%, emissions up 84%, and two constraint signals that were invisible a moment ago.

How to read the result

  • 355.3 GWh of renewable output is spilled in one place while 492.7 GWh of demand goes unserved in another.
  • Storage is valuable here precisely because those two places are different — which is a siting statement, not a technology statement.
  • The 61× jump in average price is scarcity pricing entering the duals, and it will contaminate every revenue figure downstream.

Trust check

The 492.7 GWh of unserved energy is what this model does with these transfer limits over one week. Real operators redispatch, invoke reserves and import across ties in ways a weekly dispatch LP does not represent.

07

Freeze the solve settings for the paired comparison

A before-and-after is only valid when both runs share horizon, formulation, stride and unit-commitment treatment.

Modeler run options panel showing the solver, horizon, network model and unit-commitment settings that must match across a paired comparison
Record the solve settings once and reuse them, because a before and after comparison is only valid when both runs share horizon, formulation and stride. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open the run options and record engine, horizon, network model, unit commitment and stride.
  2. 2.Reuse the base result rather than re-solving it, so the workflow runs only the one new solve it needs.
  3. 3.Note the solver build so the pair can be reproduced later.

Expected evidence

  • A recorded settings set and a base result ID to pass into the workflow, so the comparison differs by storage alone.

Trust check

Comparing runs at different strides or formulations produces a difference you cannot attribute. Change one thing.

08

Rank the buses and read the shape of the ranking

The workflow scores each bus on normalised price spread plus normalised ramp severity, then takes the top K with a deterministic tie-break.

Modeler workflow builder showing the eight-step BESS siting study marked executable with its capacity, duration and ranked-site configuration fields
The workflow scores every bus on normalised price spread plus normalised ramp severity with a deterministic tie-break, so the same inputs return the same sites every time. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open Workflows and select the BESS siting study.
  2. 2.Set total capacity 5,000 MW, duration 4 h, ranked sites 5, and pass the base result ID.
  3. 3.Before running, read the per-bus spread from the base solve and look at its distribution, not just its order.

Expected evidence

  • A ranking dominated by three northern states, with a long flat tail — the median state's spread is ₹169/MWh.
Per-bus marginal price over the 168-hour base solve (₹/MWh)
StateMeanMinMaxSpread
punjab414,7661,163500,000498,837
haryana197,2941,075500,000498,925
delhi174,1851,075500,000498,925
rajasthan1,347021,38221,382
chhattisgarh651−20,0001,24421,244
gujarat1,154504,3284,278
the other 261,1531,0751,244169

How to read the result

  • Delhi, Haryana and Punjab hit ₹500,000/MWh — the value-of-lost-load penalty, not a price. These are delivery failures.
  • Rajasthan bottoms at ₹0 and Chhattisgarh at −₹20,000/MWh: the system paying to shed energy. That is the arbitrage story, worth roughly a twentieth per MWh.
  • Only 3 of 33 buses reach the penalty. For 26 states this week offers storage almost nothing to trade against.

Trust check

Ranking uses each bus's LP marginal price, which is the economic signature of local stress. Where a bus prices at the value-of-lost-load, the score is measuring scarcity, not tradeable spread.

09

Place the fleet and re-solve the clone

The workflow clones the scenario copy-on-write, splits capacity across the ranked buses in proportion to score, and re-solves at the same resolution.

Modeler jobs panel listing a completed nodal solve with its job identifier, model, scenario, service tier, duration and completion status
Every solve is an addressable, timed job rather than a blocking request, so a large study can be left to run and audited afterwards by its identifier. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Run the workflow and watch it in the Jobs panel rather than holding the request open.
  2. 2.Confirm the run completed and note its duration.
  3. 3.Record the study result ID and result key.

Expected evidence

  • A completed run in 11.8 s reusing the base result, with a new persisted study result and its own result key.

Trust check

Every solve is an addressable job with a recorded duration and status. If a job is missing, the number you are about to quote does not exist.

10

Read the study result critically, not gratefully

Three things in this result deserve scrutiny, and only one of them is good news.

Modeler BESS siting result showing peak shaved, daily price spread, average system price and revenue tiles above a before and after duck curve
Read the tiles against each other rather than individually: a falling price spread and a rising coincident peak in the same result mean different things to a trader and to a capacity contract. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Record the siting table: which buses, how much each.
  2. 2.Difference the system KPIs — price spread, p95 net load and average price — between base and study.
  3. 3.Check the sign on every delta before deciding whether it is an improvement.

Expected evidence

  • 98.4% of the fleet in delhi, haryana and punjab (1,640 MW each); rajasthan and chhattisgarh take 40 MW each.
  • System daily price spread ₹45,420 → ₹32,603/MWh, a 28% flattening.
  • P95 net load 236,441 → 238,604 MW — the fleet raises the coincident peak by 2,163 MW.
  • Average price ₹24,831 → ₹25,192/MWh, up slightly.

How to read the result

  • The price-spread reduction is the genuine system benefit and the one a regulator would care about.
  • The rising peak is not a bug in the solver; it is what cost-optimal storage does when nothing prices the peak.
  • Average price rises because round-trip losses are real: at 92% each way you discard about 15% of everything you cycle, and in a week with unserved energy that lost energy is expensive.

Trust check

A battery optimised against energy cost with a cyclic state-of-charge constraint and no explicit peak term will charge during hours that are already tight if the arithmetic pays. If your business case is a capacity contract, the p95 delta is the number that kills it.

11

Check the scenario lineage before trusting a sensitivity

Re-runs must land in distinct scenarios, or a later run can silently report results for an earlier one.

Modeler project tree showing the scenario list with the copy-on-write study clone recorded alongside the untouched base case
The workflow clones the scenario rather than editing it, so the base case survives intact and the study scenario carries its own lineage back to the parent. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Open the scenario tree and confirm the study clone exists alongside an untouched base.
  2. 2.Before each new configuration, remove or rename the previous ‘BESS …’ clone.
  3. 3.After each run, confirm the returned result key differs from the last one.

Expected evidence

  • One untouched base, one clone per configuration, and a different result key for every run.

How to read the result

  • This was caught on the production run: a 2-hour and a 4-hour configuration returned byte-identical results and the same content-addressed result key.
  • The persisted battery state of charge peaked at 16,783.7 MWh, which is infeasible for a 2-hour build at 5,000 MW (10,000 MWh) — proof the 2-hour figures came from the 4-hour scenario.
  • Isolating the clones and re-running produced four distinct result keys and the sensitivities below.

Trust check

The workflow currently names its clone from capacity alone, and the re-solve resolves scenarios by name. Two runs at the same MW can therefore collide: changing only duration or site count may return the earlier run's numbers. Verify by result key, not by trust.

12

Reconcile modelled value against the market, then hand it over

Convert the workflow's revenue to a per-kilowatt-year figure, compare it to a market benchmark, and attribute the gap before quoting either number.

Modeler run evidence workbench showing solver state, run manifest SHA-256, parquet source-input lineage and the units and tolerances for one immutable run
The evidence workbench is what makes a number defensible in a room: solver status, a hashed run manifest, the exact source inputs and the tolerances the result was computed to. · Captured in Modeler and hosted on Cloudinary.

Do this

  1. 1.Annualise the workflow revenue and divide by installed kW.
  2. 2.Compute a perfect-foresight arbitrage benchmark on a real day-ahead series for the same calendar window and the same round-trip efficiency.
  3. 3.Export the run manifest, source-input lineage and tolerances from the evidence workbench with both result IDs.

Expected evidence

  • Model: ₹16.41 bn per 168 h ≈ ₹171,000 per kW-year — several times the capital cost of the asset, every year.
  • Market: ₹1.01 bn per week ≈ ₹10,543 per kW-year, on IEX day-ahead over 6–12 July 2026 (mean ₹3,443/MWh, floor ₹200, ceiling ₹10,000).
  • A 16.2× gap, and a run manifest hash that lets a reviewer reproduce both halves.

Worked calculation

₹16,413,352,093 per 168 h × 52.14 weeks ÷ 5,000,000 kW ≈ ₹171,000/kW-year

How to read the result

  • Perfect foresight and a single daily cycle make ₹10,543/kW-year an optimistic merchant estimate, and it is still sixteen times smaller than the modelled system value.
  • That difference is the wedge capacity contracts, ancillary payments and viability-gap funding exist to close.
  • Quoting only the model oversells the asset; quoting only the market understates what it is worth to the grid. The useful question — who pays the difference, under what contract — lives in the gap.
Modelled system value against a market-priced merchant benchmark, same week, same fleet
BasisWeekAnnualisedPer kW-year
Model, LP marginal prices₹16.41 bn₹856 bn₹171,000
Real IEX day-ahead, perfect foresight₹1.01 bn₹53 bn₹10,543
Ratio16.2×16.2×16.2×

Trust check

The workflow's revenue is derived from the LP's marginal prices. In a week where the model fails to serve 492.7 GWh, those prices sit at the ₹500,000/MWh value-of-lost-load, so the figure is the system's value of avoided unserved energy — not revenue anyone would pay into a market. The result card is additionally captioned ₹/yr while rendering the horizon total, which is one week for every demo-week dataset.

Before you share

Completion checklist

  • The decision names a capacity, a duration, a site count and a metric that could falsify it.
  • The same week was solved on both engines, and the difference between them was read before siting anything.
  • The per-bus spread distribution was inspected, not just its ordering, and buses pricing at the value-of-lost-load were identified as scarcity rather than tradeable spread.
  • Base and study runs share horizon, formulation and stride, and both result IDs are recorded.
  • Every sensitivity landed in its own scenario and returned a distinct result key.
  • The p95 net-load delta was checked for sign, not assumed to improve.
  • Modelled revenue was reconciled against a market series for the same window, and the gap was attributed rather than averaged away.
  • Dataset gaps — here, the absent wind carrier — are stated next to the conclusion they affect.

Answer desk

Frequently asked questions

Why does the model put almost everything in Delhi, Haryana and Punjab?

Because on this network those three states cannot be reached at peak, so their marginal price hits the ₹500,000/MWh value-of-lost-load and dominates a ranking built on price spread. That is a delivery constraint, not an arbitrage opportunity — storage there is competing with a transmission line, and the correct comparison is against building one.

Should I build two-hour or four-hour storage?

On this run the system barely notices: halving duration changes the system-wide price-spread reduction by 0.2%, from ₹12,817 to ₹12,793/MWh. The owner notices a great deal — revenue falls 35%, from ₹16.41 bn to ₹10.66 bn for the week. If you are paid for system value, the cheaper two-hour asset wins; if you are paid on energy, the extra two hours are a third of your revenue.

Does storage revenue scale with fleet size?

No, it falls. Going from 5 GW to 20 GW quadruples the fleet and halves total revenue, ₹16.41 bn to ₹7.10 bn — an 89% collapse per MW — while system benefit roughly doubles, with the spread reduction rising to ₹27,917/MWh and average system price finally falling to ₹21,043/MWh. Storage destroys the spread it earns on, so a pipeline business case built by multiplying the first tranche is wrong by roughly a factor of eight.

Can this tell me which substation to build at?

No. Buses here are whole states, so the answer is Haryana, not a particular 400 kV yard in Haryana. For a yard-level answer use a nodal EHV network whose buses are individual substations, and expect longer solves. For a single-site developer screen at one bus, the 30 MW walkthrough in ‘Small BESS project tutorial’ is the closer starting point.

Is the ₹171,000 per kW-year figure wrong?

It is not wrong, it is mislabelled. It is the system's value of avoided unserved energy, computed from LP duals that sit at the value-of-lost-load in a week with 492.7 GWh unserved. It is not a merchant revenue. Price the same dispatch at a real day-ahead series and you get ₹10,543 per kW-year, and the ratio between the two is the policy question.

← Previous30 MW BESS exampleNext →150 MW data-centre example