Skip to content

Add a framework for modular model extensions#343

Open
idelder wants to merge 4 commits into
TemoaProject:unstablefrom
idelder:extensions/framework
Open

Add a framework for modular model extensions#343
idelder wants to merge 4 commits into
TemoaProject:unstablefrom
idelder:extensions/framework

Conversation

@idelder

@idelder idelder commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

NOTE: Currently based on top of PR #340. These changes begin with commit Move schema components out of tutorial database for SSOT

Creates a framework so that new model extensions can be developed that append components onto Temoa including new parameters, variables, database tables, and constraints. These extensions are take-or-leave, where Temoa runs as normal without them but they can be easily included in the config file to add new capabilities to the model. Documentation has been added to explain how this all works, which I'll just include below. I have also moved the growth rate constraints to an extension because they were a bit bulky in the database and not typically used in base models.

.. _extensions:

Extension Framework

Temoa includes a lightweight framework for adding optional model components
without modifying the core model. An extension can contribute its own database
tables, Pyomo components (sets, parameters, and constraints), and data-loading
rules. Extensions are declared once and then enabled per run through
configuration.

This page describes how the framework works and how to author a new extension.
A ready-to-copy scaffold lives at temoa/extensions/template.

Overview

Use an extension when you want to add modeling capability that is:

  • Optional -- only active when explicitly enabled.
  • Self-contained -- owns its own tables and model components.
  • Non-invasive -- adds to the core model rather than editing it.

If a feature is fundamental to every Temoa run, it belongs in
temoa/components (a core component) instead of an extension.

Each extension is described by a single :class:ExtensionSpec (declarative
metadata plus hook functions). The spec is registered with the framework, after
which users can enable the extension by id.

Lifecycle

When a run is configured with extensions = ["..."], the framework threads the
enabled specs through configuration, model construction, and data loading:

.. code-block:: text

config: extensions = ["my_ext"]
|
v
resolve_extension_specs() validate ids -> ExtensionSpec list
|
v
TemoaModel(extensions=[...])
|
+--> apply_model_extension_hooks()
| calls spec.register_model_components(model)
| -> attaches Params / Sets / Constraints to the model
|
v
HybridLoader
+--> ensure_enabled_extension_tables_exist() (offer to append schema)
+--> assert_disabled_extension_tables_are_empty()
+--> merge_regional_group_tables()
+--> build_manifest() -> appends spec.build_manifest_items(model)
-> loads each owned table into its component

The relevant code lives in :mod:temoa.extensions.framework,
:mod:temoa.core.model, and :mod:temoa.data_io.hybrid_loader.

ExtensionSpec reference

.. list-table::
:header-rows: 1
:widths: 30 70

    • Field
    • Purpose
    • extension_id
    • Unique, lowercase id. This is what users put in extensions = [...].
    • owned_tables
    • Tuple of database tables owned exclusively by this extension. Used by the
      disabled/enabled table guards.
    • regional_group_tables
    • Map of table -> column for tables whose region column may hold a
      regional group name. Merged into the loader's regional-group handling.
    • register_model_components
    • Hook Callable[[TemoaModel], None] that attaches model components.
    • build_manifest_items
    • Hook Callable[[TemoaModel], list[LoadItem]] describing how to load the
      extension's data.
    • schema_sql_path
    • Path to a .sql file applied (with consent) when the extension is
      enabled but its tables are missing.
    • fail_if_tables_populated_when_disabled
    • When True, loading fails if the extension is disabled but its owned
      tables contain data, preventing silently-ignored inputs.

Recommended package layout

Mirror the structure of the core model (temoa/core + temoa/components) so
extension code is organized the same way as the rest of the codebase:

.. code-block:: text

temoa/extensions/<your_extension>/
init.py # re-export the ExtensionSpec
extension.py # the ExtensionSpec definition
data_manifest.py # build_manifest_items()
tables.sql # CREATE TABLE IF NOT EXISTS for owned tables
core/
init.py
model.py # typing subtype + register_model_components()
components/
init.py
.py # one module per constraint family

Centralize the component declarations in core/model.py (just as
temoa/core/model.py does) and keep the index-set and constraint-rule logic in
components/ modules (just as temoa/components does).

.. _extensions-typing:

The typing pattern

Core model components are declared as attribute assignments inside
TemoaModel.__init__ (for example self.time_optimize = Set(...)), which is
why the type checker knows about them. An extension instead adds attributes from
outside the class, so without help the type checker reports
"TemoaModel" has no attribute ... and provides no autocomplete.

Three rules make typing carry over cleanly:

  1. Declare a TYPE_CHECKING-only subtype. In core/model.py, define a
    subclass of TemoaModel that annotates every component the extension adds.
    It inherits all core attributes, so component code sees both core and
    extension members.

    .. code-block:: python

    if TYPE_CHECKING:
    from temoa.core.model import TemoaModel

       class ExampleModel(TemoaModel):
           example_new_capacity_limit: Param
           example_new_capacity_limit_constraint_rpt: Set
           example_new_capacity_limit_constraint: Constraint
    
  2. Annotate component functions with the subtype. Index-set and
    constraint-rule functions take model: ExampleModel. This restores both
    mypy coverage and editor autocomplete.

  3. Keep spec hooks on the base type and cast internally. Functions stored
    on the ExtensionSpec (register_model_components and
    build_manifest_items) must keep model: TemoaModel to match the hook
    callable types. Narrowing the parameter is a contravariance error. cast
    once at the top:

    .. code-block:: python

    def register_model_components(model: TemoaModel) -> None:
    m = cast('ExampleModel', model)
    m.example_new_capacity_limit = Param(...)

.. note::
Extension attribute names share the single TemoaModel namespace at
runtime. Keep them unique across extensions to avoid collisions.

Adding a new extension

#. Copy the template. Duplicate temoa/extensions/template to
temoa/extensions/<your_extension>/.
#. Rename ids and tables. Update extension_id, owned_tables,
regional_group_tables, params, sets, and constraints to your domain.
#. Declare components. Add annotations to the typing subtype in
core/model.py and create the components in register_model_components.
#. Write the constraint logic. Add index-set and rule functions under
components/ and annotate them with your subtype.
#. Describe data loading. Add one LoadItem per owned table in
data_manifest.py.
#. Define the schema. Add a CREATE TABLE IF NOT EXISTS per owned table to
tables.sql.
#. Register the spec. Import your spec in
:func:temoa.extensions.framework.get_known_extension_specs and add it to the
specs list. (Until you do this, the extension is inert -- enabling it
raises an "Unknown extension id" error.)
#. Enable it. Add the id to your configuration TOML:

.. code-block:: toml

  extensions = ["<your_extension>"]

Data loading and LoadItem

build_manifest_items returns one :class:temoa.data_io.loader_manifest.LoadItem
per database table the extension reads. Common fields:

.. list-table::
:header-rows: 1
:widths: 30 70

    • Field
    • Purpose
    • component
    • The Pyomo Set or Param to populate.
    • table
    • Source table name in the database.
    • columns
    • Columns to select; for a Param the final column is the value.
    • index_length
    • Number of leading columns that form the index.
    • validator_name / validation_map
    • Source-trace validation: a viable-set name on the loader and which index
      columns it applies to.
    • is_table_required
    • Set False for optional extension inputs so a missing table is not an
      error.

Verification

After authoring an extension, confirm:

#. Types -- mypy temoa/extensions/<your_extension> reports no issues.
#. Imports -- python -c "import temoa.extensions.<your_extension>.extension".
#. Wiring -- a model built with the extension enabled attaches the expected
components, and the test suite passes.

The template extension

temoa/extensions/template is a complete, type-checked, but deliberately
unregistered scaffold. Because it is not listed in
get_known_extension_specs, it cannot be enabled until you register it, so it
never affects normal runs. Copy the folder as the starting point for a new
extension; every file carries # TEMPLATE: comments explaining what to change.

.. _extension-catalog:

Available extensions

The extensions that ship with Temoa are documented on their own pages below.
Each page describes the extension's parameters and constraints. This list grows
as new extensions are added.

.. toctree::
:maxdepth: 1

extensions/growth_rates

Summary by CodeRabbit

  • New Features

    • Added an optional extensions framework for enabling model capabilities per run.
    • Added the growth_rates extension, providing configurable capacity and new-capacity growth, degrowth, and acceleration limits.
    • Added extension-aware database loading, schema handling, and configuration support.
    • Added documentation and a reusable template for creating extensions.
  • Changes

    • Removed growth and degrowth limits from the core model; they are now provided through the optional extension.
    • Improved tutorial database creation and validation.
    • Enhanced handling of existing capacity and retired capacity data.

@idelder idelder force-pushed the extensions/framework branch from 360e30d to 21f098a Compare June 30, 2026 14:46
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds an extension framework with runtime model and loader integration, migrates growth and degrowth limits into the optional growth_rates extension, provides an extension template scaffold, updates capacity handling, and refreshes schemas, documentation, tutorial assets, and test fixtures.

Changes

Extension runtime and loading

Layer / File(s) Summary
Extension resolution and model wiring
temoa/extensions/framework.py, temoa/core/config.py, temoa/core/model.py, temoa/_internal/..., temoa/extensions/...
Extension specifications are normalized, validated, applied to models, and forwarded through model-building and sequencing entry points.
Manifest and loader integration
temoa/data_io/..., temoa/extensions/stochastics/scenario_creator.py
Extension-owned manifest items, index widths, regional tables, schema checks, and myopic/lifetime loading paths are integrated into data loading.

Growth-rate migration

Layer / File(s) Summary
Core limit removal and capacity utilities
temoa/components/..., temoa/core/model.py, temoa/db_schema/temoa_schema_v4.sql
Growth and degrowth limit components and schema tables are removed from the core model, while adjusted existing-capacity support is added.
growth_rates extension
temoa/extensions/growth_rates/...
Adds extension-owned parameters, schemas, manifests, typed model registration, and capacity/new-capacity growth, degrowth, and delta constraints.
Extension template scaffold
temoa/extensions/template/...
Adds an intentionally unregistered example extension with model, constraint, manifest, specification, and schema templates.

Supporting assets

Layer / File(s) Summary
Documentation and fixtures
docs/source/..., temoa/tutorial_assets/..., tests/...
Documents the framework and growth-rates extension, rebuilds tutorial databases from canonical schemas, converts tutorial SQL to data-only upserts, and updates test databases and expected set hashes.

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

Possibly related PRs

  • TemoaProject/temoa#164: Introduced the manifest-driven loading architecture extended here for extension-owned tables and index_length.
  • TemoaProject/temoa#340: Related adjusted existing-capacity, retired-capacity, and lifetime-loader changes.
  • TemoaProject/temoa#198: Also changes tutorial SQLite database creation in _copy_tutorial_resources.

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

Suggested reviewers: jdecarolis, SutubraResearch

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a modular extension framework to the model.
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.
✨ 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 extensions/framework branch from 21f098a to e4b6a1e Compare June 30, 2026 15:05
@idelder idelder marked this pull request as draft June 30, 2026 16:08
@idelder idelder marked this pull request as ready for review June 30, 2026 16:08
@idelder idelder force-pushed the extensions/framework branch from e4b6a1e to 426582b Compare June 30, 2026 16:09

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

🤖 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 87-89: Fix the typo in the degrowth-delta description by updating
the prose for limit_degrowth_new_capacity_delta in growth_rates.rst so that
“intertia” reads “inertia”. Keep the surrounding explanation unchanged and
ensure the corrected wording remains in the parameter description text.

In `@temoa/cli.py`:
- Around line 650-652: The foreign-key validation error in the tutorial data
load fallback is chained to the wrong exception via `from e`, which makes the
traceback misleading. Update the `sqlite3.IntegrityError` raise in the fallback
path to suppress the prior package-loading failure and use `from None` instead.
Keep the FK violation message in the `cli.py` tutorial-load logic, but do not
chain it to the earlier
`ModuleNotFoundError`/`FileNotFoundError`/`AttributeError`.
- Around line 590-599: The SQLite load flow should explicitly close the database
handle after the import work and avoid carrying the previous exception into the
fallback FK failure. Update the tutorial data load logic around the
sqlite3.connect context so the connection is cleanly closed once PRAGMA and
foreign_key_check are done, and in the fallback path where the IntegrityError is
raised, change the exception chaining to detach it from the earlier import/path
error by using a direct raise from None. Keep the existing fk_violations check
and error message, but ensure the cleanup and error reporting behavior is
handled in the same load routine.

In `@temoa/components/technology.py`:
- Around line 445-448: The guard in the existing-capacity handling path is
treating zero and negative adjusted capacity the same, which hides the
over-retired state. Update the logic around get_adjusted_existing_capacity in
technology.py so adjusted_cap == 0 continues to mean fully retired and can be
skipped, but adjusted_cap < 0 is detected explicitly and handled as an error or
corrective case instead of falling through. Make sure the related
adjusted_capacity_constraint() path cannot receive a negative baseline from the
same get_adjusted_existing_capacity / retired_existing_capacity flow.

In `@temoa/core/model.py`:
- Around line 449-450: `lifetime_tech` is too restrictive compared with the raw
loader contract, causing valid lifetime metadata for declared-but-unused
technologies to fail model build. Update the lifetime-tech initialization in
`TemoaModel` so `_load_lifetime_tech()` can accept rows for technologies that
are declared but not present in `efficiency` or `existing_capacity`, aligning
`self.lifetime_tech_rt` and `self.lifetime_tech` with the same sparse/lenient
handling used by `create_sparse_dicts()` and the loader path.
- Around line 224-226: `vintage_all` is incorrectly narrowed to only
`vintage_exist | time_optimize`, which drops historical vintages that still
appear in `main.efficiency` but are not in `existing_capacity`. Update the
`Model` initialization around `vintage_exist`, `vintage_optimize`, and
`vintage_all` so `vintage_all` still includes every valid historical vintage
loaded by `HybridLoader._build_efficiency_dataset()`, while preserving the
intended optimization set behavior.

In `@temoa/data_io/hybrid_loader.py`:
- Around line 697-764: The custom loaders _load_lifetime_tech,
_load_lifetime_process, and _load_lifetime_survival_curve are bypassing the
optional-table handling by querying the database directly, which can raise
sqlite3.OperationalError on older databases. Update these methods in
hybrid_loader.py to reuse the already-fetched rows passed through the
data/raw_data/filtered_data flow instead of issuing new SELECTs against
lifetime_tech, lifetime_process, and lifetime_survival_curve. Keep the existing
viability and myopic filtering logic, but apply it to the fetched rows so
optional tables remain safely skipped when absent.

In `@temoa/data_io/loader_manifest.py`:
- Line 53: Propagate the new index_length field through manifest consumers so
key handling matches HybridLoader behavior. Update scenario_creator (and any
related manifest parsing/mapping logic) to use the item’s index_length instead
of hardcoding item.columns[:-1], so growth-rate items with shorter stored keys
don’t break strict zipping. Use the LoaderManifest and scenario_creator symbols
to locate the mapping logic and ensure downstream perturbation code derives
column/key pairs from the declared index_length.

In `@temoa/extensions/growth_rates/components/growth_capacity.py`:
- Around line 102-110: The first-period fallback in the growth capacity logic
unconditionally uses model.time_exist.last(), which breaks when there is no
historical period. Update the constraint/rule in growth_capacity.py around the p
== model.time_optimize.first() branch to guard for an empty model.time_exist set
before reading last(), and either skip the fallback or use an explicit
zero-history baseline consistently with the other time-boundary handling in this
module. Keep the fix localized to the capacity_prev calculation so the
growth_rates constraint construction does not fail on models without historical
data.

In `@temoa/extensions/growth_rates/components/growth_new_capacity_delta.py`:
- Around line 111-123: The initialization logic in growth_new_capacity_delta for
the first optimization period assumes two historical entries exist, but
model.time_exist.prev(p_prev) will fail when the history is shorter. Update the
first-period branch to explicitly check the length of model.time_exist before
using prev(), and in the one-entry or zero-entry case use a safe fallback such
as skipping the delta calculation or setting a zero baseline. Keep the change
localized around the p == model.time_optimize.first() block and the
p_prev/p_prev2 handling.

In `@temoa/extensions/growth_rates/components/growth_new_capacity.py`:
- Around line 103-109: The first-period baseline in the growth capacity logic is
still using raw model.existing_capacity, which skips retirement adjustments and
can produce an incorrect bound in myopic runs. Update the p ==
model.time_optimize.first() branch in growth_new_capacity so new_cap_prev is
built from get_adjusted_existing_capacity() instead of
value(model.existing_capacity[...]), keeping the same region/tech/time filtering
and using the adjusted capacity for the baseline calculation.

In `@temoa/extensions/stochastics/scenario_creator.py`:
- Line 99: Extension-aware stochastic loading is still assuming the last column
is the only value column, which breaks multi-column items. Update the table
indexing logic in scenario_creator.py so the `table_index_map` construction and
any downstream stochastic filter setup use each `LoadItem`’s `index_length`
instead of `item.columns[:-1]`, and make sure `build_instance(...)` paths that
process extension manifest items preserve the correct index/value split. Use the
existing `LoadItem.index_length`, `table_index_map`, and `build_instance`
symbols to locate and align the index handling.

In `@temoa/extensions/template/components/example_limit.py`:
- Around line 51-56: The example_limit aggregation is scanning every
v_new_capacity key, which should be avoided in this template. Update the logic
in the example_limit component to mirror the core limit rule by iterating over
regions and techs directly and filtering via process_periods, using the same
symbols model.v_new_capacity, regions, techs, and p to keep the pattern
efficient for downstream extensions.

In `@temoa/extensions/template/tables.sql`:
- Around line 6-14: The composite primary key on example_new_capacity_limit is
still nullable in SQLite, so update the table definition to make both region and
tech_or_group explicitly NOT NULL while keeping the existing PRIMARY KEY
(region, tech_or_group) constraint. Use the example_new_capacity_limit CREATE
TABLE statement in tables.sql and ensure the scaffold prevents malformed key
rows from being inserted.

In `@tests/conftest.py`:
- Around line 61-67: The extension fixture setup in conftest.py loads
extension-owned tables before foreign key enforcement is enabled, so invalid
rows can slip through unnoticed. After the fixture-loading loop that iterates
over get_known_extension_specs() and runs executescript(), add a foreign key
validation step using PRAGMA foreign_key_check on the same connection before the
test DB is considered ready. Keep the existing foreign_keys setting, but ensure
the check runs after all extension schema/data setup so fixture corruption is
caught consistently with the CLI build path.

In `@tests/testing_configs/config_myopic_capacities.toml`:
- Around line 4-5: The myopic test config is pointing both input_database and
output_database at the shared fixture SQLite file, so MyopicSequencer will write
in place and leak state across parametrized runs. Update this config to use a
per-test copy for output_database (while keeping input_database on the fixture)
so each run writes to its own database; if needed, align the test setup around
TemoaConfig.build_config() and MyopicSequencer so the output path is isolated
from the shared fixture.
🪄 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: c8e8bbef-60d4-49de-8a4c-ba07b0f332b1

📥 Commits

Reviewing files that changed from the base of the PR and between 57fa4c3 and 426582b.

📒 Files selected for processing (59)
  • .pre-commit-config.yaml
  • docs/source/computational_implementation.rst
  • docs/source/extensions.rst
  • docs/source/extensions/growth_rates.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/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_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/cli.py Outdated
Comment thread temoa/cli.py Outdated
Comment thread temoa/components/technology.py
Comment thread temoa/core/model.py
Comment thread temoa/extensions/stochastics/scenario_creator.py
Comment thread temoa/extensions/template/components/example_limit.py
Comment thread temoa/extensions/template/tables.sql
Comment thread tests/conftest.py
Comment thread tests/testing_configs/config_myopic_capacities.toml
Comment thread temoa/cli.py Outdated
@idelder idelder force-pushed the extensions/framework branch from 426582b to 57639c9 Compare July 6, 2026 14:58

@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

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

6-14: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Make the primary-key columns explicitly NOT NULL.

SQLite still accepts NULL in a composite PRIMARY KEY, so malformed rows can slip into this scaffold. This was already flagged previously.

🧩 Proposed fix
 CREATE TABLE IF NOT EXISTS example_new_capacity_limit
 (
-    region        TEXT,
-    tech_or_group TEXT,
+    region        TEXT NOT NULL,
+    tech_or_group TEXT NOT NULL,
     value         REAL NOT NULL DEFAULT 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/template/tables.sql` around lines 6 - 14, The composite
primary key in example_new_capacity_limit should be made explicitly NOT NULL on
both key columns, since SQLite can otherwise allow NULLs in a PRIMARY KEY.
Update the CREATE TABLE definition for region and tech_or_group so the
primary-key fields are declared NOT NULL while keeping the existing PRIMARY KEY
(region, tech_or_group) constraint.
tests/testing_configs/config_myopic_capacities.toml (1)

4-5: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep the myopic output off the shared fixture.

This still writes back into tests/testing_outputs/myopic_capacities.sqlite, so parametrized runs can leak state across cases. This is the same issue flagged in the previous review.

🤖 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 `@tests/testing_configs/config_myopic_capacities.toml` around lines 4 - 5, The
myopic capacities test config is still pointing both input_database and
output_database at the shared fixture, which can leak state between parametrized
runs. Update the configuration used by this test so the output_database no
longer writes back to tests/testing_outputs/myopic_capacities.sqlite, and
instead uses a separate per-run or isolated output target while keeping the
relevant config keys in config_myopic_capacities.toml consistent.
temoa/cli.py (1)

641-654: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Same conn.close()-inside-with bug in the fallback path.

Identical issue as the packaged-resource path above: conn.close() at line 650 runs before the enclosing with block's __exit__, which will then attempt commit()/rollback() on an already-closed connection and raise sqlite3.ProgrammingError.

🐛 Proposed fix
-        with sqlite3.connect(target_database) as conn:
+        import contextlib
+        with contextlib.closing(sqlite3.connect(target_database)) as conn:
             conn.executescript(fallback_schema.read_text(encoding='utf-8'))
             conn.execute('PRAGMA foreign_keys = OFF;')
             conn.executescript(sql_content)
             conn.execute('PRAGMA foreign_keys = ON;')
+            conn.commit()
             fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall()
-            conn.close()
             if fk_violations:
                 raise sqlite3.IntegrityError(
                     f'Foreign key check failed after tutorial data load: {fk_violations[:5]}'
                 ) from None

Based on learnings, prefer with contextlib.closing(sqlite3.connect(...)) as con: for explicit connection closing; same root cause as the packaged-resource path flagged above.

🤖 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/cli.py` around lines 641 - 654, The fallback database load path in the
sqlite3 connection block has the same premature-close problem as the
packaged-resource path: `conn.close()` is called inside the `with
sqlite3.connect(...) as conn:` scope, causing the context manager’s exit to
operate on an already-closed connection. Update this code in the fallback path
to use the same connection-lifecycle pattern as the other path (for example,
`contextlib.closing` around `sqlite3.connect(...)` or otherwise remove the
manual close and let the context manager manage it) in the `cli.py` database
generation logic around `conn`, `fk_violations`, and the `PRAGMA
foreign_key_check` flow.

Source: Learnings

temoa/extensions/growth_rates/components/growth_new_capacity.py (1)

103-109: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

First-period baseline still bypasses get_adjusted_existing_capacity, and lacks a guard for empty time_exist.

Lines 105-109 sum model.existing_capacity[...] directly, so myopic runs with early retirements compute the growth/degrowth bound from pre-retirement capacity. Additionally, line 104's model.time_exist.last() has no guard for an empty time_exist set (same class of issue raised in growth_capacity.py).

🐛 Proposed fix
-from temoa.components.utils import Operator, operator_expression
+from temoa.components.utils import (
+    Operator,
+    get_adjusted_existing_capacity,
+    operator_expression,
+)
@@
     if p == model.time_optimize.first():
-        p_prev = model.time_exist.last()
-        new_cap_prev = sum(
-            value(model.existing_capacity[_r, _t, _v])
-            for _r, _t, _v in model.existing_capacity.sparse_keys()
-            if _r in regions and _t in techs and _v == p_prev
-        )
+        if len(model.time_exist) == 0:
+            new_cap_prev = 0
+        else:
+            p_prev = model.time_exist.last()
+            new_cap_prev = sum(
+                get_adjusted_existing_capacity(model, _r, _t, _v)
+                for _r, _t, _v in model.existing_capacity.sparse_keys()
+                if _r in regions and _t in techs and _v == p_prev
+            )
🤖 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/growth_rates/components/growth_new_capacity.py` around lines
103 - 109, The first-period baseline in growth_new_capacity still reads directly
from existing_capacity and skips get_adjusted_existing_capacity, so update the p
== model.time_optimize.first() branch in growth_new_capacity logic to derive
new_cap_prev from the adjusted existing-capacity helper instead of raw values.
Also add the same empty-time_exist guard used elsewhere before calling
model.time_exist.last(), so the first-period path handles an empty time_exist
safely and consistently.
temoa/extensions/growth_rates/components/growth_new_capacity_delta.py (1)

111-136: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unguarded time_exist boundary calls plus unadjusted existing capacity in the first-period fallback.

Line 118's model.time_exist.prev(p_prev) still fails if time_exist has fewer than two entries (and line 112's .last() fails if it has none). Separately, lines 114, 120, and 130 use raw value(model.existing_capacity[...]), bypassing get_adjusted_existing_capacity, so myopic early-retirement adjustments are ignored in this constraint's baseline — the same gap already flagged/fixed in growth_capacity.py.

🛡️ Proposed fix
-from temoa.components.utils import Operator, operator_expression
+from temoa.components.utils import (
+    Operator,
+    get_adjusted_existing_capacity,
+    operator_expression,
+)
@@
     if p == model.time_optimize.first():
-        p_prev = model.time_exist.last()
-        new_cap_prev = sum(
-            value(model.existing_capacity[_r, _t, _v])
-            for _r, _t, _v in model.existing_capacity.sparse_keys()
-            if _r in regions and _t in techs and _v == p_prev
-        )
-        p_prev2 = model.time_exist.prev(p_prev)
-        new_cap_prev2 = sum(
-            value(model.existing_capacity[_r, _t, _v])
-            for _r, _t, _v in model.existing_capacity.sparse_keys()
-            if _r in regions and _t in techs and _v == p_prev2
-        )
+        if len(model.time_exist) < 2:
+            return Constraint.Skip
+        p_prev = model.time_exist.last()
+        new_cap_prev = sum(
+            get_adjusted_existing_capacity(model, _r, _t, _v)
+            for _r, _t, _v in model.existing_capacity.sparse_keys()
+            if _r in regions and _t in techs and _v == p_prev
+        )
+        p_prev2 = model.time_exist.prev(p_prev)
+        new_cap_prev2 = sum(
+            get_adjusted_existing_capacity(model, _r, _t, _v)
+            for _r, _t, _v in model.existing_capacity.sparse_keys()
+            if _r in regions and _t in techs and _v == p_prev2
+        )
     else:
         p_prev = model.time_optimize.prev(p)
         new_cap_prev = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev)
         if p == model.time_optimize.at(2):
-            p_prev2 = model.time_exist.last()
-            new_cap_prev2 = sum(
-                value(model.existing_capacity[_r, _t, _v])
-                for _r, _t, _v in model.existing_capacity.sparse_keys()
-                if _r in regions and _t in techs and _v == p_prev2
-            )
+            if len(model.time_exist) == 0:
+                new_cap_prev2 = 0
+            else:
+                p_prev2 = model.time_exist.last()
+                new_cap_prev2 = sum(
+                    get_adjusted_existing_capacity(model, _r, _t, _v)
+                    for _r, _t, _v in model.existing_capacity.sparse_keys()
+                    if _r in regions and _t in techs and _v == p_prev2
+                )
🤖 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/growth_rates/components/growth_new_capacity_delta.py` around
lines 111 - 136, The fallback path in growth_new_capacity_delta is still using
unguarded time_exist lookups and raw existing-capacity values. Update the
first-period and p==at(2) branches in the constraint logic that uses
model.time_exist.last() and model.time_exist.prev(...) so they safely handle
cases where time_exist has fewer than two periods, and replace the direct
value(model.existing_capacity[...]) baseline sums with
get_adjusted_existing_capacity so the existing-capacity baseline matches the
myopic early-retirement adjustment used elsewhere.
temoa/extensions/growth_rates/components/growth_capacity.py (1)

102-111: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Still missing guard for empty time_exist before calling .last().

Line 103 unconditionally calls model.time_exist.last(); on a model with no historical periods this raises during constraint construction rather than falling back cleanly.

🛡️ Proposed fix
     if p == model.time_optimize.first():
-        p_prev = model.time_exist.last()
-        capacity_prev = sum(
-            get_adjusted_existing_capacity(model, _r, _t, _v)
-            * value(model.process_life_frac[_r, p_prev, _t, _v])
-            for _r, _t, _v in model.existing_capacity.sparse_keys()
-            if _r in regions
-            and _t in techs
-            and _v + value(model.lifetime_process[_r, _t, _v]) > p_prev
-        )
+        if len(model.time_exist) == 0:
+            capacity_prev = 0
+        else:
+            p_prev = model.time_exist.last()
+            capacity_prev = sum(
+                get_adjusted_existing_capacity(model, _r, _t, _v)
+                * value(model.process_life_frac[_r, p_prev, _t, _v])
+                for _r, _t, _v in model.existing_capacity.sparse_keys()
+                if _r in regions
+                and _t in techs
+                and _v + value(model.lifetime_process[_r, _t, _v]) > p_prev
+            )
🤖 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/growth_rates/components/growth_capacity.py` around lines 102
- 111, The capacity calculation in growth_capacity.py still assumes historical
periods exist, because the branch in the growth constraint uses
model.time_exist.last() unconditionally when p equals
model.time_optimize.first(). Update the constraint logic around this
first-period case to guard against an empty model.time_exist set before calling
.last(), and short-circuit or fall back safely when there is no prior period so
the constraint can be built without raising. Use the existing growth capacity
constraint block and the p == model.time_optimize.first() branch as the place to
apply the check.
temoa/data_io/hybrid_loader.py (1)

697-764: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Optional lifetime tables aren't actually optional in the loaders — unresolved from prior review.

_load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve are registered with is_table_required=False, but they re-query their tables directly via cur.execute(...) without checking existence. On a database missing one of these tables, _fetch_data() already safely returns [] for raw_data, but these loaders ignore it and will raise sqlite3.OperationalError instead of a graceful skip.

🛡️ Proposed fix: reuse fetched rows
 def _load_lifetime_tech(
     self,
     data: dict[str, object],
     raw_data: Sequence[tuple[object, ...]],
     filtered_data: Sequence[tuple[object, ...]],
 ) -> None:
     """Loads the lifetime_tech component."""
-    model = TemoaModel()
-    cur = self.con.cursor()
-    rows_to_load = cur.execute('SELECT region, tech, lifetime FROM lifetime_tech').fetchall()
+    model = self.model
+    rows_to_load = list(raw_data)
     rt_getter = itemgetter(0, 1)
     ...

 def _load_lifetime_process(...):
     """Loads the lifetime_process component."""
-    model = TemoaModel()
-    cur = self.con.cursor()
     mi = self.myopic_index
-    if mi:
-        rows_to_load = cur.execute(
-            'SELECT region, tech, vintage, lifetime FROM lifetime_process WHERE vintage <= ?',
-            (mi.last_demand_year,),
-        ).fetchall()
-    else:
-        rows_to_load = cur.execute(
-            'SELECT region, tech, vintage, lifetime FROM lifetime_process'
-        ).fetchall()
+    model = self.model
+    rows_to_load = list(raw_data)
+    if mi:
+        rows_to_load = [row for row in rows_to_load if row[2] <= mi.last_demand_year]
     ...

 def _load_lifetime_survival_curve(...):
     """Loads the lifetime_survival_curve component."""
-    model = TemoaModel()
-    cur = self.con.cursor()
     mi = self.myopic_index
-    if mi:
-        rows_to_load = cur.execute(
-            'SELECT region, period, tech, vintage, fraction FROM lifetime_survival_curve '
-            'WHERE vintage <= ?',
-            (mi.last_demand_year,),
-        ).fetchall()
-    else:
-        rows_to_load = cur.execute(
-            'SELECT region, period, tech, vintage, fraction FROM lifetime_survival_curve'
-        ).fetchall()
+    model = self.model
+    rows_to_load = list(raw_data)
+    if mi:
+        rows_to_load = [row for row in rows_to_load if row[3] <= mi.last_demand_year]
     ...
🤖 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 697 - 764, The optional lifetime
loaders still bypass the safe fetched data path and query SQLite directly, so
missing tables can raise OperationalError. Update _load_lifetime_tech,
_load_lifetime_process, and _load_lifetime_survival_curve to reuse the rows
already fetched by _fetch_data() from raw_data/filtered_data instead of calling
cur.execute on the tables again. Keep the existing viability and myopic
filtering logic, but apply it to the passed-in rows so these optional tables can
be skipped gracefully when absent.
🤖 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/cli.py`:
- Around line 590-600: The tutorial database load logic in the sqlite3
connection block is closing the connection manually even though the context
manager on sqlite3.connect already handles cleanup. Remove the explicit
conn.close() in this block and the matching close in the fallback path so the
remaining FK check and block exit can complete normally; use the existing conn
variable in the load flow and let the context manager manage commit/rollback and
closure.

In `@temoa/data_io/hybrid_loader.py`:
- Around line 659-764: The new loaders are creating throwaway TemoaModel
instances just to access component names, which is unnecessary overhead. Update
_load_retired_existing_capacity, _load_lifetime_tech, _load_lifetime_process,
and _load_lifetime_survival_curve to use self.model instead of instantiating
TemoaModel(), matching the pattern already used in create_data_dict and
_load_regional_global_indices. Keep the existing cursor/query logic unchanged
and pass the component objects from self.model into _load_component_data.

In `@temoa/extensions/framework.py`:
- Around line 186-195: Harden _table_has_rows in framework.py by validating
table_name before using it in the f-string SQL query. Keep the existing
sqlite_master existence check, then ensure the identifier is limited to the
trusted owned-table set from spec.owned_tables or otherwise matches the
validated sqlite_master result before constructing SELECT 1 FROM
main.{table_name} LIMIT 1. Make the validation part of _table_has_rows so the
query only interpolates a trusted table name.
- Around line 159-167: The prompt in the `should_apply` flow can break
non-interactive runs because `input()` may raise `EOFError` when `silent` is
false and stdin has no TTY. Update the prompt handling in this branch to either
skip prompting when stdin is not interactive or catch `EOFError` and fall back
to the existing non-apply path, using the `spec.extension_id` prompt logic as
the place to apply the guard.

---

Duplicate comments:
In `@temoa/cli.py`:
- Around line 641-654: The fallback database load path in the sqlite3 connection
block has the same premature-close problem as the packaged-resource path:
`conn.close()` is called inside the `with sqlite3.connect(...) as conn:` scope,
causing the context manager’s exit to operate on an already-closed connection.
Update this code in the fallback path to use the same connection-lifecycle
pattern as the other path (for example, `contextlib.closing` around
`sqlite3.connect(...)` or otherwise remove the manual close and let the context
manager manage it) in the `cli.py` database generation logic around `conn`,
`fk_violations`, and the `PRAGMA foreign_key_check` flow.

In `@temoa/data_io/hybrid_loader.py`:
- Around line 697-764: The optional lifetime loaders still bypass the safe
fetched data path and query SQLite directly, so missing tables can raise
OperationalError. Update _load_lifetime_tech, _load_lifetime_process, and
_load_lifetime_survival_curve to reuse the rows already fetched by _fetch_data()
from raw_data/filtered_data instead of calling cur.execute on the tables again.
Keep the existing viability and myopic filtering logic, but apply it to the
passed-in rows so these optional tables can be skipped gracefully when absent.

In `@temoa/extensions/growth_rates/components/growth_capacity.py`:
- Around line 102-111: The capacity calculation in growth_capacity.py still
assumes historical periods exist, because the branch in the growth constraint
uses model.time_exist.last() unconditionally when p equals
model.time_optimize.first(). Update the constraint logic around this
first-period case to guard against an empty model.time_exist set before calling
.last(), and short-circuit or fall back safely when there is no prior period so
the constraint can be built without raising. Use the existing growth capacity
constraint block and the p == model.time_optimize.first() branch as the place to
apply the check.

In `@temoa/extensions/growth_rates/components/growth_new_capacity_delta.py`:
- Around line 111-136: The fallback path in growth_new_capacity_delta is still
using unguarded time_exist lookups and raw existing-capacity values. Update the
first-period and p==at(2) branches in the constraint logic that uses
model.time_exist.last() and model.time_exist.prev(...) so they safely handle
cases where time_exist has fewer than two periods, and replace the direct
value(model.existing_capacity[...]) baseline sums with
get_adjusted_existing_capacity so the existing-capacity baseline matches the
myopic early-retirement adjustment used elsewhere.

In `@temoa/extensions/growth_rates/components/growth_new_capacity.py`:
- Around line 103-109: The first-period baseline in growth_new_capacity still
reads directly from existing_capacity and skips get_adjusted_existing_capacity,
so update the p == model.time_optimize.first() branch in growth_new_capacity
logic to derive new_cap_prev from the adjusted existing-capacity helper instead
of raw values. Also add the same empty-time_exist guard used elsewhere before
calling model.time_exist.last(), so the first-period path handles an empty
time_exist safely and consistently.

In `@temoa/extensions/template/tables.sql`:
- Around line 6-14: The composite primary key in example_new_capacity_limit
should be made explicitly NOT NULL on both key columns, since SQLite can
otherwise allow NULLs in a PRIMARY KEY. Update the CREATE TABLE definition for
region and tech_or_group so the primary-key fields are declared NOT NULL while
keeping the existing PRIMARY KEY (region, tech_or_group) constraint.

In `@tests/testing_configs/config_myopic_capacities.toml`:
- Around line 4-5: The myopic capacities test config is still pointing both
input_database and output_database at the shared fixture, which can leak state
between parametrized runs. Update the configuration used by this test so the
output_database no longer writes back to
tests/testing_outputs/myopic_capacities.sqlite, and instead uses a separate
per-run or isolated output target while keeping the relevant config keys in
config_myopic_capacities.toml consistent.
🪄 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: 25f6efc9-bf87-4daa-9deb-bd5d0444ec90

📥 Commits

Reviewing files that changed from the base of the PR and between 426582b and 57639c9.

📒 Files selected for processing (59)
  • .pre-commit-config.yaml
  • docs/source/computational_implementation.rst
  • docs/source/extensions.rst
  • docs/source/extensions/growth_rates.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/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_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/mathematical_formulation.rst
  • tests/testing_data/mediumville.sql
  • temoa/db_schema/temoa_schema_v4.sql
  • docs/source/param_desc_and_tables.rst

Comment thread temoa/cli.py Outdated
Comment thread temoa/data_io/hybrid_loader.py
Comment thread temoa/extensions/framework.py
Comment thread temoa/extensions/framework.py
@idelder idelder force-pushed the extensions/framework branch from 57639c9 to 78760c3 Compare July 6, 2026 15:26
@idelder idelder marked this pull request as draft July 6, 2026 15:29

@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.

Caution

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

⚠️ Outside diff range comments (2)
temoa/extensions/stochastics/scenario_creator.py (1)

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

Nit: move import contextlib to top-level imports.

Inline import works but is inconsistent with the module-level import block at Lines 1-17.

🤖 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/stochastics/scenario_creator.py` at line 38, Move the
contextlib import into the module’s top-level import block to match the existing
import style. Update the imports in scenario_creator.py so contextlib is grouped
with the other module imports near the top, and remove the inline import from
the local scope.
temoa/cli.py (1)

587-600: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract shared helper for schema+data DB build to remove duplication.

The primary and fallback branches duplicate the same open-connection / executescript(schema) / toggle-FK / executescript(data) / foreign_key_check sequence almost verbatim. A future fix to one path (e.g., error handling, FK-check batching) risks being missed in the other.

♻️ Proposed refactor
+def _build_tutorial_database(target_database: Path, schema_content: str, sql_content: str) -> None:
+    with contextlib.closing(sqlite3.connect(target_database)) as conn:
+        conn.executescript(schema_content)
+        conn.execute('PRAGMA foreign_keys = OFF;')
+        conn.executescript(sql_content)
+        conn.execute('PRAGMA foreign_keys = ON;')
+        fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall()
+        if fk_violations:
+            raise sqlite3.IntegrityError(
+                f'Foreign key check failed after tutorial data load: {fk_violations[:5]}'
+            )
+
 ...
-        with contextlib.closing(sqlite3.connect(target_database)) as conn:
-            conn.executescript(schema_content)
-            conn.execute('PRAGMA foreign_keys = OFF;')
-            conn.executescript(sql_content)
-            conn.execute('PRAGMA foreign_keys = ON;')
-            fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall()
-            if fk_violations:
-                raise sqlite3.IntegrityError(
-                    f'Foreign key check failed after tutorial data load: {fk_violations[:5]}'
-                )
+        _build_tutorial_database(target_database, schema_content, sql_content)
 ...
-        with contextlib.closing(sqlite3.connect(target_database)) as conn:
-            conn.executescript(fallback_schema.read_text(encoding='utf-8'))
-            conn.execute('PRAGMA foreign_keys = OFF;')
-            conn.executescript(sql_content)
-            conn.execute('PRAGMA foreign_keys = ON;')
-            fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall()
-            if fk_violations:
-                raise sqlite3.IntegrityError(
-                    f'Foreign key check failed after tutorial data load: {fk_violations[:5]}'
-                ) from None
+        try:
+            _build_tutorial_database(
+                target_database, fallback_schema.read_text(encoding='utf-8'), sql_content
+            )
+        except sqlite3.IntegrityError as fk_e:
+            raise sqlite3.IntegrityError(str(fk_e)) from None

Also applies to: 628-653

🤖 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/cli.py` around lines 587 - 600, The schema-and-data database build
logic is duplicated between the primary and fallback paths, so extract the
repeated sqlite3 connection/executescript/foreign-key-toggle/foreign_key_check
sequence into a shared helper. Update the relevant code in cli.py around the
existing tutorial load flow and the fallback branch to call that helper,
preserving the current behavior in both paths while keeping the foreign-key
validation and error handling centralized.
🤖 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.

Outside diff comments:
In `@temoa/cli.py`:
- Around line 587-600: The schema-and-data database build logic is duplicated
between the primary and fallback paths, so extract the repeated sqlite3
connection/executescript/foreign-key-toggle/foreign_key_check sequence into a
shared helper. Update the relevant code in cli.py around the existing tutorial
load flow and the fallback branch to call that helper, preserving the current
behavior in both paths while keeping the foreign-key validation and error
handling centralized.

In `@temoa/extensions/stochastics/scenario_creator.py`:
- Line 38: Move the contextlib import into the module’s top-level import block
to match the existing import style. Update the imports in scenario_creator.py so
contextlib is grouped with the other module imports near the top, and remove the
inline import from the local scope.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5622942e-0645-4b76-b0cb-cb6542474514

📥 Commits

Reviewing files that changed from the base of the PR and between 57639c9 and 78760c3.

📒 Files selected for processing (3)
  • temoa/cli.py
  • temoa/data_io/loader_manifest.py
  • temoa/extensions/stochastics/scenario_creator.py

@idelder idelder force-pushed the extensions/framework branch from 78760c3 to 6405a22 Compare July 6, 2026 15:32
@idelder idelder marked this pull request as ready for review July 6, 2026 15:40
@idelder idelder marked this pull request as draft July 6, 2026 15:50
@idelder idelder marked this pull request as ready for review July 6, 2026 15:59

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

Caution

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

⚠️ Outside diff range comments (1)
temoa/extensions/growth_rates/components/growth_new_capacity_delta.py (1)

1-149: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Extract shared logic across the three growth-rate constraint builders.

growth_capacity.py, growth_new_capacity.py, and growth_new_capacity_delta.py duplicate near-identical index-building, growth/degrowth wrapper dispatch, and first/second-period existing-capacity fallback logic. This duplication is the direct cause of the inconsistencies found in this review — one file correctly applies get_adjusted_existing_capacity() and guards time_exist boundaries, the other two don't. Consolidating the shared "resolve previous-period capacity (adjusted, with time_exist boundary guards)" logic and the growth/degrowth index/rule dispatch pattern into a common helper module would prevent this class of divergence going forward.

🤖 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/growth_rates/components/growth_new_capacity_delta.py` around
lines 1 - 149, The growth-rate constraint builders are duplicating the same
index/rule dispatch and previous-capacity fallback logic, which has already
caused inconsistent handling of existing capacity and time_exist boundaries.
Refactor the shared pieces used by limit_growth_new_capacity_delta_indices,
limit_degrowth_new_capacity_delta_indices,
limit_growth_new_capacity_delta_constraint_rule, and
limit_degrowth_new_capacity_delta_constraint_rule into a common helper so the
previous-period capacity resolution is centralized. Move the first/second-period
lookup and adjusted existing-capacity handling into that shared helper, then
have limit_growth_new_capacity_delta delegate to it for both growth and degrowth
paths.
♻️ Duplicate comments (2)
temoa/data_io/hybrid_loader.py (2)

697-764: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Optional lifetime tables still crash on older schemas.

_load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve are declared is_table_required=False in the manifest but re-query the tables unconditionally via cur.execute(...), ignoring the already-fetched raw_data/filtered_data. If an older database lacks one of these optional tables, this raises sqlite3.OperationalError instead of gracefully skipping, as the manifest contract promises.

🤖 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 697 - 764, The optional lifetime
loaders are re-querying the database and can still fail on missing tables even
though the manifest marks them optional. Update _load_lifetime_tech,
_load_lifetime_process, and _load_lifetime_survival_curve to use the already
provided raw_data/filtered_data inputs instead of calling cur.execute against
the table names, and keep the existing myopic/viable filtering logic applied to
those in-memory rows. This will let the optional-table path in the HybridLoader
methods skip gracefully on older schemas without raising
sqlite3.OperationalError.

659-764: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Throwaway TemoaModel() instantiation in new loaders.

_load_retired_existing_capacity and the three lifetime loaders each construct a fresh TemoaModel() just to read .name, while self.model (already extension-aware, used elsewhere in this same diff, e.g. create_data_dict) provides the same names without rebuilding the entire Sets/Params graph.

🤖 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 659 - 764, The new loader
methods are creating unnecessary throwaway TemoaModel instances just to access
component names. Update _load_retired_existing_capacity, _load_lifetime_tech,
_load_lifetime_process, and _load_lifetime_survival_curve to use the existing
self.model instead of instantiating TemoaModel(), matching the extension-aware
pattern already used elsewhere like create_data_dict. Keep the existing
query/filter/load logic unchanged and just switch the model reference used for
model.retired_existing_capacity, model.lifetime_tech, model.lifetime_process,
and model.lifetime_survival_curve.
🤖 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/cli.py`:
- Around line 587-601: The database initialization flow in the tutorial data
load block relies on implicit persistence; add an explicit commit on the sqlite3
connection in the schema/sql load sequence before the contextlib.closing block
ends. Update the logic around sqlite3.connect(target_database),
conn.executescript, and the foreign_key_check handling so the successful load is
made durable directly, while preserving the existing IntegrityError path for
fk_violations.

In `@temoa/components/technology.py`:
- Around line 445-459: The validator in technology handling currently downgrades
a missing active existing-capacity case to logger.warning, which allows invalid
non-myopic inputs through. Update the logic around
get_adjusted_existing_capacity, model.time_optimize.first(), and
model.process_periods so that myopic runs still only warn, but the
base/non-myopic path raises an exception when surviving capacity should extend
into future periods yet the process is not active. Keep the warning message text
for the myopic branch, and make the non-myopic branch fail fast with the same
context.

In `@temoa/components/time.py`:
- Around line 192-198: The special-case in param_period_length is still needed
for process_life_frac and technology.model_process_life_indices, not just the
growth_rates extension, so update the stale comment to reflect that dependency.
Harden the non-last time_exist branch in param_period_length by replacing the -1
sentinel with a failure path that raises if an existing period other than the
last is ever requested, so future callers don’t silently rely on an invalid
value. Keep the logic keyed to param_period_length, time_exist, and
process_life_frac so it’s easy to find if the period handling is refactored
later.

In `@temoa/data_io/hybrid_loader.py`:
- Around line 249-252: The reshape in hybrid_loader’s indexed-row handling is
wrapping a single trailing value column into a 1-element tuple, which later
makes _load_component_data store tuples instead of plain values. Update the
raw_data reshaping logic so it only packs trailing columns into a tuple when
there are 2 or more trailing value columns, and leaves the last column as a
scalar when index_length is len(item.columns) - 1. Use the existing
item.index_length and the indexed-Param path in _load_component_data to verify
the shape stays compatible with t[:-1]: t[-1].

In `@temoa/data_io/loader_manifest.py`:
- Around line 35-36: Clarify the `index_length` contract in `LoaderManifest` so
callers know it must leave more than one trailing value column, not just a
single trailing value. Update the `LoaderManifest` docstring and, if
appropriate, add validation in the dataclass initializer or `__post_init__` to
reject `index_length == len(columns) - 1`. Use the `index_length` field and the
`LoaderManifest` class as the primary places to fix this.

In `@temoa/extensions/growth_rates/components/growth_new_capacity_delta.py`:
- Around line 113-136: The fallback baselines in the growth-rate delta logic are
still using raw existing capacity instead of the retirement-adjusted totals.
Update the first-period and p == model.time_optimize.at(2) branches in
growth_new_capacity_delta.py so new_cap_prev and new_cap_prev2 are computed from
the same retirement-adjusted capacity source used elsewhere (for example the
cap_rtv/new_cap_rtv path), matching the existing_capacity-based logic only where
no retired capacity adjustment is needed.

In `@temoa/extensions/growth_rates/components/growth_new_capacity.py`:
- Around line 103-104: The first-period branch in growth_new_capacity’s
constraint logic should not call model.time_exist.last() without checking that
the set has entries, because it can crash during constraint construction. Update
the logic around the p == model.time_optimize.first() case to guard against an
empty model.time_exist before assigning p_prev, and choose a safe fallback or
skip the lookup when the set is empty. Keep the fix localized to the
growth_new_capacity constraint code path so it matches the similar handling
needed in growth_capacity.

In `@temoa/tutorial_assets/config_sample.toml`:
- Around line 21-26: The sample extensions example is misleading because it
references an unregistered extension id, which can cause Unknown extension id(s)
if copied as-is. Update the example in config_sample.toml to only show
registered extensions, and align the comment/example with the actual IDs
returned by get_known_extension_specs so users only see valid extension names.

---

Outside diff comments:
In `@temoa/extensions/growth_rates/components/growth_new_capacity_delta.py`:
- Around line 1-149: The growth-rate constraint builders are duplicating the
same index/rule dispatch and previous-capacity fallback logic, which has already
caused inconsistent handling of existing capacity and time_exist boundaries.
Refactor the shared pieces used by limit_growth_new_capacity_delta_indices,
limit_degrowth_new_capacity_delta_indices,
limit_growth_new_capacity_delta_constraint_rule, and
limit_degrowth_new_capacity_delta_constraint_rule into a common helper so the
previous-period capacity resolution is centralized. Move the first/second-period
lookup and adjusted existing-capacity handling into that shared helper, then
have limit_growth_new_capacity_delta delegate to it for both growth and degrowth
paths.

---

Duplicate comments:
In `@temoa/data_io/hybrid_loader.py`:
- Around line 697-764: The optional lifetime loaders are re-querying the
database and can still fail on missing tables even though the manifest marks
them optional. Update _load_lifetime_tech, _load_lifetime_process, and
_load_lifetime_survival_curve to use the already provided raw_data/filtered_data
inputs instead of calling cur.execute against the table names, and keep the
existing myopic/viable filtering logic applied to those in-memory rows. This
will let the optional-table path in the HybridLoader methods skip gracefully on
older schemas without raising sqlite3.OperationalError.
- Around line 659-764: The new loader methods are creating unnecessary throwaway
TemoaModel instances just to access component names. Update
_load_retired_existing_capacity, _load_lifetime_tech, _load_lifetime_process,
and _load_lifetime_survival_curve to use the existing self.model instead of
instantiating TemoaModel(), matching the extension-aware pattern already used
elsewhere like create_data_dict. Keep the existing query/filter/load logic
unchanged and just switch the model reference used for
model.retired_existing_capacity, model.lifetime_tech, model.lifetime_process,
and model.lifetime_survival_curve.
🪄 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: 632c2a54-fab4-40ad-b9a9-40c96a0c8b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 78760c3 and 6405a22.

📒 Files selected for processing (59)
  • .pre-commit-config.yaml
  • docs/source/computational_implementation.rst
  • docs/source/extensions.rst
  • docs/source/extensions/growth_rates.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/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_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)
  • temoa/db_schema/temoa_schema_v4.sql
  • docs/source/mathematical_formulation.rst
  • docs/source/param_desc_and_tables.rst
  • tests/testing_data/mediumville.sql

Comment thread temoa/cli.py
Comment thread temoa/components/technology.py
Comment thread temoa/components/time.py
Comment thread temoa/data_io/hybrid_loader.py Outdated
Comment thread temoa/data_io/loader_manifest.py
Comment thread temoa/extensions/growth_rates/components/growth_new_capacity_delta.py Outdated
Comment thread temoa/extensions/growth_rates/components/growth_new_capacity.py Outdated
Comment thread temoa/tutorial_assets/config_sample.toml
@idelder idelder force-pushed the extensions/framework branch from 6405a22 to 640771d Compare July 6, 2026 17:32

@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.

Caution

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

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

587-601: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: extract the schema+data DB-build into a helper to remove duplication.

Lines 587-601 and the fallback at Lines 642-655 duplicate the full build sequence (connect → executescript schema → FK OFF → executescript data → FK ON → foreign_key_check → commit). A small helper keeps the FK-toggle/integrity logic in one place and avoids the two paths drifting.

♻️ Sketch
def _build_tutorial_db(target_database: Path, schema_content: str, sql_content: str) -> None:
    with contextlib.closing(sqlite3.connect(target_database)) as conn:
        conn.executescript(schema_content)
        conn.execute('PRAGMA foreign_keys = OFF;')
        conn.executescript(sql_content)
        conn.execute('PRAGMA foreign_keys = ON;')
        fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall()
        if fk_violations:
            raise sqlite3.IntegrityError(
                f'Foreign key check failed after tutorial data load: {fk_violations[:5]}'
            )
        conn.commit()
🤖 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/cli.py` around lines 587 - 601, The tutorial DB build sequence is
duplicated in the main path and the fallback, so extract the shared
connect/executescript/foreign_keys toggle/foreign_key_check/commit flow into a
helper such as _build_tutorial_db in temoa/cli.py. Update both call sites to use
that helper with the existing schema_resource and sql_resource content so the
integrity logic stays in one place and the two paths cannot drift.
♻️ Duplicate comments (2)
temoa/data_io/hybrid_loader.py (1)

697-764: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Optional lifetime tables are queried unconditionally.

_load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve issue SELECT ... FROM lifetime_* directly without a table_exists guard (contrast _load_retired_existing_capacity at Line 668). If these optional tables are absent in an older schema, cur.execute(...) raises sqlite3.OperationalError instead of skipping cleanly. Reuse the already-fetched raw_data/filtered_data, or guard with table_exists, so absent optional tables are handled gracefully.

#!/bin/bash
# Confirm these components are declared optional in the manifest
rg -nP -C3 "lifetime_tech|lifetime_process|lifetime_survival_curve" temoa/data_io/component_manifest.py
🤖 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 697 - 764, The optional lifetime
loaders currently query lifetime_tech, lifetime_process, and
lifetime_survival_curve directly, so missing tables can raise
sqlite3.OperationalError. Update _load_lifetime_tech, _load_lifetime_process,
and _load_lifetime_survival_curve in HybridLoader to first check table_exists
(or otherwise reuse the already available raw_data/filtered_data presence)
before executing SELECT statements, and skip loading when the table is absent.
Keep the existing viability filtering and _load_component_data calls intact for
the table-present path.
temoa/extensions/growth_rates/components/growth_new_capacity_delta.py (1)

114-127: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

time_exist.prev() still crashes on a single-entry history.

The if model.time_exist: guard only rules out an empty set. When time_exist has exactly one element, p_prev = model.time_exist.last() is that sole element and model.time_exist.prev(p_prev) (Line 122) raises during constraint construction. Guard for len(model.time_exist) >= 2 before computing p_prev2, leaving new_cap_prev2 at its 0.0 baseline otherwise.

🛡️ Proposed guard
     if p == model.time_optimize.first():
         if model.time_exist:
             p_prev = model.time_exist.last()
             new_cap_prev = quicksum(
                 value(model.existing_capacity[_r, _t, _v])
                 for _r, _t, _v in model.existing_capacity.sparse_keys()
                 if _r in regions and _t in techs and _v == p_prev
             )
-            p_prev2 = model.time_exist.prev(p_prev)
-            new_cap_prev2 = quicksum(
-                value(model.existing_capacity[_r, _t, _v])
-                for _r, _t, _v in model.existing_capacity.sparse_keys()
-                if _r in regions and _t in techs and _v == p_prev2
-            )
+            if len(model.time_exist) >= 2:
+                p_prev2 = model.time_exist.prev(p_prev)
+                new_cap_prev2 = quicksum(
+                    value(model.existing_capacity[_r, _t, _v])
+                    for _r, _t, _v in model.existing_capacity.sparse_keys()
+                    if _r in regions and _t in techs and _v == p_prev2
+                )
🤖 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/growth_rates/components/growth_new_capacity_delta.py` around
lines 114 - 127, In growth_new_capacity_delta’s constraint logic, the current
model.time_exist check only covers non-empty history, so
model.time_exist.prev(p_prev) can still fail when there is only one historical
period. Update the branch around p == model.time_optimize.first() to require at
least two entries in time_exist before computing p_prev2, and otherwise leave
new_cap_prev2 at its existing 0.0 default; use the existing symbols
model.time_exist, p_prev, p_prev2, and new_cap_prev2 to place the guard
correctly.
🤖 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.

Outside diff comments:
In `@temoa/cli.py`:
- Around line 587-601: The tutorial DB build sequence is duplicated in the main
path and the fallback, so extract the shared connect/executescript/foreign_keys
toggle/foreign_key_check/commit flow into a helper such as _build_tutorial_db in
temoa/cli.py. Update both call sites to use that helper with the existing
schema_resource and sql_resource content so the integrity logic stays in one
place and the two paths cannot drift.

---

Duplicate comments:
In `@temoa/data_io/hybrid_loader.py`:
- Around line 697-764: The optional lifetime loaders currently query
lifetime_tech, lifetime_process, and lifetime_survival_curve directly, so
missing tables can raise sqlite3.OperationalError. Update _load_lifetime_tech,
_load_lifetime_process, and _load_lifetime_survival_curve in HybridLoader to
first check table_exists (or otherwise reuse the already available
raw_data/filtered_data presence) before executing SELECT statements, and skip
loading when the table is absent. Keep the existing viability filtering and
_load_component_data calls intact for the table-present path.

In `@temoa/extensions/growth_rates/components/growth_new_capacity_delta.py`:
- Around line 114-127: In growth_new_capacity_delta’s constraint logic, the
current model.time_exist check only covers non-empty history, so
model.time_exist.prev(p_prev) can still fail when there is only one historical
period. Update the branch around p == model.time_optimize.first() to require at
least two entries in time_exist before computing p_prev2, and otherwise leave
new_cap_prev2 at its existing 0.0 default; use the existing symbols
model.time_exist, p_prev, p_prev2, and new_cap_prev2 to place the guard
correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 80c0f9b1-80df-4dfc-869b-6f70d0a889b6

📥 Commits

Reviewing files that changed from the base of the PR and between 6405a22 and 640771d.

📒 Files selected for processing (9)
  • temoa/cli.py
  • temoa/components/limits.py
  • temoa/components/utils.py
  • temoa/data_io/hybrid_loader.py
  • temoa/data_io/loader_manifest.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/stochastics/scenario_creator.py

@idelder idelder force-pushed the extensions/framework branch from 640771d to 6dabfb1 Compare July 6, 2026 17:54

@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.

♻️ Duplicate comments (1)
temoa/data_io/hybrid_loader.py (1)

697-764: 🩺 Stability & Availability | 🟠 Major

Optional lifetime tables can still raise OperationalError in custom loaders.

_load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve re-query lifetime_tech / lifetime_process / lifetime_survival_curve directly and unconditionally. When these optional tables are absent (older schema), _fetch_data() safely returns [], but the custom loader still issues its own SELECT, raising sqlite3.OperationalError. Reuse the already-fetched raw_data/filtered_data (applying the viability + myopic filters to those rows) so absent optional tables remain skipped.

#!/bin/bash
# Confirm these lifetime LoadItems are declared optional (is_table_required=False)
rg -nP -C4 "lifetime_tech|lifetime_process|lifetime_survival_curve" temoa/data_io/component_manifest.py temoa/extensions/growth_rates/data_manifest.py
🤖 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 697 - 764, The optional lifetime
loaders are re-querying the database directly, which can still throw
OperationalError when the tables are missing in older schemas. Update
_load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve
to consume the already supplied raw_data/filtered_data instead of executing new
SELECTs on self.con, then apply the same viability and myopic filtering to those
in-memory rows before passing them into _load_component_data. Keep the logic
aligned with TemoaModel and the existing itemgetter-based filters so these
optional components stay safely skipped when absent.
🤖 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.

Duplicate comments:
In `@temoa/data_io/hybrid_loader.py`:
- Around line 697-764: The optional lifetime loaders are re-querying the
database directly, which can still throw OperationalError when the tables are
missing in older schemas. Update _load_lifetime_tech, _load_lifetime_process,
and _load_lifetime_survival_curve to consume the already supplied
raw_data/filtered_data instead of executing new SELECTs on self.con, then apply
the same viability and myopic filtering to those in-memory rows before passing
them into _load_component_data. Keep the logic aligned with TemoaModel and the
existing itemgetter-based filters so these optional components stay safely
skipped when absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6e79c36a-1652-4e74-a2bf-a93b03b091d5

📥 Commits

Reviewing files that changed from the base of the PR and between 6405a22 and 6dabfb1.

📒 Files selected for processing (9)
  • temoa/cli.py
  • temoa/components/limits.py
  • temoa/components/utils.py
  • temoa/data_io/hybrid_loader.py
  • temoa/data_io/loader_manifest.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/stochastics/scenario_creator.py

@jdecarolis

Copy link
Copy Markdown
Collaborator

@idelder -- Thanks! This is a great idea, which can help reduce bloat in the core model. How do you envision tracking changes to the extensions over time? Do they accumulate in the extensions folder and get included with regular Temoa releases?

@idelder

idelder commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Yeah I was envisioning having them be tracked just like any other feature additions, except they don't bloat the database/core model. Realistically, building the extensions usually does require some minor changes to the core model code (refactoring things to be used by the extension, injecting one or two things into the model instantiation queue if the extension is active) but they should be designed to have absolutely zero impact on core runtime if inactive.

@idelder

idelder commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Hm wasn't expecting any merge conflicts. I'll rebase locally and repush

idelder added 2 commits July 10, 2026 09:48
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
idelder added 2 commits July 10, 2026 09:48
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
@idelder idelder force-pushed the extensions/framework branch from 6dabfb1 to 1fbc8c8 Compare July 10, 2026 13:48
@idelder

idelder commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Recording this for next time

image

@jdecarolis

Copy link
Copy Markdown
Collaborator

@idelder Yes looked like there were some direct conflicts that needed to get sorted during the rebase.

@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

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)

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

Docstring doesn't mention the new extension_ids parameter.

Consider documenting extension_ids in the docstring Args section for API clarity.

🤖 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` around lines 26 - 39, The docstring for
build_manifest does not document the extension_ids parameter. Add an Args entry
describing extension_ids, including its purpose and the behavior when it is
None, while preserving the existing documentation.
♻️ Duplicate comments (8)
temoa/extensions/template/tables.sql (1)

6-14: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Make composite key columns NOT NULL.

SQLite allows NULL in composite PRIMARY KEY columns, so malformed (region, tech_or_group) rows can slip into this scaffold. This was previously raised and remains unaddressed.

🛡️ Proposed fix
 CREATE TABLE IF NOT EXISTS example_new_capacity_limit
 (
-    region        TEXT,
-    tech_or_group TEXT,
+    region        TEXT NOT NULL,
+    tech_or_group TEXT NOT NULL,
     value         REAL NOT NULL DEFAULT 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/template/tables.sql` around lines 6 - 14, Update the
example_new_capacity_limit table definition so both composite primary-key
columns, region and tech_or_group, are explicitly declared NOT NULL before the
PRIMARY KEY constraint.
tests/testing_configs/config_myopic_capacities.toml (1)

4-5: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Input and output database still point to the same file.

MyopicSequencer writes in place when input_database and output_database target the same SQLite file, so parametrized runs will mutate the shared fixture and leak state into later test cases. This was flagged in a previous review and remains unresolved.

🤖 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 `@tests/testing_configs/config_myopic_capacities.toml` around lines 4 - 5,
Update config_myopic_capacities.toml so input_database and output_database
reference distinct SQLite files, preventing MyopicSequencer runs from mutating
the shared input fixture and leaking state across parametrized tests.
temoa/tutorial_assets/config_sample.toml (1)

21-26: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Example still references a non-existent extension.

"unit_commitment" is not a registered extension — only growth_rates is wired into get_known_extension_specs(). Copying this example verbatim will raise Unknown extension id(s). This was flagged in a previous review and remains unresolved.

📝 Proposed fix
-# Example: extensions = ["unit_commitment", "growth_rates"]
+# Example: extensions = ["growth_rates"]
🤖 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/tutorial_assets/config_sample.toml` around lines 21 - 26, Update the
extensions example comment near the extensions configuration to remove the
unregistered “unit_commitment” identifier and reference only the supported
“growth_rates” extension, matching get_known_extension_specs().
tests/conftest.py (1)

61-67: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Still missing PRAGMA foreign_key_check after extension fixture loading.

PRAGMA foreign_keys = ON at Line 79 does not validate rows inserted while enforcement was off, so a bad row in any extension-owned table will still produce a "successful" test DB. This was flagged in a previous review and remains unresolved.

Suggested fix
         # 4. Turn foreign keys back on
         con.execute('PRAGMA foreign_keys = ON')
+        fk_violations = con.execute('PRAGMA foreign_key_check').fetchall()
+        if fk_violations:
+            raise sqlite3.IntegrityError(
+                f'Foreign key check failed after test DB load: {fk_violations[:5]}'
+            )
         con.commit()
🤖 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 `@tests/conftest.py` around lines 61 - 67, Add a `PRAGMA foreign_key_check`
validation immediately after the extension schemas and fixture data are loaded
in the test database setup, using the connection variable `con`; ensure
violations raise or fail setup rather than allowing an invalid database to
proceed.
temoa/extensions/framework.py (1)

186-195: 🔒 Security & Privacy | 🟡 Minor | 💤 Low value

Quote the interpolated identifier in _table_has_rows.

table_name is trusted today (sourced from spec.owned_tables), but the f-string interpolation is fragile if extension metadata ever becomes less trusted. Quote the identifier defensively; the preceding sqlite_master check already validates existence.

♻️ Suggested hardening
-    query = f'SELECT 1 FROM main.{table_name} LIMIT 1'
+    query = f'SELECT 1 FROM main."{table_name}" LIMIT 1'
     return cur.execute(query).fetchone() is not None
🤖 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/framework.py` around lines 186 - 195, In _table_has_rows,
defensively quote the interpolated table_name identifier in the SELECT query,
using SQLite identifier-quoting with proper escaping, while preserving the
existing sqlite_master existence check and row-existence behavior.

Source: Linters/SAST tools

temoa/data_io/hybrid_loader.py (2)

659-764: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Reuse self.model instead of instantiating throwaway TemoaModel() instances.

_load_retired_existing_capacity, _load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve each build a fresh TemoaModel() (Lines 675, 704, 720, 746) purely to access a component's .name. self.model is already available, extension-aware, and used elsewhere in this file (e.g. create_data_dict, _load_regional_global_indices).

🤖 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 659 - 764, Replace the throwaway
TemoaModel() instances in _load_retired_existing_capacity, _load_lifetime_tech,
_load_lifetime_process, and _load_lifetime_survival_curve with self.model, using
the corresponding component attributes when calling _load_component_data.
Preserve the existing query and filtering behavior while relying on the
extension-aware model already attached to the loader.

697-764: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Custom lifetime loaders bypass the optional-table safety net.

These loaders are marked is_table_required=False in the manifest, but re-query lifetime_tech/lifetime_process/lifetime_survival_curve directly rather than reusing the already-fetched raw_data, so a database missing one of these optional tables will raise sqlite3.OperationalError instead of being skipped gracefully (as _fetch_data intends).

🛡️ Proposed fix: reuse fetched rows instead of re-querying
 def _load_lifetime_tech(
     self,
     data: dict[str, object],
     raw_data: Sequence[tuple[object, ...]],
     filtered_data: Sequence[tuple[object, ...]],
 ) -> None:
     """Loads the lifetime_tech component."""
     model = self.model
-    cur = self.con.cursor()
-    rows_to_load = cur.execute('SELECT region, tech, lifetime FROM lifetime_tech').fetchall()
+    rows_to_load = list(raw_data)
     rt_getter = itemgetter(0, 1)
     if self.viable_rt:
         valid_rt = self.viable_rt.members | self.viable_existing_rt
         rows_to_load = [item for item in rows_to_load if rt_getter(item) in valid_rt]
     self._load_component_data(data, model.lifetime_tech, rows_to_load)

Apply the analogous change (filtering raw_data/filtered_data by mi.last_demand_year in Python) to _load_lifetime_process and _load_lifetime_survival_curve.

🤖 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 697 - 764, Update
_load_lifetime_tech, _load_lifetime_process, and _load_lifetime_survival_curve
to stop querying SQLite directly and instead use the rows supplied through
raw_data or filtered_data by _fetch_data. Preserve the existing
viable_rt/viable_rtv filtering, and apply the myopic_index last_demand_year
cutoff in Python for process and survival-curve rows so missing optional tables
are skipped safely.

Source: Linters/SAST tools

temoa/data_io/loader_manifest.py (1)

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

Docstring could still clarify the single-trailing-value case.

The underlying data-corruption bug this comment referenced was fixed in hybrid_loader.py (reshape only triggers when there are 2+ trailing columns), so this is now purely a documentation clarity nit rather than a correctness issue.

Based on a previous review comment on this file recommending the docstring "state that index_length must leave more than one trailing value column."

🤖 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/loader_manifest.py` around lines 35 - 36, Update the
`index_length` parameter documentation in the loader manifest docstring to
explicitly state that it must leave more than one trailing value column,
clarifying that a single trailing value column does not require this setting.
Preserve the existing explanation about index columns and multi-value-column
tables.
🤖 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 95-99: Update the growth-rates documentation to reference every
registered constraint rule using its actual `*_constraint_rule` symbol from
`temoa.extensions.growth_rates.core.model`; replace the three incorrect
non-degrowth autodoc targets and add entries for all three corresponding
degrowth constraints, covering all six registered rules.

In `@temoa/extensions/framework.py`:
- Around line 159-167: Guard the interactive prompt in the missing-table
handling logic so non-interactive execution cannot crash. In the block
controlled by `if not silent` within the relevant extension validation function,
either check `sys.stdin.isatty()` before calling `input()` or catch `EOFError`;
on non-interactive input or EOF, leave `should_apply` as `False` so the existing
missing-tables `RuntimeError` path executes.

In `@temoa/extensions/growth_rates/components/growth_new_capacity_delta.py`:
- Around line 78-88: Update the constraint docstring in the growth
capacity-delta component so its Θ set references use the actual parameter names:
replace the growth set with Θ_limit_growth_new_capacity_delta and the degrowth
set with Θ_limit_degrowth_new_capacity_delta, preserving the surrounding
equations.

In `@temoa/extensions/growth_rates/components/growth_new_capacity.py`:
- Around line 76-84: Update the mathematical docstring in the growth-capacity
constraint documentation to replace the incorrect set names
limit_growth_capacity and limit_degrowth_capacity with the actual parameter set
names limit_growth_new_capacity and limit_degrowth_new_capacity, without
changing the constraint logic.

---

Outside diff comments:
In `@temoa/data_io/component_manifest.py`:
- Around line 26-39: The docstring for build_manifest does not document the
extension_ids parameter. Add an Args entry describing extension_ids, including
its purpose and the behavior when it is None, while preserving the existing
documentation.

---

Duplicate comments:
In `@temoa/data_io/hybrid_loader.py`:
- Around line 659-764: Replace the throwaway TemoaModel() instances in
_load_retired_existing_capacity, _load_lifetime_tech, _load_lifetime_process,
and _load_lifetime_survival_curve with self.model, using the corresponding
component attributes when calling _load_component_data. Preserve the existing
query and filtering behavior while relying on the extension-aware model already
attached to the loader.
- Around line 697-764: Update _load_lifetime_tech, _load_lifetime_process, and
_load_lifetime_survival_curve to stop querying SQLite directly and instead use
the rows supplied through raw_data or filtered_data by _fetch_data. Preserve the
existing viable_rt/viable_rtv filtering, and apply the myopic_index
last_demand_year cutoff in Python for process and survival-curve rows so missing
optional tables are skipped safely.

In `@temoa/data_io/loader_manifest.py`:
- Around line 35-36: Update the `index_length` parameter documentation in the
loader manifest docstring to explicitly state that it must leave more than one
trailing value column, clarifying that a single trailing value column does not
require this setting. Preserve the existing explanation about index columns and
multi-value-column tables.

In `@temoa/extensions/framework.py`:
- Around line 186-195: In _table_has_rows, defensively quote the interpolated
table_name identifier in the SELECT query, using SQLite identifier-quoting with
proper escaping, while preserving the existing sqlite_master existence check and
row-existence behavior.

In `@temoa/extensions/template/tables.sql`:
- Around line 6-14: Update the example_new_capacity_limit table definition so
both composite primary-key columns, region and tech_or_group, are explicitly
declared NOT NULL before the PRIMARY KEY constraint.

In `@temoa/tutorial_assets/config_sample.toml`:
- Around line 21-26: Update the extensions example comment near the extensions
configuration to remove the unregistered “unit_commitment” identifier and
reference only the supported “growth_rates” extension, matching
get_known_extension_specs().

In `@tests/conftest.py`:
- Around line 61-67: Add a `PRAGMA foreign_key_check` validation immediately
after the extension schemas and fixture data are loaded in the test database
setup, using the connection variable `con`; ensure violations raise or fail
setup rather than allowing an invalid database to proceed.

In `@tests/testing_configs/config_myopic_capacities.toml`:
- Around line 4-5: Update config_myopic_capacities.toml so input_database and
output_database reference distinct SQLite files, preventing MyopicSequencer runs
from mutating the shared input fixture and leaking state across parametrized
tests.
🪄 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: 08b439ca-57b0-47d4-84f3-df4bf45d4394

📥 Commits

Reviewing files that changed from the base of the PR and between 6dabfb1 and 1fbc8c8.

📒 Files selected for processing (51)
  • docs/source/computational_implementation.rst
  • docs/source/extensions.rst
  • docs/source/extensions/growth_rates.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/limits.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/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/test_cli.py
  • tests/testing_configs/config_myopic_capacities.toml
  • tests/testing_data/mediumville_sets.json
  • tests/testing_data/test_system_sets.json
  • tests/testing_data/utopia_sets.json
  • tests/testing_data/utopia_v3.sql
💤 Files with no reviewable changes (3)
  • temoa/db_schema/temoa_schema_v4.sql
  • docs/source/mathematical_formulation.rst
  • docs/source/param_desc_and_tables.rst

Comment thread docs/source/extensions/growth_rates.rst
Comment thread temoa/extensions/framework.py
Comment thread temoa/extensions/growth_rates/components/growth_new_capacity.py
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