Skip to content

EIA profile-based powerplant age imputation - #31

Merged
irm-codebase merged 26 commits into
modelblocks-org:mainfrom
ddahawkins-TUDelft:feature/deterministic-age-imputation
Aug 3, 2026
Merged

EIA profile-based powerplant age imputation#31
irm-codebase merged 26 commits into
modelblocks-org:mainfrom
ddahawkins-TUDelft:feature/deterministic-age-imputation

Conversation

@ddahawkins-TUDelft

Copy link
Copy Markdown
Collaborator

Fixes #

Summary of changes in this pull request

Adds a deterministic alternative to group-average powerplant age imputation.

The new method:

  • derives annual commissioning weights from positive changes in reference capacity stock;
  • preserves observed commissioning years;
  • allocates missing-capacity plants against the remaining target profile;
  • respects technology lifetime constraints;
  • writes row-level and annual profile diagnostics as Parquet;
  • produces diagnostic plots comparing observed, imputed, and target profiles.

Validation

Tested on:

  • MEX wind, solar, and fossil datasets;
  • all European categories;
  • multi-country diagnostic profile outputs.

Notes

Powerplant categories are harmonised with the reference statistics categories for:

  • bioenergybiomass and waste
  • fossilfossil fuels

Reviewer checklist

  • There are no pip dependencies in the module's environment files (workflow/envs/).
  • All rules use pathvars (e.g., <results>) in their inputs and outputs.
  • The integration test-suite is successful, including:
    • pre-commit.ci tests pass.
    • tests pass for all relevant OS configurations (linux, osx, windows).
  • Module documentation is up-to-date, including:
    • INTERFACE.yaml mentions all relevant pathvars and wildcards.
    • README.md describes how to use the module and has the necessary citations.

@irm-codebase
irm-codebase self-requested a review July 8, 2026 13:25
@ddahawkins-TUDelft

Copy link
Copy Markdown
Collaborator Author

Adding comment. The new method is only applied to assets with the 'operating' status. It is not yet applied to historical (retired) nor future statuses (pre-construction, construction, announcements). Once you are happy with the methodology as a concept, I can extend it as these other categories will require slightly different target profiles e.g. a linear forecast for the future ones.

Comment thread workflow/scripts/impute_ages.py
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py Outdated
@ddahawkins-TUDelft

Copy link
Copy Markdown
Collaborator Author

@irm-codebase I have just pushed a final commit that I think addresses your comments above (see resolved) and also adds the 'future assets' capability that you can test. I have endeavored to remove the 'bloat' code but let me know if you catch something.

Please note that in this new version, I import your _utils into _schema as I use _utils to host a single-version-of-truth dictionary of the date imputation sources (see the object: DATE_SOURCE_METADATA in _utils). I use this in _schema to validate the results and in the impute_ages code to assign those labels.

@irm-codebase
irm-codebase self-requested a review July 25, 2026 12:22

@irm-codebase irm-codebase left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the code is not running at the moment.
I can only point some structural stuff for the time being.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used. I assume it can be deleted?

Comment thread workflow/scripts/_schemas.py Outdated
Comment on lines +125 to +130
def date_source_labels() -> dict[str, str]:
"""Return date-source display labels."""
return {
source_type: metadata["label"]
for source_type, metadata in DATE_SOURCE_METADATA.items()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: this kind of label boilerplate code can be avoided with the help of the inflection library, which can be installed like so:

pixi add --feature module inflection  # installs the lib for the module
pixi run export-snakemake-env module  # export the environment so rules can use it

Then just run:

from inflection import humanize

print(humanize("imputed_capacity_profile")
# Imputed capacity profile

This will make the the name easy to read while matching the dataset naming, which helps avoid confusion. It also forces the dev to come up with concise but clear naming too 😉

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what you are asking, but humanize() doesnt add all the context that might helper a user unless i make all the variable names very very long. So I would prefer to keep my DATE_SOURCE_METADATA as-is .... it provides a "single source of truth' generally for this rule so it would have to exist anyway.

Comment thread workflow/scripts/_utils.py Outdated
Comment on lines +52 to +53
DATE_SOURCE_METADATA = {
"observed": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be removed with some smart helpers. See below.

Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py Outdated
Comment thread workflow/scripts/impute_ages.py
@ddahawkins-TUDelft

Copy link
Copy Markdown
Collaborator Author

Okay, i have uploaded an updated PR that passes my local tests and implements most of your feedback. Can you run your tests for it please?

@irm-codebase irm-codebase left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now run this and I identified several bugs. I'll detail them below.

Comment thread pixi.toml
Comment on lines +64 to +73
args = ["env", "platform", "outdir"]
depends-on = [
{ task = "_export-conda-explicit-spec", args = ["{{ env }}", "{{ platform }}", "{{ outdir }}"] },
]
cmd = "mv '{{ outdir }}/{{ env }}_{{ platform }}_conda_spec.txt' '{{ outdir }}/{{ env }}.{{ platform }}.pin.txt'"

[tasks._export-conda-explicit-spec]
description = "Export one Pixi environment/platform as an explicit conda spec file"
args = ["env", "platform", "outdir"]
cmd = "pixi workspace export conda-explicit-spec --environment '{{ env }}' --platform '{{ platform }}' '{{ outdir }}'"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed? Did it not work on windows?
These commands are standard in all modules, so this would imply a higher issue in our templating.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait... what? This new code is the code from the template.
Maybe I did not catch that pixi.toml had a regression in the previous review?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cause is that this branch sits before the latest template update. I should've seen that.
After this there should be no required changes to pixi.toml

Comment on lines +128 to +134
def _build_reference_retirement_profile(
reference_capacity_df: pd.DataFrame,
country_id: str,
categories: list[str],
years: pd.Index,
) -> pd.DataFrame:
"""Build an annual retirement profile from capacity-stock reductions."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like retirements are assigned oddly in some cases. For the MEX testcase

  • powerplant_id: GEM_L100000832511-G100000838075
  • name: is Huerto Solar Durango solar farm---
Image

This plant has no start or end date in the GEM dataset, meaning it will be imputed with the capacity profile (red bit at the start). However:

  • It is assigned extremely early
  • The start date is mislabled as 'observed', when it was imputed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, the right-hand side of this is fixed now.
We still have a left-hand side issue, as the powerplant is being assigned an unlikely date.

Image

I do not think Mexico was that much of a pioneer when it comes to PV ;)
The solution would be to re-run the imputation of start-dates for these plants so they fit within the commissioning profile.

This is a nice-to-have as I highly doubt most people will use this module for historical profiling.
We should still document this as an issue for a later date, though.

Comment thread workflow/scripts/impute_ages.py
@irm-codebase
irm-codebase self-requested a review August 3, 2026 09:20

@irm-codebase irm-codebase left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great @ddahawkins-TUDelft !

One thing missing is the README update, and better handling of the start year of retired facilities that have neither a start or end date.
I'll submit a PR for that soon-ish.

@irm-codebase
irm-codebase merged commit 25b2e3b into modelblocks-org:main Aug 3, 2026
4 checks passed
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.

2 participants