From e652b35f714cc4171f338ba178ded15e38d2161c Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Wed, 9 Sep 2026 02:22:07 +0000 Subject: [PATCH 1/9] Tell an AEMO reader how to run the expansion path The case study translated the ISP model to PyPSA and to a Sienna system, and said nothing about the portfolio the third pipeline writes. It now has an expansion section beside the Sienna one: the mappings rows the candidate categories need, the prompts the chain asks in the order it asks them, and the four files the run writes. The run states no measured number. The AEMO model is not in this repository and nobody has counted a portfolio written from it, so the section says as much and points the reader at the decisions.md of their own run. It also says what the chain cannot ask: a chained pipeline prompts for its first leg's source and its last leg's sinks only, so the base year is 2020 unless the two legs are run separately. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas C. King --- docs/case_studies/aemo-isp-2024.md | 92 ++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/docs/case_studies/aemo-isp-2024.md b/docs/case_studies/aemo-isp-2024.md index 38fcd53..42e9eec 100644 --- a/docs/case_studies/aemo-isp-2024.md +++ b/docs/case_studies/aemo-isp-2024.md @@ -158,6 +158,86 @@ Then select `solve`. Give the model type `sienna` and the system `linearised`, and the HiGHS defaults. Leave the time limit empty. For more data about these prompts, refer to [the solve tutorial](../tutorials/solve.md#sienna-path). +### The expansion path + +The same model also translates to a Sienna investments portfolio, which is what a partner +running an expansion in PowerSystemsInvestments.jl needs. That run writes the expansion +problem: the technologies the plan may build, the demand they meet, and the caps they run +under. It is a translation only. interop runs no expansion solve, so this path stops at the +files. This section covers the Step Change scenario. The other two follow the same steps. + +Write a second mappings file, `inputs/plexos_expansion_mappings.yaml`. It is the dispatch +file plus one row for each carrier a candidate takes. A candidate whose carrier the file does +not name is left out of the portfolio, and `decisions.md` names each one. + +The dispatch run above leaves seven categories out of its file, because a dispatch system has +no place for what they hold. The expansion run names the ones that hold candidate plants, +because those plants are what it is about. A generator takes the name of its `Fuel` where it +burns one and its category where it does not, so each of those categories takes one +`category` row: + +| `plexos_name` | `sienna_component_type` | `sienna_prime_mover_type` | +| --- | --- | --- | +| `2023 REZ NSW`, and the sibling category of each other state | `RenewableDispatch` | `WT` for a wind candidate, `PVe` for a solar one | +| `New Entrants NSW`, and the sibling category of each other state | `RenewableDispatch`, or `ThermalStandard` with the `sienna_fuel_type` of the fuel the plant burns | The prime mover of the plant: `WT`, `PVe`, `CC` or `CT` | +| `LTESA Projects`, `Policy Projects`, `VRET Projects` | The type of the plant the project builds | The prime mover of that plant | + +The translator reads no meaning from a category name, so give each row the type of the plant +that category holds in your copy of the model. A generator candidate becomes a +`SupplyTechnology`, and its `sienna_component_type` must be a type a generator becomes: +`ThermalStandard`, `RenewableDispatch` or `HydroDispatch`. A row that sends a generator's +carrier to `EnergyReservoirStorage` names a type the candidate never becomes, so the run +leaves that candidate out and `decisions.md` names it. + +A `Battery` candidate and a pumped-storage candidate need no row of their own. Each takes a +`storage_kind` row, and the mappings pipeline supplies one for all three storage kinds. + +Leave `REZ Augmentation` and `Group REZ Augmentation` out of this file as well. They are +transmission augmentations written as generators, and the portfolio holds no transport +technology, so a row for one states a plant your model does not mean. + +Select `translate`. Then give these answers: + +| Prompt | Answer | +| --- | --- | +| Source framework | `plexos` | +| Destination framework | `sienna` | +| Pipeline | `plexos-to-sienna-investments` | +| the PLEXOS `` input XML | `case_study_inputs/aemo-isp-2024/2024 ISP Step Change Model.xml` | +| which PLEXOS Model to translate | `Step Change` | +| a four-digit year such as 2026 | `2025` | +| the SiennaSchemas system.json | `outputs/system.json` | +| the SiennaSchemas portfolio document to write | `outputs/portfolio.json` | +| User mappings file | `inputs/plexos_expansion_mappings.yaml` | + +Keep the default at every other prompt. The two sinks also ask for the HDF5 companion, the +extensions sidecar, the JSON indent width, and the basenames the portfolio names its base +system and its time-series companion by. The mappings prompt comes last, after the file +prompts of both sinks. + +That run writes four files: the three the Sienna path writes, and `outputs/portfolio.json` +beside them. The portfolio names `system.json` in its `base_system_file`, so the two are read +together. The base system holds the fleet that already runs, and a build the plan has yet to +decide is in the portfolio and not in it. + +Every cost in a portfolio is quoted in a base year, and no PLEXOS field states one. A chained +pipeline prompts for the source of its first leg and the sinks of its last, and for no step in +between, so this run states the default base year of 2020. To state another one, run the two +legs yourself: `plexos-to-pypsa`, then `pypsa-to-sienna-investments` over the network and the +sidecar it wrote. That pipeline is one leg, so it prompts for its steps, and the base year is +the `base_year` of `step[2]`. + +The counts for this path are not measured. The sections above give counts from a real run of +the PyPSA path and the Sienna path; nobody has yet recorded how many technologies, demand +requirements and caps the portfolio holds, or how many candidates each rule leaves out. The +`decisions.md` of your own run reports both: each component it left out, with the reason, and +each source field it did not map. + +[The mapping document](../translation_mappings/translation-from-plexos-to-sienna-investments.md) +states what each field of the portfolio comes from, and +[the gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the +portfolio leaves out and what each loss does to an expansion. + ## The headline number **What you can check by yourself.** All three scenarios translate. All three solves give @@ -227,6 +307,14 @@ The Sienna path also keeps no reserves file at all, because the first leg of the that file inside the run's scratch space. Run `plexos-to-pypsa` on its own if you want the reserves. +**The expansion path states no number of its own.** interop writes the portfolio and stops +there. No solve in this repository reads one, so nothing here says what a plan would build or +what it would cost. The portfolio also states one expansion problem and no schedule: it +carries no investment periods and no representative days, and it holds no transport +technology, so a plan built from it cannot build transmission. +[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) lists everything +else the portfolio leaves out. + A solve keeps no reserve headroom. Thus the dispatch is less constrained than the dispatch in the source model. @@ -250,3 +338,7 @@ network model `dcp`. Use `copperplate` for a faster answer that ignores the line The result tables are 440 MB on disk. The solve also writes `problem_results.bin`, which is 330 MB. + +The expansion run reads the same model over the same year as the Sienna path and writes one +more document beside the three files. It runs no solve, so none of the solve compute above +applies to it. From 699ed85ef411c6f17593f70130513e81f009b277 Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Wed, 9 Sep 2026 02:22:14 +0000 Subject: [PATCH 2/9] Say what an investments portfolio leaves out of an expansion The gap analysis covered dispatch fidelity alone. It now carries seven more entries for the portfolio: a constraint over part of the model, a candidate whose build nothing prices, the transmission no technology can build, the requirements a technology names, the target year and the intensity limit a carbon cap has no source for, a storage build priced by its discharge alone, and the periods and representative days that belong to whoever solves the portfolio. Where the subject is an expansion the fourth heading reads "The effect on the expansion", and the header table says so. Three documents claimed a scope the repository has outgrown. The PLEXOS to Sienna page said the translator does not translate capacity expansion, the PLEXOS to PyPSA page called line expansion out of scope because version 1 was dispatch only, and the PyPSA to Sienna page put a genuine expansion problem in the future. Each now points at the investments document, which the README's documentation list also names. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas C. King --- README.md | 1 + .../plexos-to-sienna-gap-analysis.md | 159 +++++++++++++++++- .../translation-from-plexos-to-pypsa.md | 4 +- ...ation-from-plexos-to-sienna-investments.md | 4 +- .../translation-from-plexos-to-sienna.md | 13 +- .../translation-from-pypsa-to-sienna.md | 2 +- 6 files changed, 173 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 52e5bd3..f9f1325 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,7 @@ Use cases live under `interop/core/use_cases/`, each implementing a Protocol por - `docs/translation_mappings/translation-from-pypsa-to-sienna.md` is the authoritative mapping reference for PyPSA / Sienna field translation. - `docs/translation_mappings/translation-from-plexos-to-pypsa.md` states what each part of a PLEXOS model becomes in PyPSA. - `docs/translation_mappings/translation-from-plexos-to-sienna.md` states the same for Sienna, and `plexos-to-sienna-gap-analysis.md` beside it states what that translation loses and what each loss does to a dispatch. +- `docs/translation_mappings/translation-from-plexos-to-sienna-investments.md` states what a PLEXOS expansion plan becomes in a Sienna investments portfolio, which `plexos-to-sienna-investments` writes beside the base system. - `docs/developer_documentation/mutation-testing.md` covers the mutmut workflow. - `tests/features/README.md` explains the in-process REPL-driven BDD harness and when subprocess is needed. diff --git a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md index bb13c66..09d212f 100644 --- a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md +++ b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md @@ -2,10 +2,15 @@ This document lists each thing a `plexos-to-sienna` run loses, and what that loss does to a dispatch. It covers the translation and the validation run that proves the system solves in -PowerSimulations.jl. +PowerSimulations.jl. The entries from +[A `Constraint` over part of the model](#a-constraint-over-part-of-the-model) onwards cover +the `plexos-to-sienna-investments` run instead, which writes an expansion portfolio beside a +base system of its own. For what the translation keeps, refer to -[Translation from PLEXOS to Sienna](translation-from-plexos-to-sienna.md). +[Translation from PLEXOS to Sienna](translation-from-plexos-to-sienna.md), and for the +portfolio to +[Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md). Each entry gives four things: @@ -16,6 +21,10 @@ Each entry gives four things: | The cause | Why | | The effect on the dispatch | What the solve then does differently from your PLEXOS model | +Where the subject is an expansion rather than a dispatch, the fourth heading reads **The +effect on the expansion**: what a plan built from the portfolio does differently from the +expansion your PLEXOS model states. + --- ## Reserve requirements @@ -350,3 +359,149 @@ does not read yet. **The effect on the dispatch.** None. The number is in the solve output; no report collects it for you. + +--- + +## A `Constraint` over part of the model + +**The PLEXOS data.** A `Constraint` holding a weighted sum over the objects it names to a +right-hand side: an emission cap over a group of plants, a target over one region, a budget +over one technology. + +**What happens to it.** The portfolio writes a `CarbonCaps` only for a constraint whose +members cover every generator and storage object the base system and the portfolio hold. A +constraint naming fewer is left out, `decisions.md` names it, and the log warns. Every +constraint still reaches the `extensions.json` sidecar, whether or not it became a cap. + +**The cause.** `CarbonCaps` names no members and no region: a cap in a portfolio holds the +whole portfolio. A cap written from a constraint over part of the model would hold every +technology in the problem, which is a different limit from the one your model states. + +**The effect on the expansion.** Nothing bounds the group the constraint names, so the plan +may build and run those objects up to their own limits. A model whose targets are all +regional or technology-scoped reaches the portfolio with no cap at all. Read the constraints +in the sidecar before you trust what the plan builds. + +--- + +## A candidate whose build nothing prices + +**The PLEXOS data.** A `Generator` or a `Battery` stating `Max Units Built` but not all of +`Build Cost`, `WACC` and `Economic Life`. + +**What happens to it.** The PLEXOS leg leaves the build out. An object with nothing running +yet is its build and nothing else, so the whole object goes and `decisions.md` names it. An +object that already runs keeps the capacity it runs, and only the build it may add is +dropped. The second leg drops a candidate that reaches it with no finite upper bound on +capacity, no finite lifetime, no overnight cost or no discount rate, and names each one the +same way. + +**The cause.** PyPSA annuitises an overnight cost with a discount rate over a lifetime, and +refuses a network that states one of the three without the others. A Sienna technology states +its own price and its own financing, and `TechnologyFinancialData` requires both a return on +equity and a capital recovery period. + +**The effect on the expansion.** That candidate is not in the portfolio, so no plan built +from it can build that technology. The alternative is worse: a candidate whose build nothing +prices would be built for free, at whatever size its limits allow. + +--- + +## Transmission a plan may build + +**The PLEXOS data.** A `Line` or a transformer the plan may expand, and the `Line.Type` that +says which technology LT Plan expands it with. + +**What happens to it.** Neither leg writes an expandable branch. The base system holds each +line and each link at the rating it already has, and the portfolio holds no technology for a +corridor. + +**The cause.** SiennaSchemas states transport technologies of its own, and this translation +writes none of them. The PLEXOS leg fixes the capacity of every line and link, so nothing +reaches the hub for a second leg to read either. + +**The effect on the expansion.** The network is fixed. A plan can put new capacity only where +the corridors that already exist can carry it, so it builds nearer to the demand than your +model would, and an expansion your model meets by reinforcing a corridor is met by generation +or not at all. + +--- + +## The requirements a technology names + +**The PLEXOS data.** The objects a `Constraint` names, and the coefficient weighting each +one. + +**What happens to it.** The portfolio writes no `requirements` on anything. Every +`SupplyTechnology`, `StorageTechnology` and `DemandRequirement` leaves the list empty, and +`decisions.md` records the field as not mapped. + +**The cause.** `requirements` holds the ids of the requirements a component is subject to. +The only requirement this translation writes is a `CarbonCaps` that holds the whole +portfolio, so it names no members and no component names it. SiennaSchemas states other +requirement types beside it, and this translation writes none of them. + +**The effect on the expansion.** A consumer that applies a requirement to the technologies +naming it applies nothing to any of them. Nothing is lost for the one cap the portfolio can +hold, since that cap applies to the whole problem by its own definition. A narrower target +has no way to reach a technology at all. + +--- + +## The year a cap applies in, and a cap on carbon intensity + +**The PLEXOS data.** A `Constraint` right-hand side, stated for a year or over the whole +horizon, and the span it applies over. + +**What happens to it.** The yearly right-hand side becomes `max_mtons`, the cap's limit in +million tonnes, and the horizon-wide one is read where the constraint states no yearly limit. +`CarbonCaps.target_year` and `CarbonCaps.max_tons_mwh` are left unmapped and are absent from +the document; `decisions.md` records both. + +**The cause.** PLEXOS states the span a right-hand side applies over, not the year it applies +in, and it has no rate-based right-hand side for `max_tons_mwh` to carry. + +**The effect on the expansion.** The cap states no year, so it is the limit of the whole +problem the consumer solves, whichever year your model stated it for. With `max_tons_mwh` +absent the consumer applies the schema's own default of 100000000 Mt/MWh, which no plan can +reach, so nothing limits the carbon intensity of what it builds. + +--- + +## A storage build that prices only its discharge + +**The PLEXOS data.** The `Build Cost` of a `Battery`, or of a pumped-storage turbine, which +prices the unit by its power. + +**What happens to it.** It becomes the technology's `capital_costs.discharge_capital_cost`. +The `charge_capital_cost` and the `energy_capital_cost` beside it are written as zero curves. + +**The cause.** A Sienna storage technology adds charge power, discharge power and energy +independently and prices each of the three. PLEXOS prices the object by its power alone, and +PyPSA carries one overnight cost for a storage unit, so neither states the other two prices. + +**The effect on the expansion.** A consumer that sizes the three parts separately takes the +largest energy and the largest charging power its limits allow for nothing, because nothing +prices either. The whole price of a storage build sits on its discharge capacity, which is +the right number only if you read the source's build cost as the price of a whole unit. + +--- + +## The years a plan steps through + +**The PLEXOS data.** The investment periods of an LT Plan, the years it steps through, and +the representative days and weights it samples each year with. + +**What happens to it.** Neither reaches the portfolio. The portfolio states one expansion +problem, with no schedule of periods and no representative-day weighting. + +**The cause.** A portfolio document holds the technologies, the requirements and the regions +of an expansion problem. The periods and the representative days are terms of the solve, so +they belong on the request that solves the portfolio rather than in it. interop runs no +expansion solve to put them on: its `solve` command runs PyPSA and PowerSimulations.jl, and +both dispatch a fixed fleet. + +**The effect on the expansion.** Whoever solves the portfolio chooses the periods and the +sampling. A plan built over a different set of years, or against a different set of +representative days, from the ones your PLEXOS model uses builds a different fleet, so its +result and your model's LT Plan result are not the same quantity. diff --git a/docs/translation_mappings/translation-from-plexos-to-pypsa.md b/docs/translation_mappings/translation-from-plexos-to-pypsa.md index 9c1b6ea..883dd61 100644 --- a/docs/translation_mappings/translation-from-plexos-to-pypsa.md +++ b/docs/translation_mappings/translation-from-plexos-to-pypsa.md @@ -160,7 +160,9 @@ mark of an HVDC line or of a trade path. The translator does **not** read the `Line.Type` property. That property sets the technology that LT Plan uses when it expands a line: `0` for AC and `1` for DC. It does not -set the operation of a line that exists. Version 1 translates dispatch only. +set the operation of a line that exists. The translator writes no expandable line either: +only a `Generator`, a `Battery` and a pumped-storage turbine become extendable components. +Refer to [What a candidate is](#what-a-candidate-is). A line must have a `Node From` membership and a `Node To` membership. If a line does not have both, it connects nothing. The translator does not write it, and it records this. diff --git a/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md b/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md index 61ad925..109426c 100644 --- a/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md +++ b/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md @@ -7,7 +7,9 @@ portfolio. It gives the source of each field. > build, not when it builds it: it does not carry the investment periods, the representative > days or their weights. It does not translate an expansion of a transmission line, and it > writes no aggregation of candidates by category. Refer to -> [Not translated](#not-translated). +> [Not translated](#not-translated) and to +> [the gap analysis](plexos-to-sienna-gap-analysis.md), which states what each loss does to an +> expansion. One run of `plexos-to-sienna-investments` writes **two documents**. `system.json` is the base power system: the fleet that already runs. `portfolio.json` is the expansion problem: the diff --git a/docs/translation_mappings/translation-from-plexos-to-sienna.md b/docs/translation_mappings/translation-from-plexos-to-sienna.md index 30aa452..0f06650 100644 --- a/docs/translation_mappings/translation-from-plexos-to-sienna.md +++ b/docs/translation_mappings/translation-from-plexos-to-sienna.md @@ -3,12 +3,15 @@ This document tells you what each part of your PLEXOS model becomes in the Sienna system. It gives the source of each field. -> **Scope:** the translator accepts electricity-only models, and it translates them for -> dispatch. It does not translate capacity expansion, custom constraints or hydro cascades. -> It does not carry the reserves to a file you keep. Refer to -> [Not translated](#not-translated) and to +> **Scope:** the translator accepts electricity-only models, and the `plexos-to-sienna` +> pipeline translates them for dispatch. It writes no capacity expansion: what your plan may +> build becomes a Sienna investments portfolio instead, which the +> `plexos-to-sienna-investments` pipeline writes. Refer to +> [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md). +> This pipeline does not translate custom constraints or hydro cascades, and it does not +> carry the reserves to a file you keep. Refer to [Not translated](#not-translated) and to > [the gap analysis](plexos-to-sienna-gap-analysis.md), which states what each loss does to a -> dispatch. +> dispatch and what the portfolio leaves out of an expansion. The `plexos-to-sienna` pipeline runs through a PyPSA network on the way. This document does not describe that network. It states the mapping as one step, because that is what you give diff --git a/docs/translation_mappings/translation-from-pypsa-to-sienna.md b/docs/translation_mappings/translation-from-pypsa-to-sienna.md index b759cc5..f094cd6 100644 --- a/docs/translation_mappings/translation-from-pypsa-to-sienna.md +++ b/docs/translation_mappings/translation-from-pypsa-to-sienna.md @@ -79,7 +79,7 @@ This document targets the SiennaSchemas **Operations** namespace (the dispatchab - It adds policy `Requirements` (`CarbonCaps`, `CarbonTax`, `CapacityReserveMargin`, `EnergyShareRequirements`, …) and `Financials`. - It bridges to Operations through each technology's `power_systems_type` (the Operations type it realises into) and `TopologyMapping` (region → buses). -**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. It is **out of scope for v1**. For v1 we translate the **solved** fleet: take `p_nom_opt` (post-solve capacities) as fixed Operations capacity and drop the expansion parameters (or keep round-trip crumbs in the `ext` sidecar). A genuine expansion problem is the future Investments path. +**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. It is **out of scope for v1**. For v1 we translate the **solved** fleet: take `p_nom_opt` (post-solve capacities) as fixed Operations capacity and drop the expansion parameters (or keep round-trip crumbs in the `ext` sidecar). A genuine expansion problem is the Investments path, which the `pypsa-to-sienna-investments` pipeline now writes as a portfolio document beside the operations system; [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states what that portfolio holds, and that pipeline is its second leg. Throughout this document, **`effective_p_nom`** denotes the capacity an operations model may dispatch: `p_nom_opt` where an extendable component states one, `p_nom_min` where it states a capacity a build cannot take away, and `p_nom` otherwise. All capacity-derived fields — `base_power`, `active_power`, `active_power_limits`, `ramp_limits`, and the hydro energy budget scaling factor — use `effective_p_nom`. For a non-extendable component `effective_p_nom = p_nom`. A solve that builds none of an extendable component writes `p_nom_opt` 0, and that 0 is the capacity to translate: the plan rejected the build. A network no solve has touched leaves `p_nom_opt` out of the file altogether, and an extendable component there is rated at the `p_nom_min` it already runs. An extendable component with neither a `p_nom_opt` nor a `p_nom_min` is a candidate rather than a plant: the generator, renewable, hydro and storage mappings leave it out and record it as skipped. From 84d1fd4d1b50719357c5247cbe2b5416c16e169c Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Wed, 9 Sep 2026 02:46:11 +0000 Subject: [PATCH 3/9] Say what the expansion documents actually claim The gap analysis now splits its entries into a section per run, so an entry carries the run it covers in the heading above it rather than in its position in the file. The pointer sentence that named the first expansion entry is gone, and every entry heading drops one level under its section. The constraint entry named one of the three rules that keep a constraint out of the portfolio. It now names all three, and its heading covers the wider subject. Two causes stated something the code does not do: PyPSA refuses a network that prices a build with no discount rate, prices one with no lifetime as a perpetuity, and takes one with no overnight cost for free, and interop's solve command reads no portfolio rather than dispatching a fixed fleet in both directions. The case study now names the fourth Sienna type a generator candidate can take, and the carrier mappings file in PyPSA words that a lone pypsa-to-sienna- investments run asks for and no chain derives for it. The paragraphs stating that nobody measured the expansion path, that the expansion run costs no extra compute, and that a candidate category is what the run is about are gone, as is the second copy of what the portfolio leaves out. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas C. King --- docs/case_studies/aemo-isp-2024.md | 39 +++---- .../plexos-to-sienna-gap-analysis.md | 108 ++++++++++-------- 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/docs/case_studies/aemo-isp-2024.md b/docs/case_studies/aemo-isp-2024.md index 42e9eec..8634630 100644 --- a/docs/case_studies/aemo-isp-2024.md +++ b/docs/case_studies/aemo-isp-2024.md @@ -171,10 +171,9 @@ file plus one row for each carrier a candidate takes. A candidate whose carrier not name is left out of the portfolio, and `decisions.md` names each one. The dispatch run above leaves seven categories out of its file, because a dispatch system has -no place for what they hold. The expansion run names the ones that hold candidate plants, -because those plants are what it is about. A generator takes the name of its `Fuel` where it -burns one and its category where it does not, so each of those categories takes one -`category` row: +no place for what they hold. The expansion run names the ones that hold candidate plants. A +generator takes the name of its `Fuel` where it burns one and its category where it does not, +so each of those categories takes one `category` row: | `plexos_name` | `sienna_component_type` | `sienna_prime_mover_type` | | --- | --- | --- | @@ -185,9 +184,9 @@ burns one and its category where it does not, so each of those categories takes The translator reads no meaning from a category name, so give each row the type of the plant that category holds in your copy of the model. A generator candidate becomes a `SupplyTechnology`, and its `sienna_component_type` must be a type a generator becomes: -`ThermalStandard`, `RenewableDispatch` or `HydroDispatch`. A row that sends a generator's -carrier to `EnergyReservoirStorage` names a type the candidate never becomes, so the run -leaves that candidate out and `decisions.md` names it. +`ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` or `HydroDispatch`. A row that +sends a generator's carrier to `EnergyReservoirStorage` names a type the candidate never +becomes, so the run leaves that candidate out and `decisions.md` names it. A `Battery` candidate and a pumped-storage candidate need no row of their own. Each takes a `storage_kind` row, and the mappings pipeline supplies one for all three storage kinds. @@ -225,13 +224,10 @@ pipeline prompts for the source of its first leg and the sinks of its last, and between, so this run states the default base year of 2020. To state another one, run the two legs yourself: `plexos-to-pypsa`, then `pypsa-to-sienna-investments` over the network and the sidecar it wrote. That pipeline is one leg, so it prompts for its steps, and the base year is -the `base_year` of `step[2]`. - -The counts for this path are not measured. The sections above give counts from a real run of -the PyPSA path and the Sienna path; nobody has yet recorded how many technologies, demand -requirements and caps the portfolio holds, or how many candidates each rule leaves out. The -`decisions.md` of your own run reports both: each component it left out, with the reason, and -each source field it did not map. +the `base_year` of `step[2]`. It asks for a mappings file of its own, in PyPSA words rather +than PLEXOS ones: the chain derives that file from the PLEXOS file above, and a lone run has +nothing to derive it from, so write a `carriers` file with a `pypsa_carrier` row for each +carrier the first leg wrote, the storage carriers included. [The mapping document](../translation_mappings/translation-from-plexos-to-sienna-investments.md) states what each field of the portfolio comes from, and @@ -307,13 +303,10 @@ The Sienna path also keeps no reserves file at all, because the first leg of the that file inside the run's scratch space. Run `plexos-to-pypsa` on its own if you want the reserves. -**The expansion path states no number of its own.** interop writes the portfolio and stops -there. No solve in this repository reads one, so nothing here says what a plan would build or -what it would cost. The portfolio also states one expansion problem and no schedule: it -carries no investment periods and no representative days, and it holds no transport -technology, so a plan built from it cannot build transmission. -[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) lists everything -else the portfolio leaves out. +**The expansion path states no number of its own.** No solve in this repository reads a +portfolio, so nothing here says what a plan would build or what it would cost. +[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the +portfolio leaves out and what each loss does to an expansion. A solve keeps no reserve headroom. Thus the dispatch is less constrained than the dispatch in the source model. @@ -338,7 +331,3 @@ network model `dcp`. Use `copperplate` for a faster answer that ignores the line The result tables are 440 MB on disk. The solve also writes `problem_results.bin`, which is 330 MB. - -The expansion run reads the same model over the same year as the Sienna path and writes one -more document beside the three files. It runs no solve, so none of the solve compute above -applies to it. diff --git a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md index 09d212f..99ed716 100644 --- a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md +++ b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md @@ -1,11 +1,7 @@ # What a PLEXOS to Sienna translation loses -This document lists each thing a `plexos-to-sienna` run loses, and what that loss does to a -dispatch. It covers the translation and the validation run that proves the system solves in -PowerSimulations.jl. The entries from -[A `Constraint` over part of the model](#a-constraint-over-part-of-the-model) onwards cover -the `plexos-to-sienna-investments` run instead, which writes an expansion portfolio beside a -base system of its own. +This document lists each thing a PLEXOS to Sienna run loses. Each part below names the run it +covers. For what the translation keeps, refer to [Translation from PLEXOS to Sienna](translation-from-plexos-to-sienna.md), and for the @@ -27,7 +23,12 @@ expansion your PLEXOS model states. --- -## Reserve requirements +## What `plexos-to-sienna` loses from a dispatch + +These entries cover the translation and the validation run that proves the system solves in +PowerSimulations.jl. + +### Reserve requirements **The PLEXOS data.** Each `Reserve` object, its type, its requirement in MW or as a share of a profile, and the generators that can provide it. @@ -46,7 +47,7 @@ model, and a scarcity price your model shows does not appear. --- -## Load shedding, on a plain run +### Load shedding, on a plain run **The PLEXOS data.** Each Region states a `VoLL`, the value of lost load. @@ -64,7 +65,7 @@ than a shortfall in MWh. Run the reliability chain to get the shortfall in MWh i --- -## Unit commitment, relaxed +### Unit commitment, relaxed **The PLEXOS data.** A start cost, a minimum up time and a minimum down time on each thermal generator. @@ -87,7 +88,7 @@ two paths do not compare under that answer. --- -## Thermal availability where the model states none +### Thermal availability where the model states none **The PLEXOS data.** An `Outage Factor`, an `Outage Rating`, a `Rating` profile or a `Units Out` profile on some of the thermal fleet, and nothing on the rest. @@ -107,7 +108,7 @@ number. --- -## A hydro unit whose inflow is water, not power +### A hydro unit whose inflow is water, not power **The PLEXOS data.** A reservoir hydro turbine with a `Natural Inflow` stated in cumec or in m³/day, or with no `Natural Inflow` at all. @@ -126,7 +127,7 @@ The AEMO 2024 ISP states every inflow in cumec, so its whole reservoir fleet is --- -## A storage unit that states no energy +### A storage unit that states no energy **The PLEXOS data.** A `Battery` with no `Capacity` and no `Duration`, or a pumped storage head reservoir whose `Max Volume` is in water. @@ -142,7 +143,7 @@ across the hours it would have covered. --- -## A generator that is not a power plant +### A generator that is not a power plant **The PLEXOS data.** A generator whose category names a transmission augmentation, a policy project or another pseudo-object rather than a technology. @@ -159,7 +160,7 @@ each run. --- -## Heat rate bands +### Heat rate bands **The PLEXOS data.** A `Heat Rate` stated as several bands, so the efficiency changes with output. @@ -177,7 +178,7 @@ differently. --- -## A generator that burns more than one fuel +### A generator that burns more than one fuel **The PLEXOS data.** Several `Fuels` memberships on one generator. @@ -192,7 +193,7 @@ price. A dual-fuel unit that your model switches to a cheaper fuel does not swit --- -## A Fuel and a generator category of one name +### A Fuel and a generator category of one name **The PLEXOS data.** A `Fuel` and a generator category that share a name, for example a fuel `HVO` and a category `HVO`. @@ -210,7 +211,7 @@ groups of generators then take that type. --- -## Zones, interfaces and custom constraints +### Zones, interfaces and custom constraints **The PLEXOS data.** `Zone` objects, `Interface` flow limits and `Constraint` objects, which include energy budgets, running hour limits, RPS targets and emission caps. @@ -233,7 +234,7 @@ the dispatch. --- -## Hydro cascades and volumes in water +### Hydro cascades and volumes in water **The PLEXOS data.** `Waterway` objects joining reservoirs, and `Max Volume` and `Initial Volume` stated in 1000 m³ or a `Natural Inflow` stated in cumec. @@ -252,7 +253,7 @@ number that depends on hydro. --- -## One solve, one window +### One solve, one window **The PLEXOS data.** A Horizon, which can be many years long. @@ -270,7 +271,7 @@ shorter year. --- -## Region Price of Dump Energy +### Region Price of Dump Energy **The PLEXOS data.** A Region `Price of Dump Energy`, the price of energy the system spills. @@ -285,7 +286,7 @@ objective. The Region `VoLL` beside it does reach a reliability run: refer to --- -## Sienna holds no Monte Carlo forecast a solve reads +### Sienna holds no Monte Carlo forecast a solve reads **The PLEXOS data.** A pre-sampled model states many values for one property at one snapshot, one per replication, and a run over it draws a distribution of outcomes. @@ -303,7 +304,7 @@ counts how many of them lose load. Solve the replications and count the outcomes --- -## A Sienna objective and a PyPSA objective do not compare +### A Sienna objective and a PyPSA objective do not compare **The PLEXOS data.** A Region `VoLL`, in a reliability run on both sides of the PyPSA hub. @@ -322,7 +323,7 @@ not the same quantity and must not be compared or subtracted. --- -## A profile that reaches only some replications +### A profile that reaches only some replications **The PLEXOS data.** An outage draw that takes a unit out in one replication and leaves it available through the whole window in another. @@ -343,7 +344,7 @@ holds. --- -## A reliability solve reports its unserved energy in the results files +### A reliability solve reports its unserved energy in the results files **The PLEXOS data.** The energy a window cannot serve, which a reliability run prices at the Region `VoLL`. @@ -362,29 +363,38 @@ for you. --- -## A `Constraint` over part of the model +## What `plexos-to-sienna-investments` loses from an expansion + +These entries cover the run that writes an expansion portfolio beside a base system of its +own. + +### A `Constraint` a cap cannot carry **The PLEXOS data.** A `Constraint` holding a weighted sum over the objects it names to a right-hand side: an emission cap over a group of plants, a target over one region, a budget over one technology. -**What happens to it.** The portfolio writes a `CarbonCaps` only for a constraint whose -members cover every generator and storage object the base system and the portfolio hold. A -constraint naming fewer is left out, `decisions.md` names it, and the log warns. Every -constraint still reaches the `extensions.json` sidecar, whether or not it became a cap. +**What happens to it.** The portfolio writes a `CarbonCaps` only for a constraint that holds +its weighted sum to `<=`, states a right-hand side for a year or for the whole horizon, and +names every generator and storage object the base system and the portfolio hold. Every other +constraint is left out, `decisions.md` names it, and the log warns. Every constraint still +reaches the `extensions.json` sidecar, whether or not it became a cap. **The cause.** `CarbonCaps` names no members and no region: a cap in a portfolio holds the whole portfolio. A cap written from a constraint over part of the model would hold every -technology in the problem, which is a different limit from the one your model states. +technology in the problem, which is a different limit from the one your model states. A cap +also states one ceiling over the whole run, so a constraint held to `>=` or to `==`, and a +constraint whose only right-hand side bounds a repeating window inside the run, give it +nothing to carry. -**The effect on the expansion.** Nothing bounds the group the constraint names, so the plan -may build and run those objects up to their own limits. A model whose targets are all -regional or technology-scoped reaches the portfolio with no cap at all. Read the constraints -in the sidecar before you trust what the plan builds. +**The effect on the expansion.** Nothing bounds what the constraint names, so the plan may +build and run those objects up to their own limits. A model whose targets are all regional, +technology-scoped, or written as a floor reaches the portfolio with no cap at all. Read the +constraints in the sidecar before you trust what the plan builds. --- -## A candidate whose build nothing prices +### A candidate whose build nothing prices **The PLEXOS data.** A `Generator` or a `Battery` stating `Max Units Built` but not all of `Build Cost`, `WACC` and `Economic Life`. @@ -396,18 +406,18 @@ dropped. The second leg drops a candidate that reaches it with no finite upper b capacity, no finite lifetime, no overnight cost or no discount rate, and names each one the same way. -**The cause.** PyPSA annuitises an overnight cost with a discount rate over a lifetime, and -refuses a network that states one of the three without the others. A Sienna technology states -its own price and its own financing, and `TechnologyFinancialData` requires both a return on -equity and a capital recovery period. +**The cause.** PyPSA annuitises an overnight cost with a discount rate over a lifetime. It +refuses a network that states an overnight cost and no discount rate, it prices a build with +no lifetime as a perpetuity, and it builds for free a candidate that states no overnight cost +at all. A Sienna technology states its own price and its own financing, and +`TechnologyFinancialData` requires both a return on equity and a capital recovery period. **The effect on the expansion.** That candidate is not in the portfolio, so no plan built -from it can build that technology. The alternative is worse: a candidate whose build nothing -prices would be built for free, at whatever size its limits allow. +from it can build that technology. --- -## Transmission a plan may build +### Transmission a plan may build **The PLEXOS data.** A `Line` or a transformer the plan may expand, and the `Line.Type` that says which technology LT Plan expands it with. @@ -427,7 +437,7 @@ or not at all. --- -## The requirements a technology names +### The requirements a technology names **The PLEXOS data.** The objects a `Constraint` names, and the coefficient weighting each one. @@ -448,7 +458,7 @@ has no way to reach a technology at all. --- -## The year a cap applies in, and a cap on carbon intensity +### The year a cap applies in, and a cap on carbon intensity **The PLEXOS data.** A `Constraint` right-hand side, stated for a year or over the whole horizon, and the span it applies over. @@ -468,7 +478,7 @@ reach, so nothing limits the carbon intensity of what it builds. --- -## A storage build that prices only its discharge +### A storage build that prices only its discharge **The PLEXOS data.** The `Build Cost` of a `Battery`, or of a pumped-storage turbine, which prices the unit by its power. @@ -487,7 +497,7 @@ the right number only if you read the source's build cost as the price of a whol --- -## The years a plan steps through +### The years a plan steps through **The PLEXOS data.** The investment periods of an LT Plan, the years it steps through, and the representative days and weights it samples each year with. @@ -497,9 +507,9 @@ problem, with no schedule of periods and no representative-day weighting. **The cause.** A portfolio document holds the technologies, the requirements and the regions of an expansion problem. The periods and the representative days are terms of the solve, so -they belong on the request that solves the portfolio rather than in it. interop runs no -expansion solve to put them on: its `solve` command runs PyPSA and PowerSimulations.jl, and -both dispatch a fixed fleet. +they belong on the request that solves the portfolio rather than in it. No solve in interop +reads a portfolio: its `solve` command runs PyPSA over a network or PowerSimulations.jl over a +system, and neither steps through investment periods. **The effect on the expansion.** Whoever solves the portfolio chooses the periods and the sampling. A plan built over a different set of years, or against a different set of From 1fb6989ad4a8fba424e47422c197bf066c2ace27 Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Wed, 9 Sep 2026 03:06:19 +0000 Subject: [PATCH 4/9] Say what the portfolio writes and when the CLI asks for the mappings The PyPSA to Sienna implication paragraph said the Investments target was out of scope and then named the pipeline that writes it. It now states the one thing this document does: it is the Operations translation, so it takes the post-solve capacity and drops the expansion parameters, and the investments pipeline translates the expansion problem beside it. The requirements entry in the gap analysis said every technology leaves the list empty. The sink keeps only the fields a table wrote, and no table writes requirements, so the field is absent from the document rather than written as an empty list. The carbon cap entry quoted the schema default for max_tons_mwh and argued that no plan can reach it; the sentence now says only what the reader can check, that nothing limits the carbon intensity of what a plan builds. The unpriced-candidate entry named a Generator and a Battery, and the same rule drops a pumped-storage turbine, so it names the turbine too. The four table headings were defined once in the table and again in the paragraph under it. One row now covers a dispatch and an expansion together. The AEMO case study asked for the user mappings file fourth, before the source and sink prompts. The REPL asks for it after every source, step and sink parameter, so the dispatch table now matches the expansion table below it and the run itself. The pointer to the gap analysis in the headline-number section was the same sentence the expansion section already carries, so it is gone. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas C. King --- docs/case_studies/aemo-isp-2024.md | 4 +--- .../plexos-to-sienna-gap-analysis.md | 19 +++++++------------ .../translation-from-pypsa-to-sienna.md | 2 +- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/docs/case_studies/aemo-isp-2024.md b/docs/case_studies/aemo-isp-2024.md index 8634630..4aacb10 100644 --- a/docs/case_studies/aemo-isp-2024.md +++ b/docs/case_studies/aemo-isp-2024.md @@ -121,11 +121,11 @@ Select `translate`. Then give these answers: | Source framework | `plexos` | | Destination framework | `sienna` | | Pipeline | `plexos-to-sienna` | -| User mappings file | `inputs/plexos_user_mappings.yaml` | | the PLEXOS `` input XML | `case_study_inputs/aemo-isp-2024/2024 ISP Step Change Model.xml` | | which PLEXOS Model to translate | `Step Change` | | a four-digit year such as 2026 | `2025` | | the SiennaSchemas system.json | `outputs/system.json` | +| User mappings file | `inputs/plexos_user_mappings.yaml` | Give the year `2025` here, unlike the PyPSA run above, which leaves the year empty. The chronology of the Model is 2024-07-01 to 2025-06-30, so the year 2025 narrows it to @@ -305,8 +305,6 @@ reserves. **The expansion path states no number of its own.** No solve in this repository reads a portfolio, so nothing here says what a plan would build or what it would cost. -[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the -portfolio leaves out and what each loss does to an expansion. A solve keeps no reserve headroom. Thus the dispatch is less constrained than the dispatch in the source model. diff --git a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md index 99ed716..122b799 100644 --- a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md +++ b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md @@ -15,11 +15,7 @@ Each entry gives four things: | The PLEXOS data | What your model states | | What happens to it | Where it goes, or that it goes nowhere | | The cause | Why | -| The effect on the dispatch | What the solve then does differently from your PLEXOS model | - -Where the subject is an expansion rather than a dispatch, the fourth heading reads **The -effect on the expansion**: what a plan built from the portfolio does differently from the -expansion your PLEXOS model states. +| The effect on the dispatch, or on the expansion | What the solve, or a plan built from the portfolio, then does differently from your PLEXOS model | --- @@ -396,8 +392,8 @@ constraints in the sidecar before you trust what the plan builds. ### A candidate whose build nothing prices -**The PLEXOS data.** A `Generator` or a `Battery` stating `Max Units Built` but not all of -`Build Cost`, `WACC` and `Economic Life`. +**The PLEXOS data.** A `Generator`, a `Battery` or a pumped-storage turbine stating `Max Units +Built` but not all of `Build Cost`, `WACC` and `Economic Life`. **What happens to it.** The PLEXOS leg leaves the build out. An object with nothing running yet is its build and nothing else, so the whole object goes and `decisions.md` names it. An @@ -442,9 +438,9 @@ or not at all. **The PLEXOS data.** The objects a `Constraint` names, and the coefficient weighting each one. -**What happens to it.** The portfolio writes no `requirements` on anything. Every -`SupplyTechnology`, `StorageTechnology` and `DemandRequirement` leaves the list empty, and -`decisions.md` records the field as not mapped. +**What happens to it.** The portfolio writes no `requirements`: the field is absent from every +`SupplyTechnology`, `StorageTechnology` and `DemandRequirement`, and `decisions.md` records it +as not mapped. **The cause.** `requirements` holds the ids of the requirements a component is subject to. The only requirement this translation writes is a `CarbonCaps` that holds the whole @@ -473,8 +469,7 @@ in, and it has no rate-based right-hand side for `max_tons_mwh` to carry. **The effect on the expansion.** The cap states no year, so it is the limit of the whole problem the consumer solves, whichever year your model stated it for. With `max_tons_mwh` -absent the consumer applies the schema's own default of 100000000 Mt/MWh, which no plan can -reach, so nothing limits the carbon intensity of what it builds. +absent, nothing limits the carbon intensity of what it builds. --- diff --git a/docs/translation_mappings/translation-from-pypsa-to-sienna.md b/docs/translation_mappings/translation-from-pypsa-to-sienna.md index f094cd6..86178e6 100644 --- a/docs/translation_mappings/translation-from-pypsa-to-sienna.md +++ b/docs/translation_mappings/translation-from-pypsa-to-sienna.md @@ -79,7 +79,7 @@ This document targets the SiennaSchemas **Operations** namespace (the dispatchab - It adds policy `Requirements` (`CarbonCaps`, `CarbonTax`, `CapacityReserveMargin`, `EnergyShareRequirements`, …) and `Financials`. - It bridges to Operations through each technology's `power_systems_type` (the Operations type it realises into) and `TopologyMapping` (region → buses). -**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. It is **out of scope for v1**. For v1 we translate the **solved** fleet: take `p_nom_opt` (post-solve capacities) as fixed Operations capacity and drop the expansion parameters (or keep round-trip crumbs in the `ext` sidecar). A genuine expansion problem is the Investments path, which the `pypsa-to-sienna-investments` pipeline now writes as a portfolio document beside the operations system; [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states what that portfolio holds, and that pipeline is its second leg. +**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. This document is the Operations translation, so it drops the expansion parameters: it takes `p_nom_opt` (post-solve capacities) as fixed Operations capacity, and keeps round-trip crumbs in the `ext` sidecar. The `pypsa-to-sienna-investments` pipeline translates the expansion problem itself, and writes a portfolio document beside the operations system. [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states what that portfolio holds; that pipeline is its second leg. Throughout this document, **`effective_p_nom`** denotes the capacity an operations model may dispatch: `p_nom_opt` where an extendable component states one, `p_nom_min` where it states a capacity a build cannot take away, and `p_nom` otherwise. All capacity-derived fields — `base_power`, `active_power`, `active_power_limits`, `ramp_limits`, and the hydro energy budget scaling factor — use `effective_p_nom`. For a non-extendable component `effective_p_nom = p_nom`. A solve that builds none of an extendable component writes `p_nom_opt` 0, and that 0 is the capacity to translate: the plan rejected the build. A network no solve has touched leaves `p_nom_opt` out of the file altogether, and an extendable component there is rated at the `p_nom_min` it already runs. An extendable component with neither a `p_nom_opt` nor a `p_nom_min` is a candidate rather than a plant: the generator, renewable, hydro and storage mappings leave it out and record it as skipped. From c68f64441e6aa3436438803c0543a6a619e4f8e8 Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Wed, 9 Sep 2026 03:16:24 +0000 Subject: [PATCH 5/9] Record what the review rounds found on the case-study branch Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas C. King --- code-review/ledger.jsonl | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/code-review/ledger.jsonl b/code-review/ledger.jsonl index 7a99bd9..40357e4 100644 --- a/code-review/ledger.jsonl +++ b/code-review/ledger.jsonl @@ -209,3 +209,35 @@ {"id": "r3-18", "pr": "11", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "interop/plugins/steps/pypsa_to_sienna_investments_map_technologies.py", "line": 246, "severity": "nit", "label": "nitpick", "claim": "`_Numbering.take` has a one-line body and one call site in the same module.", "suggested_fix": "Write `numbering.next_id += out.height` in `_write_table` and delete `take`. Net: -3 lines.", "rule_created": false, "ts": "2026-09-09T01:22:12Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r4-01", "pr": "11", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "interop/core/pipeline.py", "line": 78, "severity": "minor", "label": "suggestion", "claim": "This docstring calls the consumption tracker a \"record\", the same word it uses for the extension records that tracker counts.", "suggested_fix": "Keep the paragraph, which states a constraint the code cannot state, but name the thing it is talking about.\n\nThe code now:\n\n```python\n `consumed_extensions` is what the steps of this hop have read\n off `source_extensions`. Every step builds its reader through\n `extension_reader`, so one record covers the whole hop and the\n run can report what none of them asked for.\n \"\"\"\n```\n\nThe code to use:\n\n```python\n `consumed_extensions` is what the steps of this hop have read\n off `source_extensions`. Every step builds its reader through\n `extension_reader`, so one `ExtensionConsumption` covers the\n whole hop and the run can report the records no step asked for.\n \"\"\"\n```", "rule_created": false, "ts": "2026-09-09T01:50:05Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r4-02", "pr": "11", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "interop/plugins/shared/sienna_investments_constants.py", "line": 83, "severity": "nit", "label": "nitpick", "claim": "The three `REGION = SIENNA_REGION_COLUMN` attributes are never read, and they are the only users of the `SIENNA_REGION_COLUMN` import in this file.", "suggested_fix": "Delete `REGION = SIENNA_REGION_COLUMN` from `SiennaSupplyTechnologyCol` (line 83), `SiennaStorageTechnologyCol` (line 113) and `SiennaDemandRequirementCol` (line 146), and drop `SIENNA_REGION_COLUMN` from the import at line 22. Net: -4 lines.", "rule_created": false, "ts": "2026-09-09T01:50:05Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-01", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 5, "severity": "major", "label": "issue", "claim": "The document splits two pipelines by the position of one heading, not by a section of its own.", "suggested_fix": "Delete the pointer sentence and put the split in the heading levels.\n\nThe document now:\n\n```markdown\nThis document lists each thing a `plexos-to-sienna` run loses ... The entries from\n[A `Constraint` over part of the model](#a-constraint-over-part-of-the-model) onwards cover\nthe `plexos-to-sienna-investments` run instead ...\n\n## Reserve requirements\n...\n## A `Constraint` over part of the model\n```\n\nThe document to use:\n\n```markdown\nThis document lists each thing a PLEXOS to Sienna run loses. Each part below names the run\nit covers.\n\n## What `plexos-to-sienna` loses from a dispatch\n\n### Reserve requirements\n...\n\n## What `plexos-to-sienna-investments` loses from an expansion\n\n### A `Constraint` over part of the model\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-02", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 223, "severity": "major", "label": "issue", "claim": "The steps for a base year other than 2020 leave out the carrier mappings file the second leg asks for.", "suggested_fix": "Name the second file the reader must write.\n\nThe text to use:\n\n```markdown\nTo state another one, run the two legs yourself: `plexos-to-pypsa`, then\n`pypsa-to-sienna-investments` over the network and the sidecar it wrote. The second leg asks\nfor a carrier mappings file in PyPSA words, with a `pypsa_carrier` row for each carrier the\nfirst leg wrote, because no mappings pipeline runs beside a single leg. That pipeline is one\nleg, so it prompts for its steps, and the base year is the `base_year` of `step[2]`.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-03", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 187, "severity": "major", "label": "issue", "claim": "The list of types a generator candidate can take leaves out `RenewableNonDispatch`.", "suggested_fix": "Add the fourth type.\n\nThe text to use:\n\n```markdown\n`SupplyTechnology`, and its `sienna_component_type` must be a type a generator becomes:\n`ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` or `HydroDispatch`.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-04", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 371, "severity": "major", "label": "issue", "claim": "The entry names one of the three rules that leave a `Constraint` out of the portfolio.", "suggested_fix": "State all three rules, and widen the heading to match.\n\nThe text to use:\n\n```markdown\n## A `Constraint` a cap cannot carry\n\n**What happens to it.** The portfolio writes a `CarbonCaps` only for a constraint that\ncovers every generator and storage object the two documents hold, holds its weighted sum to\n`<=`, and states an `RHS Year` or an `RHS`. The run leaves out every other constraint,\n`decisions.md` names it, and the log warns. Every constraint still reaches the\n`extensions.json` sidecar, whether or not it became a cap.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-05", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 215, "severity": "minor", "label": "issue", "claim": "The new text states the mappings prompt comes last, and the dispatch table above it states the prompt comes fourth.", "suggested_fix": "Move the mappings row of the dispatch table to the end.\n\nThe table to use:\n\n```markdown\n| Pipeline | `plexos-to-sienna` |\n| the PLEXOS `` input XML | `case_study_inputs/aemo-isp-2024/2024 ISP Step Change Model.xml` |\n| which PLEXOS Model to translate | `Step Change` |\n| a four-digit year such as 2026 | `2025` |\n| the SiennaSchemas system.json | `outputs/system.json` |\n| User mappings file | `inputs/plexos_user_mappings.yaml` |\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-06", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 224, "severity": "major", "label": "issue", "claim": "The reader cannot run `pypsa-to-sienna-investments` with the PLEXOS mappings file the section tells them to write.", "suggested_fix": "```markdown\nTo state another one, run the two legs yourself: `plexos-to-pypsa`, then\n`pypsa-to-sienna-investments` over the network and the sidecar it wrote. The second leg is a\nPyPSA run, so it asks for a mappings file in PyPSA words (`pypsa_carrier`), not the PLEXOS\nfile above. The chain derives that file for you, and a lone run does not, so write one\nnaming each carrier the first leg gave a generator. That pipeline is one leg, so it prompts\nfor its steps, and the base year is the `base_year` of `step[2]`.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-07", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 186, "severity": "minor", "label": "issue", "claim": "The list of Sienna types a generator candidate may take leaves out `RenewableNonDispatch`, which the code accepts.", "suggested_fix": "```markdown\n`SupplyTechnology`, and its `sienna_component_type` must be a type a generator becomes:\n`ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` or `HydroDispatch`.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-08", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 399, "severity": "minor", "label": "issue", "claim": "PyPSA refuses a network only for a missing discount rate, not for a missing overnight cost or lifetime.", "suggested_fix": "```markdown\n**The cause.** PyPSA annuitises an overnight cost with a discount rate over a lifetime.\nWithout a discount rate it refuses the network; without a build cost nothing prices the\nbuild; without an economic life it annuitises across its default lifetime of infinity, which\nprices the build as a perpetuity. A Sienna technology states its own price and its own\nfinancing, and `TechnologyFinancialData` requires both a return on equity and a capital\nrecovery period.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-09", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 500, "severity": "minor", "label": "issue", "claim": "The PyPSA solve does not dispatch a fixed fleet: it sizes every extendable component the PLEXOS leg writes.", "suggested_fix": "```markdown\nthey belong on the request that solves the portfolio rather than in it. No solve in this\nrepository reads a portfolio: `solve` runs PyPSA over a network and PowerSimulations.jl over\na base system, and neither reads an investment period or a representative day.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-10", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 173, "severity": "nit", "label": "nitpick", "claim": "The dispatch section leaves out seven named categories and their siblings, so the count is higher than seven.", "suggested_fix": "```markdown\nThe dispatch run above leaves seven kinds of category, and the sibling of each, out of its\nfile, because a dispatch system has no place for what they hold.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-11", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 230, "severity": "major", "label": "suggestion", "claim": "This paragraph records a measurement nobody made. It explains an absence and it states a status.", "suggested_fix": "Delete the first two sentences. Keep the sentence that tells the reader where the counts come from.\n\nThe text now:\n\n```markdown\nThe counts for this path are not measured. The sections above give counts from a real run of\nthe PyPSA path and the Sienna path; nobody has yet recorded how many technologies, demand\nrequirements and caps the portfolio holds, or how many candidates each rule leaves out. The\n`decisions.md` of your own run reports both: each component it left out, with the reason, and\neach source field it did not map.\n```\n\nThe text to use:\n\n```markdown\nThe `decisions.md` of your own run gives the counts for this path: each component the run left\nout, with the reason, and each source field it did not map.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-12", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 310, "severity": "minor", "label": "suggestion", "claim": "This paragraph repeats the expansion section above and the gap analysis. It is a third copy of two absences.", "suggested_fix": "Keep the lead sentence and the link. Delete the two sentences that repeat what other text already states.\n\nThe text now:\n\n```markdown\n**The expansion path states no number of its own.** interop writes the portfolio and stops\nthere. No solve in this repository reads one, so nothing here says what a plan would build or\nwhat it would cost. The portfolio also states one expansion problem and no schedule: it\ncarries no investment periods and no representative days, and it holds no transport\ntechnology, so a plan built from it cannot build transmission.\n[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) lists everything\nelse the portfolio leaves out.\n```\n\nThe text to use:\n\n```markdown\n**The expansion path states no number of its own.** No solve in this repository reads a\nportfolio, so nothing here says what a plan would build or what it would cost.\n[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the\nportfolio leaves out and what each loss does to an expansion.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-13", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 342, "severity": "minor", "label": "suggestion", "claim": "This paragraph gives no cost. It repeats for a third time that the expansion path runs no solve.", "suggested_fix": "Delete the paragraph.\n\nThe text now:\n\n```markdown\nThe result tables are 440 MB on disk. The solve also writes `problem_results.bin`, which is\n330 MB.\n\nThe expansion run reads the same model over the same year as the Sienna path and writes one\nmore document beside the three files. It runs no solve, so none of the solve compute above\napplies to it.\n```\n\nThe text to use:\n\n```markdown\nThe result tables are 440 MB on disk. The solve also writes `problem_results.bin`, which is\n330 MB.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-14", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 173, "severity": "nit", "label": "nitpick", "claim": "The clause \"because those plants are what it is about\" argues for the instruction. It states no fact.", "suggested_fix": "Cut the clause.\n\nThe text now:\n\n```markdown\nThe dispatch run above leaves seven categories out of its file, because a dispatch system has\nno place for what they hold. The expansion run names the ones that hold candidate plants,\nbecause those plants are what it is about. A generator takes the name of its `Fuel` where it\nburns one and its category where it does not, so each of those categories takes one\n`category` row:\n```\n\nThe text to use:\n\n```markdown\nThe dispatch run above leaves seven categories out of its file, because a dispatch system has\nno place for what they hold. The expansion run names the ones that hold candidate plants. A\ngenerator takes the name of its `Fuel` where it burns one and its category where it does not,\nso each of those categories takes one `category` row:\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-15", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 5, "severity": "minor", "label": "suggestion", "claim": "This sentence names the entries by their position. A new dispatch entry makes the pointer wrong.", "suggested_fix": "Put the expansion entries under a heading and point at the heading, not at an order.\n\nThe text now:\n\n```markdown\nThis document lists each thing a `plexos-to-sienna` run loses, and what that loss does to a\ndispatch. It covers the translation and the validation run that proves the system solves in\nPowerSimulations.jl. The entries from\n[A `Constraint` over part of the model](#a-constraint-over-part-of-the-model) onwards cover\nthe `plexos-to-sienna-investments` run instead, which writes an expansion portfolio beside a\nbase system of its own.\n```\n\nThe text to use:\n\n```markdown\nThis document lists each thing a `plexos-to-sienna` run loses, and what that loss does to a\ndispatch. It covers the translation and the validation run that proves the system solves in\nPowerSimulations.jl. The entries under\n[What the expansion portfolio loses](#what-the-expansion-portfolio-loses) cover the\n`plexos-to-sienna-investments` run, which writes an expansion portfolio beside a base system\nof its own.\n```\n\nAdd `## What the expansion portfolio loses` above the `A `Constraint` over part of the model`\nentry, and demote the seven expansion entries to `###`.", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-16", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 404, "severity": "nit", "label": "nitpick", "claim": "The sentence \"The alternative is worse\" defends the design under a heading that states the effect.", "suggested_fix": "Delete the sentence, or state the fact under **The cause**.\n\nThe text now:\n\n```markdown\n**The effect on the expansion.** That candidate is not in the portfolio, so no plan built\nfrom it can build that technology. The alternative is worse: a candidate whose build nothing\nprices would be built for free, at whatever size its limits allow.\n```\n\nThe text to use:\n\n```markdown\n**The effect on the expansion.** That candidate is not in the portfolio, so no plan built\nfrom it can build that technology.\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-17", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "nit", "label": "nitpick", "claim": "The word \"now\" states a status. The same sentence calls the Investments path out of scope.", "suggested_fix": "Drop the word.\n\nThe text now:\n\n```markdown\nA genuine expansion problem is the Investments path, which the `pypsa-to-sienna-investments` pipeline now writes as a portfolio document beside the operations system; ...\n```\n\nThe text to use:\n\n```markdown\nA genuine expansion problem is the Investments path, which the `pypsa-to-sienna-investments` pipeline writes as a portfolio document beside the operations system; ...\n```", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-18", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 230, "severity": "minor", "label": "suggestion", "claim": "This paragraph says the expansion path has no measured counts, which the block at line 310 already says, and it then repeats the pointer to `decisions.md` given twice earlier in the same section.", "suggested_fix": "Delete lines 230 to 234 and one blank line beside them. The block at lines 310 to 316 keeps the statement that the path gives no number, and lines 170 and 187 keep the `decisions.md` pointer. Net: -6 lines.", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-19", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/case_studies/aemo-isp-2024.md", "line": 342, "severity": "minor", "label": "suggestion", "claim": "The last paragraph of \"What it costs\" states no cost and repeats for a third time that the expansion run does no solve.", "suggested_fix": "Delete lines 342 to 344 and the blank line before them. Add a size or a compute figure for the expansion run only if one is measured. Net: -4 lines.", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-20", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 1, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 5, "severity": "minor", "label": "suggestion", "claim": "The intro splits the document by position, so any entry added or moved makes the sentence wrong, and lines 24 to 26 already tell the reader how to recognise an expansion entry.", "suggested_fix": "Delete the sentence from \"The entries from\" on line 5 to \"base system of its own.\" on line 8, and let the fourth-heading rule at lines 24 to 26 mark which run each entry covers. Net: -3 lines.", "rule_created": false, "ts": "2026-09-09T02:36:47Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-01", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "major", "label": "issue", "claim": "This paragraph says the Investments target is out of scope for v1, and then says a pipeline writes it.", "suggested_fix": "The text now:\n\n```md\nIt is **out of scope for v1**. For v1 we translate the **solved** fleet: take `p_nom_opt`\n(post-solve capacities) as fixed Operations capacity and drop the expansion parameters (or\nkeep round-trip crumbs in the `ext` sidecar). A genuine expansion problem is the Investments\npath, which the `pypsa-to-sienna-investments` pipeline now writes as a portfolio document\nbeside the operations system; [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md)\nstates what that portfolio holds, and that pipeline is its second leg.\n```\n\nThe text to use:\n\n```md\nThis document is the Operations translation, so it drops the expansion parameters: it takes\n`p_nom_opt` (post-solve capacities) as fixed Operations capacity, and keeps round-trip crumbs\nin the `ext` sidecar. The `pypsa-to-sienna-investments` pipeline translates the expansion\nproblem itself, and writes a portfolio document beside the operations system.\n[Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md)\nstates what that portfolio holds; that pipeline is its second leg.\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it blocks the merge", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-02", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/case_studies/aemo-isp-2024.md", "line": 214, "severity": "minor", "label": "issue", "claim": "The new text puts the mappings prompt last, but the table at line 124 puts it second.", "suggested_fix": "`_run_translate` in `interop/adapters/inbound/interactive_cli/app.py` collects the source, step and sink parameters, and calls `_prompt_user_mappings` after them. Correct the Sienna path table as well.\n\nThe table now:\n\n```md\n| Pipeline | `plexos-to-sienna` |\n| User mappings file | `inputs/plexos_user_mappings.yaml` |\n| the PLEXOS `` input XML | `case_study_inputs/...xml` |\n| which PLEXOS Model to translate | `Step Change` |\n| a four-digit year such as 2026 | `2025` |\n| the SiennaSchemas system.json | `outputs/system.json` |\n```\n\nThe table to use:\n\n```md\n| Pipeline | `plexos-to-sienna` |\n| the PLEXOS `` input XML | `case_study_inputs/...xml` |\n| which PLEXOS Model to translate | `Step Change` |\n| a four-digit year such as 2026 | `2025` |\n| the SiennaSchemas system.json | `outputs/system.json` |\n| User mappings file | `inputs/plexos_user_mappings.yaml` |\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-03", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 399, "severity": "minor", "label": "issue", "claim": "This entry names a `Generator` and a `Battery`, but the same rule also drops a pumped-storage turbine.", "suggested_fix": "`rate_object` in `interop/plugins/shared/plexos_pypsa_translations/_storage_shared.py` calls `find_unpriced_candidate` for a pumped-storage turbine too.\n\nThe text now:\n\n```md\n**The PLEXOS data.** A `Generator` or a `Battery` stating `Max Units Built` but not all of\n`Build Cost`, `WACC` and `Economic Life`.\n```\n\nThe text to use:\n\n```md\n**The PLEXOS data.** A `Generator`, a `Battery` or a pumped-storage turbine stating\n`Max Units Built` but not all of `Build Cost`, `WACC` and `Economic Life`.\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-04", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 445, "severity": "minor", "label": "issue", "claim": "The document says each technology leaves the `requirements` list empty, but the portfolio writes no `requirements` field.", "suggested_fix": "```markdown\n**What happens to it.** The portfolio writes no `requirements` on anything. The field is\nabsent from every `SupplyTechnology`, `StorageTechnology` and `DemandRequirement`, and\n`decisions.md` records it as not mapped.\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-05", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/case_studies/aemo-isp-2024.md", "line": 214, "severity": "nit", "label": "nitpick", "claim": "The dispatch table at line 124 lists the User mappings file prompt fourth, but the REPL asks for it last.", "suggested_fix": "```markdown\n| the SiennaSchemas system.json | `outputs/system.json` |\n| User mappings file | `inputs/plexos_user_mappings.yaml` |\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-06", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "minor", "label": "suggestion", "claim": "The new clause is a status note. The word \"now\" ages badly, and the paragraph still calls the same path out of scope.", "suggested_fix": "Delete the scope claim and the word \"now\", and say plainly which chain the pipeline is a leg of.\n\nThe text now:\n\n```markdown\n**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. It is **out of scope for v1**. For v1 we translate the **solved** fleet: take `p_nom_opt` (post-solve capacities) as fixed Operations capacity and drop the expansion parameters (or keep round-trip crumbs in the `ext` sidecar). A genuine expansion problem is the Investments path, which the `pypsa-to-sienna-investments` pipeline now writes as a portfolio document beside the operations system; [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states what that portfolio holds, and that pipeline is its second leg.\n```\n\nThe text to use:\n\n```markdown\n**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. This document translates the **solved** fleet: take `p_nom_opt` (post-solve capacities) as fixed Operations capacity and drop the expansion parameters (or keep round-trip crumbs in the `ext` sidecar). A genuine expansion problem is the Investments path, which the `pypsa-to-sienna-investments` pipeline writes as a portfolio document beside the operations system. [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states what that portfolio holds; that pipeline is the second leg of the PLEXOS chain.\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-07", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/case_studies/aemo-isp-2024.md", "line": 306, "severity": "minor", "label": "suggestion", "claim": "The last two lines duplicate the pointer at the end of the expansion path section, word for word.", "suggested_fix": "Keep the sentence that is new here and delete the repeated pointer.\n\nThe text now:\n\n```markdown\n**The expansion path states no number of its own.** No solve in this repository reads a\nportfolio, so nothing here says what a plan would build or what it would cost.\n[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the\nportfolio leaves out and what each loss does to an expansion.\n```\n\nThe text to use:\n\n```markdown\n**The expansion path states no number of its own.** No solve in this repository reads a\nportfolio, so nothing here says what a plan would build or what it would cost.\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-08", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/case_studies/aemo-isp-2024.md", "line": 166, "severity": "nit", "label": "nitpick", "claim": "\"It is a translation only\" repeats the clause beside it, which already says interop runs no expansion solve.", "suggested_fix": "Delete the first of the two sentences.\n\nThe text now:\n\n```markdown\nunder. It is a translation only. interop runs no expansion solve, so this path stops at the\nfiles. This section covers the Step Change scenario. The other two follow the same steps.\n```\n\nThe text to use:\n\n```markdown\nunder. interop runs no expansion solve, so this path stops at the files. This section covers\nthe Step Change scenario. The other two follow the same steps.\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-09", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 20, "severity": "nit", "label": "suggestion", "claim": "This paragraph repeats the heading table above it, so the two copies of the fourth heading can drift apart.", "suggested_fix": "Fold the variant into the table row and delete the paragraph.\n\nThe text now:\n\n```markdown\n| The effect on the dispatch | What the solve then does differently from your PLEXOS model |\n\nWhere the subject is an expansion rather than a dispatch, the fourth heading reads **The\neffect on the expansion**: what a plan built from the portfolio does differently from the\nexpansion your PLEXOS model states.\n```\n\nThe text to use:\n\n```markdown\n| The effect on the dispatch, or on the expansion | What the solve, or a plan built from the portfolio, then does differently from your PLEXOS model |\n```", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-10", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 445, "severity": "minor", "label": "issue", "claim": "The entry says every technology \"leaves the list empty\", but the sink drops a null field, so `requirements` is absent from the document rather than written as an empty list.", "suggested_fix": "Join the two sentences and say what the sink writes: \"The portfolio writes no `requirements`: the field is absent from every `SupplyTechnology`, `StorageTechnology` and `DemandRequirement`, and `decisions.md` records it as not mapped.\" Net: -1 lines.", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-11", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 475, "severity": "minor", "label": "issue", "claim": "The default for `max_tons_mwh` is quoted in \"Mt/MWh\", which contradicts the field's own name and the unit the sibling mapping table gives it.", "suggested_fix": "Drop the third-party default from the sentence, or state the unit the field name gives: \"With `max_tons_mwh` absent, nothing limits the carbon intensity of what it builds.\" Net: -1 lines.", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r2-12", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/case_studies/aemo-isp-2024.md", "line": 308, "severity": "nit", "label": "nitpick", "claim": "The pointer to the gap analysis is written twice in this file, in the same words.", "suggested_fix": "Delete the two closing lines of the expansion paragraph in \"The headline number\", keeping \"**The expansion path states no number of its own.** No solve in this repository reads a portfolio, so nothing here says what a plan would build or what it would cost.\" The identical pointer at line 234 already sends the reader there. Net: -2 lines.", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} From 5d448a66552fb332b5bf04b7137bdde4d5015d7c Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Thu, 10 Sep 2026 10:00:08 +0000 Subject: [PATCH 6/9] Cut the repeated causes and the pointers the expansion documents duplicate This commit answers the round 3 review comments r3-01, r3-02, r3-03, r3-04, r3-05, r3-07, r3-08, r3-09, r3-10, r3-11, r3-12, r3-13 and r3-14. The gap analysis stated one cause three times, that a `CarbonCaps` holds the whole portfolio. The first entry states it, and the other two refer to it. The case study held a table whose cells stated rules, not values. A list of the categories replaces it, and each item names the types and the prime movers that category takes. `translation-from-pypsa-to-sienna.md` said the `pypsa-to-sienna-investments` pipeline translates the expansion problem, and four bullets below it called the same translation unfunded and open. The bullets go. r3-01 asked for a new `translation-from-pypsa-to-sienna-investments.md`. This commit does not write one. It says instead what a PyPSA reader finds in the PLEXOS document, and what that document's source column does not give them. r3-02 asked to replace two paragraphs with a pointer to `#across-all-components`. That section states one of the two rules. This commit points at it for that rule, and keeps the type list and the `storage_kind` rule, which no other document holds. r3-06 and r3-15 are deferred. An automated run acts only on a blocking comment or one that removes lines. --- code-review/ledger.jsonl | 15 ++++++++ docs/case_studies/aemo-isp-2024.md | 34 ++++++++++--------- .../plexos-to-sienna-gap-analysis.md | 31 ++++++----------- .../translation-from-plexos-to-pypsa.md | 1 - .../translation-from-plexos-to-sienna.md | 9 +++-- .../translation-from-pypsa-to-sienna.md | 9 ++--- 6 files changed, 50 insertions(+), 49 deletions(-) diff --git a/code-review/ledger.jsonl b/code-review/ledger.jsonl index 40357e4..e87a7d9 100644 --- a/code-review/ledger.jsonl +++ b/code-review/ledger.jsonl @@ -241,3 +241,18 @@ {"id": "r2-10", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 445, "severity": "minor", "label": "issue", "claim": "The entry says every technology \"leaves the list empty\", but the sink drops a null field, so `requirements` is absent from the document rather than written as an empty list.", "suggested_fix": "Join the two sentences and say what the sink writes: \"The portfolio writes no `requirements`: the field is absent from every `SupplyTechnology`, `StorageTechnology` and `DemandRequirement`, and `decisions.md` records it as not mapped.\" Net: -1 lines.", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r2-11", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 475, "severity": "minor", "label": "issue", "claim": "The default for `max_tons_mwh` is quoted in \"Mt/MWh\", which contradicts the field's own name and the unit the sibling mapping table gives it.", "suggested_fix": "Drop the third-party default from the sentence, or state the unit the field name gives: \"With `max_tons_mwh` absent, nothing limits the carbon intensity of what it builds.\" Net: -1 lines.", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r2-12", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 2, "file": "docs/case_studies/aemo-isp-2024.md", "line": 308, "severity": "nit", "label": "nitpick", "claim": "The pointer to the gap analysis is written twice in this file, in the same words.", "suggested_fix": "Delete the two closing lines of the expansion paragraph in \"The headline number\", keeping \"**The expansion path states no number of its own.** No solve in this repository reads a portfolio, so nothing here says what a plan would build or what it would cost.\" The identical pointer at line 234 already sends the reader there. Net: -2 lines.", "rule_created": false, "ts": "2026-09-09T02:59:14Z", "source": "reviewer", "verdict": "accepted", "reason": "it takes lines away", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-01", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "major", "label": "issue", "claim": "The diff sends a PyPSA reader to a PLEXOS document, because the `pypsa-to-sienna-investments` pipeline has no document of its own.", "suggested_fix": "Say that the linked document states every field the portfolio holds, and that its source column names the PLEXOS property behind each field, not the PyPSA one.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-02", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/case_studies/aemo-isp-2024.md", "line": 184, "severity": "major", "label": "issue", "claim": "The case study states two general translator rules that `translation-from-plexos-to-sienna-investments.md` already states.", "suggested_fix": "Point at `#across-all-components` for the rule that document holds, and keep the two facts it does not hold.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-03", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 436, "severity": "major", "label": "suggestion", "claim": "Three entries of the new expansion part give the same cause, that a `CarbonCaps` holds the whole portfolio.", "suggested_fix": "State the cause once in the first entry, and let the other two refer to it.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-04", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/case_studies/aemo-isp-2024.md", "line": 178, "severity": "major", "label": "issue", "claim": "No cell of the new category table gives a value the reader can copy into the mappings file.", "suggested_fix": "Replace the table with a list of the categories, each naming the types and prime movers it takes.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-05", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 86, "severity": "major", "label": "issue", "claim": "The rewritten paragraph says the `pypsa-to-sienna-investments` pipeline translates the expansion problem, but the four bullets below it still call that translation unfunded, open and a choice the reader must confirm.", "suggested_fix": "Cut the four bullets to one sentence.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-06", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/case_studies/aemo-isp-2024.md", "line": 175, "severity": "minor", "label": "issue", "claim": "The carrier rule is stated without the heat-rate condition, so a reader writes the wrong mappings row for a generator that names a `Fuel` but states no `Heat Rate`.", "suggested_fix": "Write \"where it burns one at a heat rate\".", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-07", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 380, "severity": "minor", "label": "suggestion", "claim": "The sentence defends the choice not to approximate a cap, which the mapping document already states.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-08", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 505, "severity": "minor", "label": "suggestion", "claim": "The sentence about interop's `solve` command documents an absence of a feature the reader is not looking at.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-09", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/case_studies/aemo-isp-2024.md", "line": 219, "severity": "minor", "label": "suggestion", "claim": "The last sentence copies the opening paragraph of the mapping document this section links two paragraphs later.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-10", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 450, "severity": "minor", "label": "suggestion", "claim": "The last two sentences of the effect paragraph repeat the cause paragraph above them and the effect of the first entry.", "suggested_fix": "Delete both sentences.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-11", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 451, "severity": "nit", "label": "suggestion", "claim": "The middle sentence repeats the cause paragraph three lines above it.", "suggested_fix": "Delete the sentence. The same fix as r3-10.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-12", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/translation-from-plexos-to-sienna.md", "line": 8, "severity": "nit", "label": "suggestion", "claim": "The \"Refer to\" line repeats the document title the sentence before it gives.", "suggested_fix": "Put the link on the pipeline name and delete the \"Refer to\" line.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-13", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 364, "severity": "nit", "label": "nitpick", "claim": "The intro sentence states what the heading above it states.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-14", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/translation-from-plexos-to-pypsa.md", "line": 165, "severity": "nit", "label": "nitpick", "claim": "The sentence before this pointer already names every component that becomes extendable.", "suggested_fix": "Delete the pointer.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-15", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/case_studies/aemo-isp-2024.md", "line": 173, "severity": "nit", "label": "issue", "claim": "The count of seven categories contradicts the same document, which says the seven names each have sibling categories per state.", "suggested_fix": "Write \"seven named categories, and their siblings\".", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "dropped", "reason": "repeats r1-10, which round 1 already answered", "category": null, "promote_now": false, "suppressed_by": null} diff --git a/docs/case_studies/aemo-isp-2024.md b/docs/case_studies/aemo-isp-2024.md index 4aacb10..18a2d2a 100644 --- a/docs/case_studies/aemo-isp-2024.md +++ b/docs/case_studies/aemo-isp-2024.md @@ -173,20 +173,23 @@ not name is left out of the portfolio, and `decisions.md` names each one. The dispatch run above leaves seven categories out of its file, because a dispatch system has no place for what they hold. The expansion run names the ones that hold candidate plants. A generator takes the name of its `Fuel` where it burns one and its category where it does not, -so each of those categories takes one `category` row: - -| `plexos_name` | `sienna_component_type` | `sienna_prime_mover_type` | -| --- | --- | --- | -| `2023 REZ NSW`, and the sibling category of each other state | `RenewableDispatch` | `WT` for a wind candidate, `PVe` for a solar one | -| `New Entrants NSW`, and the sibling category of each other state | `RenewableDispatch`, or `ThermalStandard` with the `sienna_fuel_type` of the fuel the plant burns | The prime mover of the plant: `WT`, `PVe`, `CC` or `CT` | -| `LTESA Projects`, `Policy Projects`, `VRET Projects` | The type of the plant the project builds | The prime mover of that plant | - -The translator reads no meaning from a category name, so give each row the type of the plant -that category holds in your copy of the model. A generator candidate becomes a -`SupplyTechnology`, and its `sienna_component_type` must be a type a generator becomes: -`ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` or `HydroDispatch`. A row that -sends a generator's carrier to `EnergyReservoirStorage` names a type the candidate never -becomes, so the run leaves that candidate out and `decisions.md` names it. +so add one `category` row for each of these: + +- `2023 REZ NSW`, and the sibling category of each other state: a wind or a solar candidate, + so `RenewableDispatch` with the prime mover `WT` or `PVe`; +- `New Entrants NSW`, and the sibling category of each other state: a wind, a solar or a gas + candidate, so the prime mover `WT`, `PVe`, `CC` or `CT`; +- `LTESA Projects`, `Policy Projects` and `VRET Projects`: the type and the prime mover of the + plant the project builds. + +The translator reads no meaning from a category name, so give each row the +`sienna_component_type` and the `sienna_prime_mover_type` of the plant that category holds in +your copy of the model. A generator candidate becomes a `SupplyTechnology`, so its +`sienna_component_type` must be `ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` +or `HydroDispatch`. A `ThermalStandard` row also takes the `sienna_fuel_type` of the fuel the +plant burns. +[Across all components](../translation_mappings/translation-from-plexos-to-sienna-investments.md#across-all-components) +states what happens to a candidate whose carrier your file sends to the other kind's type. A `Battery` candidate and a pumped-storage candidate need no row of their own. Each takes a `storage_kind` row, and the mappings pipeline supplies one for all three storage kinds. @@ -216,8 +219,7 @@ prompts of both sinks. That run writes four files: the three the Sienna path writes, and `outputs/portfolio.json` beside them. The portfolio names `system.json` in its `base_system_file`, so the two are read -together. The base system holds the fleet that already runs, and a build the plan has yet to -decide is in the portfolio and not in it. +together. Every cost in a portfolio is quoted in a base year, and no PLEXOS field states one. A chained pipeline prompts for the source of its first leg and the sinks of its last, and for no step in diff --git a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md index 122b799..ef49b74 100644 --- a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md +++ b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md @@ -361,9 +361,6 @@ for you. ## What `plexos-to-sienna-investments` loses from an expansion -These entries cover the run that writes an expansion portfolio beside a base system of its -own. - ### A `Constraint` a cap cannot carry **The PLEXOS data.** A `Constraint` holding a weighted sum over the objects it names to a @@ -377,11 +374,9 @@ constraint is left out, `decisions.md` names it, and the log warns. Every constr reaches the `extensions.json` sidecar, whether or not it became a cap. **The cause.** `CarbonCaps` names no members and no region: a cap in a portfolio holds the -whole portfolio. A cap written from a constraint over part of the model would hold every -technology in the problem, which is a different limit from the one your model states. A cap -also states one ceiling over the whole run, so a constraint held to `>=` or to `==`, and a -constraint whose only right-hand side bounds a repeating window inside the run, give it -nothing to carry. +whole portfolio. A cap also states one ceiling over the whole run, so a constraint held to +`>=` or to `==`, and a constraint whose only right-hand side bounds a repeating window inside +the run, give it nothing to carry. **The effect on the expansion.** Nothing bounds what the constraint names, so the plan may build and run those objects up to their own limits. A model whose targets are all regional, @@ -443,14 +438,12 @@ one. as not mapped. **The cause.** `requirements` holds the ids of the requirements a component is subject to. -The only requirement this translation writes is a `CarbonCaps` that holds the whole -portfolio, so it names no members and no component names it. SiennaSchemas states other -requirement types beside it, and this translation writes none of them. +The only requirement this translation writes is the cap above, which names no members, so no +component names it. SiennaSchemas states other requirement types beside it, and this +translation writes none of them. **The effect on the expansion.** A consumer that applies a requirement to the technologies -naming it applies nothing to any of them. Nothing is lost for the one cap the portfolio can -hold, since that cap applies to the whole problem by its own definition. A narrower target -has no way to reach a technology at all. +naming it applies nothing to any of them. --- @@ -467,9 +460,9 @@ the document; `decisions.md` records both. **The cause.** PLEXOS states the span a right-hand side applies over, not the year it applies in, and it has no rate-based right-hand side for `max_tons_mwh` to carry. -**The effect on the expansion.** The cap states no year, so it is the limit of the whole -problem the consumer solves, whichever year your model stated it for. With `max_tons_mwh` -absent, nothing limits the carbon intensity of what it builds. +**The effect on the expansion.** The cap applies to every year of the run, whichever year +your model stated it for. With `max_tons_mwh` absent, nothing limits the carbon intensity of +what it builds. --- @@ -502,9 +495,7 @@ problem, with no schedule of periods and no representative-day weighting. **The cause.** A portfolio document holds the technologies, the requirements and the regions of an expansion problem. The periods and the representative days are terms of the solve, so -they belong on the request that solves the portfolio rather than in it. No solve in interop -reads a portfolio: its `solve` command runs PyPSA over a network or PowerSimulations.jl over a -system, and neither steps through investment periods. +they belong on the request that solves the portfolio rather than in it. **The effect on the expansion.** Whoever solves the portfolio chooses the periods and the sampling. A plan built over a different set of years, or against a different set of diff --git a/docs/translation_mappings/translation-from-plexos-to-pypsa.md b/docs/translation_mappings/translation-from-plexos-to-pypsa.md index 883dd61..81ff442 100644 --- a/docs/translation_mappings/translation-from-plexos-to-pypsa.md +++ b/docs/translation_mappings/translation-from-plexos-to-pypsa.md @@ -162,7 +162,6 @@ The translator does **not** read the `Line.Type` property. That property sets th technology that LT Plan uses when it expands a line: `0` for AC and `1` for DC. It does not set the operation of a line that exists. The translator writes no expandable line either: only a `Generator`, a `Battery` and a pumped-storage turbine become extendable components. -Refer to [What a candidate is](#what-a-candidate-is). A line must have a `Node From` membership and a `Node To` membership. If a line does not have both, it connects nothing. The translator does not write it, and it records this. diff --git a/docs/translation_mappings/translation-from-plexos-to-sienna.md b/docs/translation_mappings/translation-from-plexos-to-sienna.md index 0f06650..9358510 100644 --- a/docs/translation_mappings/translation-from-plexos-to-sienna.md +++ b/docs/translation_mappings/translation-from-plexos-to-sienna.md @@ -5,11 +5,10 @@ It gives the source of each field. > **Scope:** the translator accepts electricity-only models, and the `plexos-to-sienna` > pipeline translates them for dispatch. It writes no capacity expansion: what your plan may -> build becomes a Sienna investments portfolio instead, which the -> `plexos-to-sienna-investments` pipeline writes. Refer to -> [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md). -> This pipeline does not translate custom constraints or hydro cascades, and it does not -> carry the reserves to a file you keep. Refer to [Not translated](#not-translated) and to +> build becomes a Sienna investments portfolio instead, which +> [the `plexos-to-sienna-investments` pipeline](translation-from-plexos-to-sienna-investments.md) +> writes. This pipeline does not translate custom constraints or hydro cascades, and it does +> not carry the reserves to a file you keep. Refer to [Not translated](#not-translated) and to > [the gap analysis](plexos-to-sienna-gap-analysis.md), which states what each loss does to a > dispatch and what the portfolio leaves out of an expansion. diff --git a/docs/translation_mappings/translation-from-pypsa-to-sienna.md b/docs/translation_mappings/translation-from-pypsa-to-sienna.md index 86178e6..84b5e51 100644 --- a/docs/translation_mappings/translation-from-pypsa-to-sienna.md +++ b/docs/translation_mappings/translation-from-pypsa-to-sienna.md @@ -79,16 +79,11 @@ This document targets the SiennaSchemas **Operations** namespace (the dispatchab - It adds policy `Requirements` (`CarbonCaps`, `CarbonTax`, `CapacityReserveMargin`, `EnergyShareRequirements`, …) and `Financials`. - It bridges to Operations through each technology's `power_systems_type` (the Operations type it realises into) and `TopologyMapping` (region → buses). -**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. This document is the Operations translation, so it drops the expansion parameters: it takes `p_nom_opt` (post-solve capacities) as fixed Operations capacity, and keeps round-trip crumbs in the `ext` sidecar. The `pypsa-to-sienna-investments` pipeline translates the expansion problem itself, and writes a portfolio document beside the operations system. [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states what that portfolio holds; that pipeline is its second leg. +**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. This document is the Operations translation, so it drops the expansion parameters: it takes `p_nom_opt` (post-solve capacities) as fixed Operations capacity, and keeps round-trip crumbs in the `ext` sidecar. The `pypsa-to-sienna-investments` pipeline translates the expansion problem itself, and writes a portfolio document beside the operations system. It is the second leg of `plexos-to-sienna-investments`, so [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states every field the portfolio holds. The source column of that document names the PLEXOS property behind each field, not the PyPSA one. Throughout this document, **`effective_p_nom`** denotes the capacity an operations model may dispatch: `p_nom_opt` where an extendable component states one, `p_nom_min` where it states a capacity a build cannot take away, and `p_nom` otherwise. All capacity-derived fields — `base_power`, `active_power`, `active_power_limits`, `ramp_limits`, and the hydro energy budget scaling factor — use `effective_p_nom`. For a non-extendable component `effective_p_nom = p_nom`. A solve that builds none of an extendable component writes `p_nom_opt` 0, and that 0 is the capacity to translate: the plan rejected the build. A network no solve has touched leaves `p_nom_opt` out of the file altogether, and an extendable component there is rated at the `p_nom_min` it already runs. An extendable component with neither a `p_nom_opt` nor a `p_nom_min` is a candidate rather than a plant: the generator, renewable, hydro and storage mappings leave it out and record it as skipped. -**Why Operations and not Investments.** This is a deliberate scope choice tied to the deliverable, not a claim that Operations is the more natural fit: - -- **The deliverable targets PowerSimulations.jl** — the *operations* solver (dispatch, UC, PCM). The Investments schemas are consumed by PowerSystemsInvestments.jl, a separate and newer solver that is not the funded target. The acceptance test ("it runs in PowerSimulations.jl") pins us to Operations. -- **Maturity.** Operations + PowerSimulations.jl is the mature, well-exercised path; the `Investments/` schemas and PowerSystemsInvestments.jl are newer. -- **Investments is a larger, separate translation** with its own open mappings: aggregating PyPSA buses into `Node`/`Zone` regions, reconciling PyPSA's (often pre-annuitised) `capital_cost` with `discount_rate`/`capital_recovery_period`/`interest_rate`, and mapping PyPSA `GlobalConstraints` (e.g. CO₂ limits) to `Requirements` (`CarbonCaps`/`CarbonTax`). -- **Counterpoint (kept honest):** a PyPSA-Eur network *is* natively a capacity-expansion model, so Investments is the faithful target if the goal is **expansion interoperability** rather than **dispatching a solved system**. If that is the intent, the target flips to Investments. Confirm the deliverable's intent (dispatch-in-PowerSimulations vs expansion-in-Sienna) before assuming Operations is sufficient. +**Why Operations and not Investments.** This document is the Operations translation, and PowerSimulations.jl is its target. ### System container From 1e86643c5d3d4c3ffa66f9a0375ea2ef8ab53393 Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Thu, 10 Sep 2026 10:09:29 +0000 Subject: [PATCH 7/9] Move the general expansion rules into the mapping document This commit answers the round 4 review comments r4-01 to r4-05, r4-07 to r4-10 and r4-12 to r4-22. Two rules held only by the AEMO case study now sit in `translation-from-plexos-to-sienna-investments.md`, where a reader of another model finds them: the four base system types a generator candidate may name, and how to state a base year other than the default. The case study points at each. Round 3 of this cycle wrote four wrong statements. This commit repairs them: - "the cap above" named the entry above, which is the transmission entry. Deleting the `requirements` entry under r4-04 removes the reference. - "The cap applies to every year of the run" contradicted the cap entry, which states one ceiling for the whole run. - The PLEXOS investments document does not state every field the portfolio holds. Three financial fields appear in no table there. - That document heads its column `From`, not "source". r4-01 asked for a second gap analysis file. This commit does not write one. It gives the entry table one row per run, which answers the part of the comment about a cell that states two cases. A second file reverses r1-01 and r1-20, which round 1 accepted. r4-11 is superseded by r4-19: both delete the same fact, and one copy stays. r4-06, r4-17 and r4-18 are deferred. An automated run acts only on a blocking comment or one that removes lines. --- code-review/ledger.jsonl | 22 +++++++++ docs/case_studies/aemo-isp-2024.md | 45 +++++-------------- .../plexos-to-sienna-gap-analysis.md | 34 +++----------- ...ation-from-plexos-to-sienna-investments.md | 14 ++++++ .../translation-from-pypsa-to-sienna.md | 6 +-- 5 files changed, 57 insertions(+), 64 deletions(-) diff --git a/code-review/ledger.jsonl b/code-review/ledger.jsonl index e87a7d9..0c683f8 100644 --- a/code-review/ledger.jsonl +++ b/code-review/ledger.jsonl @@ -256,3 +256,25 @@ {"id": "r3-13", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 364, "severity": "nit", "label": "nitpick", "claim": "The intro sentence states what the heading above it states.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r3-14", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/translation_mappings/translation-from-plexos-to-pypsa.md", "line": 165, "severity": "nit", "label": "nitpick", "claim": "The sentence before this pointer already names every component that becomes extendable.", "suggested_fix": "Delete the pointer.", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r3-15", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 3, "file": "docs/case_studies/aemo-isp-2024.md", "line": 173, "severity": "nit", "label": "issue", "claim": "The count of seven categories contradicts the same document, which says the seven names each have sibling categories per state.", "suggested_fix": "Write \"seven named categories, and their siblings\".", "rule_created": false, "ts": "2026-09-10T09:59:59Z", "source": "reviewer", "verdict": "dropped", "reason": "repeats r1-10, which round 1 already answered", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-01", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 18, "severity": "major", "label": "suggestion", "claim": "This file now holds two pipelines, but its name states one pipeline only, and the four-item table carries both cases in one cell.", "suggested_fix": "Give the table one row per run. Do not split the file.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "thermo", "verdict": "partial", "reason": "The table cell is fixed. The file split is declined: it reverses r1-01 and r1-20, which round 1 accepted, and it needs a new file plus every inbound link.", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-02", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-plexos-to-sienna-investments.md", "line": 6, "severity": "major", "label": "issue", "claim": "The case study states two translator rules that no mapping document states: the base system types a generator candidate may name, and the rows a storage candidate needs.", "suggested_fix": "Add both rules to `Across all components`, and let the case study point at it.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "thermo", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-03", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 224, "severity": "major", "label": "issue", "claim": "One paragraph states seven facts, and it holds the only instructions in the repository for a lone `pypsa-to-sienna-investments` run.", "suggested_fix": "Move the general rules to `The base year` of the mapping document, and leave a pointer.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "thermo", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-04", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 431, "severity": "major", "label": "suggestion", "claim": "The `requirements` entry states nothing that the cap entry above it and the mapping document do not state already.", "suggested_fix": "Delete the entry. The `requirements` row of the mapping document carries the fact.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "thermo", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-05", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 86, "severity": "minor", "label": "suggestion", "claim": "The sentence under **Why Operations and not Investments** repeats the sentence that opens the paragraph above it.", "suggested_fix": "Delete the line and its heading.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "thermo", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-06", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "README.md", "line": 307, "severity": "minor", "label": "suggestion", "claim": "The README names no document for what the expansion path loses.", "suggested_fix": "Name the gap analysis in the investments bullet.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "thermo", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-07", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 441, "severity": "minor", "label": "issue", "claim": "\"the cap above\" points a reader at the entry above, which is the transmission entry, not the cap entry.", "suggested_fix": "Round 3 wrote the wrong cross-reference. Deleting the entry under r4-04 removes it.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "correctness", "verdict": "accepted", "reason": "a repair of an error round 3 of this cycle introduced", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-08", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 463, "severity": "minor", "label": "question", "claim": "\"The cap applies to every year of the run\" contradicts the cap entry, which says a cap states one ceiling over the whole run.", "suggested_fix": "Say the cap holds the whole run, so a yearly right-hand side becomes a budget for the whole run.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "correctness", "verdict": "accepted", "reason": "a repair of an error round 3 of this cycle introduced", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-09", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "minor", "label": "issue", "claim": "The claim that the PLEXOS investments document \"states every field the portfolio holds\" is wrong, because three portfolio financial fields appear in no table there.", "suggested_fix": "Say it states the source of each technology, requirement and attribute field.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "correctness", "verdict": "accepted", "reason": "a repair of an error round 3 of this cycle introduced", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-10", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "nit", "label": "issue", "claim": "The sentence names a \"source column\" that the PLEXOS investments document does not have; its column is headed `From`.", "suggested_fix": "Name the `From` column.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "correctness", "verdict": "accepted", "reason": "a repair of an error round 3 of this cycle introduced", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-11", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 166, "severity": "minor", "label": "issue", "claim": "\"It is a translation only. interop runs no expansion solve\" duplicates the paragraph under \"The headline number\".", "suggested_fix": "Delete one of the two copies.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pruning", "verdict": "superseded", "reason": "r4-19 deletes the other copy. Applying both would delete the fact from the document.", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-12", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 173, "severity": "nit", "label": "suggestion", "claim": "The clause \"because a dispatch system has no place for what they hold\" restates a reason the dispatch section above gives in sharper words.", "suggested_fix": "Delete the clause.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pruning", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-13", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 215, "severity": "nit", "label": "suggestion", "claim": "The list of the other prompts duplicates the REPL the reader is looking at while they answer it.", "suggested_fix": "Keep \"Keep the default at every other prompt\" and the prompt order sentence.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pruning", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-14", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 497, "severity": "minor", "label": "issue", "claim": "\"so they belong on the request that solves the portfolio rather than in it\" argues that the schema is right rather than stating the cause.", "suggested_fix": "Delete the clause.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pruning", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-15", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 82, "severity": "nit", "label": "suggestion", "claim": "\"which is a separate translation target\" is now said twice in one paragraph, because the next sentence names the pipeline that does it.", "suggested_fix": "Delete the clause.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pruning", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-16", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-pypsa-to-sienna.md", "line": 14, "severity": "minor", "label": "issue", "claim": "The revision note still promises the rationale and the open question that round 3 removed.", "suggested_fix": "Say the document targets Operations and that `pypsa-to-sienna-investments` translates the expansion problem.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "a repair of an error round 3 of this cycle introduced", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-17", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/translation-from-plexos-to-pypsa.md", "line": 205, "severity": "minor", "label": "issue", "claim": "The `s_nom_extendable` row still gives \"Version 1 dispatches only\" as the reason, which the sentence at line 164 contradicts.", "suggested_fix": "Write \"The translator writes no expandable line.\"", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-18", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 128, "severity": "minor", "label": "issue", "claim": "`sem-2024-2032.md` and `caiso-sa26.md` still list the User mappings file prompt before the input XML, which this branch moved to last.", "suggested_fix": "Move the row to the end of the table in both case studies.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "deferred", "reason": "an automated run acts only on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-19", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 308, "severity": "minor", "label": "suggestion", "claim": "The paragraph under \"What the number does not cover\" states what the section intro at line 166 already states.", "suggested_fix": "Delete the paragraph.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-20", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 3, "severity": "nit", "label": "nitpick", "claim": "\"Each part below names the run it covers\" only points at the two headings below, which name their run in plain sight.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-21", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 182, "severity": "nit", "label": "nitpick", "claim": "The trailing clause of the third bullet states the instruction the paragraph two lines below gives for every row.", "suggested_fix": "Delete the clause.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-22", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 234, "severity": "nit", "label": "nitpick", "claim": "The closing paragraph points at the mapping document, which the section already links.", "suggested_fix": "Keep the gap analysis pointer only.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} diff --git a/docs/case_studies/aemo-isp-2024.md b/docs/case_studies/aemo-isp-2024.md index 18a2d2a..912e205 100644 --- a/docs/case_studies/aemo-isp-2024.md +++ b/docs/case_studies/aemo-isp-2024.md @@ -170,29 +170,21 @@ Write a second mappings file, `inputs/plexos_expansion_mappings.yaml`. It is the file plus one row for each carrier a candidate takes. A candidate whose carrier the file does not name is left out of the portfolio, and `decisions.md` names each one. -The dispatch run above leaves seven categories out of its file, because a dispatch system has -no place for what they hold. The expansion run names the ones that hold candidate plants. A -generator takes the name of its `Fuel` where it burns one and its category where it does not, -so add one `category` row for each of these: +The dispatch run above leaves seven categories out of its file. The expansion run names the +ones that hold candidate plants. A generator takes the name of its `Fuel` where it burns one +and its category where it does not, so add one `category` row for each of these: - `2023 REZ NSW`, and the sibling category of each other state: a wind or a solar candidate, so `RenewableDispatch` with the prime mover `WT` or `PVe`; - `New Entrants NSW`, and the sibling category of each other state: a wind, a solar or a gas candidate, so the prime mover `WT`, `PVe`, `CC` or `CT`; -- `LTESA Projects`, `Policy Projects` and `VRET Projects`: the type and the prime mover of the - plant the project builds. +- `LTESA Projects`, `Policy Projects` and `VRET Projects`. The translator reads no meaning from a category name, so give each row the `sienna_component_type` and the `sienna_prime_mover_type` of the plant that category holds in -your copy of the model. A generator candidate becomes a `SupplyTechnology`, so its -`sienna_component_type` must be `ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` -or `HydroDispatch`. A `ThermalStandard` row also takes the `sienna_fuel_type` of the fuel the -plant burns. +your copy of the model. [Across all components](../translation_mappings/translation-from-plexos-to-sienna-investments.md#across-all-components) -states what happens to a candidate whose carrier your file sends to the other kind's type. - -A `Battery` candidate and a pumped-storage candidate need no row of their own. Each takes a -`storage_kind` row, and the mappings pipeline supplies one for all three storage kinds. +states the types a row may name, and what happens to a row that names another kind's type. Leave `REZ Augmentation` and `Group REZ Augmentation` out of this file as well. They are transmission augmentations written as generators, and the portfolio holds no transport @@ -212,28 +204,18 @@ Select `translate`. Then give these answers: | the SiennaSchemas portfolio document to write | `outputs/portfolio.json` | | User mappings file | `inputs/plexos_expansion_mappings.yaml` | -Keep the default at every other prompt. The two sinks also ask for the HDF5 companion, the -extensions sidecar, the JSON indent width, and the basenames the portfolio names its base -system and its time-series companion by. The mappings prompt comes last, after the file +Keep the default at every other prompt. The mappings prompt comes last, after the file prompts of both sinks. That run writes four files: the three the Sienna path writes, and `outputs/portfolio.json` beside them. The portfolio names `system.json` in its `base_system_file`, so the two are read together. -Every cost in a portfolio is quoted in a base year, and no PLEXOS field states one. A chained -pipeline prompts for the source of its first leg and the sinks of its last, and for no step in -between, so this run states the default base year of 2020. To state another one, run the two -legs yourself: `plexos-to-pypsa`, then `pypsa-to-sienna-investments` over the network and the -sidecar it wrote. That pipeline is one leg, so it prompts for its steps, and the base year is -the `base_year` of `step[2]`. It asks for a mappings file of its own, in PyPSA words rather -than PLEXOS ones: the chain derives that file from the PLEXOS file above, and a lone run has -nothing to derive it from, so write a `carriers` file with a `pypsa_carrier` row for each -carrier the first leg wrote, the storage carriers included. - -[The mapping document](../translation_mappings/translation-from-plexos-to-sienna-investments.md) -states what each field of the portfolio comes from, and -[the gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the +Every cost in a portfolio is quoted in a base year, and no PLEXOS field states one. This run +takes the default base year of 2020. To state another one, refer to +[The base year](../translation_mappings/translation-from-plexos-to-sienna-investments.md#the-base-year). + +[The gap analysis](../translation_mappings/plexos-to-sienna-gap-analysis.md) states what the portfolio leaves out and what each loss does to an expansion. ## The headline number @@ -305,9 +287,6 @@ The Sienna path also keeps no reserves file at all, because the first leg of the that file inside the run's scratch space. Run `plexos-to-pypsa` on its own if you want the reserves. -**The expansion path states no number of its own.** No solve in this repository reads a -portfolio, so nothing here says what a plan would build or what it would cost. - A solve keeps no reserve headroom. Thus the dispatch is less constrained than the dispatch in the source model. diff --git a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md index ef49b74..416cd16 100644 --- a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md +++ b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md @@ -1,7 +1,6 @@ # What a PLEXOS to Sienna translation loses -This document lists each thing a PLEXOS to Sienna run loses. Each part below names the run it -covers. +This document lists each thing a PLEXOS to Sienna run loses. For what the translation keeps, refer to [Translation from PLEXOS to Sienna](translation-from-plexos-to-sienna.md), and for the @@ -15,7 +14,8 @@ Each entry gives four things: | The PLEXOS data | What your model states | | What happens to it | Where it goes, or that it goes nowhere | | The cause | Why | -| The effect on the dispatch, or on the expansion | What the solve, or a plan built from the portfolio, then does differently from your PLEXOS model | +| The effect on the dispatch | What the solve then does differently from your PLEXOS model | +| The effect on the expansion | What a plan built from the portfolio then does differently from your PLEXOS model | --- @@ -428,25 +428,6 @@ or not at all. --- -### The requirements a technology names - -**The PLEXOS data.** The objects a `Constraint` names, and the coefficient weighting each -one. - -**What happens to it.** The portfolio writes no `requirements`: the field is absent from every -`SupplyTechnology`, `StorageTechnology` and `DemandRequirement`, and `decisions.md` records it -as not mapped. - -**The cause.** `requirements` holds the ids of the requirements a component is subject to. -The only requirement this translation writes is the cap above, which names no members, so no -component names it. SiennaSchemas states other requirement types beside it, and this -translation writes none of them. - -**The effect on the expansion.** A consumer that applies a requirement to the technologies -naming it applies nothing to any of them. - ---- - ### The year a cap applies in, and a cap on carbon intensity **The PLEXOS data.** A `Constraint` right-hand side, stated for a year or over the whole @@ -460,9 +441,9 @@ the document; `decisions.md` records both. **The cause.** PLEXOS states the span a right-hand side applies over, not the year it applies in, and it has no rate-based right-hand side for `max_tons_mwh` to carry. -**The effect on the expansion.** The cap applies to every year of the run, whichever year -your model stated it for. With `max_tons_mwh` absent, nothing limits the carbon intensity of -what it builds. +**The effect on the expansion.** The cap holds the whole run, whichever year your model +stated it for, so a yearly right-hand side becomes a budget for the whole run. With +`max_tons_mwh` absent, nothing limits the carbon intensity of what it builds. --- @@ -494,8 +475,7 @@ the representative days and weights it samples each year with. problem, with no schedule of periods and no representative-day weighting. **The cause.** A portfolio document holds the technologies, the requirements and the regions -of an expansion problem. The periods and the representative days are terms of the solve, so -they belong on the request that solves the portfolio rather than in it. +of an expansion problem. The periods and the representative days are terms of the solve. **The effect on the expansion.** Whoever solves the portfolio chooses the periods and the sampling. A plan built over a different set of years, or against a different set of diff --git a/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md b/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md index 109426c..946c40c 100644 --- a/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md +++ b/docs/translation_mappings/translation-from-plexos-to-sienna-investments.md @@ -60,6 +60,11 @@ Where the intermediate form loses something, this document says so. `capacity_limits.min` is that same capacity, which a build cannot take away. - **A candidate whose carrier your mappings file does not name is left out.** The run completes and `decisions.md` names each one. +- **A generator candidate names one of four base system types.** Your mappings file must send + its carrier to `ThermalStandard`, `RenewableDispatch`, `RenewableNonDispatch` or + `HydroDispatch`, and a `ThermalStandard` row also states the `sienna_fuel_type`. A `Battery` + candidate and a pumped-storage candidate need no row of their own, because the mappings + pipeline supplies a `storage_kind` row for all three storage kinds. - **A candidate whose carrier your mappings file sends to another kind's type is left out.** A generator becomes a `SupplyTechnology` and a battery or a pumped-storage turbine becomes a `StorageTechnology`, so a carrier sent to a base system type the other kind holds names a @@ -214,6 +219,15 @@ both come from the `base_year` parameter of the `pypsa_to_sienna_investments_map step. It defaults to **2020**, which is the year SiennaSchemas itself defaults a construction year to. Set it to the dollar year your `Build Cost` and `FO&M Charge` are quoted in. +A chained pipeline prompts for the source of its first leg and the sinks of its last, and for +no step in between, so a `plexos-to-sienna-investments` run always takes the default. To state +another year, run the two legs yourself: `plexos-to-pypsa`, then `pypsa-to-sienna-investments` +over the network and the sidecar it wrote. The second leg prompts for its steps, so the base +year is the `base_year` of `step[2]`. That leg also asks for a mappings file in PyPSA words, +which the chain derives from your PLEXOS file and a lone run cannot, so write a `carriers` +file with one `pypsa_carrier` row for each carrier the first leg wrote, the storage carriers +included. + ### What the PyPSA hub cannot carry The route runs through a PyPSA network, and three values have no PyPSA column: diff --git a/docs/translation_mappings/translation-from-pypsa-to-sienna.md b/docs/translation_mappings/translation-from-pypsa-to-sienna.md index 84b5e51..77cc9fd 100644 --- a/docs/translation_mappings/translation-from-pypsa-to-sienna.md +++ b/docs/translation_mappings/translation-from-pypsa-to-sienna.md @@ -11,7 +11,7 @@ Concretely, versus `main` (≈280 insertions / 294 deletions): - **Time series.** Replaced the `SingleTimeSeries`/`add_time_series!` framing with SiennaSchemas `TimeSeriesAssociation` records (integer `owner_id`); value arrays are **retained in HDF5** keyed by `time_series_uuid` (the schema's named external store). Standardised on the **per-unit shape + `scaling_factor_multiplier`** convention. - **Component extensions.** PyPSA fields with no SiennaSchemas home now go in a separate `extensions.json` sidecar, a document keyed by kind whose records are identified by `name`, replacing the PowerSystems.jl `ext` dict. - **References & ordering.** `get_component(...)` / "added to the `System`" became integer-`id` references with an id-assignment ordering (the lists themselves are unordered). -- **Investments boundary.** Added that SiennaSchemas' `Investments/` namespace is a separate, parallel model (the home for capacity expansion); v1 targets **Operations** (PowerSimulations.jl), with the rationale and the open question of whether expansion interoperability is intended. +- **Investments boundary.** Added that SiennaSchemas' `Investments/` namespace is a separate, parallel model (the home for capacity expansion); this document targets **Operations** (PowerSimulations.jl), and `pypsa-to-sienna-investments` translates the expansion problem. --- @@ -79,12 +79,10 @@ This document targets the SiennaSchemas **Operations** namespace (the dispatchab - It adds policy `Requirements` (`CarbonCaps`, `CarbonTax`, `CapacityReserveMargin`, `EnergyShareRequirements`, …) and `Financials`. - It bridges to Operations through each technology's `power_systems_type` (the Operations type it realises into) and `TopologyMapping` (region → buses). -**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, which is a separate translation target (PyPSA expansion → SiennaSchemas Investments), not extra fields on an Operations component. This document is the Operations translation, so it drops the expansion parameters: it takes `p_nom_opt` (post-solve capacities) as fixed Operations capacity, and keeps round-trip crumbs in the `ext` sidecar. The `pypsa-to-sienna-investments` pipeline translates the expansion problem itself, and writes a portfolio document beside the operations system. It is the second leg of `plexos-to-sienna-investments`, so [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states every field the portfolio holds. The source column of that document names the PLEXOS property behind each field, not the PyPSA one. +**Implication for this translation.** PyPSA's capacity-expansion fields (`p_nom_extendable`, `capital_cost`, `p_nom_min`/`p_nom_max`, `build_year`, `lifetime`) belong to **Investments**, not extra fields on an Operations component. This document is the Operations translation, so it drops the expansion parameters: it takes `p_nom_opt` (post-solve capacities) as fixed Operations capacity, and keeps round-trip crumbs in the `ext` sidecar. The `pypsa-to-sienna-investments` pipeline translates the expansion problem itself, and writes a portfolio document beside the operations system. It is the second leg of `plexos-to-sienna-investments`, so [Translation from PLEXOS to a Sienna investments portfolio](translation-from-plexos-to-sienna-investments.md) states the source of each technology, requirement and attribute field the portfolio holds. The `From` column of that document names the PLEXOS property behind each field, not the PyPSA one. Throughout this document, **`effective_p_nom`** denotes the capacity an operations model may dispatch: `p_nom_opt` where an extendable component states one, `p_nom_min` where it states a capacity a build cannot take away, and `p_nom` otherwise. All capacity-derived fields — `base_power`, `active_power`, `active_power_limits`, `ramp_limits`, and the hydro energy budget scaling factor — use `effective_p_nom`. For a non-extendable component `effective_p_nom = p_nom`. A solve that builds none of an extendable component writes `p_nom_opt` 0, and that 0 is the capacity to translate: the plan rejected the build. A network no solve has touched leaves `p_nom_opt` out of the file altogether, and an extendable component there is rated at the `p_nom_min` it already runs. An extendable component with neither a `p_nom_opt` nor a `p_nom_min` is a candidate rather than a plant: the generator, renewable, hydro and storage mappings leave it out and record it as skipped. -**Why Operations and not Investments.** This document is the Operations translation, and PowerSimulations.jl is its target. - ### System container SiennaSchemas defines per-component schemas but not how a whole system is packaged, so we define the container: a **JSON object mapping each Sienna type name to a list of that type's component objects**. From 62f21d5a2553539543dd0306673de77177c01b4f Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Thu, 10 Sep 2026 13:33:36 +0000 Subject: [PATCH 8/9] Answer the five comments the automated rounds deferred The user asked for each of these, so this commit answers r3-06, r4-17, r4-18, r4-06 and r1-10. The AEMO case study stated that a generator takes the name of its `Fuel` where it burns one. The translator reads the `Fuel` only where the generator also states a `Heat Rate`. The sentence now says so, and it counts the sibling categories of each named category. The `s_nom_extendable` row of the PLEXOS to PyPSA document gave "Version 1 dispatches only" as the reason for `False`. The same document states that a `Generator`, a `Battery` and a pumped-storage turbine do become extendable. The row now gives the reason that holds: the translator writes no expandable line. The SEM and CAISO case studies put the User mappings file prompt after the pipeline prompt. `_prompt_translate` collects the source, step and sink parameters first and asks for the mappings file last, so both tables now put that row at the end. The README named no document for what an expansion loses. The investments bullet now names the gap analysis. --- README.md | 2 +- code-review/ledger.jsonl | 5 +++++ docs/case_studies/aemo-isp-2024.md | 7 ++++--- docs/case_studies/caiso-sa26.md | 2 +- docs/case_studies/sem-2024-2032.md | 2 +- .../translation-from-plexos-to-pypsa.md | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f9f1325..6dd2158 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,7 @@ Use cases live under `interop/core/use_cases/`, each implementing a Protocol por - `docs/translation_mappings/translation-from-pypsa-to-sienna.md` is the authoritative mapping reference for PyPSA / Sienna field translation. - `docs/translation_mappings/translation-from-plexos-to-pypsa.md` states what each part of a PLEXOS model becomes in PyPSA. - `docs/translation_mappings/translation-from-plexos-to-sienna.md` states the same for Sienna, and `plexos-to-sienna-gap-analysis.md` beside it states what that translation loses and what each loss does to a dispatch. -- `docs/translation_mappings/translation-from-plexos-to-sienna-investments.md` states what a PLEXOS expansion plan becomes in a Sienna investments portfolio, which `plexos-to-sienna-investments` writes beside the base system. +- `docs/translation_mappings/translation-from-plexos-to-sienna-investments.md` states what a PLEXOS expansion plan becomes in a Sienna investments portfolio, which `plexos-to-sienna-investments` writes beside the base system, and `plexos-to-sienna-gap-analysis.md` states what the portfolio leaves out and what each loss does to an expansion. - `docs/developer_documentation/mutation-testing.md` covers the mutmut workflow. - `tests/features/README.md` explains the in-process REPL-driven BDD harness and when subprocess is needed. diff --git a/code-review/ledger.jsonl b/code-review/ledger.jsonl index 0c683f8..fc08ed4 100644 --- a/code-review/ledger.jsonl +++ b/code-review/ledger.jsonl @@ -278,3 +278,8 @@ {"id": "r4-20", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/translation_mappings/plexos-to-sienna-gap-analysis.md", "line": 3, "severity": "nit", "label": "nitpick", "claim": "\"Each part below names the run it covers\" only points at the two headings below, which name their run in plain sight.", "suggested_fix": "Delete the sentence.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r4-21", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 182, "severity": "nit", "label": "nitpick", "claim": "The trailing clause of the third bullet states the instruction the paragraph two lines below gives for every row.", "suggested_fix": "Delete the clause.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} {"id": "r4-22", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 4, "file": "docs/case_studies/aemo-isp-2024.md", "line": 234, "severity": "nit", "label": "nitpick", "claim": "The closing paragraph points at the mapping document, which the section already links.", "suggested_fix": "Keep the gap analysis pointer only.", "rule_created": false, "ts": "2026-09-10T10:09:20Z", "source": "pointless", "verdict": "accepted", "reason": "an automated run acts on a blocking comment or one that removes lines", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r3-06", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 5, "file": "docs/case_studies/aemo-isp-2024.md", "line": 174, "severity": "minor", "label": "issue", "claim": "The carrier rule omits the heat-rate condition, so a reader writes the wrong mappings row for a generator that names a `Fuel` and states no `Heat Rate`.", "suggested_fix": "Write \"where it burns one at a heat rate\".", "rule_created": false, "ts": "2026-09-10T13:33:36Z", "source": "user", "verdict": "accepted", "reason": "the user asked for it", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-17", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 5, "file": "docs/translation_mappings/translation-from-plexos-to-pypsa.md", "line": 205, "severity": "minor", "label": "issue", "claim": "The `s_nom_extendable` row gives \"Version 1 dispatches only\" as the reason, which line 164 contradicts.", "suggested_fix": "Write \"The translator writes no expandable line.\"", "rule_created": false, "ts": "2026-09-10T13:33:36Z", "source": "user", "verdict": "accepted", "reason": "the user asked for it", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-18", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 5, "file": "docs/case_studies/sem-2024-2032.md", "line": 97, "severity": "minor", "label": "issue", "claim": "`sem-2024-2032.md` and `caiso-sa26.md` list the User mappings file prompt before the input XML, but the REPL asks for it after every source, step and sink prompt.", "suggested_fix": "Move the row to the end of the table in both case studies.", "rule_created": false, "ts": "2026-09-10T13:33:36Z", "source": "user", "verdict": "accepted", "reason": "the user asked for it", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r4-06", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 5, "file": "README.md", "line": 307, "severity": "minor", "label": "issue", "claim": "The investments bullet names no document for what the expansion path loses.", "suggested_fix": "Name `plexos-to-sienna-gap-analysis.md` in the investments bullet.", "rule_created": false, "ts": "2026-09-10T13:33:36Z", "source": "user", "verdict": "accepted", "reason": "the user asked for it", "category": null, "promote_now": false, "suppressed_by": null} +{"id": "r1-10", "pr": "12", "repo": "transition-zero/tz-oss-interop", "round": 5, "file": "docs/case_studies/aemo-isp-2024.md", "line": 173, "severity": "nit", "label": "issue", "claim": "The count of seven categories is lower than the number a reader must leave out, because each named category has sibling categories per state.", "suggested_fix": "Write \"seven named categories, and their siblings\".", "rule_created": false, "ts": "2026-09-10T13:33:36Z", "source": "user", "verdict": "accepted", "reason": "the user asked for it", "category": null, "promote_now": false, "suppressed_by": null} diff --git a/docs/case_studies/aemo-isp-2024.md b/docs/case_studies/aemo-isp-2024.md index 912e205..25b3379 100644 --- a/docs/case_studies/aemo-isp-2024.md +++ b/docs/case_studies/aemo-isp-2024.md @@ -170,9 +170,10 @@ Write a second mappings file, `inputs/plexos_expansion_mappings.yaml`. It is the file plus one row for each carrier a candidate takes. A candidate whose carrier the file does not name is left out of the portfolio, and `decisions.md` names each one. -The dispatch run above leaves seven categories out of its file. The expansion run names the -ones that hold candidate plants. A generator takes the name of its `Fuel` where it burns one -and its category where it does not, so add one `category` row for each of these: +The dispatch run above leaves seven named categories, and their siblings, out of its file. +The expansion run names the ones that hold candidate plants. A generator takes the name of its +`Fuel` where it burns one at a heat rate, and its category where it does not, so add one +`category` row for each of these: - `2023 REZ NSW`, and the sibling category of each other state: a wind or a solar candidate, so `RenewableDispatch` with the prime mover `WT` or `PVe`; diff --git a/docs/case_studies/caiso-sa26.md b/docs/case_studies/caiso-sa26.md index 9ad8378..3a66221 100644 --- a/docs/case_studies/caiso-sa26.md +++ b/docs/case_studies/caiso-sa26.md @@ -178,12 +178,12 @@ Select `translate`. Then give these answers: | Source framework | `plexos` | | Destination framework | `sienna` | | Pipeline | `plexos-to-sienna-monte-carlo` | -| User mappings file | `inputs/plexos_user_mappings.yaml` | | the PLEXOS `` input XML | `case_study_inputs/caiso-sa26/CAISOSA26 20260429.xml` | | which PLEXOS Model to translate | `M09Y2026 SA26` | | a four-digit year such as 2026 | Leave empty, as for the PyPSA run above. | | directory to hold the ensemble | `outputs/caiso-m09-sienna` | | names each replication's directory | Keep the default, `{sample}` | +| User mappings file | `inputs/plexos_user_mappings.yaml` | That run writes one directory per replication, `1`, `2` and `3`, each holding four files: `system.json`, its HDF5 companion `system_time_series_storage.h5`, `extensions.json`, and the diff --git a/docs/case_studies/sem-2024-2032.md b/docs/case_studies/sem-2024-2032.md index fed7d88..af72352 100644 --- a/docs/case_studies/sem-2024-2032.md +++ b/docs/case_studies/sem-2024-2032.md @@ -94,11 +94,11 @@ Select `translate`. Then give these answers: | Source framework | `plexos` | | Destination framework | `sienna` | | Pipeline | `plexos-to-sienna` | -| User mappings file | `inputs/plexos_user_mappings.yaml` | | the PLEXOS `` input XML | `case_study_inputs/sem-2024-2032/PUBLIC Validation 2024-2032 Model 2025-03-14.xml` | | which PLEXOS Model to translate | `Opt A 24-32 (Avail, Uplift, Wheeling)--MIP` | | a four-digit year such as 2026 | `2026` | | the SiennaSchemas system.json | `outputs/system.json` | +| User mappings file | `inputs/plexos_user_mappings.yaml` | That run writes three files: `outputs/system.json`, its HDF5 companion `outputs/system_time_series_storage.h5`, and `outputs/extensions.json`. Those three files diff --git a/docs/translation_mappings/translation-from-plexos-to-pypsa.md b/docs/translation_mappings/translation-from-plexos-to-pypsa.md index 81ff442..c8ba8aa 100644 --- a/docs/translation_mappings/translation-from-plexos-to-pypsa.md +++ b/docs/translation_mappings/translation-from-plexos-to-pypsa.md @@ -202,7 +202,7 @@ PyPSA `Line` has no cost for its flow. Thus for such a line the charge is `not m | `length` | km | `Length` | `direct` | | `active` | | `True` | `default` | | `carrier` | | `AC` | `default` | -| `s_nom_extendable` | | `False`. Version 1 dispatches only. | `default` | +| `s_nom_extendable` | | `False`. The translator writes no expandable line. | `default` | | `v_ang_min` / `v_ang_max` | | PLEXOS states no voltage-angle limit. | `not mapped` | `s_nom` is one rating for the two directions. Thus if the forward limit and the reverse From 058a3ed7fb9a7798449baa1024bed18fdbb29ef4 Mon Sep 17 00:00:00 2001 From: "Thomas C. King" Date: Thu, 10 Sep 2026 20:25:23 +0000 Subject: [PATCH 9/9] Say that a constraint stating nothing about the plan stays in CARBON_CAP_SKIPS fills a null `applies_to_expansion_plan` as true, so only a constraint that states false is left out. --- docs/translation_mappings/plexos-to-sienna-gap-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md index 8e1d578..37f831d 100644 --- a/docs/translation_mappings/plexos-to-sienna-gap-analysis.md +++ b/docs/translation_mappings/plexos-to-sienna-gap-analysis.md @@ -374,7 +374,7 @@ all five of these conditions: - it holds its weighted sum to `<=`; - it states a right-hand side for a year or for the whole horizon; - that right-hand side is a finite number; -- its `Include in LT Plan` is true. +- its `Include in LT Plan` is not false, and a constraint that states nothing stays in. Every other constraint is left out, `decisions.md` names it, and the log warns. Every constraint still reaches the `extensions.json` sidecar, whether or not it became a cap.