Skip to content

Read a PLEXOS expansion plan, and the years it states - #18

Open
thomaschristopherking wants to merge 1 commit into
issue-116-pypsa-sienna-dispatchable-capacityfrom
issue-116-plexos-expansion-and-years
Open

thomaschristopherking wants to merge 1 commit into
issue-116-pypsa-sienna-dispatchable-capacityfrom
issue-116-plexos-expansion-and-years

Conversation

@thomaschristopherking

@thomaschristopherking thomaschristopherking commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

TL;DR

A PLEXOS model states what it may build in Build Cost, Max Units Built, WACC,
Economic Life, Technical Life and FO&M Charge, and states when each unit arrives and
leaves in a dated Units property. The reader named none of them, so the AEMO 2024 ISP file's
8,539 Generator.Build Cost rows, 372 objects with a Max Units Built and 604 dated
generator rows all stopped here. Max Units Built above zero now makes an object a candidate,
and a candidate becomes an extendable PyPSA component carrying what building it costs.

Second of five for transition-zero/tz-infra-interop#116,
stacked on #17.

Files to review (27, +1679 / -289):

File Why
docs/specs/2026-09-08-plexos-sienna-investments-design.md (start here) The approved design for the whole series, and why the annuity stays PyPSA's.
interop/plugins/shared/plexos_pypsa_translations/_expansion.py (new) What an object may build, and what building it costs.
interop/plugins/shared/plexos_pypsa_translations/_lifespan.py (new) Walks the dated Units bands for the build year and the retirement year.
interop/plugins/sources/plexos_dated_properties.py Stages every date band beside the value in force.
interop/plugins/shared/plexos_pypsa_translations/_storage_shared.py The same candidate rule for a Battery and a pumped-storage turbine.
docs/translation_mappings/translation-from-plexos-to-pypsa.md The authoritative mapping: What a candidate is, and the dated-property rules.

Why

A capacity-expansion plan is most of what a PLEXOS model says. Before this, an object with no
units in service looked retired and a dated property collapsed to the single year being
translated, so a user could translate the AEMO ISP and get a network that could not answer
the question the ISP exists to answer.

How

A candidate is an object with something left to build. Its capacity is one unit's rated
power times Max Units Built, not the zero its Units states, because PyPSA reads p_nom
for every per-unit field on the component. One rule drives the six expansion fields, and
PyPSA works the annuity out from overnight_cost, discount_rate and lifetime rather than
the translator assembling a capital cost of its own.

A build nobody prices is not a build. A candidate that states no Build Cost, WACC or
Economic Life, or states a Build Cost or Economic Life of zero, would be built for free
or priced as a perpetuity. An object that already runs keeps the capacity it runs and loses
only its build; one with nothing running is left out entirely. A WACC of zero is kept: it
is the rate of a model that does not discount.

Three values travel in the extensions sidecar because PyPSA has no column for them. The
yearly FO&M Charge is the interesting one: PyPSA reads fom_cost as a charge for the whole
modelled horizon and adds it to the annuitised build cost without scaling, so writing a yearly
number there prices a two-day run as if it lasted a year.

Reviewer notes

  • The dated years are not a separable commit. The generator and storage derivation takes a
    Lifespan alongside the candidate it rates, and seven modules read both. Splitting them
    would mean inventing an intermediate state that never existed, so they arrive together.
  • A candidate's static Rating reads against one unit. PLEXOS states a Rating per unit,
    as it states Max Capacity. A generator that runs has a p_nom of however many units it
    runs, so the two agree; a candidate has a p_nom of every unit it may build, so its
    Rating derates one unit's Max Capacity and is held at full availability.
  • Focus area: derive_expansion and find_unpriced_candidate in _expansion.py. Those
    two decide which objects reach the network at all.

Tests

608 scenarios pass, and all 17 pre-commit hooks.

uv run pytest -q

Open workspace in Conductor

A PLEXOS model states what it may build in `Build Cost`, `Max Units Built`,
`WACC`, `Economic Life`, `Technical Life` and `FO&M Charge`, and states when each
unit arrives and leaves in a dated `Units` property. The reader named none of
them. The AEMO 2024 ISP file alone carries 8,539 `Generator.Build Cost` rows, 372
objects with a `Max Units Built` and 604 dated generator rows, and all of it
stopped here.

`Max Units Built` above zero makes an object a candidate. A candidate's capacity
is one unit's rated power times `Max Units Built` rather than the zero its `Units`
states, and that one rule drives `p_nom_extendable`, `p_nom_min`, `p_nom_max`,
`overnight_cost`, `discount_rate` and `lifetime`. PyPSA works the annuity out
from the last three, so the translator assembles no capital cost of its own.

Three values have no PyPSA column, so they travel in the extensions sidecar: the
size of one unit, the technology lifetime, and the yearly `FO&M Charge`. PyPSA
reads `fom_cost` as a charge for the whole modelled horizon and adds it without
scaling, so a yearly number written there prices a two-day run as if it lasted a
year.

`stage_plexos_xml` stages every date band beside the value in force rather than
narrowing each dated property to the year being translated, and `_lifespan` walks
those bands for the build year and the retirement year.

The dated years and the expansion plan arrive together because they are one
reading, not two: the generator and storage derivation takes a `Lifespan`
alongside the candidate it rates, and seven modules read both.

Signed-off-by: Thomas C. King <thomas.k@transitionzero.org>
@thomaschristopherking
thomaschristopherking requested a review from a team as a code owner September 11, 2026 09:11
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: b791acdb-4260-433f-b7a2-8dacd00b2402

📥 Commits

Reviewing files that changed from the base of the PR and between 1248426 and 8f25bc9.

📒 Files selected for processing (27)
  • docs/specs/2026-09-08-plexos-sienna-investments-design.md
  • docs/translation_mappings/translation-from-plexos-to-pypsa.md
  • interop/core/extensions.py
  • interop/plugins/shared/constants.py
  • interop/plugins/shared/plexos_constants.py
  • interop/plugins/shared/plexos_dates.py
  • interop/plugins/shared/plexos_pypsa_translations/_batteries.py
  • interop/plugins/shared/plexos_pypsa_translations/_expansion.py
  • interop/plugins/shared/plexos_pypsa_translations/_generator_decisions.py
  • interop/plugins/shared/plexos_pypsa_translations/_generator_derivation.py
  • interop/plugins/shared/plexos_pypsa_translations/_generator_lookups.py
  • interop/plugins/shared/plexos_pypsa_translations/_generators.py
  • interop/plugins/shared/plexos_pypsa_translations/_lifespan.py
  • interop/plugins/shared/plexos_pypsa_translations/_shared.py
  • interop/plugins/shared/plexos_pypsa_translations/_storage_hydro.py
  • interop/plugins/shared/plexos_pypsa_translations/_storage_shared.py
  • interop/plugins/shared/plexos_pypsa_translations/_storage_units.py
  • interop/plugins/shared/plexos_pypsa_translations/constants.py
  • interop/plugins/shared/plexos_pypsa_translations/decisions.py
  • interop/plugins/shared/plexos_units.py
  • interop/plugins/shared/pypsa_constants.py
  • interop/plugins/sinks/emit_pypsa_network.py
  • interop/plugins/sources/plexos_dated_properties.py
  • interop/plugins/sources/stage_plexos_xml.py
  • tests/features/plexos_to_pypsa/dated_properties.feature
  • tests/features/plexos_to_pypsa/generators.feature
  • tests/features/plexos_to_pypsa/storage_units.feature

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant