Skip to content

Add discrete capacity extension#345

Open
idelder wants to merge 18 commits into
TemoaProject:unstablefrom
idelder:extension/integer_capacity
Open

Add discrete capacity extension#345
idelder wants to merge 18 commits into
TemoaProject:unstablefrom
idelder:extension/integer_capacity

Conversation

@idelder

@idelder idelder commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

NOTE currently based on top of PR #343. This is also different from but compatible with a future unit commitment extension, except that unit commitment divides capacity within a technology into subunits (e.g., gas turbines are each divided into 100 MW sub units) whereas this applies integer capacity units at the technology or tech-group/region summation level, for things like chunky investment/retirement.

image image image image image

Summary by CodeRabbit

  • New Features

    • Added a modular extension framework, with bundled extension support and new configuration options to enable them.
    • Introduced new capacity-related extensions for growth rates and discrete capacity limits.
    • Added myopic capacity scenario support and expanded tutorial/test data generation.
  • Bug Fixes

    • Improved handling of existing capacity and retirement adjustments in model calculations.
    • Added stricter database/schema validation when loading tutorial and test data.
  • Documentation

    • Expanded docs with extension setup guides and new extension reference pages.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Introduces a declarative extension framework (ExtensionSpec, resolution, hooks) wired through config, model, manifest/loader, and sequencers, with growth_rates, discrete_capacity, and template extensions. Growth/degrowth constraints move out of core into the growth_rates extension. Refactors existing/retired capacity and lifetime handling, and changes tutorial database generation to apply schema before data.

Changes

Extension Framework

Layer / File(s) Summary
Extension framework primitives
temoa/extensions/framework.py
Defines ExtensionSpec, id normalization/resolution, model/manifest hook orchestration, regional-group table merging, and SQLite table existence/emptiness checks with schema append prompts.
Config and model wiring
temoa/core/config.py, temoa/core/model.py
TemoaConfig and TemoaModel accept, normalize, resolve, and apply extensions during construction/build.
Manifest and loader integration
temoa/data_io/component_manifest.py, temoa/data_io/hybrid_loader.py, temoa/data_io/loader_manifest.py
build_manifest appends extension LoadItems; HybridLoader resolves extension specs, merges regional-group tables, validates table presence, and reshapes rows via new index_length field.
Sequencer propagation
temoa/_internal/run_actions.py, temoa/_internal/temoa_sequencer.py, temoa/extensions/method_of_morris/*, temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py, temoa/extensions/myopic/myopic_sequencer.py, temoa/extensions/single_vector_mga/sv_mga_sequencer.py, temoa/extensions/stochastics/scenario_creator.py, temoa/tutorial_assets/config_sample.toml
build_instance gains extensions; all sequencers forward config.extensions into it.
growth_rates extension
temoa/extensions/growth_rates/...
Implements capacity/new-capacity/delta ramping constraints, registration, data manifest, spec, and schema SQL.
discrete_capacity extension
temoa/extensions/discrete_capacity/...
Implements integer decision variables and constraints enforcing whole-unit capacity, registration, data manifest, spec, and schema SQL.
Template extension scaffold
temoa/extensions/template/...
Adds an unregistered example extension demonstrating the authoring pattern.
Extension documentation
docs/source/extensions.rst, docs/source/extensions/growth_rates.rst, docs/source/extensions/discrete_capacity.rst, docs/source/index.rst, docs/source/computational_implementation.rst
Documents the framework, spec fields, authoring workflow, and bundled extensions.
Growth/degrowth removal from core
temoa/components/limits.py, temoa/core/model.py, temoa/db_schema/temoa_schema_v4.sql, docs/source/mathematical_formulation.rst, docs/source/param_desc_and_tables.rst
Removes now-extension-owned growth/degrowth index/constraint code, tables, and docs from core.
Extension test coverage
tests/test_extensions.py, tests/conftest.py
Validates extension registration, spec invariants, schema consistency, and component attachment; builds extension-owned tables in test DB.

Existing/Retired Capacity and Lifetime Refactor

Layer / File(s) Summary
Adjusted capacity helper
temoa/components/utils.py
Adds get_adjusted_existing_capacity and broadens operator_expression typing.
Applying adjusted capacity
temoa/components/capacity.py, temoa/components/technology.py
Uses the helper in retirement/capacity constraints; replaces raised errors with warnings in check_existing_capacity.
Model lifetime/vintage params
temoa/core/model.py, temoa/components/time.py
Redefines vintage_all/period_length, adds retired_existing_capacity, and introduces lifetime_tech_rt.
Manifest/loader lifetime filtering
temoa/data_io/component_manifest.py, temoa/data_io/hybrid_loader.py
Adds retired-capacity loading and viability-set-based lifetime filtering for myopic runs.
Myopic capacity tests
tests/testing_configs/config_myopic_capacities.toml, tests/testing_data/myopic_capacities.sql, tests/conftest.py, tests/test_full_runs.py, tests/legacy_test_values.py, tests/testing_data/*_sets.json, tests/testing_data/mediumville.sql
Adds myopic capacities fixture, stress-test scenarios, and updated expected hashes/values.

Tutorial Database Schema-First Generation

Layer / File(s) Summary
Schema-first DB generation
temoa/cli.py
Applies v4 schema before tutorial data, toggles FK pragma, and validates via PRAGMA foreign_key_check.
utopia_v3 fixture
tests/testing_data/utopia_v3.sql, tests/test_cli.py
Adds new full test fixture and repoints CLI test to it.

Estimated code review effort: 5 (Critical) | ~150 minutes

Sequence Diagram(s)

sequenceDiagram
participant Sequencer
participant build_instance
participant TemoaModel
participant HybridLoader
Sequencer->>build_instance: extensions=config.extensions
build_instance->>TemoaModel: TemoaModel(extensions=...)
TemoaModel->>TemoaModel: apply_model_extension_hooks
Sequencer->>HybridLoader: build manifest with extension_ids
HybridLoader->>TemoaModel: resolve_extension_specs
Loading
sequenceDiagram
participant CLI
participant SQLiteConn
CLI->>SQLiteConn: execute temoa_schema_v4.sql
CLI->>SQLiteConn: PRAGMA foreign_keys = OFF
CLI->>SQLiteConn: execute utopia.sql
CLI->>SQLiteConn: PRAGMA foreign_keys = ON
CLI->>SQLiteConn: PRAGMA foreign_key_check
SQLiteConn-->>CLI: violations or none
CLI->>SQLiteConn: commit
Loading

Possibly related PRs

  • TemoaProject/temoa#158: Removal of growth/degrowth limit functions from temoa/components/limits.py directly relates to that PR's introduction of those functions.
  • TemoaProject/temoa#164: Extends the manifest-driven HybridLoader/component_manifest.py pipeline built by that PR's declarative manifest refactor.
  • TemoaProject/temoa#198: Updates _copy_tutorial_resources in temoa/cli.py originally introduced by that PR.

Suggested labels: Feature, docs, database-schema, refactor

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the discrete capacity extension.
✨ 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 force-pushed the extension/integer_capacity branch from bff1c69 to 9cf073b Compare June 30, 2026 16:28
@idelder idelder force-pushed the extension/integer_capacity branch from 9cf073b to 879e411 Compare July 6, 2026 15:27
@idelder idelder marked this pull request as draft July 6, 2026 15:29
pre-commit-ci Bot and others added 16 commits July 6, 2026 11:30
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>
…counting purposes

Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
…ly retirement, and growth rate constraints all together

Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
…capacities

Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
@idelder idelder force-pushed the extension/integer_capacity branch from 879e411 to 6b21b35 Compare July 6, 2026 15:32
@idelder idelder marked this pull request as ready for review July 6, 2026 15:48
@idelder idelder marked this pull request as draft July 6, 2026 15:50
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
@idelder idelder force-pushed the extension/integer_capacity branch from 6b21b35 to 9c61cf6 Compare July 6, 2026 17:56
@idelder idelder marked this pull request as ready for review July 6, 2026 18:35

@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: 12

🤖 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 `@docs/source/extensions/growth_rates.rst`:
- Around line 101-199: The docs page is duplicated starting in the second half,
which will render all sections twice and reintroduce the same
_extension-growth-rates label collision. Remove the repeated block from the
growth_rates.rst content after the first complete Constraints section, keeping
only one copy of the extension overview, Parameters table, per-parameter
subsections, and autofunction references.

In `@temoa/data_io/hybrid_loader.py`:
- Around line 654-657: Initialize viable_existing_techs in __init__ alongside
viable_existing_rt and viable_existing_rtv, since it is currently only set
inside _load_existing_capacity when rows_to_load is present. Add the same
empty-set default there so the HybridLoader instance always has
viable_existing_techs defined before any reader accesses it, even when no
existing capacity data is loaded.
- Around line 704-711: The optional lifetime loader path in _load_lifetime_tech
still queries lifetime_tech directly even though its manifest entry is not
required, so a missing table can raise OperationalError instead of being
skipped. Add a table_exists(...) check in _load_lifetime_tech before opening the
cursor and fetching rows, following the same pattern used by
_load_retired_existing_capacity, and apply the same guard to
_load_lifetime_process and _load_lifetime_survival_curve so all three optional
loaders safely no-op when their tables are absent.

In `@temoa/extensions/growth_rates/components/growth_capacity.py`:
- Around line 93-98: The `periods` set in `growth_capacity.py` is still being
derived from the full `cap_rpt` map, so `Constraint.Skip` only happens when
everything is empty instead of when the filtered region/tech slice is empty.
Update the `growth_capacity` constraint logic to build `periods` from
`cap_indices` rather than `cap_rpt`, keeping the empty-slice behavior aligned
with the sibling rules that already use the filtered indices.

In `@temoa/extensions/integer_capacity/components/integer_capacity.py`:
- Around line 18-26: The integer-capacity index builders are generating unused
(region, technology, vintage/period) combinations, which creates unnecessary
variables and constraints. Update limit_integer_new_capacity_indices and
limit_integer_net_capacity_indices so they only return indices that actually
exist in model.process_periods and model.v_capacity_available_by_period_and_tech
before creating the Set. Keep the filtering in the constraint rules as a
safeguard, but avoid building the full cross-product in these helper functions.

In `@temoa/extensions/integer_capacity/core/model.py`:
- Around line 28-30: The capacity unit-size Params in IntegerCapacityModel are
currently unvalidated because limit_integer_new_capacity and
limit_integer_net_capacity use the default Any domain, so bad database values
can slip into the constraint logic. Update the Param declarations in the model
setup to use an explicit numeric domain such as PositiveReals, or
NonNegativeReals if zero is allowed, so invalid values are caught during model
construction. Apply the same domain change consistently to both parameters
referenced in the integer capacity constraint methods.

In `@temoa/extensions/template/components/example_limit.py`:
- Around line 18-19: Update the imports in example_limit.py to use a direct
grouped import from temoa.components instead of aliasing the modules as
geography and technology, and adjust the new_cap_rtv loop to iterate over the
dict directly rather than calling keys(). Use the existing geography/technology
references and the new_cap_rtv iteration site to locate the cleanup, and apply
the same dict-iteration fix wherever the same pattern appears in the file.
- Around line 51-56: The constraint in the example_limit template is doing a
full scan of model.v_new_capacity.keys() inside each rule evaluation, which
causes repeated O(index × keyspace) work. Update the logic around the new_cap
calculation to precompute a lookup or grouped structure for v_new_capacity
before the constraint rule runs, then have the rule use that cached grouping
instead of iterating all keys each time. Use the example_limit template’s
new_cap and new_cap_rtv block as the place to refactor.

In `@temoa/extensions/template/core/model.py`:
- Around line 58-60: The example parameter declaration for
example_new_capacity_limit is too permissive because it uses Any even though the
value is meant to be a numeric cap. Update the Param definition in the template
model to use an appropriate numeric domain such as NonNegativeReals so derived
extensions follow best practice and reject non-numeric values; keep the change
localized to the example_new_capacity_limit symbol.

In `@temoa/extensions/template/extension.py`:
- Line 30: The path construction in the extension setup should use the more
idiomatic Path parent accessor instead of indexing into parents. Update the
schema_sql_path assignment in the extension.py initialization code to use
Path(__file__).parent when building the tables.sql path, keeping the same
behavior while simplifying the expression.

In `@temoa/tutorial_assets/config_sample.toml`:
- Line 23: The example in config_sample.toml references an extension id that is
not registered, so update the commented extensions example to use only ids that
are currently available. Adjust the sample near the extensions example so it
matches the known extension ids resolved by resolve_extension_specs, such as
growth_rates and integer_capacity, and avoid mentioning unit_commitment unless
it is actually registered.

In `@tests/test_extensions.py`:
- Around line 63-79: The table-name assertion in
test_extension_schema_matches_owned_tables is too permissive because it matches
substrings and can hide missing CREATE TABLE entries. Update the check so each
owned table is verified with a boundary-aware match against schema_sql, using
the existing test_extension_schema_matches_owned_tables logic and
spec.owned_tables to locate the relevant table definitions without allowing
prefix collisions like limit_growth_new_capacity versus
limit_growth_new_capacity_delta.
🪄 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: eaa1c908-0d39-461b-b23e-e07dcecd2520

📥 Commits

Reviewing files that changed from the base of the PR and between 2bde478 and 9c61cf6.

📒 Files selected for processing (69)
  • .pre-commit-config.yaml
  • docs/source/computational_implementation.rst
  • docs/source/extensions.rst
  • docs/source/extensions/growth_rates.rst
  • docs/source/extensions/integer_capacity.rst
  • docs/source/index.rst
  • docs/source/mathematical_formulation.rst
  • docs/source/param_desc_and_tables.rst
  • temoa/_internal/run_actions.py
  • temoa/_internal/temoa_sequencer.py
  • temoa/cli.py
  • temoa/components/capacity.py
  • temoa/components/limits.py
  • temoa/components/technology.py
  • temoa/components/time.py
  • temoa/components/utils.py
  • temoa/core/config.py
  • temoa/core/model.py
  • temoa/data_io/component_manifest.py
  • temoa/data_io/hybrid_loader.py
  • temoa/data_io/loader_manifest.py
  • temoa/db_schema/temoa_schema_v4.sql
  • temoa/extensions/framework.py
  • temoa/extensions/growth_rates/__init__.py
  • temoa/extensions/growth_rates/components/__init__.py
  • temoa/extensions/growth_rates/components/growth_capacity.py
  • temoa/extensions/growth_rates/components/growth_new_capacity.py
  • temoa/extensions/growth_rates/components/growth_new_capacity_delta.py
  • temoa/extensions/growth_rates/core/__init__.py
  • temoa/extensions/growth_rates/core/model.py
  • temoa/extensions/growth_rates/data_manifest.py
  • temoa/extensions/growth_rates/extension.py
  • temoa/extensions/growth_rates/tables.sql
  • temoa/extensions/integer_capacity/__init__.py
  • temoa/extensions/integer_capacity/components/__init__.py
  • temoa/extensions/integer_capacity/components/integer_capacity.py
  • temoa/extensions/integer_capacity/core/__init__.py
  • temoa/extensions/integer_capacity/core/model.py
  • temoa/extensions/integer_capacity/data_manifest.py
  • temoa/extensions/integer_capacity/extension.py
  • temoa/extensions/integer_capacity/tables.sql
  • temoa/extensions/method_of_morris/morris.py
  • temoa/extensions/method_of_morris/morris_evaluate.py
  • temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py
  • temoa/extensions/myopic/myopic_sequencer.py
  • temoa/extensions/single_vector_mga/sv_mga_sequencer.py
  • temoa/extensions/stochastics/scenario_creator.py
  • temoa/extensions/template/__init__.py
  • temoa/extensions/template/components/__init__.py
  • temoa/extensions/template/components/example_limit.py
  • temoa/extensions/template/core/__init__.py
  • temoa/extensions/template/core/model.py
  • temoa/extensions/template/data_manifest.py
  • temoa/extensions/template/extension.py
  • temoa/extensions/template/tables.sql
  • temoa/tutorial_assets/config_sample.toml
  • temoa/tutorial_assets/utopia.sql
  • tests/conftest.py
  • tests/legacy_test_values.py
  • tests/test_cli.py
  • tests/test_extensions.py
  • tests/test_full_runs.py
  • tests/testing_configs/config_myopic_capacities.toml
  • tests/testing_data/mediumville.sql
  • tests/testing_data/mediumville_sets.json
  • tests/testing_data/myopic_capacities.sql
  • tests/testing_data/test_system_sets.json
  • tests/testing_data/utopia_sets.json
  • tests/testing_data/utopia_v3.sql
💤 Files with no reviewable changes (4)
  • docs/source/param_desc_and_tables.rst
  • tests/testing_data/mediumville.sql
  • docs/source/mathematical_formulation.rst
  • temoa/db_schema/temoa_schema_v4.sql

Comment thread docs/source/extensions/growth_rates.rst Outdated
Comment thread temoa/data_io/hybrid_loader.py
Comment thread temoa/data_io/hybrid_loader.py
Comment thread temoa/extensions/growth_rates/components/growth_capacity.py
Comment thread temoa/extensions/integer_capacity/components/integer_capacity.py Outdated
Comment thread temoa/extensions/template/components/example_limit.py
Comment thread temoa/extensions/template/core/model.py
Comment thread temoa/extensions/template/extension.py
Comment thread temoa/tutorial_assets/config_sample.toml
Comment thread tests/test_extensions.py
@idelder idelder force-pushed the extension/integer_capacity branch from 9c61cf6 to 4855a98 Compare July 7, 2026 22:28

@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: 4

🤖 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 `@docs/source/extensions/integer_capacity.rst`:
- Around line 31-32: The table notes in the integer capacity documentation use
“forced to an integer multiple” but should match the prose and say “forced to be
an integer multiple.” Update the descriptive text for both
limit_integer_new_capacity and limit_integer_net_capacity entries in the table
so the phrasing is grammatically consistent with the later sections.

In `@temoa/extensions/integer_capacity/components/integer_capacity.py`:
- Line 51: The integer capacity constraint is converting mutable Params to plain
numbers too early via value() in the capacity expression. In
integer_capacity.py, update the constraint-building logic in the relevant
rule(s) around the unit_cap/limit_integer_new_capacity and
limit_integer_net_capacity references to use the Param objects directly instead
of eager value() extraction, so the expressions remain symbolic and reflect
updated parameter values when the model is reused.

In `@temoa/extensions/integer_capacity/tables.sql`:
- Around line 1-16: The composite primary key in the limit_integer_new_capacity
and limit_integer_net_capacity table definitions does not enforce
non-nullability on region and tech_or_group. Update both CREATE TABLE statements
so the primary-key columns are declared NOT NULL alongside the PRIMARY KEY
constraint, using the existing table definitions as the place to fix it.
- Line 4: The integer-capacity schema currently allows zero or negative unit
sizes, which can silently create a degenerate model instead of failing fast.
Update the table definition in tables.sql for the capacity field to enforce a
positive value with a DB-level CHECK constraint, and make sure the related
integer-capacity loading path in core/model.py remains consistent with that
assumption.
🪄 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: 1f808121-8741-4d41-b05b-6a8f8b647754

📥 Commits

Reviewing files that changed from the base of the PR and between 9c61cf6 and 4855a98.

📒 Files selected for processing (13)
  • docs/source/extensions.rst
  • docs/source/extensions/integer_capacity.rst
  • temoa/extensions/framework.py
  • temoa/extensions/growth_rates/components/growth_capacity.py
  • temoa/extensions/integer_capacity/__init__.py
  • temoa/extensions/integer_capacity/components/__init__.py
  • temoa/extensions/integer_capacity/components/integer_capacity.py
  • temoa/extensions/integer_capacity/core/__init__.py
  • temoa/extensions/integer_capacity/core/model.py
  • temoa/extensions/integer_capacity/data_manifest.py
  • temoa/extensions/integer_capacity/extension.py
  • temoa/extensions/integer_capacity/tables.sql
  • tests/test_extensions.py

Comment thread docs/source/extensions/integer_capacity.rst Outdated
Comment thread temoa/extensions/integer_capacity/components/integer_capacity.py Outdated
Comment thread temoa/extensions/integer_capacity/tables.sql Outdated
Comment thread temoa/extensions/discrete_capacity/tables.sql
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
@idelder idelder force-pushed the extension/integer_capacity branch from 4855a98 to bdfa92b Compare July 7, 2026 23:20

@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: 1

♻️ Duplicate comments (1)
temoa/extensions/discrete_capacity/tables.sql (1)

1-16: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add CHECK(capacity > 0) to prevent degenerate zero-unit-size models.

Both tables allow capacity = 0 (default), which combined with the NonNegativeReals Pyomo domain and the equality constraints in components/discrete_capacity.py, silently forces new_capacity/net_capacity to zero instead of failing on bad input. This is the same issue flagged on the predecessor integer_capacity/tables.sql and remains unresolved after the rename to discrete_capacity.

🛡️ Proposed fix
 CREATE TABLE IF NOT EXISTS limit_discrete_new_capacity(
     region        TEXT NOT NULL,
     tech_or_group TEXT NOT NULL,
-    capacity      REAL NOT NULL DEFAULT 0,
+    capacity      REAL NOT NULL DEFAULT 0 CHECK(capacity > 0),
     units         TEXT,
     notes         TEXT,
     PRIMARY KEY (region, tech_or_group)
 );
 CREATE TABLE IF NOT EXISTS limit_discrete_capacity(
     region        TEXT NOT NULL,
     tech_or_group TEXT NOT NULL,
-    capacity      REAL NOT NULL DEFAULT 0,
+    capacity      REAL NOT NULL DEFAULT 0 CHECK(capacity > 0),
     units         TEXT,
     notes         TEXT,
     PRIMARY KEY (region, tech_or_group)
 );
🤖 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/extensions/discrete_capacity/tables.sql` around lines 1 - 16, Add a
table-level CHECK constraint in both discrete capacity table definitions so
`capacity` cannot be zero or negative. Update the `CREATE TABLE IF NOT EXISTS`
statements in `tables.sql` for `limit_discrete_new_capacity` and
`limit_discrete_capacity` to enforce `capacity > 0`, matching the intended
behavior already implied by `components/discrete_capacity.py` and preventing
zero-sized models from being accepted silently.
🤖 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/extensions/discrete_capacity/components/discrete_capacity.py`:
- Line 51: The discrete capacity constraint setup is allowing zero-sized units,
which can collapse the equality and disable the affected technology or group. In
the discrete_capacity component, update the logic around unit_cap in the
capacity constraint construction so that limit_discrete_new_capacity and
limit_discrete_capacity are required to be strictly positive before being used.
Add a guard or validation in the relevant constraint-building path so zero
values are rejected rather than producing a zero equality.

---

Duplicate comments:
In `@temoa/extensions/discrete_capacity/tables.sql`:
- Around line 1-16: Add a table-level CHECK constraint in both discrete capacity
table definitions so `capacity` cannot be zero or negative. Update the `CREATE
TABLE IF NOT EXISTS` statements in `tables.sql` for
`limit_discrete_new_capacity` and `limit_discrete_capacity` to enforce `capacity
> 0`, matching the intended behavior already implied by
`components/discrete_capacity.py` and preventing zero-sized models from being
accepted silently.
🪄 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: 309a38ed-a69d-4b99-9bdc-a34c33c693f4

📥 Commits

Reviewing files that changed from the base of the PR and between 4855a98 and bdfa92b.

📒 Files selected for processing (13)
  • docs/source/extensions.rst
  • docs/source/extensions/discrete_capacity.rst
  • temoa/extensions/discrete_capacity/__init__.py
  • temoa/extensions/discrete_capacity/components/__init__.py
  • temoa/extensions/discrete_capacity/components/discrete_capacity.py
  • temoa/extensions/discrete_capacity/core/__init__.py
  • temoa/extensions/discrete_capacity/core/model.py
  • temoa/extensions/discrete_capacity/data_manifest.py
  • temoa/extensions/discrete_capacity/extension.py
  • temoa/extensions/discrete_capacity/tables.sql
  • temoa/extensions/framework.py
  • temoa/extensions/growth_rates/components/growth_capacity.py
  • tests/test_extensions.py

Comment thread temoa/extensions/discrete_capacity/components/discrete_capacity.py
@idelder idelder changed the title Add integer capacity extension Add discrete capacity extension Jul 8, 2026
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