Skip to content

Fix/myopic retirement#340

Open
idelder wants to merge 7 commits into
TemoaProject:unstablefrom
idelder:fix/myopic_retirement
Open

Fix/myopic retirement#340
idelder wants to merge 7 commits into
TemoaProject:unstablefrom
idelder:fix/myopic_retirement

Conversation

@idelder

@idelder idelder commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Early retirements were not being carried forward in myopic mode. This change adds a new hidden parameter that brings the results of output_retired_capacity's cap_early column forward into future myopic periods. Then, we add a utility function to calculate the adjusted existing capacity, which is the existing capacity that was build minus early retirements. This version is compatible with myopic + early retirement + survival curves + growth rate constraints from existing capacity (nightmare fuel).

Also solves a smaller bug where, ass the myopic sequencer moves forward, it checks whether previously built capacity survived thus far. If any existing capacity is not seen in the previous period's net capacity output (e.g., was retired early, or survival curved down under the threshold) then that process is removed from the myopic efficiency table and therefore is no longer a valid process going forward.

HOWEVER, these are still pulled into the existing capacity parameter for accounting purposes. This all works out fine except that I had previously put in a check that will not-so-helpfully tell users that their existing capacity should be a valid process but isn't (because previous myopic horizons decided to retire it early) then raise a ValueError and crash the run.

Changing this error to a warning fixes the issue. In perfect foresight, this should always be a bug but not necessarily a serious bug so a warning should cover that anyway.

Summary by CodeRabbit

  • New Features

    • Added support for carrying forward and loading early-retired existing capacity in myopic runs.
    • Expanded time-period handling so existing periods can be treated correctly alongside future periods.
  • Bug Fixes

    • Improved capacity calculations to better account for adjusted existing capacity after early retirements.
    • Updated validation behavior for certain existing-capacity cases to warn instead of stopping execution.
    • Fixed period-length calculations for the last existing period.

pre-commit-ci Bot and others added 7 commits June 15, 2026 16:57
updates:
- [github.com/astral-sh/uv-pre-commit: 0.11.19 → 0.11.21](astral-sh/uv-pre-commit@0.11.19...0.11.21)
- [github.com/astral-sh/ruff-pre-commit: v0.15.16 → v0.15.17](astral-sh/ruff-pre-commit@v0.15.16...v0.15.17)
updates:
- [github.com/astral-sh/uv-pre-commit: 0.11.21 → 0.11.23](astral-sh/uv-pre-commit@0.11.21...0.11.23)
- [github.com/astral-sh/ruff-pre-commit: v0.15.17 → v0.15.18](astral-sh/ruff-pre-commit@v0.15.17...v0.15.18)
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds retired-existing-capacity support for myopic runs across the model, time-period handling, data loading, and capacity calculations, and bumps two pre-commit hook revisions.

Changes

Myopic capacity adjustments

Layer / File(s) Summary
Model and time contracts
temoa/core/model.py, temoa/components/time.py
period_length now covers time_exist, retired_existing_capacity is added to the model, and param_period_length defines the last existing period span.
Retired capacity loading
temoa/data_io/component_manifest.py, temoa/data_io/hybrid_loader.py
retired_existing_capacity is loaded from output_retired_capacity, and lifetime_survival_curve no longer declares viable_rtv as a validator.
Adjusted existing capacity usage
temoa/components/utils.py, temoa/components/capacity.py, temoa/components/limits.py, temoa/components/technology.py
get_adjusted_existing_capacity subtracts retired existing capacity, and capacity, growth, and validation logic use the adjusted value.

Pre-commit revisions

Layer / File(s) Summary
Pinned hook revisions
.pre-commit-config.yaml
uv-pre-commit and ruff-pre-commit are pinned to newer revisions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

bugfix

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly reflects the main change: fixing myopic retirement handling.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@idelder

idelder commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Just opening this for the rabbit. Looks like it needs some rebasing

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.28571% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
temoa/components/technology.py 25.00% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
temoa/data_io/component_manifest.py (1)

448-448: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead validation_map after removing the validator.

With validator_name removed, _filter_data returns early (no validator), so validation_map=(0, 2, 3) is never used. Consider dropping it to avoid implying validation that no longer happens.

♻️ Suggested cleanup
         LoadItem(
             component=model.lifetime_survival_curve,
             table='lifetime_survival_curve',
             columns=['region', 'period', 'tech', 'vintage', 'fraction'],
-            validation_map=(0, 2, 3),
             is_period_filtered=False,
             is_table_required=False,
         ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@temoa/data_io/component_manifest.py` at line 448, The validation_map argument
is now dead because _filter_data no longer uses it once validator_name was
removed, so the ComponentManifest-related call still implies validation that
never happens. Update the relevant _filter_data invocation to drop
validation_map=(0, 2, 3), and keep the remaining arguments consistent with the
no-validator flow in component_manifest.py.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@temoa/components/time.py`:
- Around line 202-203: The branch in the time-period logic is unnecessarily
using an elif after a preceding return, which triggers RET505. Update the
conditional in the relevant function in time.py so the check after the earlier
return is expressed as a plain if/early-return style, keeping the same behavior
while removing the redundant elif.

In `@temoa/data_io/hybrid_loader.py`:
- Around line 639-642: The docstring on the method currently describes behavior
copied from `_load_existing_capacity` that this code does not perform. Update
the documentation for the affected method in `hybrid_loader.py` so it accurately
reflects this method’s actual purpose and remove references to handling myopic
vs. standard runs or populating `tech_exist`; use the surrounding method name
and its current implementation to rewrite the wording consistently.

---

Outside diff comments:
In `@temoa/data_io/component_manifest.py`:
- Line 448: The validation_map argument is now dead because _filter_data no
longer uses it once validator_name was removed, so the ComponentManifest-related
call still implies validation that never happens. Update the relevant
_filter_data invocation to drop validation_map=(0, 2, 3), and keep the remaining
arguments consistent with the no-validator flow in component_manifest.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 10b82a90-3eef-45e4-8c3a-95a14a1189f5

📥 Commits

Reviewing files that changed from the base of the PR and between e242703 and 211e524.

📒 Files selected for processing (9)
  • .pre-commit-config.yaml
  • temoa/components/capacity.py
  • temoa/components/limits.py
  • temoa/components/technology.py
  • temoa/components/time.py
  • temoa/components/utils.py
  • temoa/core/model.py
  • temoa/data_io/component_manifest.py
  • temoa/data_io/hybrid_loader.py

Comment thread temoa/components/time.py
Comment on lines +202 to +203
elif p in model.time_exist:
return -1 # Period length is not defined for existing periods except the last

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse elif after return (RET505).

Since the preceding branch returns, the elif can be a plain if/early-return to satisfy the linter.

♻️ Suggested tweak
     if model.time_exist and p == model.time_exist.last():
         # Need this for one specific use case (capacity growth constraints)
         return model.time_future.first() - model.time_exist.last()
-    elif p in model.time_exist:
+    if p in model.time_exist:
         return -1  # Period length is not defined for existing periods except the last
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif p in model.time_exist:
return -1 # Period length is not defined for existing periods except the last
if p in model.time_exist:
return -1 # Period length is not defined for existing periods except the last
🧰 Tools
🪛 Ruff (0.15.18)

[warning] 202-202: Unnecessary elif after return statement

Remove unnecessary elif

(RET505)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@temoa/components/time.py` around lines 202 - 203, The branch in the
time-period logic is unnecessarily using an elif after a preceding return, which
triggers RET505. Update the conditional in the relevant function in time.py so
the check after the earlier return is expressed as a plain if/early-return
style, keeping the same behavior while removing the redundant elif.

Source: Linters/SAST tools

Comment on lines +639 to +642
"""
Handles different queries for myopic vs. standard runs and also
populates the `tech_exist` set.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix copied docstring.

This is copy-pasted from _load_existing_capacity; this method neither handles a standard (non-myopic) run nor populates tech_exist.

✏️ Suggested wording
         """
-        Handles different queries for myopic vs. standard runs and also
-        populates the `tech_exist` set.
+        Loads retired existing capacity (cap_early) from prior planning periods.
+        Only active in myopic mode.
         """
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""
Handles different queries for myopic vs. standard runs and also
populates the `tech_exist` set.
"""
"""
Loads retired existing capacity (cap_early) from prior planning periods.
Only active in myopic mode.
"""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@temoa/data_io/hybrid_loader.py` around lines 639 - 642, The docstring on the
method currently describes behavior copied from `_load_existing_capacity` that
this code does not perform. Update the documentation for the affected method in
`hybrid_loader.py` so it accurately reflects this method’s actual purpose and
remove references to handling myopic vs. standard runs or populating
`tech_exist`; use the surrounding method name and its current implementation to
rewrite the wording consistently.

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