Skip to content

Read a PLEXOS expansion plan and write a Sienna investments portfolio - #14

Closed
thomaschristopherking wants to merge 2 commits into
mainfrom
issue-116-plexos-sienna-capacity-expansion
Closed

Read a PLEXOS expansion plan and write a Sienna investments portfolio#14
thomaschristopherking wants to merge 2 commits into
mainfrom
issue-116-plexos-sienna-capacity-expansion

Conversation

@thomaschristopherking

@thomaschristopherking thomaschristopherking commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

TL;DR

A PLEXOS model states what it may build in Build Cost, Max Units Built, WACC,
Economic Life, Technical Life and FO&M Charge, and states when each unit arrives and
leaves in a dated Units property. The reader named none of them, so the AEMO 2024 ISP
file's 8,539 Generator.Build Cost rows, 372 objects with a Max Units Built, 604 dated
generator rows and 186 Constraints all stopped at the first leg. A PLEXOS expansion plan now
reaches PyPSA as extendable components and Sienna as a PortfolioDocument.

Closes the work of transition-zero/tz-infra-interop#116.
Replaces the four-branch stack #9, #10, #11 and #12, squashed into one commit.

Files to review (101, +7270 / -750): one feature commit, plus 0ef9fab, which deletes
the code-review/ directory.

File Why
docs/specs/2026-09-08-plexos-sienna-investments-design.md (start here) The approved design: what each leg carries, and why the annuity stays PyPSA's.
interop/plugins/shared/plexos_pypsa_translations/_expansion.py (new) What a PLEXOS object may build, and what building it costs.
interop/plugins/shared/plexos_pypsa_translations/_lifespan.py (new) Walks the dated Units bands for the build year and the retirement year.
interop/plugins/steps/pypsa_to_sienna_investments_map_technologies.py (new) Every candidate becomes a technology, a requirement or an attribute.
interop/plugins/shared/sienna_investments_constants.py (new) The SiennaSchemas Investments vocabulary, which the repository had none of.
interop/plugins/sinks/emit_sienna_portfolio.py (new) Writes the PortfolioDocument beside the operations system.
interop/plugins/shared/pypsa_sienna_translations/_shared.py capacity_choice: which column rates a component, and the value that column states.
docs/translation_mappings/translation-from-plexos-to-sienna-investments.md (new) The whole mapping in PLEXOS words, and where the hub loses something.

Why

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

How

A candidate is an object with something left to build. Max Units Built above zero makes
one, and its capacity is one unit's rated power x Max Units Built rather than the zero its
Units states. That one rule drives the PyPSA side: p_nom_extendable, p_nom_min,
p_nom_max, overnight_cost, discount_rate and lifetime all follow from it, and PyPSA
works out the annuity from the last three rather than the translator assembling a capital
cost of its own.

Three PyPSA fields have no home for what PLEXOS states, so they travel in the extensions
sidecar: the size of one unit, the technology lifetime, and the yearly FO&M Charge. The
charge is the interesting one. PyPSA reads fom_cost as a charge for the whole modelled
horizon and adds it to the annuitised build cost without scaling, so writing a yearly number
there priced a two-day run as if it lasted a year. Sienna's operation_costs.fixed is
yearly, so the portfolio reads the sidecar and falls back to fom_cost for a network a user
wrote themselves.

Capacity the plan has not decided is not capacity an operations model may dispatch. The
pypsa-to-sienna leg rates a component by p_nom_opt, then by the lower of p_nom_min and
p_nom, then by p_nom. A component with none of those is a build, not a plant, and the
generator, renewable, hydro, storage and link mappings leave it out and record it as skipped.
Capping p_nom_min by p_nom is what separates a brownfield plant from a forced minimum
build on a greenfield candidate.

Reviewer notes

  • The stack was squashed, not merged. Write a Sienna investments portfolio from an expansion plan #11 and Give the AEMO case study an expansion path, and the gap analysis the portfolio's gaps #12 sat on a stale Keep the years a PLEXOS plan states, and carry its constraints #10 and were missing 35
    commits, including every review fix. The squash merges the current lower stack into the tip
    first; a118f7b in the pre-squash history is the one integration fix that needed, where the
    portfolio's operation_costs.fixed read a fom_cost column the review rounds had removed.
  • Seventeen review rounds ran over the first leg. They found two criticals, both the same
    shape: this work makes a zero base_power, p_nom or max_hours an ordinary value, and
    code downstream divided by it.
  • The code-review/ directory goes. It held a ledger of every review comment and the
    flag rules a reviewer had to obey. Nothing read any of it, so 0ef9fab removes the four
    files. That commit is a clean deletion and needs no review.
  • A model's data never stops a translation. A candidate that prices no build, a battery
    built of nothing, a hydro unit that converts no water: each is left out of the output and
    named in decisions.md and the log, rather than raising or writing a number nobody meant.
  • Focus area: capacity_choice in pypsa_sienna_translations/_shared.py decides both the
    capacity and the source attribute decisions.md names for it. One chain gives both so they
    cannot disagree, and I would like a second opinion on the p_nom_min cap.

Tests

638 scenarios pass. Every gate passes: ruff, mypy --strict, lint-imports, the two
plugin lints, and the four repository lint scripts.

uv run pytest -q

Follow-up

The portfolio's gaps are listed in docs/translation_mappings/plexos-to-sienna-gap-analysis.md.

@thomaschristopherking
thomaschristopherking requested a review from a team as a code owner September 10, 2026 21:02
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: daa30676-44b4-481e-a123-bc584a87f14e


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

❤️ Share

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

@github-actions

Copy link
Copy Markdown

maintainability index

Gate: ✅ PASS
Score: A (84.88) mean over 260 files (function-weighted: 70% mean-of-functions, 30% file)
Lowest file: interop/adapters/inbound/interactive_cli/app.py at A (54.14)
Most strained function: interop/adapters/inbound/interactive_cli/app.py::_run_solve (139 lines, CC 22) at MI 33.5

All files grade A.

Full report
File Combined File MI Func mean Worst function
interop/adapters/inbound/interactive_cli/app.py 54.14 9.79 73.16 _run_solve (139ln, CC22)
interop/plugins/shared/pypsa_constants.py 55.28 55.28 55.28
interop/plugins/steps/sienna_to_pypsa/map_generators.py 57.45 29.65 69.37 _derive_thermal (66ln, CC10)
interop/plugins/shared/sienna_investments_constants.py 57.60 57.60 57.60
interop/plugins/validators/pypsa/bus_reference_integrity.py 60.63 68.17 57.39 validate (26ln, CC7)
interop/plugins/shared/plexos_pypsa_translations/_storage_hydro.py 63.17 46.18 70.45 _derive_turbine (29ln, CC3)
interop/plugins/steps/sienna_to_powersimulations/map_components.py 63.94 28.60 79.08 _map_bus_backed_component (134ln, CC15)
interop/plugins/sinks/emit_sienna_portfolio.py 64.11 42.72 73.27 build_payload (34ln, CC5)
interop/plugins/steps/sienna_to_pypsa/map_transmission.py 64.18 38.69 75.10 _map_lines (52ln, CC5)
interop/plugins/shared/plexos_pypsa_translations/_generator_decisions.py 65.19 33.63 78.72 record_generator (28ln, CC8)
libs/interop-testing/src/interop_testing/steps/power_simulations.py 65.81 42.98 75.60 assert_h5_has_ts_association (37ln, CC6)
interop/plugins/sinks/emit_sienna_system_json.py 66.95 40.68 78.21 _build_bus_components (18ln, CC6)
interop/plugins/sources/stage_sienna_results.py 66.96 54.94 72.10 load_into_state (24ln, CC4)
interop/plugins/shared/plexos_pypsa_translations/_generator_derivation.py 67.10 30.80 82.65 _fuel_use (16ln, CC3)
interop/plugins/validators/pypsa/unique_names.py 67.29 82.81 60.63 validate (25ln, CC4)
interop/plugins/steps/pypsa_to_results_map_observations.py 67.34 43.27 77.65 _map_dispatch (46ln, CC5)
interop/di/discovery.py 67.95 35.46 81.88 _discover_off_the_shelf_plugins (34ln, CC3)
interop/plugins/sources/plexos_horizon.py 68.16 46.84 77.30 _model_horizon_id (14ln, CC8)
interop/plugins/steps/sienna_to_results/map_results.py 68.50 47.39 77.54 _map_storage_dispatch (36ln, CC4)
interop/plugins/sources/stage_plexos_xml.py 69.46 27.71 87.36 _property_row (27ln, CC4)
interop/plugins/validators/pypsa/generators.py 69.84 67.85 70.69 validate (13ln, CC2)
interop/plugins/steps/pypsa_to_sienna_investments_map_technologies.py 69.88 36.52 84.18 run (27ln, CC5)
interop/adapters/outbound/markdown_report.py 70.28 40.84 82.89 _render_field (12ln, CC6)
interop/plugins/shared/plexos_pypsa_translations/_constraints.py 70.37 47.57 80.14 _carry (18ln, CC5)
interop/plugins/shared/plexos_pypsa_translations/_transmission.py 70.42 38.23 84.21 _read_transmission (24ln, CC6)
interop/plugins/validators/pypsa/storage_units.py 70.52 70.12 70.69 validate (13ln, CC2)
interop/plugins/sinks/emit_results_parquet.py 70.89 52.47 78.79 write (27ln, CC3)
libs/interop-testing/src/interop_testing/builders/pypsa_networks.py 70.89 33.58 86.89 add_generator (59ln, CC12)
interop/plugins/steps/sienna_to_pypsa/map_storage_units.py 71.09 41.90 83.60 _derive_phs (37ln, CC1)
interop/adapters/outbound/csv_report.py 71.24 44.99 82.50 _rows_for_event (15ln, CC7)
interop/adapters/inbound/interactive_cli/schema_prompts.py 71.28 51.67 79.68 _annotation_includes_path (9ln, CC6)
interop/plugins/validators/pypsa/lines.py 71.28 72.66 70.69 validate (13ln, CC2)
interop/plugins/shared/plexos_pypsa_translations/_batteries.py 71.34 54.35 78.62 _battery_initial_level (15ln, CC3)
interop/plugins/shared/plexos_pypsa_translations/_loads.py 71.35 41.35 84.20 _derive_loads (21ln, CC4)
interop/lints/plugin_filesystem.py 71.46 50.97 80.24 _function_violations (23ln, CC13)
interop/plugins/sources/plexos_csv_layouts.py 71.64 45.32 82.92 warn_unstageable_layout (10ln, CC3)
interop/plugins/shared/plexos_dates.py 72.01 49.98 81.44 covers (4ln, CC4)
interop/adapters/inbound/interactive_cli/history.py 72.06 49.18 81.86 record (11ln, CC3)
interop/plugins/validators/pypsa/links.py 72.11 75.42 70.69 validate (13ln, CC2)
interop/plugins/shared/pypsa_sienna_user_mappings.py 72.17 56.58 78.85 get_prime_mover_map (10ln, CC5)
interop/plugins/steps/pypsa_to_sienna_map_components.py 72.52 40.05 86.44 _map_links (36ln, CC3)
interop/plugins/steps/pypsa_to_sienna_relate_components.py 72.63 61.38 77.45 _build_arcs (21ln, CC5)
interop/plugins/shared/plexos_pypsa_translations/_generators.py 72.75 42.60 85.68 _map_one (28ln, CC8)
interop/plugins/sources/stage_pypsa_network_file.py 72.79 51.02 82.12 _stage_time_series (25ln, CC5)
libs/interop-testing/src/interop_testing/builders/plexos_tables.py 73.25 38.13 88.30 _add_property (44ln, CC7)
interop/templates/examples/pypsa/plugins/steps/normalise_carrier.py 73.38 66.49 76.33 run (42ln, CC7)
interop/plugins/steps/sienna_to_pypsa/map_loads.py 73.73 62.73 78.44 _record_load_time_series (22ln, CC4)
interop/plugins/shared/plexos_pypsa_translations/_expansion.py 74.05 44.69 86.63 derive_expansion (35ln, CC3)
interop/plugins/sinks/emit_pypsa_network.py 74.18 49.11 84.93 _add_buses (20ln, CC3)
libs/interop-testing/src/interop_testing/builders/sienna_systems.py 74.19 37.36 89.98 add_line (44ln, CC2)
interop/plugins/shared/plexos_pypsa_translations/_load_shedding.py 74.68 55.83 82.76 add_load_shedding_generators (20ln, CC7)
interop/plugins/steps/sienna_to_pypsa/relate_components.py 74.83 70.56 76.66 run (36ln, CC7)
interop/core/use_cases/compare.py 74.83 49.48 85.69 _coverage (21ln, CC2)
libs/interop-testing/src/interop_testing/steps/sienna_system.py 74.88 33.84 92.47 assert_h5_ts_values (17ln, CC6)
interop/plugins/shared/plexos_units.py 75.02 49.41 86.00 stated_units (12ln, CC6)
interop/lints/plugin_inheritance.py 75.18 55.35 83.67 _check_file (30ln, CC9)
interop/plugins/sinks/emit_power_simulations_h5_sidecar.py 75.21 54.42 84.13 write_h5 (70ln, CC7)
interop/plugins/sources/stage_sienna_system_json.py 75.24 52.18 85.12 load_into_state (22ln, CC4)
interop/adapters/outbound/julia_solver.py 75.25 45.89 87.83 _run_pipeline_inner (159ln, CC2)
interop/plugins/shared/plexos_sienna_user_mappings.py 75.39 67.26 78.88 _reject_unknown_storage_kinds (12ln, CC6)
interop/plugins/shared/pypsa_sienna_translations/_lines.py 75.40 45.84 88.07 fill_line_defaults (38ln, CC6)
libs/interop-testing/src/interop_testing/builders/plexos_csv_files.py 75.42 65.12 79.84 write_csv (10ln, CC5)
interop/plugins/shared/pypsa_sienna_translations/_ts_info.py 75.68 74.11 76.35 collect_ts_info (40ln, CC3)
interop/plugins/shared/plexos_pypsa_translations/_storage_shared.py 75.73 43.48 89.55 rate_object (22ln, CC6)
interop/plugins/shared/plexos_pypsa_translations/_shared.py 75.93 51.40 86.44 _membership_pairs (16ln, CC2)
interop/core/cli_overrides.py 76.14 57.10 84.30 __init__ (8ln, CC2)
interop/plugins/sources/stage_pypsa_network_ensemble.py 76.20 52.75 86.25 load_into_state (21ln, CC3)
interop/adapters/outbound/markdown_comparison_report.py 76.24 53.78 85.87 render (48ln, CC6)
interop/core/composition/document.py 76.33 47.16 88.83 validate_frameworks (9ln, CC3)
interop/core/adapters_config.py 76.61 58.20 84.50 load_adapters_config (14ln, CC6)
interop/plugins/shared/pypsa_destination.py 76.76 95.81 68.60 append_destination_rows (12ln, CC3)
interop/di/factories.py 77.21 42.31 92.17 make_adapter_factory (25ln, CC2)
interop/plugins/shared/pypsa_sienna_investments_translations/_carbon_caps.py 77.34 59.02 85.19 _read_record (14ln, CC7)
interop/templates/examples/pypsa/plugins/sinks/emit_sienna_csv.py 77.34 74.49 78.56 write (11ln, CC5)
libs/interop-testing/src/interop_testing/steps/sienna_portfolio.py 77.53 73.08 79.44 assert_base_system_attribute_field (17ln, CC3)
interop/plugins/shared/plexos_pypsa_translations/_reserves.py 77.58 50.89 89.02 _load_profiles (17ln, CC2)
interop/plugins/steps/sienna_to_powersimulations/_cost_events.py 77.89 57.47 86.65 _record_cost_curve_events (106ln, CC4)
interop/plugins/shared/pypsa_sienna_investments_translations/_associations.py 78.03 98.02 69.47 build_association_rows (15ln, CC1)
interop/plugins/shared/plexos_pypsa_translations/_buses.py 78.13 59.75 86.00 map_buses (14ln, CC5)
libs/interop-testing/src/interop_testing/steps/pypsa_network/build_network.py 78.28 39.07 95.08 given_network_contains_generator (42ln, CC15)
interop/plugins/shared/translation_runner.py 78.45 56.20 87.98 _build_source_field (12ln, CC2)
interop/plugins/sinks/emit_power_simulations_system_json.py 78.47 63.71 84.79 _build_all_components (18ln, CC5)
interop/plugins/shared/pypsa_sienna_translations/_component_mapping.py 78.54 78.54 78.54
interop/core/use_cases/translate.py 78.70 50.09 90.97 _run_leg (16ln, CC1)
interop/plugins/shared/plexos_pypsa_translations/_storage_units.py 78.84 53.52 89.69 _dropped_values (21ln, CC3)
interop/core/composition/addressing.py 79.07 58.57 87.85 find_node (14ln, CC6)
interop/core/user_mappings_loader.py 79.09 52.67 90.41 _load (17ln, CC3)
interop/plugins/sources/plexos_dated_properties.py 79.19 58.39 88.10 _stepped_frame (16ln, CC4)
interop/core/composition/planning.py 79.21 51.25 91.20 _reject_step_overrides_on_a_chain (9ln, CC3)
interop/plugins/validators/pypsa/loads.py 79.48 73.81 81.91 _check_static (13ln, CC2)
interop/plugins/sinks/emit_json.py 79.70 61.76 87.39 write (11ln, CC3)
interop/plugins/steps/plexos_sienna_mappings_to_carriers.py 79.75 60.11 88.17 _derive_from (13ln, CC3)
interop/core/use_cases/solve.py 79.85 57.87 89.27 _solve_sienna (28ln, CC3)
libs/interop-testing/src/interop_testing/builders/plexos_resources.py 79.93 41.88 96.24 _add_storage (12ln, CC3)
interop/plugins/shared/plexos_pypsa_translations/_generator_lookups.py 80.03 62.92 87.36 _aggregate_series (13ln, CC4)
interop/adapters/outbound/highs_network_solver.py 80.08 62.62 87.56 _zero_look_ahead_outputs (9ln, CC3)
interop/plugins/sinks/emit_power_simulations_files.py 80.12 57.66 89.75 write (20ln, CC2)
libs/interop-testing/src/interop_testing/steps/pypsa_network/_written_network.py 80.21 59.63 89.03 assert_series (15ln, CC5)
interop/plugins/shared/pypsa_sienna_translations/_hydro.py 80.24 59.59 89.09 _hydro_skips (10ln, CC2)
interop/adapters/inbound/headless_cli/app.py 80.26 59.54 89.15 run_headless (54ln, CC10)
libs/interop-testing/src/interop_testing/projects.py 80.46 75.31 82.67 write_project_plugin_in_subdir (5ln, CC1)
interop/plugins/shared/warning_text.py 80.52 97.41 73.29 name_a_few (7ln, CC2)
libs/interop-testing/src/interop_testing/steps/reports.py 80.66 66.14 86.89 assert_manifest_fields (5ln, CC4)
interop/plugins/shared/pypsa_sienna_translations/_loads.py 80.66 56.44 91.04 _load_max_active_power (44ln, CC6)
interop/plugins/shared/plexos_pypsa_translations/_lifespan.py 80.69 56.55 91.03 _derive_year (7ln, CC2)
interop/core/use_cases/init_project.py 80.84 68.96 85.93 __call__ (11ln, CC3)
interop/plugins/sinks/emit_sienna_files.py 80.95 54.53 92.27 write (9ln, CC2)
interop/plugins/shared/pypsa_sienna_translations/_ensemble.py 81.16 83.00 80.38 _report_partial (20ln, CC3)
libs/interop-testing/src/interop_testing/builders/plexos_models.py 81.38 50.10 94.78 save (12ln, CC5)
libs/interop-testing/src/interop_testing/steps/files.py 81.42 61.25 90.07 assert_json_array_length (10ln, CC4)
interop/plugins/sinks/emit_sienna_files_ensemble.py 81.49 72.11 85.52 replication_paths (8ln, CC1)
interop/plugins/shared/pypsa_sienna_translations/_links.py 81.54 53.45 93.58 link_in_scope (8ln, CC1)
interop/plugins/steps/caiso_plexos_to_results.py 81.54 51.18 94.56 run (10ln, CC3)
interop/templates/examples/pypsa/plugins/sources/stage_pypsa_csv.py 81.60 69.87 86.63 load_into_state (30ln, CC6)
interop/plugins/sinks/emit_pypsa_network_ensemble.py 81.69 68.98 87.13 write (12ln, CC3)
interop/plugins/shared/pypsa_time_series.py 81.72 57.60 92.05 series_timing (16ln, CC2)
interop/core/use_cases/solve_network.py 81.82 60.31 91.04 _last_day_of_window (10ln, CC5)
interop/plugins/shared/plexos_constants.py 81.93 75.42 84.72 is_plexos_true (3ln, CC1)
interop/plugins/shared/pypsa_sienna_translations/_storage.py 81.98 59.28 91.71 fill_storage_defaults (18ln, CC1)
interop/plugins/shared/sienna_constants.py 82.13 40.42 100.00 time_series_uuid (4ln, CC1)
interop/plugins/shared/pypsa_sienna_translations/_generators.py 82.20 48.47 96.66 build_generator_ts_association (41ln, CC4)
interop/plugins/shared/power_simulations_schema.py 82.75 42.49 100.00 get_new_uuid (2ln, CC1)
interop/plugins/sources/plexos_tables.py 82.77 89.69 79.80 class_id_of (6ln, CC3)
interop/plugins/sinks/emit_carrier_mappings.py 82.82 73.76 86.71 write (10ln, CC3)
interop/core/runner.py 82.86 57.74 93.62 _build_params (12ln, CC4)
interop/plugins/steps/sienna_to_powersimulations/fill_availability.py 82.94 63.82 91.13 run (12ln, CC4)
libs/interop-testing/src/interop_testing/steps/plexos_model.py 82.97 49.44 97.34 given_model_contains_late_fraction_period_column_data_file (13ln, CC1)
interop/plugins/shared/pypsa_sienna_investments_translations/_existing.py 83.36 64.31 91.52 _named_years (7ln, CC3)
interop/core/extensions.py 83.39 48.70 98.26 report_unconsumed (7ln, CC4)
interop/logging_setup.py 83.46 85.61 82.54 configure_logging (14ln, CC5)
interop/ports/inbound/translate.py 83.65 72.41 88.47 _describe_files (9ln, CC4)
interop/plugins/sinks/emit_sienna_h5_sidecar.py 84.09 66.06 91.82 _association_rows (5ln, CC3)
interop/plugins/shared/pypsa_sienna_translations/_renewables.py 84.14 61.90 93.67 _renewable_cost_loss (25ln, CC2)
interop/plugins/shared/plexos_pypsa_translations/_ensemble.py 84.35 81.18 85.71 choose_ensemble_samples (25ln, CC4)
interop/plugins/shared/plexos_pypsa_translations/constants.py 84.51 84.51 84.51
interop/di/container.py 84.65 53.11 98.17 reporter_builder (27ln, CC1)
libs/interop-testing/src/interop_testing/builders/sienna_documents.py 84.80 57.40 96.54 find_sienna_component (10ln, CC6)
interop/plugins/shared/staged_samples.py 84.81 77.03 88.15 filter_to_sample (8ln, CC3)
interop/plugins/shared/pypsa_sienna_investments_translations/_shared.py 85.00 66.05 93.12 build_expansion_skips (66ln, CC1)
interop/plugins/sources/stage_caiso_plexos_stack_model.py 85.12 74.39 89.72 load_into_state (15ln, CC2)
interop/plugins/shared/pypsa_sienna_translations/_shared.py 85.14 61.16 95.42 ts_association_row (33ln, CC2)
interop/plugins/shared/plexos_pypsa_translations/decisions.py 85.17 58.30 96.69 record (15ln, CC5)
interop/plugins/shared/sienna_pypsa_translations/mapping.py 85.90 83.97 86.72 bus_id_to_v_nom (10ln, CC3)
interop/adapters/outbound/markdown_validation_report.py 85.95 70.49 92.58 render (10ln, CC3)
interop/core/use_cases/validate.py 86.20 71.01 92.70 __call__ (37ln, CC2)
libs/interop-testing/src/interop_testing/steps/pypsa_network/assert_network.py 86.33 56.37 99.17 assert_pypsa_network_empty (7ln, CC5)
interop/core/pipeline.py 86.43 67.72 94.45 load (12ln, CC2)
interop/plugins/shared/pypsa_sienna_translations/_buses.py 86.53 55.09 100.00 fill_bus_defaults (18ln, CC1)
interop/ports/inbound/solve.py 86.60 71.34 93.14 is_success (6ln, CC2)
interop/plugins/shared/plexos_pypsa_translations/_storage_turbines.py 87.00 86.80 87.09 _states_pump_efficiency (12ln, CC2)
interop/ports/outbound/validation.py 87.04 85.79 87.58 __init__ (8ln, CC3)
interop/plugins/shared/validators.py 87.06 89.30 86.10 check_column_bounds (27ln, CC4)
libs/interop-testing/src/interop_testing/pipeline_driver.py 87.08 90.22 85.73 run_pipeline (28ln, CC5)
interop/plugins/shared/extensions_sidecar.py 87.59 77.36 91.98 _stage_extensions_sidecar (7ln, CC3)
interop/main.py 87.60 85.79 88.37 app (31ln, CC3)
interop/plugins/steps/plexos_to_pypsa/map_components.py 87.73 71.41 94.73 _models_with_a_horizon (9ln, CC2)
libs/interop-testing/src/interop_testing/builders/plexos_generator_specs.py 87.74 70.96 94.93 _property_values (6ln, CC3)
libs/interop-testing/src/interop_testing/builders/caiso_stack_models.py 87.79 67.85 96.34 _set_category (9ln, CC3)
interop/adapters/outbound/local_filesystem.py 87.93 64.75 97.86 _resolve (4ln, CC3)
interop/plugins/sinks/emit_extensions_json.py 87.94 84.09 89.59 write (7ln, CC2)
libs/interop-testing/src/interop_testing/builders/sienna_results.py 88.54 68.01 97.33 save (13ln, CC4)
interop/adapters/inbound/headless_cli/overrides.py 88.57 76.75 93.64 parse_override_flags (16ln, CC6)
interop/plugins/shared/pypsa_sienna_investments_translations/_storage.py 89.05 63.49 100.00 fill_storage_technology_defaults (22ln, CC1)
interop/plugins/shared/pypsa_sienna_investments_translations/_supply.py 89.41 67.26 98.90 operation_cost_struct (20ln, CC1)
interop/core/use_cases/pipeline_catalog.py 89.75 72.22 97.26 _promptable_steps (5ln, CC2)
interop/plugins/steps/plexos_to_pypsa/map_loads.py 89.90 66.34 100.00 __init__ (7ln, CC2)
interop/plugins/sinks/_sienna_files.py 89.98 84.83 92.18 validate_refs (7ln, CC2)
interop/plugins/sinks/noop.py 90.05 66.83 100.00 __init__ (2ln, CC1)
libs/interop-testing/src/interop_testing/files.py 90.10 87.03 91.42 navigate_json (26ln, CC7)
interop/plugins/shared/sienna_pypsa_translations/constants.py 90.13 86.66 91.61 pypsa_carrier (34ln, CC8)
interop/plugins/steps/plexos_to_pypsa/map_buses.py 90.24 67.47 100.00 __init__ (2ln, CC1)
interop/plugins/steps/plexos_to_pypsa/map_constraints.py 90.24 67.47 100.00 __init__ (2ln, CC1)
interop/plugins/steps/plexos_to_pypsa/map_reserves.py 90.24 67.47 100.00 __init__ (2ln, CC1)
interop/plugins/steps/plexos_to_pypsa/map_storage_units.py 90.24 67.47 100.00 __init__ (2ln, CC1)
interop/plugins/steps/plexos_to_pypsa/map_transmission.py 90.24 67.47 100.00 __init__ (2ln, CC1)
interop/ports/outbound/reporting.py 90.48 68.27 100.00 render (1ln, CC1)
libs/interop-testing/src/interop_testing/steps/pipeline.py 90.69 87.54 92.04 assert_log_does_not_contain (5ln, CC2)
interop/plugins/shared/framework_reporting.py 90.71 79.25 95.62 _derived (15ln, CC2)
interop/plugins/sources/noop.py 91.00 69.99 100.00 load_into_state (2ln, CC1)
interop/plugins/steps/sienna_to_pypsa/map_buses.py 91.42 75.42 98.27 map_buses (21ln, CC4)
interop/ports/outbound/filesystem.py 91.49 75.25 98.45 location_name (4ln, CC2)
interop/plugins/steps/noop.py 91.68 72.27 100.00 run (2ln, CC1)
interop/core/reporting.py 91.93 80.92 96.64 append (7ln, CC3)
interop/adapters/outbound/http_filesystem.py 92.33 78.45 98.29 __init__ (3ln, CC2)
interop/ports/outbound/comparison_report.py 93.42 78.08 100.00 render (1ln, CC1)
interop/plugins/sources/stage_plexos_sienna_mappings.py 93.47 78.24 100.00 _stated_row (3ln, CC1)
interop/ports/inbound/pipeline_catalog.py 93.52 78.40 100.00 by_framework_pair (3ln, CC1)
interop/plugins/steps/sienna_to_pypsa/map_components.py 93.99 79.97 100.00 __init__ (3ln, CC1)
interop/plugins/shared/pypsa_sienna_translations/_arcs.py 94.18 80.61 100.00 build_arcs_source_table (27ln, CC1)
interop/plugins/shared/results_constants.py 94.33 94.33 94.33
interop/plugins/shared/sienna_carrier_targets.py 95.54 85.12 100.00 carrier_target_discriminator (8ln, CC3)
interop/plugins/shared/pypsa_sienna_translations/_areas.py 96.13 87.11 100.00 build_areas_source_table (14ln, CC1)
interop/plugins/steps/plexos_to_pypsa/map_generators.py 98.09 93.63 100.00 __init__ (2ln, CC1)
interop/plugins/steps/plexos_to_pypsa_add_load_shedding.py 98.96 96.55 100.00 __init__ (2ln, CC1)
interop/__init__.py 100.00 100.00 100.00
interop/adapters/__init__.py 100.00 100.00 100.00
interop/adapters/inbound/__init__.py 100.00 100.00 100.00
interop/adapters/inbound/base.py 100.00 100.00 100.00 run (1ln, CC1)
interop/adapters/inbound/headless_cli/__init__.py 100.00 100.00 100.00
interop/adapters/inbound/interactive_cli/__init__.py 100.00 100.00 100.00
interop/adapters/outbound/__init__.py 100.00 100.00 100.00
interop/adapters/outbound/noop_report.py 100.00 100.00 100.00 render (2ln, CC1)
interop/adapters/outbound/write_tracking_filesystem.py 100.00 100.00 100.00 __init__ (3ln, CC1)
interop/core/__init__.py 100.00 100.00 100.00
interop/core/composition/__init__.py 100.00 100.00 100.00
interop/core/factories.py 100.00 100.00 100.00 __call__ (1ln, CC1)
interop/core/plugin_errors.py 100.00 100.00 100.00 __init__ (7ln, CC1)
interop/core/results_format.py 100.00 100.00 100.00
interop/core/use_cases/__init__.py 100.00 100.00 100.00
interop/core/user_mappings.py 100.00 100.00 100.00
interop/di/__init__.py 100.00 100.00 100.00
interop/lints/__init__.py 100.00 100.00 100.00
interop/plugins/__init__.py 100.00 100.00 100.00
interop/plugins/shared/__init__.py 100.00 100.00 100.00
interop/plugins/shared/caiso_plexos_constants.py 100.00 100.00 100.00
interop/plugins/shared/constants.py 100.00 100.00 100.00
interop/plugins/shared/plexos_pypsa_translations/__init__.py 100.00 100.00 100.00
interop/plugins/shared/pypsa_ensemble_manifest.py 100.00 100.00 100.00 dump_ensemble_manifest (2ln, CC1)
interop/plugins/shared/pypsa_sienna_investments_translations/__init__.py 100.00 100.00 100.00
interop/plugins/shared/pypsa_sienna_investments_translations/_demand.py 100.00 100.00 100.00 build_demand_translations (8ln, CC1)
interop/plugins/shared/pypsa_sienna_investments_translations/_financials.py 100.00 100.00 100.00 build_portfolio_financial_data (11ln, CC1)
interop/plugins/shared/pypsa_sienna_investments_translations/_topology.py 100.00 100.00 100.00 build_topology_source_table (9ln, CC1)
interop/plugins/shared/pypsa_sienna_translations/__init__.py 100.00 100.00 100.00
interop/plugins/shared/pypsa_sienna_translations/_prime_mover.py 100.00 100.00 100.00 enrich_prime_mover (16ln, CC3)
interop/plugins/shared/results_manifest.py 100.00 100.00 100.00 _timezone_must_be_iana (8ln, CC2)
interop/plugins/shared/sienna_pypsa_translations/__init__.py 100.00 100.00 100.00
interop/plugins/shared/sienna_pypsa_translations/reporters.py 100.00 100.00 100.00 record_dropped (4ln, CC1)
interop/plugins/shared/sienna_results_constants.py 100.00 100.00 100.00 _build_variable_csv (2ln, CC1)
interop/plugins/shared/sienna_to_powersimulations/__init__.py 100.00 100.00 100.00
interop/plugins/sinks/__init__.py 100.00 100.00 100.00
interop/plugins/sources/__init__.py 100.00 100.00 100.00
interop/plugins/steps/__init__.py 100.00 100.00 100.00
interop/plugins/steps/plexos_to_pypsa/__init__.py 100.00 100.00 100.00
interop/plugins/steps/sienna_to_powersimulations/__init__.py 100.00 100.00 100.00
interop/plugins/steps/sienna_to_pypsa/__init__.py 100.00 100.00 100.00
interop/plugins/steps/sienna_to_results/__init__.py 100.00 100.00 100.00
interop/plugins/validators/__init__.py 100.00 100.00 100.00
interop/plugins/validators/pypsa/__init__.py 100.00 100.00 100.00
interop/ports/__init__.py 100.00 100.00 100.00
interop/ports/errors.py 100.00 100.00 100.00 __init__ (8ln, CC1)
interop/ports/inbound/__init__.py 100.00 100.00 100.00
interop/ports/inbound/compare.py 100.00 100.00 100.00 summary (2ln, CC1)
interop/ports/inbound/init_project.py 100.00 100.00 100.00 __init__ (3ln, CC1)
interop/ports/inbound/overrides.py 100.00 100.00 100.00
interop/ports/inbound/validate.py 100.00 100.00 100.00 __call__ (10ln, CC1)
interop/ports/outbound/__init__.py 100.00 100.00 100.00
interop/ports/outbound/netcdf.py 100.00 100.00 100.00 netcdf_engine (11ln, CC3)
interop/ports/outbound/network_solver.py 100.00 100.00 100.00 solve (13ln, CC1)
interop/ports/outbound/solver.py 100.00 100.00 100.00 is_provisioned (7ln, CC1)
interop/ports/outbound/unit_commitment.py 100.00 100.00 100.00
interop/ports/outbound/validation_report.py 100.00 100.00 100.00 render (1ln, CC1)
libs/interop-testing/src/interop_testing/__init__.py 100.00 100.00 100.00
libs/interop-testing/src/interop_testing/builders/__init__.py 100.00 100.00 100.00
libs/interop-testing/src/interop_testing/builders/plexos_vocabulary.py 100.00 100.00 100.00
libs/interop-testing/src/interop_testing/steps/__init__.py 100.00 100.00 100.00
libs/interop-testing/src/interop_testing/steps/caiso_stack_model.py 100.00 100.00 100.00 given_caiso_stack_model (2ln, CC1)
libs/interop-testing/src/interop_testing/steps/isolation.py 100.00 100.00 100.00 isolated_cwd (5ln, CC1)
libs/interop-testing/src/interop_testing/steps/plexos_resources.py 100.00 100.00 100.00 given_model_contains_battery (17ln, CC1)
libs/interop-testing/src/interop_testing/steps/pypsa_network/__init__.py 100.00 100.00 100.00
libs/interop-testing/src/interop_testing/steps/sienna_results.py 100.00 100.00 100.00 _series (2ln, CC2)

@thomaschristopherking
thomaschristopherking force-pushed the issue-116-plexos-sienna-capacity-expansion branch from c53bc70 to c23ab24 Compare September 10, 2026 21:03
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Mutation testing report

Score: 80.6% (319 killed / 396 tested)

⚠️ The run was cut short with 273 mutants unchecked, so this score covers only the part that ran.

Status Count
🎉 Killed 319
🙁 Survived 77
🫥 No tests 1404
⏰ Timeout 68
🤔 Suspicious 0
🔇 Skipped 0
💥 Segfault 0
🧙 Caught by type check 1270
❓ Not checked 273

Timeout-prone functions

Each timeout burns its whole budget (timeout_multiplier and timeout_constant in pyproject.toml) before mutmut kills it, so the functions below dominate wall-clock cost. Adding them to do_not_mutate is the fastest way to cut CI time.

Function Timeouts
interop.adapters.outbound.highs_network_solver.xǁHighsNetworkSolverǁsolve 23
interop.adapters.outbound.highs_network_solver.x__reported_cost 7
interop.adapters.outbound.highs_network_solver.x__zero_look_ahead_outputs 6
interop.adapters.outbound.highs_network_solver.xǁHighsNetworkSolverǁsnapshot_bounds 5
interop.core.use_cases.solve_network.x__last_day_of_window 4
interop.core.use_cases.solve_network.xǁSolveNetworkUsingPortǁ_solve_one 3
interop.ports.outbound.netcdf.x_netcdf_engine 3
interop.adapters.outbound.highs_network_solver.x__snapshots_between 3
interop.adapters.outbound.highs_network_solver.x__warn_if_cut_short 3
interop.core.use_cases.solve_network.xǁSolveNetworkUsingPortǁ_resolve_bounds 2
interop.core.use_cases.solve_network.x_solve_windows 2
interop.core.use_cases.solve_network.x__end_of_day 2
interop.adapters.outbound.highs_network_solver.x__snapshot_coordinate 1
interop.adapters.outbound.highs_network_solver.x__solver_options 1
interop.adapters.outbound.highs_network_solver.x__record_reported_snapshots 1
interop.adapters.outbound.highs_network_solver.x__discard_look_ahead 1
interop.adapters.outbound.highs_network_solver.x__combine 1

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

`Max Units Built` above zero now makes an object a candidate, and a candidate
becomes an extendable PyPSA component carrying what building it costs.
`stage_plexos_xml` stages every date band beside the value in force, and a
readable Constraint reaches the extensions sidecar.
`plexos-to-sienna-investments` writes a SiennaSchemas `PortfolioDocument` beside
the base system it expands, and `pypsa-to-sienna-investments` does the same for a
PyPSA network that states its own expansion. The AEMO case study gains the
expansion path, and the gap analysis gains the portfolio's gaps.

Closes the work of transition-zero/tz-infra-interop#116.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas C. King <thomas.k@transitionzero.org>
@thomaschristopherking
thomaschristopherking force-pushed the issue-116-plexos-sienna-capacity-expansion branch from c23ab24 to 66820a5 Compare September 10, 2026 21:25
The `code-review/` directory held what an automated review cycle had learned
about this repository: a ledger of every comment and its verdict, plus the
always-flag and never-flag rules a reviewer had to obey. The ledger reached 494
records and 502 of this branch's added lines, and told a reviewer nothing about
the code it sat beside.

Nothing reads any of it. No test, script, pre-commit hook or workflow references
the directory, so this removes the four files and no more.

Signed-off-by: Thomas C. King <thomas.k@transitionzero.org>
@thomaschristopherking

Copy link
Copy Markdown
Contributor Author

Superseded by the stack #17, #18, #19, #20 and #21, which splits this into five reviewable parts, plus #22 for the unrelated code-review/ deletion. The stack tip is byte-identical to this branch. Closing in favour of it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant