Skip to content

typing first pass#165

Closed
ParticularlyPythonicBS wants to merge 29 commits intotemoa_davey_codefrom
mypy_typing
Closed

typing first pass#165
ParticularlyPythonicBS wants to merge 29 commits intotemoa_davey_codefrom
mypy_typing

Conversation

@ParticularlyPythonicBS
Copy link
Member

@ParticularlyPythonicBS ParticularlyPythonicBS commented Oct 17, 2025

this was quite an undertaking, core files are all more or less fully typed now, there may be better ways to type some of these things that are to be discovered in later passes.
Adds a typing sub directory for collecting defined types and stubbing out bits of pyomo and pandas.
Ideally would want to get the typing even stricter but even the current config gave 14k errors when I started so this will take some iterating.

Summary by CodeRabbit

  • Chores
    • Updated development tooling and dependencies to latest versions.
    • Enhanced code quality infrastructure with stricter type checking and validation rules.
    • Refined project configuration for improved developer experience.

@coderabbitai
Copy link

coderabbitai bot commented Oct 17, 2025

Walkthrough

This PR adds type annotations to function signatures and module-level variables across the codebase, reorganizes development dependencies from requirements-dev.txt to pyproject.toml with a dedicated [dependency-groups] dev block, introduces comprehensive mypy configuration with strictness settings targeting Python 3.12, updates pre-commit hook versions, and adds a stubs directory for type stubs.

Changes

Cohort / File(s) Summary
Type Annotations
definitions.py, main.py
Added type hints to variables and function signatures; OUTPUT_PATH now annotated as Path | None; get_OUTPUT_PATH() returns Path; set_OUTPUT_PATH() returns None; runModelUI() parameter annotated as str with -> None return type; setup_logging() parameter debug_level annotated as bool with -> None return type
Pre-commit and Tool Configuration
.pre-commit-config.yaml, .coderabbit.yaml
Bumped hook versions (uv-pre-commit 0.8.0→0.9.3, pre-commit-hooks v5.0.0→v6.0.0, ruff v0.12.4→v0.14.1); added pyupgrade v3.21.0 hook with --py312-plus argument; excluded stubs/** from code review path filters
Project Configuration and Dependencies
pyproject.toml, requirements-dev.txt
Migrated dev dependencies to [dependency-groups] dev block in pyproject.toml including gurobipy, types-deprecated, ruff, pre-commit, pytest, pytest-cov, and pandas-stubs; removed dev entries from requirements-dev.txt; added extensive [tool.mypy] configuration targeting Python 3.12 with strict type checking for core modules (temoa.core.\, temoa.components.\, etc.) and module-specific overrides for external dependencies and tests
Version Control
.gitignore
Added unignore rule for stubs/ directory

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

The changes involve straightforward type annotation additions and dependency reorganization, but the comprehensive mypy configuration in pyproject.toml requires careful review of multiple strictness settings and per-module overrides to ensure alignment with project standards.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "typing first pass" accurately describes the primary changes in the changeset, which center on adding comprehensive type annotations to core project files, configuring mypy for strict type checking, and creating a typing subdirectory with type stubs. The title is concise, clear, and avoids noise; a teammate scanning the git history would readily understand that this PR focuses on implementing typing improvements. While the title could be slightly more specific (such as specifying which files or the scope of changes), it conveys meaningful information about the changeset and is not misleading or overly vague like generic filler terms would be.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mypy_typing

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 46

Caution

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

⚠️ Outside diff range comments (24)
temoa/components/reserves.py (2)

141-150: Fix variable shadowing in exchange-tech loop (incorrect cross-product).

Inner for t in M.tech_reserve overwrites (t, v) from the previous loop, producing wrong combinations. Filter instead of cross-multiplying.

Apply this diff:

-        available += sum(
-            M.V_Capacity[r1r2, p, t, v]
-            * value(M.ReserveCapacityDerate[r, p, s, t, v])
-            * value(M.CapacityFactorProcess[r, p, s, d, t, v])
-            * value(M.CapacityToActivity[r1r2, t])
-            * value(M.SegFrac[p, s, d])
-            for (t, v) in M.processReservePeriods[r1r2, p]
-            for t in M.tech_reserve
-        )
+        available += sum(
+            M.V_Capacity[r1r2, p, t, v]
+            * value(M.ReserveCapacityDerate[r, p, s, t, v])
+            * value(M.CapacityFactorProcess[r, p, s, d, t, v])
+            * value(M.CapacityToActivity[r1r2, t])
+            * value(M.SegFrac[p, s, d])
+            for (t, v) in M.processReservePeriods[r1r2, p]
+            if t in M.tech_reserve
+        )

216-224: Same shadowing bug in Static variant.

Identical issue as Dynamic: avoid cross-product by filtering.

Apply this diff:

-        available += sum(
-            value(M.CapacityCredit[r1r2, p, t, v])
-            * M.V_Capacity[r1r2, p, t, v]
-            * value(M.CapacityToActivity[r1r2, t])
-            * value(M.SegFrac[p, s, d])
-            for (t, v) in M.processReservePeriods[r1r2, p]
-            for t in M.tech_reserve
-        )
+        available += sum(
+            value(M.CapacityCredit[r1r2, p, t, v])
+            * M.V_Capacity[r1r2, p, t, v]
+            * value(M.CapacityToActivity[r1r2, t])
+            * value(M.SegFrac[p, s, d])
+            for (t, v) in M.processReservePeriods[r1r2, p]
+            if t in M.tech_reserve
+        )
temoa/_internal/table_writer.py (4)

169-175: Docstring param mismatch

Docstring refers to “M: solved model”, but function takes brick: DataBrick. Update the docstring.

-    tailored write function to capture appropriate monte carlo results
-    :param M: solve model
+    Tailored write function to capture appropriate Monte Carlo results
+    :param brick: DataBrick holding results

261-274: Batch objective inserts and single commit

Avoid per-row commit; use executemany for performance.

-        for obj_name, obj_value in obj_vals:
-            qry = 'INSERT INTO OutputObjective VALUES (?, ?, ?)'
-            data = (scenario_name, obj_name, obj_value)
-            self.con.execute(qry, data)
-            self.con.commit()
+        qry = 'INSERT INTO OutputObjective VALUES (?, ?, ?)'
+        rows = [(scenario_name, obj, val) for (obj, val) in obj_vals]
+        self.con.executemany(qry, rows)
+        self.con.commit()

409-458: Include zero-valued OUT flows or test non-None explicitly

if flow_out_value: skips zeros. If the intent is to aggregate even zero outputs and filter later by epsilon, check for is not None.

-            flow_out_value = self.flow_register[fi].get(FlowType.OUT, None)
-            if flow_out_value:
+            flow_out_value = self.flow_register[fi].get(FlowType.OUT)
+            if flow_out_value is not None:

651-661: Specify file encoding

Be explicit to avoid platform defaults.

-        with open(script_file, 'r') as table_script:
+        with open(script_file, 'r', encoding='utf-8') as table_script:
temoa/components/technology.py (4)

411-423: Blocker: p used before assignment (NameError)

num_seg = len(M.TimeSeason[p]) * len(M.time_of_day) uses p before it’s defined. Compute inside the loop.

-    num_seg = len(M.TimeSeason[p]) * len(M.time_of_day)
-    for (r, p, i, t, v, o), count in count_rpitvo.items():
+    for (r, p, i, t, v, o), count in count_rpitvo.items():
+        num_seg = len(M.TimeSeason[p]) * len(M.time_of_day)
         if count > 0:
             M.isEfficiencyVariable[r, p, i, t, v, o] = True
             if count < num_seg:
                 logger.info(
                     'Some but not all EfficiencyVariable values were set (%i out of a possible %i) for: %s'
                     ' Missing values will default to value set in Efficiency table.',
                     count,
                     num_seg,
                     (r, p, i, t, v, o),
                 )

118-120: Return float literal

Return 1.0 to satisfy the annotated float return type.

-        return 1
+        return 1.0

58-61: Use set comprehension

Slightly cleaner and faster.

-    indices = set((r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys())
+    indices = {(r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()}

347-361: Prefer set comprehension over generator-to-set

Nit: simplify to a set comprehension.

-        symdiff_str: set[str] = set(str(i) for i in symdiff)
+        symdiff_str: set[str] = {str(i) for i in symdiff}
 ...
-        symdiff_str2: set[str] = set(str(i) for i in symdiff)
+        symdiff_str2: set[str] = {str(i) for i in symdiff}
temoa/_internal/exchange_tech_cost_ledger.py (1)

41-53: Fix link split validation

The current condition if not r1 and r2: only catches an empty exporter. It misses cases where the importer is empty. Since both regions are required for dictionary keys and downstream method calls, the validation should catch either being empty.

-        r1, r2 = link.split('-')
-        if not r1 and r2:
+        r1, r2 = link.split('-')
+        if not r1 or not r2:
             raise ValueError(f'problem splitting region-region: {link}')

Consider adding unit tests for these edge cases (e.g., '-B', 'A-', '-').

temoa/components/costs.py (1)

690-695: Drop quotes in annotations; keep return type consistent

Remove redundant quotes per UP037. The body returns a numeric (pv_to_annuity of concrete values), so float is fine.

-def ParamLoanAnnualize_rule(M: 'TemoaModel', r: str, t: str, v: int) -> float:
+def ParamLoanAnnualize_rule(M: TemoaModel, r: str, t: str, v: int) -> float:
temoa/_internal/run_actions.py (1)

60-71: Optional: clearer version check

Current comparison works but is non-obvious. Consider comparing only major/minor explicitly.

-def check_python_version(min_major: int, min_minor: int) -> bool:
-    if (min_major, min_minor) >= version_info:
+def check_python_version(min_major: int, min_minor: int) -> bool:
+    if (version_info.major, version_info.minor) < (min_major, min_minor):
         logger.error(
             'Model is being run with python %d.%d.  Expecting version %d.%d or later.  ',
             version_info.major,
             version_info.minor,
             min_major,
             min_minor,
         )
         return False
     return True
temoa/_internal/table_data_puller.py (1)

591-599: Use numeric values for emissions flows/costs; avoid Pyomo expressions in abs/conditionals

Multiplying floats by Params without value() yields Pyomo expressions; later abs(...) and comparisons will break. Wrap Params in value(...) and compute costs numerically.

-    for r, p, e, s, d, i, t, v, o in normal:
-        flows[EI(r, p, t, v, e)] += (
-            value(M.V_FlowOut[r, p, s, d, i, t, v, o]) * M.EmissionActivity[r, e, i, t, v, o]
-        )
+    for r, p, e, s, d, i, t, v, o in normal:
+        flows[EI(r, p, t, v, e)] += (
+            value(M.V_FlowOut[r, p, s, d, i, t, v, o]) * value(M.EmissionActivity[r, e, i, t, v, o])
+        )
-    for r, p, e, i, t, v, o in annual:
-        flows[EI(r, p, t, v, e)] += (
-            value(M.V_FlowOutAnnual[r, p, i, t, v, o]) * M.EmissionActivity[r, e, i, t, v, o]
-        )
+    for r, p, e, i, t, v, o in annual:
+        flows[EI(r, p, t, v, e)] += (
+            value(M.V_FlowOutAnnual[r, p, i, t, v, o]) * value(M.EmissionActivity[r, e, i, t, v, o])
+        )
-        undiscounted_emiss_cost = (
-            flows[ei] * M.CostEmission[ei.r, ei.p, ei.e] * M.PeriodLength[ei.p]
-        )
+        undiscounted_emiss_cost = (
+            flows[ei] * value(M.CostEmission[ei.r, ei.p, ei.e]) * value(M.PeriodLength[ei.p])
+        )
-        discounted_emiss_cost = costs.fixed_or_variable_cost(
+        discounted_emiss_cost = costs.fixed_or_variable_cost(
             cap_or_flow=flows[ei],
-            cost_factor=M.CostEmission[ei.r, ei.p, ei.e],
-            cost_years=M.PeriodLength[ei.p],
+            cost_factor=value(M.CostEmission[ei.r, ei.p, ei.e]),
+            cost_years=value(M.PeriodLength[ei.p]),
             GDR=GDR,
             P_0=p_0,
             p=ei.p,
         )
-        undiscounted_emiss_cost = (
-            embodied_flows[ei]
-            * M.CostEmission[ei.r, ei.v, ei.e]
-            * M.PeriodLength[ei.v]  # treat as fixed cost distributed over construction period
-        )
+        undiscounted_emiss_cost = (
+            embodied_flows[ei]
+            * value(M.CostEmission[ei.r, ei.v, ei.e])
+            * value(M.PeriodLength[ei.v])  # treat as fixed cost distributed over construction period
+        )
-        discounted_emiss_cost = costs.fixed_or_variable_cost(
+        discounted_emiss_cost = costs.fixed_or_variable_cost(
             cap_or_flow=embodied_flows[ei],
-            cost_factor=M.CostEmission[ei.r, ei.v, ei.e],
-            cost_years=M.PeriodLength[
+            cost_factor=value(M.CostEmission[ei.r, ei.v, ei.e]),
+            cost_years=value(M.PeriodLength[
                 ei.v
-            ],  # treat as fixed cost distributed over construction period
+            ]),  # treat as fixed cost distributed over construction period
             GDR=GDR,
             P_0=p_0,
             p=ei.v,
         )
-            eol_flows[EI(r, p, t, v, e)] += value(
-                M.V_AnnualRetirement[r, p, t, v] * M.EmissionEndOfLife[r, e, t, v]
-            )  # for eol costs
-            flows[EI(r, p, t, v, e)] += value(
-                M.V_AnnualRetirement[r, p, t, v] * M.EmissionEndOfLife[r, e, t, v]
-            )  # add eol to process emissions
+            eol_flows[EI(r, p, t, v, e)] += value(M.V_AnnualRetirement[r, p, t, v]) * value(
+                M.EmissionEndOfLife[r, e, t, v]
+            )  # for eol costs
+            flows[EI(r, p, t, v, e)] += value(M.V_AnnualRetirement[r, p, t, v]) * value(
+                M.EmissionEndOfLife[r, e, t, v]
+            )  # add eol to process emissions
-        undiscounted_emiss_cost = (
-            eol_flows[ei]
-            * M.CostEmission[ei.r, ei.p, ei.e]
-            * M.PeriodLength[ei.p]  # treat as fixed cost distributed over retirement period
-        )
+        undiscounted_emiss_cost = (
+            eol_flows[ei]
+            * value(M.CostEmission[ei.r, ei.p, ei.e])
+            * value(M.PeriodLength[ei.p])  # treat as fixed cost distributed over retirement period
+        )
-        discounted_emiss_cost = costs.fixed_or_variable_cost(
+        discounted_emiss_cost = costs.fixed_or_variable_cost(
             cap_or_flow=eol_flows[ei],
-            cost_factor=M.CostEmission[ei.r, ei.p, ei.e],
-            cost_years=M.PeriodLength[
+            cost_factor=value(M.CostEmission[ei.r, ei.p, ei.e]),
+            cost_years=value(M.PeriodLength[
                 ei.p
-            ],  # treat as fixed cost distributed over retirement period
+            ]),  # treat as fixed cost distributed over retirement period
             GDR=GDR,
             P_0=p_0,
             p=ei.p,
         )

Additionally, remove unused parameters to silence ARG001:

-def loan_costs(
+def loan_costs(
     loan_rate: float,  # this is referred to as LoanRate in parameters
@@
-    vintage: int,
-    **kwargs: float,
+    vintage: int,
 ) -> tuple[float, float]:
-def loan_costs_survival_curve(
+def loan_costs_survival_curve(
     M: TemoaModel,
@@
-    vintage: int,
-    **kwargs: float,
+    vintage: int,
 ) -> tuple[float, float]:

Also applies to: 595-599, 612-621, 655-664, 678-683, 700-709, 714-719

temoa/components/operations.py (6)

30-37: Tighten index builder return types

Return precise tuples instead of Any to improve typing quality.

-def BaseloadDiurnalConstraintIndices(M: 'TemoaModel') -> set[tuple[Any, Any, Any, Any, Any, Any]]:
+def BaseloadDiurnalConstraintIndices(
+    M: 'TemoaModel',
+) -> set[tuple['Region', 'Period', 'Season', 'TimeOfDay', 'Technology', 'Vintage']]:
@@
-def RampUpDayConstraintIndices(M: 'TemoaModel') -> set[tuple[Any, Any, Any, Any, Any, Any]]:
+def RampUpDayConstraintIndices(
+    M: 'TemoaModel',
+) -> set[tuple['Region', 'Period', 'Season', 'TimeOfDay', 'Technology', 'Vintage']]:
@@
-def RampDownDayConstraintIndices(M: 'TemoaModel') -> set[tuple[Any, Any, Any, Any, Any, Any]]:
+def RampDownDayConstraintIndices(
+    M: 'TemoaModel',
+) -> set[tuple['Region', 'Period', 'Season', 'TimeOfDay', 'Technology', 'Vintage']]:

(If literal types are preferred, import the aliases under TYPE_CHECKING as done elsewhere.)

Also applies to: 42-49, 54-61


66-85: Indices seasonal functions: refine return type

These return either Set.Skip or a set of 6-tuples. Annotate accordingly to avoid Any, e.g., set[...] | object.

-def RampUpSeasonConstraintIndices(M: 'TemoaModel') -> Any:
+def RampUpSeasonConstraintIndices(
+    M: 'TemoaModel',
+) -> set[tuple['Region', 'Period', 'Season', 'Season', 'Technology', 'Vintage']] | object:
@@
-def RampDownSeasonConstraintIndices(M: 'TemoaModel') -> Any:
+def RampDownSeasonConstraintIndices(
+    M: 'TemoaModel',
+) -> set[tuple['Region', 'Period', 'Season', 'Season', 'Technology', 'Vintage']] | object:

Keeps compatibility with Set.Skip while removing Any.

Also applies to: 87-105


292-311: RampUpDay: use correct denominator for next slice

hourly_activity_sd_next should be divided by the next slice’s hours, not the current slice’s.

-    hourly_activity_sd_next = (
-        sum(
-            M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
-            for S_i in M.processInputs[r, p, t, v]
-            for S_o in M.processOutputsByInput[r, p, t, v, S_i]
-        )
-        / hours_adjust
-    )
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
+    hourly_activity_sd_next = (
+        sum(
+            M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
+            for S_i in M.processInputs[r, p, t, v]
+            for S_o in M.processOutputsByInput[r, p, t, v, S_i]
+        )
+        / hours_adjust_next
+    )

375-394: RampDownDay: use correct denominator for next slice

Same denominator issue here.

-    hourly_activity_sd_next = (
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )

442-461: RampUpSeason: use correct denominator for next seasonal slice

-    hourly_activity_sd_next = (
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )

521-528: RampDownSeason: use correct denominator for next seasonal slice

-    hourly_activity_sd_next = (
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )
temoa/components/commodities.py (1)

50-52: Raise specific exceptions, not bare Exception.

Use ValueError for invalid/unsatisfiable constraint construction.

-        raise Exception(msg.format(c, r, p, s, d, expr))
+        raise ValueError(msg.format(c, r, p, s, d, expr))
@@
-        raise Exception(msg.format(c, r, p, expr))
+        raise ValueError(msg.format(c, r, p, expr))
@@
-        raise Exception(msg.format(dem, r, p))
+        raise ValueError(msg.format(dem, r, p))

Also applies to: 74-75, 88-90

temoa/components/limits.py (3)

820-826: Bug: wrong region key in retirement emissions filter (r vs reg).

This misindexes retirementPeriods, potentially missing or miscounting EOL emissions.

-    retirement_emissions = sum(
-        M.V_AnnualRetirement[reg, p, t, v] * value(M.EmissionEndOfLife[reg, e, t, v])
-        for reg in regions
-        for (S_r, S_e, t, v) in M.EmissionEndOfLife.sparse_iterkeys()
-        if (r, t, v) in M.retirementPeriods and p in M.retirementPeriods[r, t, v]
-        if S_r == reg and S_e == e
-    )
+    retirement_emissions = sum(
+        M.V_AnnualRetirement[reg, p, t, v] * value(M.EmissionEndOfLife[reg, e, t, v])
+        for reg in regions
+        for (S_r, S_e, t, v) in M.EmissionEndOfLife.sparse_iterkeys()
+        if (reg, t, v) in M.retirementPeriods and p in M.retirementPeriods[reg, t, v]
+        if S_r == reg and S_e == e
+    )

615-622: Bug: denominator and outputs-by-input use i instead of S_i in Average Input Split.

This skews the total input calculation. Use S_i consistently.

-    total_inp = sum(
-        M.V_FlowOut[r, p, S_s, S_d, S_i, t, v, S_o]
-        / get_variable_efficiency(M, r, p, S_s, S_d, i, t, v, S_o)
-        for S_s in M.TimeSeason[p]
-        for S_d in M.time_of_day
-        for S_i in M.processInputs[r, p, t, v]
-        for S_o in M.processOutputsByInput[r, p, t, v, i]
-    )
+    total_inp = sum(
+        M.V_FlowOut[r, p, S_s, S_d, S_i, t, v, S_o]
+        / get_variable_efficiency(M, r, p, S_s, S_d, S_i, t, v, S_o)
+        for S_s in M.TimeSeason[p]
+        for S_d in M.time_of_day
+        for S_i in M.processInputs[r, p, t, v]
+        for S_o in M.processOutputsByInput[r, p, t, v, S_i]
+    )

199-217: Avoid potential KeyError when processReservePeriods[(r,p)] missing.

Use .get to guard legacy/edge cases in RPS check.

-    total_inp = sum(
+    total_inp = sum(
         M.V_FlowOut[r, p, s, d, S_i, t, v, S_o]
-        for (t, v) in M.processReservePeriods[r, p]
+        for (t, v) in M.processReservePeriods.get((r, p), [])
         for s in M.TimeSeason[p]
         for d in M.time_of_day
         for S_i in M.processInputs[r, p, t, v]
         for S_o in M.processOutputsByInput[r, p, t, v, S_i]
     )
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eff7cba and c4f0094.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (40)
  • .pre-commit-config.yaml (3 hunks)
  • definitions.py (1 hunks)
  • main.py (2 hunks)
  • pyproject.toml (1 hunks)
  • requirements-dev.txt (1 hunks)
  • temoa/_internal/__init__.py (1 hunks)
  • temoa/_internal/data_brick.py (3 hunks)
  • temoa/_internal/exchange_tech_cost_ledger.py (5 hunks)
  • temoa/_internal/run_actions.py (7 hunks)
  • temoa/_internal/table_data_puller.py (13 hunks)
  • temoa/_internal/table_writer.py (21 hunks)
  • temoa/_internal/temoa_sequencer.py (7 hunks)
  • temoa/components/capacity.py (13 hunks)
  • temoa/components/commodities.py (14 hunks)
  • temoa/components/costs.py (12 hunks)
  • temoa/components/emissions.py (4 hunks)
  • temoa/components/flows.py (4 hunks)
  • temoa/components/geography.py (5 hunks)
  • temoa/components/limits.py (34 hunks)
  • temoa/components/operations.py (13 hunks)
  • temoa/components/reserves.py (6 hunks)
  • temoa/components/storage.py (10 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/components/time.py (10 hunks)
  • temoa/components/utils.py (3 hunks)
  • temoa/core/config.py (7 hunks)
  • temoa/core/model.py (9 hunks)
  • temoa/data_io/hybrid_loader.py (25 hunks)
  • temoa/data_io/loader_manifest.py (3 hunks)
  • temoa/temoa_model/temoa_model.py (9 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/pandas_stubs.pyi (1 hunks)
  • temoa/types/pyomo_stubs.pyi (1 hunks)
  • temoa/utilities/__init__.py (0 hunks)
  • temoa/utilities/clear_db_outputs.py (2 hunks)
  • temoa/utilities/db_migration_to_v3.py (5 hunks)
  • temoa/utilities/db_migration_v3_to_v3_1.py (3 hunks)
  • temoa/utilities/set_spitter.py (2 hunks)
  • temoa/utilities/unit_cost_explorer.py (2 hunks)
💤 Files with no reviewable changes (1)
  • temoa/utilities/init.py
🧰 Additional context used
🧬 Code graph analysis (20)
temoa/components/utils.py (2)
temoa/extensions/monte_carlo/mc_run.py (1)
  • model (204-211)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/_internal/temoa_sequencer.py (5)
temoa/extensions/myopic/myopic_sequencer.py (2)
  • MyopicSequencer (55-578)
  • start (163-307)
temoa/extensions/method_of_morris/morris_sequencer.py (1)
  • start (154-216)
temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1)
  • start (146-340)
temoa/extensions/monte_carlo/mc_sequencer.py (1)
  • start (88-282)
temoa/extensions/single_vector_mga/sv_mga_sequencer.py (1)
  • start (78-192)
temoa/_internal/exchange_tech_cost_ledger.py (3)
temoa/types/pyomo_stubs.pyi (3)
  • value (37-37)
  • value (46-46)
  • value (70-70)
temoa/types/model_types.py (1)
  • TemoaModel (161-235)
tests/utilities/namespace_mock.py (1)
  • Namespace (30-32)
temoa/components/flows.py (1)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/temoa_model/temoa_model.py (1)
temoa/components/limits.py (3)
  • LimitGrowthCapacity (856-955)
  • LimitGrowthNewCapacity (970-1068)
  • LimitGrowthNewCapacityDelta (1085-1208)
temoa/_internal/run_actions.py (2)
temoa/core/config.py (1)
  • TemoaConfig (32-261)
temoa/types/model_types.py (1)
  • TemoaModel (161-235)
temoa/_internal/data_brick.py (3)
temoa/_internal/exchange_tech_cost_ledger.py (1)
  • CostType (22-30)
temoa/_internal/table_data_puller.py (5)
  • poll_capacity_results (67-111)
  • poll_cost_results (284-447)
  • poll_emissions (549-721)
  • poll_flow_results (114-226)
  • poll_objective (271-281)
temoa/types/model_types.py (2)
  • TemoaModel (161-235)
  • FlowType (292-299)
temoa/types/model_types.py (1)
temoa/types/pyomo_stubs.pyi (7)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoVar (41-48)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
temoa/utilities/set_spitter.py (1)
temoa/extensions/monte_carlo/mc_run.py (1)
  • model (204-211)
temoa/components/commodities.py (1)
temoa/components/time.py (1)
  • get_str_padding (111-112)
temoa/components/technology.py (1)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/data_io/hybrid_loader.py (3)
temoa/extensions/myopic/myopic_index.py (1)
  • MyopicIndex (37-57)
temoa/data_io/loader_manifest.py (1)
  • LoadItem (19-52)
temoa/model_checking/element_checker.py (1)
  • members (126-131)
temoa/utilities/unit_cost_explorer.py (3)
temoa/components/costs.py (1)
  • TotalCost_rule (503-637)
temoa/components/storage.py (1)
  • StorageEnergyUpperBound_Constraint (206-260)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/components/time.py (1)
temoa/components/commodities.py (1)
  • get_str_padding (757-758)
temoa/components/capacity.py (1)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/core/model.py (2)
temoa/types/pyomo_stubs.pyi (1)
  • AbstractModel (12-20)
temoa/components/limits.py (3)
  • LimitGrowthCapacity (856-955)
  • LimitGrowthNewCapacity (970-1068)
  • LimitGrowthNewCapacityDelta (1085-1208)
temoa/_internal/table_writer.py (4)
temoa/core/config.py (1)
  • TemoaConfig (32-261)
temoa/_internal/data_brick.py (2)
  • DataBrick (21-76)
  • flow_data (59-60)
temoa/_internal/table_data_puller.py (2)
  • poll_objective (271-281)
  • poll_capacity_results (67-111)
temoa/extensions/myopic/myopic_sequencer.py (1)
  • execute_script (506-515)
temoa/_internal/table_data_puller.py (3)
temoa/types/model_types.py (2)
  • FlowType (292-299)
  • TemoaModel (161-235)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/_internal/exchange_tech_cost_ledger.py (1)
  • CostType (22-30)
temoa/utilities/db_migration_v3_to_v3_1.py (2)
temoa/extensions/monte_carlo/mc_run.py (1)
  • model (204-211)
temoa/core/model.py (1)
  • TemoaModel (96-1134)
temoa/types/__init__.py (1)
temoa/types/pandas_stubs.pyi (1)
  • DataFrame (20-58)
🪛 Ruff (0.14.0)
main.py

165-165: Boolean-typed positional argument in function definition

(FBT001)


165-165: Boolean default positional argument in function definition

(FBT002)

definitions.py

22-22: Avoid specifying long messages outside the exception class

(TRY003)

temoa/core/config.py

156-156: Boolean-typed positional argument in function definition

(FBT001)


156-156: Boolean default positional argument in function definition

(FBT002)

temoa/components/utils.py

30-30: Dynamically typed expressions (typing.Any) are disallowed in operator_expression

(ANN401)

temoa/_internal/temoa_sequencer.py

76-76: Unused method argument: kwargs

(ARG002)

temoa/components/reserves.py

46-46: Dynamically typed expressions (typing.Any) are disallowed in ReserveMarginDynamic

(ANN401)


155-155: Dynamically typed expressions (typing.Any) are disallowed in ReserveMarginStatic

(ANN401)


234-234: Dynamically typed expressions (typing.Any) are disallowed in ReserveMargin_Constraint

(ANN401)

temoa/components/flows.py

13-13: typing.Set is deprecated, use set instead

(UP035)

temoa/utilities/db_migration_to_v3.py

198-198: Possible SQL injection vector through string-based query construction

(S608)

temoa/temoa_model/temoa_model.py

113-113: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


114-114: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


115-115: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


131-131: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


141-141: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


144-144: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


147-147: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


150-150: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


153-153: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


155-155: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


156-156: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


157-157: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


158-158: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


159-159: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


161-161: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


163-163: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


165-165: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


169-169: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


170-170: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


171-171: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


172-172: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


173-173: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


174-174: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


175-175: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


176-176: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


177-177: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


179-179: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


180-180: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


184-184: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


187-187: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


199-199: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


202-202: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


205-205: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


1137-1137: Unused function argument: M

(ARG001)

temoa/_internal/run_actions.py

134-134: Boolean-typed positional argument in function definition

(FBT001)


134-134: Boolean default positional argument in function definition

(FBT002)


135-135: Boolean-typed positional argument in function definition

(FBT001)


135-135: Boolean default positional argument in function definition

(FBT002)


217-217: Boolean-typed positional argument in function definition

(FBT001)


217-217: Boolean default positional argument in function definition

(FBT002)


359-359: Boolean-typed positional argument in function definition

(FBT001)


359-359: Boolean default positional argument in function definition

(FBT002)

temoa/components/costs.py

35-35: Remove quotes from type annotation

Remove quotes

(UP037)


35-35: Dynamically typed expressions (typing.Any) are disallowed in *_

(ANN401)


87-87: Remove quotes from type annotation

Remove quotes

(UP037)


96-96: Remove quotes from type annotation

Remove quotes

(UP037)


103-103: Remove quotes from type annotation

Remove quotes

(UP037)


109-109: Remove quotes from type annotation

Remove quotes

(UP037)


302-302: Remove quotes from type annotation

Remove quotes

(UP037)


302-302: Dynamically typed expressions (typing.Any) are disallowed in PeriodCost_rule

(ANN401)


503-503: Remove quotes from type annotation

Remove quotes

(UP037)


503-503: Dynamically typed expressions (typing.Any) are disallowed in TotalCost_rule

(ANN401)


646-646: Remove quotes from type annotation

Remove quotes

(UP037)


690-690: Remove quotes from type annotation

Remove quotes

(UP037)

temoa/types/model_types.py

10-18: typing.Dict is deprecated, use dict instead

(UP035)


10-18: typing.Set is deprecated, use set instead

(UP035)


10-18: typing.Tuple is deprecated, use tuple instead

(UP035)


156-156: Dynamically typed expressions (typing.Any) are disallowed in *args

(ANN401)


156-156: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


235-235: Dynamically typed expressions (typing.Any) are disallowed in *args

(ANN401)


235-235: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


248-248: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)


253-284: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/components/geography.py

14-14: Import from collections.abc instead: Iterable

Import from collections.abc

(UP035)


82-82: Dynamically typed expressions (typing.Any) are disallowed in RegionalExchangeCapacity_Constraint

(ANN401)

temoa/utilities/set_spitter.py

13-13: Boolean-typed positional argument in function definition

(FBT001)


13-13: Boolean default positional argument in function definition

(FBT002)

temoa/components/commodities.py

32-32: Dynamically typed expressions (typing.Any) are disallowed in supplied

(ANN401)


32-32: Dynamically typed expressions (typing.Any) are disallowed in demanded

(ANN401)


55-55: Dynamically typed expressions (typing.Any) are disallowed in supplied

(ANN401)


55-55: Dynamically typed expressions (typing.Any) are disallowed in demanded

(ANN401)


77-77: Dynamically typed expressions (typing.Any) are disallowed in supply

(ANN401)


146-146: Dynamically typed expressions (typing.Any) are disallowed in Demand_Constraint

(ANN401)


194-194: Dynamically typed expressions (typing.Any) are disallowed in DemandActivity_Constraint

(ANN401)


236-236: Dynamically typed expressions (typing.Any) are disallowed in CommodityBalance_Constraint

(ANN401)


470-470: Dynamically typed expressions (typing.Any) are disallowed in AnnualCommodityBalance_Constraint

(ANN401)


757-757: Dynamically typed expressions (typing.Any) are disallowed in obj

(ANN401)


762-762: Use format specifiers instead of percent format

(UP031)

temoa/components/technology.py

14-14: Import from collections.abc instead: Iterable

Import from collections.abc

(UP035)


64-64: Unused function argument: p

(ARG001)


75-75: Unused function argument: v

(ARG001)


347-347: Unnecessary generator (rewrite as a set comprehension)

Rewrite as a set comprehension

(C401)


359-359: Unnecessary generator (rewrite as a set comprehension)

Rewrite as a set comprehension

(C401)

temoa/data_io/hybrid_loader.py

27-27: typing.Dict is deprecated, use dict instead

(UP035)


27-27: typing.List is deprecated, use list instead

(UP035)


27-27: typing.Tuple is deprecated, use tuple instead

(UP035)


324-324: Boolean-typed positional argument in function definition

(FBT001)


479-479: Unused method argument: raw_data

(ARG002)


480-480: Unused method argument: filtered_data

(ARG002)


509-509: Unused method argument: raw_data

(ARG002)


526-526: Unused method argument: filtered_data

(ARG002)


561-561: Unused method argument: raw_data

(ARG002)


578-578: Unused method argument: raw_data

(ARG002)


595-595: Unused method argument: raw_data

(ARG002)


596-596: Unused method argument: filtered_data

(ARG002)


630-630: Unused method argument: raw_data

(ARG002)


642-642: Unused method argument: raw_data

(ARG002)


655-655: Unused method argument: raw_data

(ARG002)


670-670: Unused method argument: raw_data

(ARG002)


678-680: Avoid specifying long messages outside the exception class

(TRY003)


685-685: Unused method argument: raw_data

(ARG002)


697-697: Unused method argument: raw_data

(ARG002)


698-698: Unused method argument: filtered_data

(ARG002)


729-729: Unused method argument: raw_data

(ARG002)


745-745: Unused method argument: raw_data

(ARG002)


761-761: Unused method argument: raw_data

(ARG002)

temoa/components/operations.py

66-66: Dynamically typed expressions (typing.Any) are disallowed in RampUpSeasonConstraintIndices

(ANN401)


87-87: Dynamically typed expressions (typing.Any) are disallowed in RampDownSeasonConstraintIndices

(ANN401)


120-120: Dynamically typed expressions (typing.Any) are disallowed in BaseloadDiurnal_Constraint

(ANN401)


238-238: Dynamically typed expressions (typing.Any) are disallowed in RampUpDay_Constraint

(ANN401)


347-347: Dynamically typed expressions (typing.Any) are disallowed in RampDownDay_Constraint

(ANN401)


430-430: Dynamically typed expressions (typing.Any) are disallowed in RampUpSeason_Constraint

(ANN401)


497-497: Dynamically typed expressions (typing.Any) are disallowed in RampDownSeason_Constraint

(ANN401)

temoa/components/emissions.py

58-58: Dynamically typed expressions (typing.Any) are disallowed in LinkedEmissionsTech_Constraint

(ANN401)

temoa/components/storage.py

41-45: Unnecessary generator (rewrite as a set comprehension)

Rewrite as a set comprehension

(C401)


63-63: Dynamically typed expressions (typing.Any) are disallowed in StorageEnergy_Constraint

(ANN401)


117-117: Dynamically typed expressions (typing.Any) are disallowed in SeasonalStorageEnergy_Constraint

(ANN401)


208-208: Dynamically typed expressions (typing.Any) are disallowed in StorageEnergyUpperBound_Constraint

(ANN401)


265-265: Dynamically typed expressions (typing.Any) are disallowed in SeasonalStorageEnergyUpperBound_Constraint

(ANN401)


343-343: Dynamically typed expressions (typing.Any) are disallowed in StorageChargeRate_Constraint

(ANN401)


381-381: Dynamically typed expressions (typing.Any) are disallowed in StorageDischargeRate_Constraint

(ANN401)


417-417: Dynamically typed expressions (typing.Any) are disallowed in StorageThroughput_Constraint

(ANN401)


460-460: Dynamically typed expressions (typing.Any) are disallowed in LimitStorageFraction_Constraint

(ANN401)

temoa/components/time.py

111-111: Dynamically typed expressions (typing.Any) are disallowed in obj

(ANN401)


116-116: Use format specifiers instead of percent format

(UP031)


223-223: Unused function argument: p

(ARG001)

temoa/components/capacity.py

55-55: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


131-131: Unused function argument: v

(ARG001)


182-184: Unnecessary generator (rewrite as a set comprehension)

Rewrite as a set comprehension

(C401)


275-275: Dynamically typed expressions (typing.Any) are disallowed in AnnualRetirement_Constraint

(ANN401)


357-357: Dynamically typed expressions (typing.Any) are disallowed in CapacityAvailableByPeriodAndTech_Constraint

(ANN401)


379-379: Dynamically typed expressions (typing.Any) are disallowed in CapacityAnnual_Constraint

(ANN401)


412-412: Dynamically typed expressions (typing.Any) are disallowed in Capacity_Constraint

(ANN401)


486-486: Dynamically typed expressions (typing.Any) are disallowed in AdjustedCapacity_Constraint

(ANN401)

temoa/core/model.py

113-113: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


114-114: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


115-115: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


131-131: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


141-141: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


144-144: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


147-147: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


150-150: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


153-153: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


155-155: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


156-156: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


157-157: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


158-158: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


159-159: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


161-161: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


163-163: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


165-165: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


169-169: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


170-170: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


171-171: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


172-172: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


173-173: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


174-174: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


175-175: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


176-176: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


177-177: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


179-179: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


180-180: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


184-184: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


187-187: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


199-199: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


202-202: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


205-205: Unnecessary dict() call (rewrite as a literal)

Rewrite as a literal

(C408)


1137-1137: Unused function argument: M

(ARG001)

temoa/_internal/table_writer.py

110-110: Boolean-typed positional argument in function definition

(FBT001)


110-110: Boolean default positional argument in function definition

(FBT002)


111-111: Boolean-typed positional argument in function definition

(FBT001)


111-111: Boolean default positional argument in function definition

(FBT002)


246-246: Avoid specifying long messages outside the exception class

(TRY003)


278-278: Avoid specifying long messages outside the exception class

(TRY003)


280-280: Avoid specifying long messages outside the exception class

(TRY003)


362-362: Avoid specifying long messages outside the exception class

(TRY003)


364-364: Avoid specifying long messages outside the exception class

(TRY003)


576-576: Avoid specifying long messages outside the exception class

(TRY003)

temoa/_internal/table_data_puller.py

460-460: Unused function argument: kwargs

(ARG001)


508-508: Unused function argument: vintage

(ARG001)


509-509: Unused function argument: kwargs

(ARG001)

temoa/components/limits.py

190-190: Dynamically typed expressions (typing.Any) are disallowed in RenewablePortfolioStandard_Constraint

(ANN401)


223-223: Dynamically typed expressions (typing.Any) are disallowed in LimitResource_Constraint

(ANN401)


280-280: Dynamically typed expressions (typing.Any) are disallowed in LimitActivityShare_Constraint

(ANN401)


360-360: Dynamically typed expressions (typing.Any) are disallowed in LimitCapacityShare_Constraint

(ANN401)


393-393: Dynamically typed expressions (typing.Any) are disallowed in LimitNewCapacityShare_Constraint

(ANN401)


426-426: Dynamically typed expressions (typing.Any) are disallowed in LimitAnnualCapacityFactor_Constraint

(ANN401)


484-484: Dynamically typed expressions (typing.Any) are disallowed in LimitSeasonalCapacityFactor_Constraint

(ANN401)


545-545: Dynamically typed expressions (typing.Any) are disallowed in LimitTechInputSplit_Constraint

(ANN401)


572-572: Dynamically typed expressions (typing.Any) are disallowed in LimitTechInputSplitAnnual_Constraint

(ANN401)


598-598: Dynamically typed expressions (typing.Any) are disallowed in LimitTechInputSplitAverage_Constraint

(ANN401)


632-632: Dynamically typed expressions (typing.Any) are disallowed in LimitTechOutputSplit_Constraint

(ANN401)


685-685: Dynamically typed expressions (typing.Any) are disallowed in LimitTechOutputSplitAnnual_Constraint

(ANN401)


717-717: Dynamically typed expressions (typing.Any) are disallowed in LimitTechOutputSplitAverage_Constraint

(ANN401)


747-747: Dynamically typed expressions (typing.Any) are disallowed in LimitEmission_Constraint

(ANN401)


846-846: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthCapacityConstraint_rule

(ANN401)


848-848: Boolean positional value in function call

(FBT003)


851-851: Dynamically typed expressions (typing.Any) are disallowed in LimitDegrowthCapacityConstraint_rule

(ANN401)


853-853: Boolean positional value in function call

(FBT003)


857-857: Boolean-typed positional argument in function definition

(FBT001)


857-857: Boolean default positional argument in function definition

(FBT002)


858-858: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthCapacity

(ANN401)


958-958: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacityConstraint_rule

(ANN401)


960-960: Boolean positional value in function call

(FBT003)


965-965: Dynamically typed expressions (typing.Any) are disallowed in LimitDegrowthNewCapacityConstraint_rule

(ANN401)


967-967: Boolean positional value in function call

(FBT003)


971-971: Boolean-typed positional argument in function definition

(FBT001)


971-971: Boolean default positional argument in function definition

(FBT002)


972-972: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacity

(ANN401)


1073-1073: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacityDeltaConstraint_rule

(ANN401)


1075-1075: Boolean positional value in function call

(FBT003)


1080-1080: Dynamically typed expressions (typing.Any) are disallowed in LimitDegrowthNewCapacityDeltaConstraint_rule

(ANN401)


1082-1082: Boolean positional value in function call

(FBT003)


1086-1086: Boolean-typed positional argument in function definition

(FBT001)


1086-1086: Boolean default positional argument in function definition

(FBT002)


1087-1087: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacityDelta

(ANN401)


1211-1211: Dynamically typed expressions (typing.Any) are disallowed in LimitActivity_Constraint

(ANN401)


1269-1269: Dynamically typed expressions (typing.Any) are disallowed in LimitNewCapacity_Constraint

(ANN401)


1290-1290: Dynamically typed expressions (typing.Any) are disallowed in LimitCapacity_Constraint

(ANN401)

temoa/types/__init__.py

8-17: Import from collections.abc instead: Callable

Import from collections.abc

(UP035)


8-17: typing.Dict is deprecated, use dict instead

(UP035)


8-17: typing.Set is deprecated, use set instead

(UP035)


8-17: typing.Tuple is deprecated, use tuple instead

(UP035)


40-46: Use X | Y for type annotations

(UP007)


54-54: Use X | Y for type annotations

(UP007)


196-318: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

Comment on lines +186 to 187
if keep_lp_file and lp_path is not None:
save_lp(instance, lp_path)
Copy link

@coderabbitai coderabbitai bot Oct 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix save_lp Path usage (prevents LP write on fresh dirs)

save_lp currently calls Path.is_dir(lp_path) and Path.mkdir(lp_path), which are invalid. Use instance methods.

Apply this patch (outside the shown lines, in save_lp):

 def save_lp(instance: TemoaModel, lp_path: Path) -> None:
@@
-    if not lp_path:
+    if not lp_path:
         logger.warning('Requested "keep LP file", but no path is provided...skipped')
     else:
-        if not Path.is_dir(lp_path):
-            Path.mkdir(lp_path)
+        if not lp_path.is_dir():
+            lp_path.mkdir(parents=True, exist_ok=True)
         filename = lp_path / 'model.lp'
         instance.write(filename, format='lp', io_options={'symbolic_solver_labels': True})

This aligns with pathlib API and avoids runtime errors when the directory doesn’t exist.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In temoa/_internal/run_actions.py around lines 186-187, the save_lp function
currently uses incorrect calls like Path.is_dir(lp_path) and
Path.mkdir(lp_path); change these to use the Path instance methods on lp_path
(e.g., if not lp_path.is_dir(): lp_path.mkdir(parents=True, exist_ok=True) or
better, ensure lp_path.parent.mkdir(parents=True, exist_ok=True) before writing)
and ensure lp_path is a pathlib.Path object so directories are created correctly
and no runtime error occurs when writing LP files into fresh directories.

Copy link
Member Author

Choose a reason for hiding this comment

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

add this as a separate issue to be fixed in a future PR

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the save_lp Path usage fix

gh issue create \
  --title "Fix incorrect Path method calls in save_lp function" \
  --body "The \`save_lp\` function in \`temoa/_internal/run_actions.py\` currently uses incorrect static method calls \`Path.is_dir(lp_path)\` and \`Path.mkdir(lp_path)\` instead of instance methods.

These should be changed to use the Path instance methods on the \`lp_path\` object directly (e.g., \`lp_path.is_dir()\` and \`lp_path.mkdir(parents=True, exist_ok=True)\`) to ensure directories are created correctly and prevent runtime errors when writing LP files to fresh directories.

This issue was identified during review of PR #165.

**References:**
- PR: https://github.com/TemoaProject/temoa/pull/165
- Comment: https://github.com/TemoaProject/temoa/pull/165#discussion_r2438054686
- Requested by: @ParticularlyPythonicBS" \
  --assignee ParticularlyPythonicBS

Length of output: 48


@ParticularlyPythonicBS, I've created issue #168 to track the save_lp Path method fix for a future PR: #168

The issue has been assigned to you and includes references back to this PR and comment.

Comment on lines +275 to 281
def write_emissions(self, iteration: int | None = None) -> None:
"""Write the emission table to the DB"""
if not self.tech_sectors:
raise RuntimeError('tech sectors not available... code error')
if self.emission_register is None:
raise RuntimeError('emission register not available... code error')

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Standardize None checks

Use explicit is None checks; not self.tech_sectors treats empty dict as missing.

-        if not self.tech_sectors:
+        if self.tech_sectors is None:
             raise RuntimeError('tech sectors not available... code error')
-        if self.emission_register is None:
-            raise RuntimeError('emission register not available... code error')
+        if self.emission_register is None:
+            raise RuntimeError('emission register not available... code error')
📝 Committable suggestion

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

Suggested change
def write_emissions(self, iteration: int | None = None) -> None:
"""Write the emission table to the DB"""
if not self.tech_sectors:
raise RuntimeError('tech sectors not available... code error')
if self.emission_register is None:
raise RuntimeError('emission register not available... code error')
def write_emissions(self, iteration: int | None = None) -> None:
"""Write the emission table to the DB"""
if self.tech_sectors is None:
raise RuntimeError('tech sectors not available... code error')
if self.emission_register is None:
raise RuntimeError('emission register not available... code error')
🧰 Tools
🪛 Ruff (0.14.0)

278-278: Avoid specifying long messages outside the exception class

(TRY003)


280-280: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In temoa/_internal/table_writer.py around lines 275 to 281, the check `if not
self.tech_sectors:` incorrectly treats an empty dict as missing; change it to an
explicit None check `if self.tech_sectors is None:` so only the absence of the
attribute triggers the RuntimeError (leave the emission_register None check
as-is).

M.SegFracPerSeason.construct()

upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', 1, 'battery', 2020)
upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', '1', 'battery', 2020)
Copy link

@coderabbitai coderabbitai bot Oct 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Bug: time-of-day index passed as string; likely KeyError at runtime.

M.time_of_day and constructed indices use integers (e.g., Line 99 and Lines 102–111 use 1). Passing '1' (str) will not match Var/Set keys.

Apply this diff:

-upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', '1', 'battery', 2020)
+upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', 1, 'battery', 2020)

Also consider aligning the type hint of parameter d in StorageEnergyUpperBound_Constraint to int to reflect actual indexing.


I need to verify the claims in this review by examining the codebase. Let me gather the necessary information.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and inspect unit_cost_explorer.py around line 128
find . -name "unit_cost_explorer.py" -type f | head -5

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Search for StorageEnergyUpperBound_Constraint definition
rg -n "StorageEnergyUpperBound_Constraint" -A 10 -B 2 --type py

Length of output: 6912


🏁 Script executed:

#!/bin/bash
# Look for M.time_of_day definition/initialization
rg -n "time_of_day" --type py -C 3 | head -50

Length of output: 3068


🏁 Script executed:

#!/bin/bash
# Inspect the specific file and lines mentioned
find . -name "unit_cost_explorer.py" -type f -exec cat {} \;

Length of output: 4362


Let me examine the implementation of StorageEnergyUpperBound_Constraint to confirm how the d parameter is used:


---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full implementation of StorageEnergyUpperBound_Constraint
rg -n "def StorageEnergyUpperBound_Constraint" -A 50 temoa/components/storage.py

Length of output: 2536


🏁 Script executed:

#!/bin/bash
# Check how d is used in storage.py within the constraint
rg -n "M.SegFrac\[" temoa/components/storage.py -B 2 -A 2

Length of output: 613


Let me check if d is actually used anywhere in the StorageEnergyUpperBound_Constraint function body:


---

🏁 Script executed:

```shell
#!/bin/bash
# Get full implementation including return statement
rg -n "def StorageEnergyUpperBound_Constraint" -A 60 temoa/components/storage.py | head -80

Length of output: 2800


🏁 Script executed:

#!/bin/bash
# Search for all references to variable 'd' in StorageEnergyUpperBound_Constraint 
# Get the full function with line numbers
sed -n '206,260p' temoa/components/storage.py

Length of output: 2415


Pass '1' as integer, not string, to avoid KeyError at runtime.

On line 258 of temoa/components/storage.py, the parameter d is used to index M.V_StorageLevel[r, p, s, d, t, v]. The model initializes M.time_of_day with integers (line 103 of unit_cost_explorer.py: range(1, tod_slices + 1)), and the constraint set expects integer keys (StorageConstraints_rpsdtv constructed with integer 1). Passing the string '1' on line 128 will not match these integer keys and will raise a KeyError at runtime.

Apply the suggested diff:

-upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', '1', 'battery', 2020)
+upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', 1, 'battery', 2020)

Additionally, the type hint for parameter d on line 207 of temoa/components/storage.py should be changed from str to int to reflect actual usage.

📝 Committable suggestion

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

Suggested change
upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', '1', 'battery', 2020)
upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', 1, 'battery', 2020)
🤖 Prompt for AI Agents
In temoa/utilities/unit_cost_explorer.py around line 128, the call passes '1'
(string) to StorageEnergyUpperBound_Constraint which should be the integer 1 to
match M.time_of_day keys and avoid a KeyError; change the argument from '1' to
1. Also update temoa/components/storage.py at line 207 to change the type hint
for parameter d from str to int so the signature reflects actual integer usage.

Copy link
Member Author

Choose a reason for hiding this comment

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

tag this as a bug and create an issue for it

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the bug with appropriate labels and assignment
gh issue create \
  --repo TemoaProject/temoa \
  --title "Bug: Type mismatch in unit_cost_explorer.py causes KeyError at runtime" \
  --body "## Description

A type mismatch bug was identified in \`temoa/utilities/unit_cost_explorer.py\` at line 128 that will cause a KeyError at runtime.

## Problem

The call to \`StorageEnergyUpperBound_Constraint\` passes \`'1'\` (string) for the time-of-day parameter, but the model initializes \`M.time_of_day\` with integers (line 103: \`range(1, tod_slices + 1)\`). This type mismatch will cause a KeyError when the constraint attempts to index \`M.V_StorageLevel[r, p, s, d, t, v]\` at line 258 of \`temoa/components/storage.py\`.

## Location

**Primary Issue:**
- File: \`temoa/utilities/unit_cost_explorer.py\`
- Line: 128
- Current: \`StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', '1', 'battery', 2020)\`
- Should be: \`StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', 1, 'battery', 2020)\`

**Secondary Issue:**
- File: \`temoa/components/storage.py\`
- Line: 207
- The type hint for parameter \`d\` should be changed from \`str\` to \`int\` to reflect actual usage

## Impact

- Runtime KeyError when executing the unit cost explorer script
- Type hints don't reflect actual expected types, causing confusion and type-checking issues

## References

- PR #165: https://github.com/TemoaProject/temoa/pull/165
- Comment: https://github.com/TemoaProject/temoa/pull/165#discussion_r2438054845
- Requested by: @ParticularlyPythonicBS" \
  --label "bug" \
  --assignee ParticularlyPythonicBS

Length of output: 48


@ParticularlyPythonicBS, I've created issue #169 to track this bug: #169

The issue includes:

  • Detailed description of the type mismatch (string '1' vs integer 1)
  • Two locations that need fixing:
    • Primary: temoa/utilities/unit_cost_explorer.py line 128 (the argument)
    • Secondary: temoa/components/storage.py line 207 (the type hint for parameter d)
  • Impact assessment (runtime KeyError)
  • Backlinks to this PR and comment
  • Bug label applied
  • Assigned to you as requested

The issue is ready for tracking and can be addressed in a follow-up PR.


🧬 Code Graph Analysis Results

[temoa/utilities/unit_cost_explorer.py: around the StorageUpperBound usage]

  • Summary: The script imports and calls StorageEnergyUpperBound_Constraint to construct an upper-bound constraint for storage energy. Later, it attempts to instantiate a constraint via StorageEnergyUpperBoundConstraint.construct(), which appears to be a different (likely misspelled) symbol. This naming inconsistency could cause a runtime error or misbehavior when building/validating the model.

  • Relevant items to review for debugging this user-reported issue:

    • The import and usage of the constraint function in this file:
      • Location: temoa/utilities/unit_cost_explorer.py
      • Description: StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', '1', 'battery', 2020) is used to create an upper-bound constraint object for a specific rtv/rptv index.
    • The final construction call which uses a differently named symbol:
      • Location: temoa/utilities/unit_cost_explorer.py
      • Description: StorageEnergyUpperBoundConstraint.construct() is invoked, which does not match the imported function’s name.
  • File/symbol to verify during review:

    • Ensure consistency between:
      • StorageEnergyUpperBound_Constraint (imported and used to create an upper-bound constraint)
      • StorageEnergyUpperBoundConstraint (the name used to call .construct(), which seems inconsistent)

[temoa/components/storage.py: StorageEnergyUpperBound_Constraint implementation]

  • Snippet: complete function as defined in the repository (used by unit_cost_explorer.py)

    • File: temoa/components/storage.py
    • Signature:
      • def StorageEnergyUpperBound_Constraint(
        M: 'TemoaModel', r: str, p: int, s: str, d: str, t: str, v: int
        ) -> Any:
    • Core behavior:
      • Computes energy_capacity as:
        M.V_Capacity[r, p, t, v]
        • value(M.CapacityToActivity[r, t])
        • (value(M.StorageDuration[r, t]) / (24 * value(M.DaysPerPeriod)))
        • value(M.SegFracPerSeason[p, s])
        • M.DaysPerPeriod
      • Constructs an inequality:
        M.V_StorageLevel[r, p, s, d, t, v] <= energy_capacity
      • Returns the Pyomo constraint expression (expr) or Constraint.Skip if seasonal storage optimization case is detected.
  • Full snippet (as provided in the repository):

    • File: temoa/components/storage.py
    • Snippet (complete function body):
      1. def StorageEnergyUpperBound_Constraint(
      2. M: 'TemoaModel', r: str, p: int, s: str, d: str, t: str, v: int
        
      3. ) -> Any:
      4. r"""
        
      5. This constraint ensures that the amount of energy stored does not exceed
        
      6. the upper bound set by the energy capacity of the storage device, as calculated
        
      7. on the right-hand side.
        
      8. Because the number and duration of time slices are user-defined, we need to adjust
        
    1. the storage duration, which is specified in hours. First, the hourly duration is divided
      
    2. by the number of hours in a year to obtain the duration as a fraction of the year.
      
    3. Since the :math:`C2A` parameter assumes the conversion of capacity to annual activity,
      
    4. we need to express the storage duration as fraction of a year. Then, :math:`SEG_{s,d}`
      
    5. summed over the time-of-day slices (:math:`d`) multiplied by :math:`DPP` yields the
      
    6. number of days per season. This step is necessary because conventional time sliced models
      
    7. use a single day to represent many days within a given season. Thus, it is necessary to
      
    8. scale the storage duration to account for the number of days in each season.
      
    9. .. math::
      
    10.    :label: StorageEnergyUpperBound
      
    11.       \textbf{SL}_{r, p, s, d, t, v} \le
      
    12.       \textbf{CAP}_{r,t,v} \cdot C2A_{r,t} \cdot \frac {SD_{r,t}}{24 \cdot DPP}
      
    13.       \cdot \sum_{d} SEG_{s,d} \cdot DPP
      
    14.       \\
      
    15.       \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageEnergyUpperBound}}
      
    16. A season can represent many days. Within each season, flows are multiplied by the
      
    17. number of days each season represents and, so, the upper bound needs to be adjusted
      
    18. to allow day-scale flows (e.g., charge in the morning, discharge in the afternoon).
      
    19. .. figure:: images/daily_storage_representation.*
      
    20.     :align: center
      
    21.     :width: 100%
      
    22.     :figclass: center
      
    23.     :figwidth: 40%
      
    24.     Representation of a 3-day season for non-seasonal (daily) storage.
      
    25. """
      
    26. if M.isSeasonalStorage[t]:
      
    27.     return Constraint.Skip  # redundant on SeasonalStorageEnergyUpperBound
      
    28. energy_capacity = (
      
    29.     M.V_Capacity[r, p, t, v]
      
    30.     * value(M.CapacityToActivity[r, t])
      
    31.     * (value(M.StorageDuration[r, t]) / (24 * value(M.DaysPerPeriod)))
      
    32.     * value(M.SegFracPerSeason[p, s])
      
    33.     * M.DaysPerPeriod  # adjust for days in season
      
    34. )
      
    35. expr = M.V_StorageLevel[r, p, s, d, t, v] <= energy_capacity
      
    36. return expr
      

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 24

Caution

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

⚠️ Outside diff range comments (6)
pyproject.toml (1)

12-39: Remove pytest from runtime dependencies.

pytest should be a dev/test dependency, not installed for end users.

 dependencies = [
   "pyomo>=6.8.0",
   "ipython",
   "matplotlib==3.9.2",
   "pandas>=2.2.2",
   "numpy>=2.1.0",
   "joblib",
   "salib>=1.5.1",
   "pydoe>=0.3.8",
   "pyutilib>=6.0.0",
   "graphviz>=0.20.3",
   "ipykernel",
   "jupyter",
   "jupyter_contrib_nbextensions",
   "seaborn>=0.13.2",
   "tabulate>=0.9.0",
   "xlsxwriter>=3.2.0",
   "pyam-iamc>=2.2.4",
-  "pytest>=8.3.2",
   "deprecated>=1.2.14",
   "openpyxl>=3.1.5",
   "networkx>=3.3",
   "highspy>=1.7.2",
   "scipy>=1.14.1",
   "gurobipy>=11.0.3",
   "nx-vis-visualizer>=0.1.1",
   "gravis>=0.1.0",
 ]

Then keep pytest/pytest-cov only under [dependency-groups].

temoa/utilities/db_migration_v3_to_v3_1.py (4)

242-245: Syntax error: starred expression in subscript.

lifetime_process[*rtl[0:2], v] is invalid. Build tuple explicitly.

-    for rtl in data:
-        for v in time_all:
-            lifetime_process[*rtl[0:2], v] = rtl[2]
+    for rtl in data:
+        r, t, life = rtl
+        for v in time_all:
+            lifetime_process[(r, t, v)] = life

366-368: Same here: parameterize query.

-        vints = [v[0] for v in con_old.execute(f'SELECT vintage FROM Efficiency WHERE region=="{row[0]}" AND tech="{row[1]}"').fetchall()]
+        vints = [
+            v[0]
+            for v in con_old.execute(
+                'SELECT vintage FROM Efficiency WHERE region = ? AND tech = ?',
+                (row[0], row[1]),
+            ).fetchall()
+        ]

211-215: Safer column-list assembly (avoid str(list) slicing).

Build CSV of column names explicitly to prevent quoting/formatting quirks.

-    cols = [c for c in new_columns if c in old_columns]
-    data = con_old.execute(f'SELECT {str(cols)[1:-1].replace("'","")} FROM {old_name}').fetchall()
+    cols = [c for c in new_columns if c in old_columns]
+    cols_csv = ','.join(cols)
+    data = con_old.execute(f'SELECT {cols_csv} FROM {old_name}').fetchall()

Similarly when building the INSERT column list:

-    query = (
-        'INSERT OR REPLACE INTO '
-        f'{new_name}{tuple(c for c in cols) if len(cols)>1 else f'({cols[0]})'} '
-        f'VALUES ({placeholders})'
-    )
+    col_clause = f"({','.join(cols)})"
+    query = f'INSERT OR REPLACE INTO {new_name}{col_clause} VALUES ({placeholders})'

Apply likewise in the period-indexing block.

Also applies to: 223-227


194-195: Fix message typo: “Transferred”.

-    print(f'Transfered {len(data)} rows from {old_name} to {new_name}')
+    print(f'Transferred {len(data)} rows from {old_name} to {new_name}')

Also applies to: 229-230, 371-372, 330-331

temoa/components/technology.py (1)

44-51: Avoid returning None from Set initializer.

Return an empty set if not yet populated to keep Pyomo Set initialization happy.

-def ModelProcessLifeIndices(M: 'TemoaModel') -> set[tuple[Any, Any, Any, Any]] | None:
+def ModelProcessLifeIndices(M: 'TemoaModel') -> set[tuple[Any, Any, Any, Any]] | None:
     """
     Returns the set of sensical (region, period, tech, vintage) tuples.  The tuple indicates
     the periods in which a process is active, distinct from TechLifeFracIndices that
     returns indices only for processes that EOL mid-period.
     """
-    return M.activeActivity_rptv
+    return M.activeActivity_rptv or set()
♻️ Duplicate comments (4)
temoa/components/capacity.py (1)

130-133: Unused parameter remains unfixed.

Parameter v is still unused. As previously noted, rename to _v to silence the warning.

temoa/types/model_types.py (1)

96-146: Protocol completeness and precision: add TimeSeason; use typed aliases for Vars/Constraints

Align with runtime attributes and remove Any where we already have aliases.

 class TemoaModelProtocol(Protocol):
@@
-    time_season: set[Season]
+    time_season: set[Season]
+    TimeSeason: set[Season]
     time_of_day: set[TimeOfDay]
@@
-    # Model variables
-    V_FlowOut: Any
-    V_Capacity: Any
-    V_NewCapacity: Any
+    # Model variables
+    V_FlowOut: FlowVar
+    V_Capacity: CapacityVar
+    V_NewCapacity: CapacityVar
@@
-    # Model constraints
-    DemandConstraint: Any
-    CommodityBalanceConstraint: Any
-    CapacityConstraint: Any
+    # Model constraints
+    DemandConstraint: FlowConstraint
+    CommodityBalanceConstraint: FlowConstraint
+    CapacityConstraint: CapacityConstraint
#!/bin/bash
# Confirm both names exist on the runtime model; expect hits for both.
rg -nP -C2 '(?<!\w)(TimeSeason|time_season)(?!\w)' --type=py temoa/core/model.py
temoa/types/__init__.py (2)

230-257: Define Pyomo stub names and AbstractModel before exporting them

PyomoSet, PyomoParam, PyomoVar, PyomoConstraint, PyomoBuildAction, PyomoObjective, and AbstractModel are exported in __all__ but never defined here, causing import errors.

 # Pyomo domain types (2 types)
 PyomoDomain = Any  # Pyomo domain objects (NonNegativeReals, Integers, etc.)
-PyomoIndexset = Any  # Pyomo set objects used for indexing
+PyomoIndexset = Any  # Pyomo set objects used for indexing
+
+# Pyomo stub types (for type-checking) with runtime fallbacks
+if TYPE_CHECKING:
+    # Prefer local stubs to avoid runtime dependency
+    from .pyomo_stubs import (
+        PyomoSet,
+        PyomoParam,
+        PyomoVar,
+        PyomoConstraint,
+        PyomoBuildAction,
+        PyomoObjective,
+        AbstractModel,
+    )
+else:
+    PyomoSet = PyomoParam = PyomoVar = PyomoConstraint = PyomoBuildAction = PyomoObjective = AbstractModel = Any

148-156: De-duplicate identical dict types by aliasing

TimeNextSequentialdict and SequentialToSeasondict have the same shape; keep one canonical definition and alias the other.

-TimeNextSequentialdict = dict[tuple[Period, Season], Season]
-SequentialToSeasondict = dict[tuple[Period, Season], Season]
+TimeNextSequentialdict = dict[tuple[Period, Season], Season]
+SequentialToSeasondict = TimeNextSequentialdict
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4f0094 and 09c05b4.

📒 Files selected for processing (8)
  • .pre-commit-config.yaml (3 hunks)
  • pyproject.toml (1 hunks)
  • temoa/components/capacity.py (15 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/core/model.py (8 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/utilities/db_migration_v3_to_v3_1.py (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
temoa/components/capacity.py (1)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/core/model.py (3)
temoa/types/pyomo_stubs.pyi (1)
  • AbstractModel (12-20)
temoa/types/model_types.py (1)
  • TemoaModel (158-232)
temoa/components/limits.py (1)
  • LimitGrowthCapacity (856-955)
temoa/components/technology.py (1)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoVar (41-48)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/utilities/db_migration_v3_to_v3_1.py (3)
temoa/extensions/monte_carlo/mc_run.py (1)
  • model (204-211)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/types/model_types.py (1)
  • TemoaModel (158-232)
🪛 Ruff (0.14.0)
temoa/components/capacity.py

131-131: Unused function argument: v

(ARG001)


273-273: Dynamically typed expressions (typing.Any) are disallowed in AnnualRetirement_Constraint

(ANN401)


355-355: Dynamically typed expressions (typing.Any) are disallowed in CapacityAvailableByPeriodAndTech_Constraint

(ANN401)


377-377: Dynamically typed expressions (typing.Any) are disallowed in CapacityAnnual_Constraint

(ANN401)


410-410: Dynamically typed expressions (typing.Any) are disallowed in Capacity_Constraint

(ANN401)


484-484: Dynamically typed expressions (typing.Any) are disallowed in AdjustedCapacity_Constraint

(ANN401)

temoa/core/model.py

1109-1109: Unused function argument: M

(ARG001)

temoa/components/technology.py

14-14: Import from collections.abc instead: Iterable

Import from collections.abc

(UP035)


64-64: Unused function argument: p

(ARG001)


75-75: Unused function argument: v

(ARG001)

temoa/types/model_types.py

250-281: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/utilities/db_migration_v3_to_v3_1.py

294-294: Possible SQL injection vector through string-based query construction

(S608)

temoa/types/__init__.py

268-390: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)

pyproject.toml Outdated
Comment on lines 196 to 204
[dependency-groups]
dev = [
"gurobipy>=12.0.3",
"types-deprecated>=1.2.15.20250304",
"ruff>=0.2.0",
"pre-commit",
"pytest",
"pytest-cov",
]
Copy link

@coderabbitai coderabbitai bot Oct 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Unify gurobipy version across sections.

Runtime lists >=11.0.3 while dev group lists >=12.0.3; this can cause environment conflicts.

- "gurobipy>=11.0.3",
+ "gurobipy>=12.0.3",

Optionally move gurobipy to an optional extra (e.g., [project.optional-dependencies.solver]) if not strictly required at runtime. Verify solver CI compatibility after the bump.

Also applies to: 36-36


I'll verify the review comment by examining the actual pyproject.toml file to confirm the version mismatch claim.


🏁 Script executed:

#!/bin/bash
# Verify gurobipy versions in pyproject.toml
echo "=== Line 36 region ==="
sed -n '34,38p' pyproject.toml

echo ""
echo "=== Lines 196-204 region ==="
sed -n '196,204p' pyproject.toml

echo ""
echo "=== All gurobipy occurrences with context ==="
rg -n -C2 'gurobipy' pyproject.toml

Length of output: 772


I'll verify whether gurobipy is actually required at runtime or if it should be optional.


🏁 Script executed:

#!/bin/bash
# Check if gurobipy is actually used at runtime
echo "=== Searching for gurobipy imports/usage in source code ==="
rg -n 'gurobipy|from.*gurobipy|import.*gurobipy' --type=py -C2 | head -50

echo ""
echo "=== Check project description/README for solver information ==="
rg -n -i 'optional.*solver|solver.*optional|gurobi' README.md 2>/dev/null | head -20

Length of output: 152


I need to check the project's structure to understand if gurobipy is truly a core runtime dependency or an optional solver backend.


🏁 Script executed:

#!/bin/bash
# Check project metadata and optional dependencies structure
echo "=== Project description in pyproject.toml ==="
sed -n '/^\[project\]/,/^\[/p' pyproject.toml | head -30

echo ""
echo "=== Optional dependencies section ==="
sed -n '/^\[project.optional-dependencies\]/,/^\[/p' pyproject.toml

echo ""
echo "=== Check for conditional imports or solver patterns ==="
rg -n 'try.*import|except.*ImportError|Optional.*gurobi|solver.*backend' --type=py -C1 | head -40

Length of output: 2092


I need to identify what section line 36 belongs to, as it wasn't visible in my previous search of [project] dependencies.


🏁 Script executed:

#!/bin/bash
# Find section containing line 36
echo "=== Lines 1-40 to identify section structure ==="
sed -n '1,40p' pyproject.toml | cat -n

echo ""
echo "=== Full line 36 context with section markers ==="
sed -n '1,40p' pyproject.toml | grep -E '^\[|gurobipy'

Length of output: 1285


Unify gurobipy version across sections; update runtime dependency to match dev group.

The version mismatch is confirmed: runtime dependencies (line 36) specifies gurobipy>=11.0.3 while [dependency-groups] dev (line 198) specifies >=12.0.3. This creates environment conflicts where dev tests pass but runtime may fail with missing features from 12.0.3.

Update line 36 to "gurobipy>=12.0.3".

Optionally: Move gurobipy to [project.optional-dependencies] with a new solver group (not plotting or docs), since no source code directly imports it—it's a Pyomo solver backend, not a core runtime requirement. Verify Pyomo handles optional solver backends gracefully if making this change.

🤖 Prompt for AI Agents
In pyproject.toml around lines 36 and 196–204, the runtime dependency lists
gurobipy>=11.0.3 while the dev dependency uses gurobipy>=12.0.3; update the
runtime dependency at line 36 to "gurobipy>=12.0.3" so versions are consistent,
or alternatively move gurobipy into [project.optional-dependencies] as a new
"solver" group (and remove it from runtime) if it is only used as an optional
Pyomo solver backend—if you choose the optional route, ensure packaging and docs
note the new extra and verify Pyomo behaves correctly without gurobipy
installed.

Copy link
Member Author

Choose a reason for hiding this comment

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

add this as a separate issue to tackle in a later PR

Copy link

Choose a reason for hiding this comment

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

@ParticularlyPythonicBS, understood! Would you like me to create a GitHub issue to track the gurobipy version mismatch as a follow-up action?

If so, I'll create an issue with:

  • Title: "Unify gurobipy version across runtime and dev dependencies"
  • Body: Description of the version mismatch (runtime: >=11.0.3 vs dev: >=12.0.3), the potential conflict, and recommendation to either unify to >=12.0.3 or move to optional dependencies
  • Backlinks: Reference to this PR (typing first pass #165) and comment discussion
  • Assignee: You (@ParticularlyPythonicBS)

Just confirm and I'll create it for you!

Copy link
Member Author

Choose a reason for hiding this comment

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

Confirmed

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue to track the gurobipy version mismatch

gh issue create \
  --title "Unify gurobipy version across runtime and dev dependencies" \
  --body "## Description

There is a version mismatch for the gurobipy dependency in \`pyproject.toml\`:
- **Runtime dependency** (line 36): \`gurobipy>=11.0.3\`
- **Dev dependency** (line 198): \`gurobipy>=12.0.3\`

This mismatch can cause environment conflicts where dev tests may pass with features from version 12.0.3, but runtime environments may fail or behave unexpectedly with version 11.0.3.

## Recommended Solutions

1. **Option 1 (Simple)**: Update the runtime dependency at line 36 to \`gurobipy>=12.0.3\` to match the dev dependency.

2. **Option 2 (Alternative)**: Move gurobipy to \`[project.optional-dependencies]\` as a new \`solver\` group (e.g., \`[project.optional-dependencies.solver]\`), since it appears to be used as an optional Pyomo solver backend rather than a core runtime requirement. If choosing this route:
   - Remove gurobipy from runtime dependencies
   - Add it to a new optional dependency group
   - Update documentation to note the new extra installation method
   - Verify Pyomo handles optional solver backends gracefully

## Context

- **Original PR**: #165 
- **Comment thread**: https://github.com/TemoaProject/temoa/pull/165#discussion_r2440606786
- No direct gurobipy imports were found in the source code, suggesting it's a Pyomo solver backend dependency

## Priority

Medium - Should be addressed in a follow-up PR to prevent potential runtime issues in production environments." \
  --assignee ParticularlyPythonicBS

Length of output: 48


@ParticularlyPythonicBS, I've created issue #167 to track the gurobipy version mismatch: #167

The issue includes:

  • Description of the version conflict (runtime >=11.0.3 vs dev >=12.0.3)
  • Two recommended solutions (simple version unification or moving to optional dependencies)
  • Backlinks to this PR and comment discussion
  • Assigned to you

You can address this in a follow-up PR when convenient! 🎯

Comment on lines 202 to 212
def CapacityAnnualConstraintIndices(M: TemoaModel) -> set[tuple[Any, Any, Any, Any]]:
capacity_indices: set[tuple[Any, Any, Any, Any]] = set()
if M.activeActivity_rptv:
for r, p, t, v in M.activeActivity_rptv:
if t in M.tech_annual and t not in M.tech_demand:
if t not in M.tech_uncap:
capacity_indices.add((r, p, t, v))
else:
return set()

return capacity_indices
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Simplify redundant conditional logic.

The else: return set() clause is redundant; if activeActivity_rptv is falsy, the loop won't execute and an empty set will be returned anyway. Additionally, prefer a set comprehension for clarity.

Apply this diff:

 def CapacityAnnualConstraintIndices(M: TemoaModel) -> set[tuple[Any, Any, Any, Any]]:
-    capacity_indices: set[tuple[Any, Any, Any, Any]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t in M.tech_annual and t not in M.tech_demand:
-                if t not in M.tech_uncap:
-                    capacity_indices.add((r, p, t, v))
-    else:
-        return set()
-
-    return capacity_indices
+    return {
+        (r, p, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if t in M.tech_annual and t not in M.tech_demand and t not in M.tech_uncap
+    }
📝 Committable suggestion

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

Suggested change
def CapacityAnnualConstraintIndices(M: TemoaModel) -> set[tuple[Any, Any, Any, Any]]:
capacity_indices: set[tuple[Any, Any, Any, Any]] = set()
if M.activeActivity_rptv:
for r, p, t, v in M.activeActivity_rptv:
if t in M.tech_annual and t not in M.tech_demand:
if t not in M.tech_uncap:
capacity_indices.add((r, p, t, v))
else:
return set()
return capacity_indices
def CapacityAnnualConstraintIndices(M: TemoaModel) -> set[tuple[Any, Any, Any, Any]]:
return {
(r, p, t, v)
for r, p, t, v in (M.activeActivity_rptv or set())
if t in M.tech_annual and t not in M.tech_demand and t not in M.tech_uncap
}
🤖 Prompt for AI Agents
In temoa/components/capacity.py around lines 202 to 212, remove the redundant
else:return set() and replace the imperative loop with a set comprehension:
build and return a set of (r,p,t,v) for each (r,p,t,v) in M.activeActivity_rptv
that satisfies t in M.tech_annual and t not in M.tech_demand and t not in
M.tech_uncap; ensure function still returns an empty set when
M.activeActivity_rptv is falsy by directly returning the comprehension result.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

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

⚠️ Outside diff range comments (4)
pyproject.toml (1)

12-18: Verify Pyomo version vs Python 3.12 runtime

You target Python 3.12. Consider bumping pyomo>=6.8.0 to >=6.9.2 (current 6.9 series with wide wheel support and 3.12+ focus) or at least validate 6.8 wheels on 3.12 for your platforms.

I can prep a quick matrix check script if helpful.

temoa/components/technology.py (2)

63-77: Silence unused parameters p/v to appease linters

These are required by Pyomo initializer signatures; prefix with _ to avoid ARG001 noise.

-def get_default_survival(
-    M: 'TemoaModel', r: 'Region', p: 'Period', t: 'Technology', v: 'Vintage'
+def get_default_survival(
+    M: 'TemoaModel', r: 'Region', _p: 'Period', t: 'Technology', _v: 'Vintage'
 ) -> float: ...

Likewise for get_default_process_lifetime(..., _v: 'Vintage').


93-120: Return a float literal to match annotation

return 1 returns an int; make it 1.0 to match -> float.

-    if years_remaining >= period_length:
-        # try to avoid floating point round-off errors for the common case.
-        return 1
+    if years_remaining >= period_length:
+        # try to avoid floating point round-off errors for the common case.
+        return 1.0
temoa/utilities/db_migration_v3_to_v3_1.py (1)

241-247: Syntax error: invalid starred expression in subscript

lifetime_process[*rtl[0:2], v] = rtl[3] is invalid syntax. Use an explicit tuple.

-for rtl in data:
-    lifetime_process[rtvl[0:3]] = rtvl[3]
+for rtvl in data:
+    lifetime_process[(rtvl[0], rtvl[1], rtvl[2])] = rtvl[3]
@@
-for rtl in data:
-    for v in time_all:
-        lifetime_process[*rtl[0:2], v] = rtl[2]
+for rtl in data:
+    for v in time_all:
+        lifetime_process[(rtl[0], rtl[1], v)] = rtl[2]
♻️ Duplicate comments (2)
temoa/components/capacity.py (1)

130-148: Prefix unused parameter with underscore.

As noted in the previous review, parameter v is required by the Pyomo initializer signature but is not used in the function body. Please rename it to _v to indicate intentional non-use and silence the linter warning.

Apply this diff:

 def get_default_capacity_factor(
-    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage
+    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, _v: Vintage
 ) -> float:
temoa/types/__init__.py (1)

148-157: Deduplicate identical dict aliases (avoid drift)

TimeNextSequentialdict and SequentialToSeasondict are identical. Keep one canonical type and alias the other to reduce maintenance.

 TimeNextSequentialdict = dict[tuple[Period, Season], Season]
-SequentialToSeasondict = dict[tuple[Period, Season], Season]
+SequentialToSeasondict = TimeNextSequentialdict
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4f0094 and 90e3234.

📒 Files selected for processing (9)
  • .pre-commit-config.yaml (3 hunks)
  • definitions.py (1 hunks)
  • pyproject.toml (1 hunks)
  • temoa/components/capacity.py (15 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/core/model.py (8 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/utilities/db_migration_v3_to_v3_1.py (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
temoa/utilities/db_migration_v3_to_v3_1.py (2)
temoa/extensions/monte_carlo/mc_run.py (1)
  • model (204-211)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/core/model.py (3)
temoa/types/pyomo_stubs.pyi (1)
  • AbstractModel (12-20)
temoa/temoa_model/temoa_model.py (3)
  • CreateSparseDicts (60-93)
  • TemoaModel (96-1134)
  • progress_check (1137-1139)
temoa/types/model_types.py (1)
  • TemoaModel (158-232)
temoa/components/capacity.py (2)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/types/model_types.py (1)
  • TemoaModel (158-232)
temoa/components/technology.py (2)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/types/model_types.py (1)
  • TemoaModel (158-232)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoVar (41-48)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
🪛 Ruff (0.14.0)
temoa/types/__init__.py

268-390: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/utilities/db_migration_v3_to_v3_1.py

294-294: Possible SQL injection vector through string-based query construction

(S608)

temoa/core/model.py

1109-1109: Unused function argument: M

(ARG001)

temoa/components/capacity.py

131-131: Unused function argument: v

(ARG001)


273-273: Dynamically typed expressions (typing.Any) are disallowed in AnnualRetirement_Constraint

(ANN401)


355-355: Dynamically typed expressions (typing.Any) are disallowed in CapacityAvailableByPeriodAndTech_Constraint

(ANN401)


377-377: Dynamically typed expressions (typing.Any) are disallowed in CapacityAnnual_Constraint

(ANN401)


410-410: Dynamically typed expressions (typing.Any) are disallowed in Capacity_Constraint

(ANN401)


484-484: Dynamically typed expressions (typing.Any) are disallowed in AdjustedCapacity_Constraint

(ANN401)

temoa/components/technology.py

14-14: Import from collections.abc instead: Iterable

Import from collections.abc

(UP035)


64-64: Unused function argument: p

(ARG001)


75-75: Unused function argument: v

(ARG001)

definitions.py

15-15: Avoid specifying long messages outside the exception class

(TRY003)

temoa/types/model_types.py

250-281: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (6)
temoa/components/capacity.py (5)

16-44: LGTM: Idiomatic TYPE_CHECKING pattern for type imports.

The conditional import block correctly isolates type-only imports behind TYPE_CHECKING and provides Any fallbacks at runtime to avoid circular dependencies and import overhead. This is standard practice for typing in large Python projects.


54-55: LGTM: Function signature properly typed.

The type annotations are clear, and the dict literal {} correctly addresses the previous linter warning. The tuple using Any elements is acceptable for this initial typing pass, though future iterations could refine this to tuple[Region, Period, Technology, Vintage] to match the indexed data.


89-127: Typing additions to deprecated function are adequate.

Since this function is marked for removal, the added type annotations serve documentation purposes for now. No further refinement needed.


151-157: LGTM: Function signature properly typed.

The type annotations are complete and match the parameter usage in the function body.


577-639: LGTM: Function signature and set comprehensions properly typed.

The type annotations are clear, and the set comprehensions that populate M.newCapacity_rtv, M.activeCapacityAvailable_rpt, and M.activeCapacityAvailable_rptv are correctly formatted and consistent with the rest of the codebase.

temoa/types/__init__.py (1)

268-390: Based on the verification results, I found a critical issue with the review comment:

The RUF022 fix is marked as unsafe if there are any comments that take up a whole line by themselves inside the __all__ definition. Looking at the __all__ list in the file, it contains numerous category comments (e.g., # Core types, # Index types, etc.). This is exactly why the ruff check --fix command does not apply the fix—it requires the --unsafe-fixes flag because sorting would disrupt the intentional categorization delimited by comments.

The original review comment's suggestion is therefore incomplete and misleading. Additionally, when sorting does occur, ruff uses an isort-style sort that orders items first by casing (SCREAMING_SNAKE_CASE first, then CamelCase, then others), followed by natural sort within each category, which would significantly restructure the current logical grouping.

Do not apply ruff check --fix as suggested; instead, manually reorganize __all__ into alphabetical order while preserving semantic clarity, or use --unsafe-fixes if accepting the loss of category comments.

Likely an incorrect or invalid review comment.

Comment on lines +10 to 17
OUTPUT_PATH: Path | None = None


def get_OUTPUT_PATH():
def get_OUTPUT_PATH() -> Path:
if OUTPUT_PATH is None:
raise RuntimeError('Output path not yet defined')
# Use cast to help mypy understand the type
return OUTPUT_PATH
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Narrow the Optional return for mypy

get_OUTPUT_PATH() returns Path but returns the Optional global. Add a cast or assertion after the None-check.

-from pathlib import Path
+from pathlib import Path
+from typing import cast
@@
 def get_OUTPUT_PATH() -> Path:
     if OUTPUT_PATH is None:
         raise RuntimeError('Output path not yet defined')
-    # Use cast to help mypy understand the type
-    return OUTPUT_PATH
+    return cast(Path, OUTPUT_PATH)
📝 Committable suggestion

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

Suggested change
OUTPUT_PATH: Path | None = None
def get_OUTPUT_PATH():
def get_OUTPUT_PATH() -> Path:
if OUTPUT_PATH is None:
raise RuntimeError('Output path not yet defined')
# Use cast to help mypy understand the type
return OUTPUT_PATH
from pathlib import Path
from typing import cast
OUTPUT_PATH: Path | None = None
def get_OUTPUT_PATH() -> Path:
if OUTPUT_PATH is None:
raise RuntimeError('Output path not yet defined')
return cast(Path, OUTPUT_PATH)
🧰 Tools
🪛 Ruff (0.14.0)

15-15: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In definitions.py around lines 10 to 17, the function returns the global
Optional OUTPUT_PATH without convincing mypy it is non-None; after the existing
None-check add an assertion "assert OUTPUT_PATH is not None" (or alternatively
use typing.cast(Path, OUTPUT_PATH)) so the return type is narrowed to Path, then
return OUTPUT_PATH.

Comment on lines +165 to 168
def CapacityVariableIndices(
M: TemoaModel,
) -> set[tuple[Any, Any, Any]] | None:
return M.newCapacity_rtv
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove incorrect | None from return type.

The function returns M.newCapacity_rtv, which is initialized as a set in create_capacity_and_retirement_sets (line 623) and is never None. Pyomo Set initializers should return a set or iterable, not None.

Apply this diff:

 def CapacityVariableIndices(
     M: TemoaModel,
-) -> set[tuple[Any, Any, Any]] | None:
+) -> set[tuple[Any, Any, Any]]:
     return M.newCapacity_rtv
📝 Committable suggestion

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

Suggested change
def CapacityVariableIndices(
M: TemoaModel,
) -> set[tuple[Any, Any, Any]] | None:
return M.newCapacity_rtv
def CapacityVariableIndices(
M: TemoaModel,
) -> set[tuple[Any, Any, Any]]:
return M.newCapacity_rtv
🤖 Prompt for AI Agents
In temoа/components/capacity.py around lines 165 to 168, the return type
annotation of CapacityVariableIndices incorrectly includes `| None` even though
M.newCapacity_rtv is always a set; remove the `| None` branch so the function
returns a plain set type (e.g., set[tuple[Any, Any, Any]]), update the signature
accordingly, and ensure any related type hints or mypy expectations reflect that
the function never returns None.

Comment on lines +185 to 188
def CapacityAvailableVariableIndices(
M: TemoaModel,
) -> set[tuple[Any, Any, Any]] | None:
return M.activeCapacityAvailable_rpt
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove incorrect | None from return type.

The function returns M.activeCapacityAvailable_rpt, which is initialized as a set in create_capacity_and_retirement_sets (line 629) and is never None.

Apply this diff:

 def CapacityAvailableVariableIndices(
     M: TemoaModel,
-) -> set[tuple[Any, Any, Any]] | None:
+) -> set[tuple[Any, Any, Any]]:
     return M.activeCapacityAvailable_rpt
🤖 Prompt for AI Agents
In temoa/components/capacity.py around lines 185 to 188, the return type
annotation incorrectly allows None; change the function signature to return
set[tuple[Any, Any, Any]] (remove the " | None") since
M.activeCapacityAvailable_rpt is always a set initialized in
create_capacity_and_retirement_sets. Update the type annotation only (no logic
changes).

Comment on lines 1109 to 1111
def progress_check(M: 'TemoaModel', checkpoint: str) -> None:
"""A quick widget which is called by BuildAction in order to log creation progress"""
logger.debug('Model build progress: %s', checkpoint)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: silence unused M in progress_check

Rename to _M or add an underscore to avoid ARG001 without behavior change.

-def progress_check(M: 'TemoaModel', checkpoint: str) -> None:
+def progress_check(_M: 'TemoaModel', checkpoint: str) -> None:
🧰 Tools
🪛 Ruff (0.14.0)

1109-1109: Unused function argument: M

(ARG001)

🤖 Prompt for AI Agents
In temoa/core/model.py around lines 1109 to 1111, the unused parameter M in
progress_check triggers ARG001; rename it to _M (or prefix with an underscore
like _M) in the function signature while keeping the existing type annotation
and body unchanged so lint no longer flags it; then run linters/tests to verify
the warning is gone.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
temoa/components/technology.py (2)

64-66: Mark unused parameter p with underscore prefix

The parameter p is intentionally unused in this default implementation but required by the function signature contract.

Prefix with underscore to silence the linter:

 def get_default_survival(
-    M: 'TemoaModel', r: 'Region', p: 'Period', t: 'Technology', v: 'Vintage'
+    M: 'TemoaModel', r: 'Region', _p: 'Period', t: 'Technology', v: 'Vintage'
 ) -> float:

75-77: Mark unused parameter v with underscore prefix

The parameter v is intentionally unused in this default initializer but required by the signature.

Prefix with underscore to silence the linter:

 def get_default_process_lifetime(
-    M: 'TemoaModel', r: 'Region', t: 'Technology', v: 'Vintage'
+    M: 'TemoaModel', r: 'Region', t: 'Technology', _v: 'Vintage'
 ) -> int:
temoa/types/__init__.py (1)

157-180: Critical: Add missing Pyomo type definitions for exported names

Lines 346-352 of __all__ export PyomoSet, PyomoParam, PyomoVar, PyomoConstraint, PyomoBuildAction, PyomoObjective, and AbstractModel, but these names are never defined in this module. Runtime imports like from temoa.types import PyomoSet will fail with AttributeError.

Add a TYPE_CHECKING block with imports from pyomo_stubs and runtime fallbacks, mirroring your pandas/numpy pattern:

     NumpyArray = NDArray[Any]
 
 else:
     DataFrame = Any
     Series = Any
     PandasIndex = Any
     PandasDtype = Any
     TemoaDataFrame = Any  # pd.DataFrame at runtime
 
     NumpyArray = Any  # np.ndarray at runtime
+
+# Pyomo stub types (for type checking)
+if TYPE_CHECKING:
+    from .pyomo_stubs import (
+        AbstractModel,
+        PyomoBuildAction,
+        PyomoConstraint,
+        PyomoObjective,
+        PyomoParam,
+        PyomoSet,
+        PyomoVar,
+    )
+else:
+    PyomoSet = Any
+    PyomoParam = Any
+    PyomoVar = Any
+    PyomoConstraint = Any
+    PyomoBuildAction = Any
+    PyomoObjective = Any
+    AbstractModel = Any

[Based on learnings]

temoa/types/model_types.py (1)

152-228: Critical: TemoaModel is TYPE_CHECKING-only but exported in __all__

TemoaModel is defined only inside the TYPE_CHECKING block (line 154), but line 250 exports it in __all__. Runtime imports like from temoa.types.model_types import TemoaModel will fail with NameError.

Add a runtime fallback alias after the TYPE_CHECKING block:

         def __init__(self, *args: object, **kwargs: object) -> None: ...
 
+else:
+    # Runtime fallback to avoid import errors
+    TemoaModel = Any  # type: ignore[assignment]
 
 # Data structure types for model processing

This mirrors the pattern used for other TYPE_CHECKING-only types in the codebase.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90e3234 and 130334d.

📒 Files selected for processing (9)
  • .pre-commit-config.yaml (3 hunks)
  • docs/types_improvement_plan.md (1 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/core_types.py (1 hunks)
  • temoa/types/dict_types.py (1 hunks)
  • temoa/types/index_types.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/set_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
temoa/types/__init__.py (1)
temoa/types/pandas_stubs.pyi (2)
  • DataFrame (20-58)
  • Series (60-78)
temoa/components/technology.py (2)
temoa/types/model_types.py (1)
  • TemoaModel (154-228)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
  • PyomoVar (41-48)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
🪛 LanguageTool
docs/types_improvement_plan.md

[style] ~203-~203: Try moving the adverb to make the sentence clearer.
Context: ...e Examples Needed Examples: - How to properly construct index tuples - When to use dict vs set types - Prope...

(SPLIT_INFINITIVE)


[style] ~350-~350: ‘mixed together’ might be wordy. Consider a shorter alternative.
Context: ... list Issues: - Multiple concerns mixed together - Difficult to locate specific type cat...

(EN_WORDINESS_PREMIUM_MIXED_TOGETHER)

🪛 markdownlint-cli2 (0.18.1)
docs/types_improvement_plan.md

29-29: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


357-357: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


359-359: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


372-372: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


374-374: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.0)
temoa/types/__init__.py

182-358: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/components/technology.py

65-65: Unused function argument: p

(ARG001)


76-76: Unused function argument: v

(ARG001)

temoa/types/model_types.py

246-290: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (6)
.pre-commit-config.yaml (2)

42-46: Well done addressing the prior feedback on pyupgrade configuration.

The addition of the pyupgrade hook with --py312-plus argument aligns the pre-commit configuration with the project's Python 3.12+ requirement and addresses the previous reviewer feedback. This ensures pyupgrade applies only syntax transformations safe for Python 3.12.


4-4: Pre-commit-hooks v6.0.0 version bump verified as safe.

The removed hooks (check-byte-order-marker and fix-encoding-pragma) are not referenced in the configuration, and Python 3.12 satisfies the v6.0.0 requirement (Python >= 3.9). The uv and ruff version bumps are straightforward minor updates, and the addition of pyupgrade with --py312-plus properly targets the project's Python version.

temoa/types/index_types.py (1)

1-35: LGTM: Clean index type definitions

This new module provides well-structured tuple type aliases for indexing. The definitions are clear, properly typed, and follow a consistent naming pattern.

temoa/types/core_types.py (1)

1-59: LGTM: Solid foundational type aliases

This module establishes the core type vocabulary for the entire typing system. The definitions are clean, using modern syntax (PEP 604 unions, built-in generics), and provide a clear semantic foundation.

temoa/types/set_types.py (1)

1-54: LGTM: Consistent set type definitions with compatibility aliases

The set type definitions follow a clear pattern, properly using Optional for nullable sets and providing capitalized aliases for backward compatibility. Well organized by semantic grouping (ActiveFlow, ActiveFlex, Storage, Capacity).

temoa/types/dict_types.py (1)

1-116: LGTM: Comprehensive dictionary type definitions

This module provides extensive dictionary type aliases covering processes, commodities, technology classifications, time sequencing, geography, and switching flags. The consistent pattern of lowercase primary definitions with capitalized compatibility aliases maintains backward compatibility while establishing a clear primary naming convention.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

♻️ Duplicate comments (15)
temoa/types/model_types.py (3)

152-229: TemoaModel is exported but undefined at runtime — add a fallback alias.

Currently defined only under TYPE_CHECKING; importing it at runtime raises NameError. Provide a runtime alias.

 if TYPE_CHECKING:
@@
     class TemoaModel(AbstractModel):
         """
         Type stub for the main TemoaModel class.
         """
@@
         def __init__(self, *args: object, **kwargs: object) -> None: ...
+else:
+    # Runtime fallback so `from temoa.types.model_types import TemoaModel` works
+    TemoaModel = Any  # type: ignore[assignment]

134-141: Tighten variable/constraint types (use Pyomo stubs instead of Any).

Improves static guarantees and editor help.

-    V_FlowOut: Any
-    V_Capacity: Any
-    V_NewCapacity: Any
+    V_FlowOut: PyomoVar
+    V_Capacity: PyomoVar
+    V_NewCapacity: PyomoVar
@@
-        V_FlowOut: Any
-        V_Capacity: Any
-        V_NewCapacity: Any
-        V_RetiredCapacity: Any
+        V_FlowOut: PyomoVar
+        V_Capacity: PyomoVar
+        V_NewCapacity: PyomoVar
+        V_RetiredCapacity: PyomoVar
@@
-        DemandConstraint: Any
-        CommodityBalanceConstraint: Any
-        CapacityConstraint: Any
+        DemandConstraint: PyomoConstraint
+        CommodityBalanceConstraint: PyomoConstraint
+        CapacityConstraint: PyomoConstraint

Also applies to: 210-220


58-59: Provide canonical SparseDict alias (naming consistency).

Keep Sparsedict for back-compat but expose CamelCase too and export it.

 Sparsedict = dict[SparseIndex, set[SparseIndex]]
+SparseDict = Sparsedict
@@
     'TechClassification',
-    'Sparsedict',
+    'Sparsedict',
+    'SparseDict',

Also applies to: 245-290

temoa/components/capacity.py (10)

154-156: Return type shouldn’t allow None (always a set).

M.newCapacity_rtv is constructed as a set in create_capacity_and_retirement_sets.

Apply:

-def CapacityVariableIndices(
-    M: TemoaModel,
-) -> set[tuple[Any, Any, Any]] | None:
+def CapacityVariableIndices(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any]]:

174-176: Return type shouldn’t allow None (always a set).

M.activeCapacityAvailable_rpt is always a set.

Apply:

-def CapacityAvailableVariableIndices(
-    M: TemoaModel,
-) -> set[tuple[Any, Any, Any]] | None:
+def CapacityAvailableVariableIndices(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any]]:

180-188: Prefer set comprehension for clarity and speed.

Equivalent behavior, less code.

Apply:

-def RegionalExchangeCapacityConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Any, Any, Any, Any, Any]]:
-    indices: set[tuple[Any, Any, Any, Any, Any]] = set()
-    for r_e, p, i in M.exportRegions:
-        for r_i, t, v, _o in M.exportRegions[r_e, p, i]:
-            indices.add((r_e, r_i, p, t, v))
-
-    return indices
+def RegionalExchangeCapacityConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any, Any, Any]]:
+    return {
+        (r_e, r_i, p, t, v)
+        for r_e, p, i in M.exportRegions
+        for r_i, t, v, _o in M.exportRegions[r_e, p, i]
+    }

191-201: Simplify to a single comprehension; drop redundant else.

Same logic, fewer branches; returns empty set when input is falsy.

Apply:

-def CapacityAnnualConstraintIndices(M: TemoaModel) -> set[tuple[Any, Any, Any, Any]]:
-    capacity_indices: set[tuple[Any, Any, Any, Any]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t in M.tech_annual and t not in M.tech_demand:
-                if t not in M.tech_uncap:
-                    capacity_indices.add((r, p, t, v))
-    else:
-        return set()
-
-    return capacity_indices
+def CapacityAnnualConstraintIndices(M: TemoaModel) -> set[tuple[Any, Any, Any, Any]]:
+    return {
+        (r, p, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if t in M.tech_annual and t not in M.tech_demand and t not in M.tech_uncap
+    }

204-219: Collapse nested ifs and loops into a comprehension.

Cleaner and faster; still returns empty set if no activity.

Apply:

-def CapacityConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Any, Any, Any, Any, Any, Any]]:
-    capacity_indices: set[tuple[Any, Any, Any, Any, Any, Any]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t not in M.tech_annual or t in M.tech_demand:
-                if t not in M.tech_uncap:
-                    if t not in M.tech_storage:
-                        for s in M.TimeSeason[p]:
-                            for d in M.time_of_day:
-                                capacity_indices.add((r, p, s, d, t, v))
-    else:
-        return set()
-
-    return capacity_indices
+def CapacityConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any, Any, Any, Any]]:
+    return {
+        (r, p, s, d, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if (t not in M.tech_annual or t in M.tech_demand)
+        if t not in M.tech_uncap
+        if t not in M.tech_storage
+        for s in M.TimeSeason[p]
+        for d in M.time_of_day
+    }

223-232: Signature/annotation mismatch in deprecated function.

Return type declares concrete str/int tuple but implementation uses Any; align types.

Apply:

-def CapacityFactorProcessIndices(
-    M: TemoaModel,
-) -> set[tuple[str, str, str, str, int]]:
-    indices: set[tuple[Any, Any, Any, Any, Any]] = set()
+def CapacityFactorProcessIndices(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any, Any, Any]]:
+    indices: set[tuple[Any, Any, Any, Any, Any]] = set()

235-246: Simplify to a comprehension; drop redundant else.

Matches patterns above for consistency.

Apply:

-def CapacityFactorTechIndices(
-    M: TemoaModel,
-) -> set[tuple[Any, Any, Any, Any, Any]]:
-    all_cfs: set[tuple[Any, Any, Any, Any, Any]] = set()
-    if M.activeCapacityAvailable_rpt:
-        for r, p, t in M.activeCapacityAvailable_rpt:
-            for s in M.TimeSeason[p]:
-                for d in M.time_of_day:
-                    all_cfs.add((r, p, s, d, t))
-    else:
-        return set()
-    return all_cfs
+def CapacityFactorTechIndices(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any, Any, Any]]:
+    return {
+        (r, p, s, d, t)
+        for r, p, t in (M.activeCapacityAvailable_rpt or set())
+        for s in M.TimeSeason[p]
+        for d in M.time_of_day
+    }

249-251: Return type shouldn’t allow None (always a set).

M.activeCapacityAvailable_rptv is always a set.

Apply:

-def CapacityAvailableVariableIndicesVintage(
-    M: TemoaModel,
-) -> set[tuple[Any, Any, Any, Any]] | None:
+def CapacityAvailableVariableIndicesVintage(
+    M: TemoaModel,
+) -> set[tuple[Any, Any, Any, Any]]:

260-262: Avoid Any in constraint rule return types; introduce a typed alias.

Use a shared alias (e.g., ConstraintExpression) instead of Any to satisfy ANN401 and improve clarity.

Apply in temoa/types (see separate comment) to define:

# temoa/types/model_types.py or temoa/types/__init__.py
from typing import Any
ConstraintExpression = Any  # TODO: refine when Pyomo adds precise types

Then change these signatures:

-def AnnualRetirement_Constraint(...) -> Any:
+def AnnualRetirement_Constraint(...) -> ConstraintExpression:
-def CapacityAvailableByPeriodAndTech_Constraint(...) -> Any:
+def CapacityAvailableByPeriodAndTech_Constraint(...) -> ConstraintExpression:
-def CapacityAnnual_Constraint(...) -> Any:
+def CapacityAnnual_Constraint(...) -> ConstraintExpression:
-def Capacity_Constraint(...) -> Any:
+def Capacity_Constraint(...) -> ConstraintExpression:
-def AdjustedCapacity_Constraint(...) -> Any:
+def AdjustedCapacity_Constraint(...) -> ConstraintExpression:

Also applies to: 343-345, 364-366, 397-399, 471-473


119-121: Silence unused parameter v in initializer.

v is not used; prefix to avoid ARG001 without changing call sites.

Apply:

-def get_default_capacity_factor(
-    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage
-) -> float:
+def get_default_capacity_factor(
+    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, _v: Vintage
+) -> float:
temoa/types/__init__.py (2)

386-393: Pyomo stub names exported in all but not defined.

PyomoSet, PyomoParam, PyomoVar, PyomoConstraint, PyomoBuildAction, PyomoObjective, AbstractModel are missing, causing import errors.

Apply near the pandas/numpy block and before all:

@@
 else:
     DataFrame = Any
     Series = Any
     PandasIndex = Any
     PandasDtype = Any
     TemoaDataFrame = Any  # pd.DataFrame at runtime

     NumpyArray = Any  # np.ndarray at runtime

+# Pyomo stub types (type-checking only) with runtime fallbacks
+if TYPE_CHECKING:
+    from .pyomo_stubs import (
+        Set as PyomoSet,
+        Param as PyomoParam,
+        Var as PyomoVar,
+        Constraint as PyomoConstraint,
+        BuildAction as PyomoBuildAction,
+        Objective as PyomoObjective,
+        AbstractModel,
+    )
+else:
+    PyomoSet = PyomoParam = PyomoVar = PyomoConstraint = PyomoBuildAction = PyomoObjective = AbstractModel = Any
#!/bin/bash
# Verify the symbols are defined after changes
rg -nP '^\s*(Pyomo(Set|Param|Var|Constraint|BuildAction|Objective)|AbstractModel)\s*=' temoa/types/__init__.py -C1

218-218: RUF022: consider disabling sort to preserve semantic grouping.

If you prefer the current grouped all, add a one-line noqa; else sort the list.

Apply:

-__all__ = [
+# ruff: noqa: RUF022
+__all__ = [
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 130334d and 48afe58.

📒 Files selected for processing (7)
  • temoa/components/capacity.py (15 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/core_types.py (1 hunks)
  • temoa/types/database_types.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/solver_types.py (1 hunks)
  • temoa/types/validation_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
  • PyomoVar (41-48)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/types/__init__.py (5)
temoa/types/core_types.py (3)
  • OutputConfig (113-134)
  • ScenarioConfig (62-89)
  • SolverConfig (92-110)
temoa/types/database_types.py (8)
  • CapacityFactorRow (99-108)
  • CommodityRow (71-77)
  • DatabaseSchema (21-57)
  • DemandRow (111-119)
  • EfficiencyRow (87-96)
  • EmissionActivityRow (122-133)
  • TechnologyRow (61-68)
  • TimePeriodsRow (80-84)
temoa/types/solver_types.py (3)
  • SolverResultsProtocol (62-75)
  • SolverStatusEnum (22-33)
  • TerminationConditionEnum (36-59)
temoa/types/validation_types.py (4)
  • ValidationError (32-56)
  • ValidationResult (95-225)
  • ValidationSeverity (13-28)
  • ValidationWarning (60-91)
temoa/types/pandas_stubs.pyi (2)
  • DataFrame (20-58)
  • Series (60-78)
temoa/components/capacity.py (2)
temoa/types/model_types.py (1)
  • TemoaModel (154-228)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
🪛 Ruff (0.14.0)
temoa/types/solver_types.py

73-73: Dynamically typed expressions (typing.Any) are disallowed in __getitem__

(ANN401)


93-101: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/types/database_types.py

151-167: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/types/validation_types.py

229-234: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/types/model_types.py

246-290: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/types/__init__.py

218-427: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

temoa/components/capacity.py

120-120: Unused function argument: v

(ARG001)


262-262: Dynamically typed expressions (typing.Any) are disallowed in AnnualRetirement_Constraint

(ANN401)


344-344: Dynamically typed expressions (typing.Any) are disallowed in CapacityAvailableByPeriodAndTech_Constraint

(ANN401)


366-366: Dynamically typed expressions (typing.Any) are disallowed in CapacityAnnual_Constraint

(ANN401)


399-399: Dynamically typed expressions (typing.Any) are disallowed in Capacity_Constraint

(ANN401)


473-473: Dynamically typed expressions (typing.Any) are disallowed in AdjustedCapacity_Constraint

(ANN401)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (3)
temoa/components/capacity.py (2)

43-45: LGTM: typed dict literal resolves C408.

Using {} with an explicit type is correct and clearer than dict().


612-628: LGTM: set construction is clear and efficiently filtered.

Comprehensions correctly honor tech_uncap and time bounds; aligns with downstream initializers.

temoa/types/__init__.py (1)

193-201: Review comment is incorrect—PandasIndex and PandasDtype are defined in pandas_stubs.pyi.

The file defines both as type aliases on lines 94–95 (PandasIndex = Any and PandasDtype = Any), exports them in __all__, and the imports in __init__.py resolve correctly. The imports will not fail under type checking. The current code is valid and requires no changes.

Likely an incorrect or invalid review comment.

Comment on lines +62 to +135
class ScenarioConfig:
"""
Structured configuration for scenario-specific settings.

This type represents configuration options that are specific to a particular
scenario run, including solver settings, output options, and analysis modes.
"""

scenario: str
"""Name of the scenario."""

input_database: str
"""Path to the input database file."""

output_database: str
"""Path to the output database file."""

solver_name: SolverName
"""Name of the solver to use."""

save_excel: bool
"""Whether to save results to Excel format."""

save_duals: bool
"""Whether to save dual variables."""

save_storage_levels: bool
"""Whether to save storage level information."""


class SolverConfig:
"""
Configuration for solver-specific settings.

This type represents solver options and parameters that control
the optimization process.
"""

solver_name: SolverName
"""Name of the solver."""

options: dict[str, Any]
"""Solver-specific options dictionary."""

time_limit: float | None
"""Maximum time limit for solving (in seconds)."""

mip_gap: float | None
"""MIP gap tolerance for mixed-integer problems."""


class OutputConfig:
"""
Configuration for output format and content settings.

This type represents options that control what results are saved
and in what format.
"""

save_excel: bool
"""Whether to save results to Excel format."""

save_duals: bool
"""Whether to save dual variables."""

save_storage_levels: bool
"""Whether to save storage level information."""

save_lp_file: bool
"""Whether to save the LP file."""

output_path: str
"""Path where output files should be saved."""

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Config classes are unusable at runtime (no init/attributes set). Make them dataclasses.

Annotated class vars don’t create instance attributes; accessing them raises AttributeError. Convert to dataclasses and provide sensible defaults.

-from typing import Any
+from typing import Any
+from dataclasses import dataclass, field
@@
-class ScenarioConfig:
+@dataclass(slots=True)
+class ScenarioConfig:
@@
-    scenario: str
+    scenario: str
@@
-    input_database: str
+    input_database: str
@@
-    output_database: str
+    output_database: str
@@
-    solver_name: SolverName
+    solver_name: SolverName
@@
-    save_excel: bool
+    save_excel: bool = False
@@
-    save_duals: bool
+    save_duals: bool = False
@@
-    save_storage_levels: bool
+    save_storage_levels: bool = False
@@
-class SolverConfig:
+@dataclass(slots=True)
+class SolverConfig:
@@
-    solver_name: SolverName
+    solver_name: SolverName
@@
-    options: dict[str, Any]
+    options: dict[str, Any] = field(default_factory=dict)
@@
-    time_limit: float | None
+    time_limit: float | None = None
@@
-    mip_gap: float | None
+    mip_gap: float | None = None
@@
-class OutputConfig:
+@dataclass(slots=True)
+class OutputConfig:
@@
-    save_excel: bool
+    save_excel: bool = False
@@
-    save_duals: bool
+    save_duals: bool = False
@@
-    save_storage_levels: bool
+    save_storage_levels: bool = False
@@
-    save_lp_file: bool
+    save_lp_file: bool = False
@@
-    output_path: str
+    output_path: str = "."
📝 Committable suggestion

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

Suggested change
class ScenarioConfig:
"""
Structured configuration for scenario-specific settings.
This type represents configuration options that are specific to a particular
scenario run, including solver settings, output options, and analysis modes.
"""
scenario: str
"""Name of the scenario."""
input_database: str
"""Path to the input database file."""
output_database: str
"""Path to the output database file."""
solver_name: SolverName
"""Name of the solver to use."""
save_excel: bool
"""Whether to save results to Excel format."""
save_duals: bool
"""Whether to save dual variables."""
save_storage_levels: bool
"""Whether to save storage level information."""
class SolverConfig:
"""
Configuration for solver-specific settings.
This type represents solver options and parameters that control
the optimization process.
"""
solver_name: SolverName
"""Name of the solver."""
options: dict[str, Any]
"""Solver-specific options dictionary."""
time_limit: float | None
"""Maximum time limit for solving (in seconds)."""
mip_gap: float | None
"""MIP gap tolerance for mixed-integer problems."""
class OutputConfig:
"""
Configuration for output format and content settings.
This type represents options that control what results are saved
and in what format.
"""
save_excel: bool
"""Whether to save results to Excel format."""
save_duals: bool
"""Whether to save dual variables."""
save_storage_levels: bool
"""Whether to save storage level information."""
save_lp_file: bool
"""Whether to save the LP file."""
output_path: str
"""Path where output files should be saved."""
from typing import Any
from dataclasses import dataclass, field
from temoa.types.core_types import SolverName
@dataclass(slots=True)
class ScenarioConfig:
"""
Structured configuration for scenario-specific settings.
This type represents configuration options that are specific to a particular
scenario run, including solver settings, output options, and analysis modes.
"""
scenario: str
"""Name of the scenario."""
input_database: str
"""Path to the input database file."""
output_database: str
"""Path to the output database file."""
solver_name: SolverName
"""Name of the solver to use."""
save_excel: bool = False
"""Whether to save results to Excel format."""
save_duals: bool = False
"""Whether to save dual variables."""
save_storage_levels: bool = False
"""Whether to save storage level information."""
@dataclass(slots=True)
class SolverConfig:
"""
Configuration for solver-specific settings.
This type represents solver options and parameters that control
the optimization process.
"""
solver_name: SolverName
"""Name of the solver."""
options: dict[str, Any] = field(default_factory=dict)
"""Solver-specific options dictionary."""
time_limit: float | None = None
"""Maximum time limit for solving (in seconds)."""
mip_gap: float | None = None
"""MIP gap tolerance for mixed-integer problems."""
@dataclass(slots=True)
class OutputConfig:
"""
Configuration for output format and content settings.
This type represents options that control what results are saved
and in what format.
"""
save_excel: bool = False
"""Whether to save results to Excel format."""
save_duals: bool = False
"""Whether to save dual variables."""
save_storage_levels: bool = False
"""Whether to save storage level information."""
save_lp_file: bool = False
"""Whether to save the LP file."""
output_path: str = "."
"""Path where output files should be saved."""
🤖 Prompt for AI Agents
In temoa/types/core_types.py around lines 62 to 135 the three config classes
(ScenarioConfig, SolverConfig, OutputConfig) only declare annotated class
variables and so provide no instance attributes at runtime; convert each to a
dataclass (from dataclasses import dataclass, field) and declare instance fields
with sensible defaults: use empty strings for path/name fields, False for
boolean flags, dict factory for options, Optional[float] (or float | None) with
default None for time_limit and mip_gap, and supply a default or Optional for
solver_name; this ensures instances have initialized attributes and works at
runtime.

@ParticularlyPythonicBS ParticularlyPythonicBS added Maintenance Code quality fixes and deprecation management type-safety refactor labels Oct 17, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

♻️ Duplicate comments (9)
temoa/components/costs.py (4)

35-37: Past feedback not addressed: Return concrete float and avoid Any

The previous review correctly identified that this function should return a concrete float rather than a Param object, and should use object instead of Any for the throwaway parameter.

Apply this diff:

-def get_default_loan_rate(M: TemoaModel, *_: Any) -> float:
+def get_default_loan_rate(M: TemoaModel, *_: object) -> float:
     """get the default loan rate from the DefaultLoanRate param"""
-    return M.DefaultLoanRate()
+    return float(value(M.DefaultLoanRate))

87-88: Past feedback not addressed: Return concrete int

The previous review correctly identified that this function should return a concrete int rather than a Param object.

Apply this diff:

 def get_loan_life(M: TemoaModel, r: str, t: str, v: int) -> int:
-    return M.LifetimeProcess[r, t, v]
+    return int(value(M.LifetimeProcess[r, t, v]))

96-100: Past feedback not addressed: Remove fragile truthiness check

The previous review correctly identified that the truthiness check on a Pyomo Set can be misleading. The set comprehension will handle an empty source correctly, so the guard is unnecessary.

Apply this diff:

 def CostFixedIndices(M: TemoaModel) -> set[tuple[str, int, str, int]]:
-    # we pull the unlimited capacity techs from this index.  They cannot have fixed costs
-    if M.activeActivity_rptv:
-        return {(r, p, t, v) for r, p, t, v in M.activeActivity_rptv if t not in M.tech_uncap}
-    return set()
+    # Unlimited capacity techs cannot have fixed costs
+    return {
+        (r, p, t, v)
+        for r, p, t, v in M.activeActivity_rptv
+        if t not in M.tech_uncap
+    }

103-106: Past feedback not addressed: Return Python set and remove fragile truthiness check

The previous review correctly identified that this function should return a Python set (not a Pyomo Set) and should remove the misleading truthiness check.

Apply this diff:

 def CostVariableIndices(M: TemoaModel) -> set[tuple[str, int, str, int]]:
-    if M.activeActivity_rptv:
-        return M.activeActivity_rptv
-    return set()
+    return {(r, p, t, v) for r, p, t, v in M.activeActivity_rptv}
temoa/types/solver_types.py (1)

70-71: Avoid Any in protocol surface: use object.

Keep the protocol permissive without exposing Any (ANN401).

-    solver: Any
+    solver: object
temoa/types/database_types.py (2)

33-46: Return Sequence for schema access, not concrete list.

Keep API flexible and consistent with tables: Mapping[..., Sequence[...] ].

-    def get_table_columns(self, table: TableName) -> list[ColumnName]:
+    def get_table_columns(self, table: TableName) -> Sequence[ColumnName]:

61-135: Optional: If rows are dict-like, TypedDicts give stronger guarantees.

If these rows are dictionaries, replace Protocols with TypedDicts for field-level typing; otherwise keep as-is.

temoa/types/core_types.py (2)

72-92: Provide defaults for ScenarioConfig flags to simplify construction.

Reduces boilerplate and aligns with prior guidance.

-    save_excel: bool
+    save_excel: bool = False
@@
-    save_duals: bool
+    save_duals: bool = False
@@
-    save_storage_levels: bool
+    save_storage_levels: bool = False

125-139: Provide sensible defaults for OutputConfig.

Defaults make the config ergonomic and safer.

-    save_excel: bool
+    save_excel: bool = False
@@
-    save_duals: bool
+    save_duals: bool = False
@@
-    save_storage_levels: bool
+    save_storage_levels: bool = False
@@
-    save_lp_file: bool
+    save_lp_file: bool = False
@@
-    output_path: str
+    output_path: str = "."
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48afe58 and 7a50db4.

📒 Files selected for processing (6)
  • temoa/components/costs.py (12 hunks)
  • temoa/types/core_types.py (1 hunks)
  • temoa/types/database_types.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/solver_types.py (1 hunks)
  • temoa/types/validation_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
  • PyomoVar (41-48)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
temoa/components/costs.py (1)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
🪛 Ruff (0.14.0)
temoa/components/costs.py

35-35: Dynamically typed expressions (typing.Any) are disallowed in *_

(ANN401)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (8)
temoa/components/costs.py (4)

109-119: LGTM: Type annotations look correct

The function signature and return type are properly typed, and the set comprehension correctly produces a Python set.


175-175: LGTM: Explicit casts improve type safety

The explicit int() casts ensure that Pyomo parameters are converted to concrete Python integers before use in calculations, and the parameter type annotations improve clarity.

Also applies to: 187-187, 190-190, 241-241, 291-293


302-302: Good improvement over Any

The return type float | Expression is a significant improvement over the previous float | Any. The union with float accounts for edge cases where the sum might yield a numeric rather than an Expression object.


503-503: LGTM: Function signatures properly typed

All three function signatures have appropriate type annotations that accurately reflect their return types and parameters.

Also applies to: 646-646, 690-690

temoa/types/validation_types.py (3)

1-11: LGTM! Clean module structure.

The module docstring is clear and the imports are appropriately scoped to standard library types.


13-29: LGTM! Well-designed enum.

The str + Enum inheritance pattern is a Python best practice that provides both string compatibility and type safety.


228-235: LGTM! Export list is properly configured.

The RUF022 suppression has been correctly applied per previous review feedback, preserving the intentional grouping.

temoa/types/model_types.py (1)

152-228: TemoaModel should not be exported from temoa/types/model_types.py—remove it from all.

The review comment identifies a real problem: TemoaModel is listed in __all__ but only defined under TYPE_CHECKING, making it unavailable at runtime. However, the proposed fix is incorrect.

The root cause is architectural: temoa/types/model_types.py is a type definitions module providing type stubs for the actual TemoaModel class, which lives in temoa/core/model.py. Throughout the codebase, TemoaModel is imported from temoa.core.model or the top-level temoa package—never from temoa.types.model_types.

The correct fix is to remove TemoaModel from __all__ in temoa/types/model_types.py (line 251), not to add a runtime alias. The types module should export only type definitions and protocols, not the canonical runtime class.

Likely an incorrect or invalid review comment.

Comment on lines +94 to +126
@dataclass(slots=True)
class ValidationResult:
"""
Represents the complete result of a validation operation.

This dataclass aggregates all validation errors and warnings found
during a validation operation, along with summary information.

Attributes:
errors: List of validation errors found
warnings: List of validation warnings found
is_valid: Whether the validation passed (no errors)
summary: Optional summary message
"""

errors: list[ValidationError]
warnings: list[ValidationWarning]
is_valid: bool
summary: str | None = None

@classmethod
def create_success(cls, summary: str | None = None) -> 'ValidationResult':
"""
Create a successful validation result with no errors or warnings.

Args:
summary: Optional summary message

Returns:
ValidationResult indicating success
"""
return cls(errors=[], warnings=[], is_valid=True, summary=summary)

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding post_init validation to prevent inconsistent state.

Direct instantiation can create inconsistent ValidationResult objects (e.g., errors present but is_valid=True). While the factory methods maintain consistency, adding validation strengthens the type's invariants.

 @dataclass(slots=True)
 class ValidationResult:
     """
     Represents the complete result of a validation operation.
 
     This dataclass aggregates all validation errors and warnings found
     during a validation operation, along with summary information.
 
     Attributes:
         errors: List of validation errors found
         warnings: List of validation warnings found
         is_valid: Whether the validation passed (no errors)
         summary: Optional summary message
     """
 
     errors: list[ValidationError]
     warnings: list[ValidationWarning]
     is_valid: bool
     summary: str | None = None
+
+    def __post_init__(self) -> None:
+        """Validate consistency of is_valid with errors list."""
+        if self.errors and self.is_valid:
+            raise ValueError("ValidationResult cannot be valid with errors present")
+        if not self.errors and not self.is_valid:
+            raise ValueError("ValidationResult cannot be invalid without errors")
📝 Committable suggestion

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

Suggested change
@dataclass(slots=True)
class ValidationResult:
"""
Represents the complete result of a validation operation.
This dataclass aggregates all validation errors and warnings found
during a validation operation, along with summary information.
Attributes:
errors: List of validation errors found
warnings: List of validation warnings found
is_valid: Whether the validation passed (no errors)
summary: Optional summary message
"""
errors: list[ValidationError]
warnings: list[ValidationWarning]
is_valid: bool
summary: str | None = None
@classmethod
def create_success(cls, summary: str | None = None) -> 'ValidationResult':
"""
Create a successful validation result with no errors or warnings.
Args:
summary: Optional summary message
Returns:
ValidationResult indicating success
"""
return cls(errors=[], warnings=[], is_valid=True, summary=summary)
@dataclass(slots=True)
class ValidationResult:
"""
Represents the complete result of a validation operation.
This dataclass aggregates all validation errors and warnings found
during a validation operation, along with summary information.
Attributes:
errors: List of validation errors found
warnings: List of validation warnings found
is_valid: Whether the validation passed (no errors)
summary: Optional summary message
"""
errors: list[ValidationError]
warnings: list[ValidationWarning]
is_valid: bool
summary: str | None = None
def __post_init__(self) -> None:
"""Validate consistency of is_valid with errors list."""
if self.errors and self.is_valid:
raise ValueError("ValidationResult cannot be valid with errors present")
if not self.errors and not self.is_valid:
raise ValueError("ValidationResult cannot be invalid without errors")
@classmethod
def create_success(cls, summary: str | None = None) -> 'ValidationResult':
"""
Create a successful validation result with no errors or warnings.
Args:
summary: Optional summary message
Returns:
ValidationResult indicating success
"""
return cls(errors=[], warnings=[], is_valid=True, summary=summary)
🤖 Prompt for AI Agents
In temoa/types/validation_types.py around lines 94 to 126, the ValidationResult
dataclass can be instantiated into inconsistent states (for example errors
non-empty while is_valid is True); add a __post_init__ method that enforces
invariants by validating types and consistency: ensure errors and warnings are
lists (or tuples) of ValidationError/ValidationWarning, coerce None to empty
lists if needed, and raise a ValueError when is_valid is True but errors is
non-empty (or when is_valid is False but errors is empty if you prefer that
invariant). Keep the factory methods unchanged but rely on __post_init__ to
prevent direct construction of invalid instances and include clear error
messages describing the violated invariant.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
temoa/types/model_types.py (1)

252-296: Export the SparseDict alias in all.

SparseDict is defined at line 59 as a canonical alias for Sparsedict, but only Sparsedict appears in __all__. Export both names for consistency and to support the preferred naming style.

Apply this diff:

     'TechClassification',
     'Sparsedict',
+    'SparseDict',
     # Named tuples for indexing
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf5526 and a65f147.

📒 Files selected for processing (3)
  • temoa/types/database_types.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/validation_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
  • PyomoVar (41-48)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
🪛 Ruff (0.14.0)
temoa/types/validation_types.py

146-146: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (2)
temoa/types/validation_types.py (1)

94-228: Well-structured validation result implementation with previous feedback addressed.

The code successfully incorporates most previous review suggestions:

  • Factory methods maintain consistency
  • create_failure() now validates non-empty errors (lines 145-146)
  • add_error() correctly maintains is_valid state (line 176)
  • Boolean checks use idiomatic bool() (lines 196, 200)

The __post_init__ validation from previous reviews remains unimplemented, but this is acceptable given the current safeguards. Direct instantiation can still create inconsistent states (e.g., errors=[] with is_valid=False), but the factory methods prevent this. If you expect direct instantiation by consumers, consider adding the validation; otherwise, the current design is sufficient.

Regarding the static analysis hint (TRY003) on line 146: the exception message is clear, contextual, and appropriately scoped. You can safely ignore this hint.

temoa/types/database_types.py (1)

62-145: LGTM: Row protocols and query result types are well-designed.

The row protocols provide clear, type-safe interfaces for database records, and the query result aliases appropriately use abstract container types (Sequence, Mapping) with object instead of Any.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (1)
temoa/types/database_types.py (1)

61-135: If rows are dict-like, prefer TypedDicts over Protocols.

Stronger field guarantees and better tooling if actual rows are dicts.

I can draft TypedDict variants if you confirm the row shapes are dicts.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a65f147 and 845e4ac.

📒 Files selected for processing (3)
  • temoa/types/database_types.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/validation_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
  • PyomoVar (41-48)
temoa/core/model.py (1)
  • TemoaModel (96-1106)
🪛 Ruff (0.14.0)
temoa/types/validation_types.py

146-146: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (8)
temoa/types/validation_types.py (6)

1-11: LGTM: Clean module setup.

The module docstring clearly describes the purpose, and imports are minimal and appropriate.


13-29: LGTM: Well-designed severity enum.

The string enum pattern enables easy serialization while maintaining type safety. Documentation is clear and comprehensive.


31-57: LGTM: ValidationError is well-designed.

The dataclass correctly uses slots, includes helpful context documentation with examples (as requested in prior review), and provides clear string formatting.


59-92: LGTM: ValidationWarning provides good ergonomics.

The convenience wrapper for warnings with a conversion method to ValidationError is a clean design pattern. The slots and documentation are appropriate.


94-228: LGTM: ValidationResult provides comprehensive functionality.

The factory methods, mutators, and helper methods create a well-designed API. The implementation correctly:

  • Validates non-empty errors in create_failure()
  • Updates is_valid when errors are added via add_error()
  • Uses idiomatic boolean checks with bool()
  • Provides clear string formatting

230-237: LGTM: Export list is complete and properly annotated.

All public types are correctly exported, and the noqa comment appropriately preserves the intended grouping while suppressing the linter rule.

temoa/types/model_types.py (1)

250-297: Exports look consistent and suppression is appropriate.

all grouping with RUF022 suppression is fine.

temoa/types/database_types.py (1)

148-166: LGTM on schema/query type surfaces.

Good use of Mapping/Sequence and RUF022 suppression.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

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

⚠️ Outside diff range comments (3)
temoa/components/technology.py (3)

45-52: Return precise alias instead of Any-heavy tuple.

This returns M.activeActivity_rptv; use the set alias (or concrete tuple types) to improve type checking.

-def ModelProcessLifeIndices(M: 'TemoaModel') -> set[tuple[Any, Any, Any, Any]] | None:
+from temoa.types import ActiveActivitySet  # at top (TYPE_CHECKING not required)
+
+def ModelProcessLifeIndices(M: 'TemoaModel') -> ActiveActivitySet:

142-146: Use sparse_iterkeys() (portable) instead of sparse_keys().

Pyomo commonly exposes sparse_iterkeys(); sparse_keys() may be unavailable depending on version.

-    exist_indices = M.ExistingCapacity.sparse_keys()
+    exist_indices = set(M.ExistingCapacity.sparse_iterkeys())

414-425: Bug: num_seg uses stale p outside the loop. Compute per (r,p,...) inside the loop.

Current code reads len(M.TimeSeason[p]) using the last p seen; move the computation inside the loop.

-    num_seg = len(M.TimeSeason[p]) * len(M.time_of_day)
-    for (r, p, i, t, v, o), count in count_rpitvo.items():
+    for (r, p, i, t, v, o), count in count_rpitvo.items():
+        num_seg = len(M.TimeSeason[p]) * len(M.time_of_day)
         if count > 0:
             M.isEfficiencyVariable[r, p, i, t, v, o] = True
             if count < num_seg:
♻️ Duplicate comments (2)
temoa/temoa_model/temoa_model.py (1)

1109-1111: Silence unused model arg in progress_check.

Rename to underscore to satisfy ARG001.

-def progress_check(M: 'TemoaModel', checkpoint: str) -> None:
+def progress_check(_: 'TemoaModel', checkpoint: str) -> None:
temoa/core/model.py (1)

1110-1112: Silence unused model arg in progress_check.

Rename param to underscore.

-def progress_check(M: TemoaModel, checkpoint: str) -> None:
+def progress_check(_: TemoaModel, checkpoint: str) -> None:
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 845e4ac and 01ba8c9.

📒 Files selected for processing (10)
  • temoa/components/emissions.py (3 hunks)
  • temoa/components/flows.py (6 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/components/time.py (13 hunks)
  • temoa/core/model.py (8 hunks)
  • temoa/temoa_model/temoa_model.py (8 hunks)
  • temoa/types/database_types.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/pyomo_opt_stubs.pyi (1 hunks)
  • temoa/types/solver_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
temoa/types/model_types.py (2)
temoa/types/pyomo_stubs.pyi (7)
  • AbstractModel (12-20)
  • PyomoBuildAction (64-67)
  • PyomoConstraint (50-55)
  • PyomoObjective (57-62)
  • PyomoParam (32-39)
  • PyomoSet (22-30)
  • PyomoVar (41-48)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/types/solver_types.py (1)
temoa/types/pyomo_opt_stubs.pyi (4)
  • SolverResults (11-17)
  • SolverStatus (19-26)
  • TerminationCondition (28-47)
  • solver (17-17)
temoa/temoa_model/temoa_model.py (4)
temoa/types/pyomo_stubs.pyi (1)
  • AbstractModel (12-20)
temoa/core/model.py (3)
  • CreateSparseDicts (61-94)
  • TemoaModel (97-1107)
  • progress_check (1110-1112)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/components/limits.py (3)
  • LimitGrowthCapacity (856-955)
  • LimitGrowthNewCapacity (970-1068)
  • LimitGrowthNewCapacityDelta (1085-1208)
temoa/components/technology.py (1)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/components/time.py (1)
temoa/components/commodities.py (1)
  • get_str_padding (757-758)
temoa/core/model.py (3)
temoa/types/pyomo_stubs.pyi (1)
  • AbstractModel (12-20)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/components/limits.py (3)
  • LimitGrowthCapacity (856-955)
  • LimitGrowthNewCapacity (970-1068)
  • LimitGrowthNewCapacityDelta (1085-1208)
🪛 Ruff (0.14.0)
temoa/temoa_model/temoa_model.py

1109-1109: Unused function argument: M

(ARG001)

temoa/components/technology.py

65-65: Unused function argument: p

(ARG001)


76-76: Unused function argument: v

(ARG001)

temoa/components/time.py

111-111: Dynamically typed expressions (typing.Any) are disallowed in obj

(ANN401)


116-116: Use format specifiers instead of percent format

(UP031)


223-223: Unused function argument: p

(ARG001)

temoa/core/model.py

1110-1110: Unused function argument: M

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (4)
temoa/types/pyomo_opt_stubs.pyi (3)

1-9: LGTM: Clear documentation and appropriate imports.

The file header and imports are well-structured for a type stub file.


19-26: No issues found. SolverStatus enum stub is accurate.

The Pyomo SolverStatus enum contains members: ok, warning, error, aborted, and unknown, which matches exactly the values defined in the stub file. The enum members use the correct lowercase string values and the stub is properly documented.


11-17: SolverResults stub is verified as correct.

The stub's __getitem__ return type of Any correctly reflects that Pyomo's SolverResults.__getitem__ returns stored section objects (MapContainer/ListContainer/SolutionSet), which aligns with the codebase usage patterns where results are accessed and chained (e.g., result['Solution'].Status, result['Solution'].Constraint.items()). The @property annotation on solver matches actual property access in the code.

temoa/types/database_types.py (1)

8-20: Typed schema module looks solid.

Good use of Mapping/Sequence and precise Protocols; no blockers.

Also applies to: 22-59, 61-146, 148-166

Comment on lines +32 to 65
def FlowVariableIndices(
M: 'TemoaModel',
) -> set[RegionPeriodSeasonTimeInputTechVintageOutput] | None:
return M.activeFlow_rpsditvo


def FlowVariableAnnualIndices(M: 'TemoaModel'):
def FlowVariableAnnualIndices(
M: 'TemoaModel',
) -> ActiveFlowAnnualSet:
return M.activeFlow_rpitvo


def FlexVariablelIndices(M: 'TemoaModel'):
def FlexVariablelIndices(
M: 'TemoaModel',
) -> set[RegionPeriodSeasonTimeInputTechVintageOutput] | None:
return M.activeFlex_rpsditvo


def FlexVariableAnnualIndices(M: 'TemoaModel'):
def FlexVariableAnnualIndices(
M: 'TemoaModel',
) -> ActiveFlexAnnualSet:
return M.activeFlex_rpitvo


def FlowInStorageVariableIndices(M: 'TemoaModel'):
def FlowInStorageVariableIndices(
M: 'TemoaModel',
) -> set[RegionPeriodSeasonTimeInputTechVintageOutput] | None:
return M.activeFlowInStorage_rpsditvo


def CurtailmentVariableIndices(M: 'TemoaModel'):
def CurtailmentVariableIndices(
M: 'TemoaModel',
) -> set[RegionPeriodSeasonTimeInputTechVintageOutput] | None:
return M.activeCurtailment_rpsditvo
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Tighten return types: these initializers always return sets.

Given model attrs are initialized to set(), drop “| None” from the return annotations for clarity (unless Pyomo requires None for empty). Your call if legacy behavior depends on None.

Also applies to: 38-41, 44-47, 50-53, 56-59, 62-65

🤖 Prompt for AI Agents
In temoa/components/flows.py around lines 32-65, several index initializer
functions (FlowVariableIndices, FlexVariablelIndices,
FlowInStorageVariableIndices, CurtailmentVariableIndices) are annotated to
return set[RegionPeriodSeasonTimeInputTechVintageOutput] | None even though the
corresponding model attributes are always initialized to set(); remove the "|
None" from these return type annotations so they return just
set[RegionPeriodSeasonTimeInputTechVintageOutput]; ensure any imports or type
aliases still resolve and only keep None if Pyomo or legacy behavior actually
requires it.

Comment on lines 101 to 103
M.commodityBalance_rpc = commodity_upstream.intersection(commodity_downstream) # type: ignore[assignment]

# 2. Active Flow Indices (Time-Sliced)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove type ignore by matching the declared type (dict vs set).

If M.commodityBalance_rpc remains a dict, wrap the set into a dict (or, preferably, change the annotation to a set as suggested in the model file).

-    M.commodityBalance_rpc = commodity_upstream.intersection(commodity_downstream)  # type: ignore[assignment]
+    M.commodityBalance_rpc = {
+        k: True for k in commodity_upstream.intersection(commodity_downstream)
+    }

If you switch the annotation to a set (recommended), keep the current assignment and drop the ignore. Based on learnings.

🤖 Prompt for AI Agents
In temoa/components/flows.py around lines 101 to 103, M.commodityBalance_rpc is
being assigned a set (the intersection of commodity_upstream and
commodity_downstream) while its declared type is a dict, so remove the type
ignore by making the types consistent: either wrap the set into a dict structure
that matches the declared dict type (e.g., map keys to desired values) or,
preferably, change the M.commodityBalance_rpc annotation to a set type to match
the current assignment and then drop the "# type: ignore[assignment]" comment.

Comment on lines +181 to +188
M.seasonalStorageLevelIndices_rpstv = {
(r, p, s_stor, t, v)
for r, p, t in M.storageVintages
if t in M.tech_seasonal_storage
for v in M.storageVintages[r, p, t]
for _p, s_stor in M.sequential_to_season
if _p == p
)
}
Copy link

@coderabbitai coderabbitai bot Oct 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: using sequential_to_season keys, not values, for seasonal storage indices.

You’re unpacking dict keys; the last element should be the mapped season value, not s_seq. Use .items() and bind the value.

-    M.seasonalStorageLevelIndices_rpstv = {
-        (r, p, s_stor, t, v)
-        for r, p, t in M.storageVintages
-        if t in M.tech_seasonal_storage
-        for v in M.storageVintages[r, p, t]
-        for _p, s_stor in M.sequential_to_season
-        if _p == p
-    }
+    M.seasonalStorageLevelIndices_rpstv = {
+        (r, p, s, t, v)
+        for r, p, t in M.storageVintages
+        if t in M.tech_seasonal_storage
+        for v in M.storageVintages[r, p, t]
+        for (_p, s_seq), s in M.sequential_to_season.items()
+        if _p == p
+    }
📝 Committable suggestion

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

Suggested change
M.seasonalStorageLevelIndices_rpstv = {
(r, p, s_stor, t, v)
for r, p, t in M.storageVintages
if t in M.tech_seasonal_storage
for v in M.storageVintages[r, p, t]
for _p, s_stor in M.sequential_to_season
if _p == p
)
}
M.seasonalStorageLevelIndices_rpstv = {
(r, p, s, t, v)
for r, p, t in M.storageVintages
if t in M.tech_seasonal_storage
for v in M.storageVintages[r, p, t]
for (_p, s_seq), s in M.sequential_to_season.items()
if _p == p
}
🤖 Prompt for AI Agents
In temoа/components/flows.py around lines 181–188 the set comprehension is
iterating M.sequential_to_season as keys only, so it binds the sequence key
instead of the mapped seasonal value; change the iteration to
M.sequential_to_season.items() and bind (seq_key, season_value) (or similar
names), compare seq_key to p and use season_value as s_stor in the comprehension
so the season index comes from the dict value rather than its key.

Copy link
Member Author

Choose a reason for hiding this comment

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

create an issue for this

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

I'll create a GitHub issue for this bug.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the seasonal storage indices bug

gh issue create \
  --repo TemoaProject/temoa \
  --title "Bug: seasonal storage indices use dict keys instead of values in flows.py" \
  --body "## Description

A bug was identified in \`temoa/components/flows.py\` in the \`create_commodity_balance_and_flow_sets\` function around lines 181-188.

The set comprehension for \`M.seasonalStorageLevelIndices_rpstv\` iterates over \`M.sequential_to_season\` as keys only, binding \`s_stor\` to the sequence key instead of the mapped seasonal value. The iteration should use \`.items()\` to properly unpack both the key and value.

## Current Code

\`\`\`python
M.seasonalStorageLevelIndices_rpstv = {
    (r, p, s_stor, t, v)
    for r, p, t in M.storageVintages
    if t in M.tech_seasonal_storage
    for v in M.storageVintages[r, p, t]
    for _p, s_stor in M.sequential_to_season
    if _p == p
}
\`\`\`

## Expected Behavior

The iteration should use \`.items()\` to bind the dict key to \`(_p, s_seq)\` and the value to \`s\` (or \`s_stor\`), ensuring the season index comes from the dict value rather than its key.

## References

- Pull Request: https://github.com/TemoaProject/temoa/pull/165
- Original Comment: https://github.com/TemoaProject/temoa/pull/165#discussion_r2441112094
- Reported by: @ParticularlyPythonicBS" \
  --assignee ParticularlyPythonicBS

Length of output: 48


@ParticularlyPythonicBS I've created issue #170 to track this bug. The issue includes the description of the problem, the current code, expected behavior, and links back to this PR and comment.

Comment on lines +98 to +106
# Time-related sets
time_exist: PyomoSet
time_future: PyomoSet
time_optimize: PyomoSet
vintage_exist: PyomoSet
vintage_optimize: PyomoSet
time_season: PyomoSet
time_of_day: PyomoSet

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add missing TimeSeason attribute in Protocol and stub.

Used extensively (e.g., M.TimeSeason[p]). Add it for structural typing parity.

Apply:

 class TemoaModelProtocol(Protocol):
@@
-    time_season: PyomoSet
+    time_season: PyomoSet
     time_of_day: PyomoSet
+    # Indexed seasonal set by period
+    TimeSeason: PyomoSet
@@
 class TemoaModel(AbstractModel):
@@
-        time_season: PyomoSet
+        time_season: PyomoSet
         time_of_day: PyomoSet
+        # Indexed seasonal set by period
+        TimeSeason: PyomoSet

Also applies to: 162-171

🤖 Prompt for AI Agents
In temoa/types/model_types.py around lines 98-106 (and also apply same change to
lines 162-171), the Protocol and stub are missing the TimeSeason attribute
(TimeSeason: PyomoSet) used elsewhere (e.g., M.TimeSeason[p]); add a new
attribute named TimeSeason typed as PyomoSet to the Protocol and to the
corresponding stub definitions so structural typing matches usage and prevents
attribute errors.

Comment on lines +28 to +47
class TerminationCondition(Enum):
"""Stub for Pyomo TerminationCondition enum."""

OPTIMAL = 'optimal'
INFEASIBLE = 'infeasible'
UNBOUNDED = 'unbounded'
INFEASIBLE_OR_UNBOUNDED = 'infeasibleOrUnbounded'
MAX_TIME_LIMIT = 'maxTimeLimit'
MAX_ITERATIONS = 'maxIterations'
MAX_EVALUATIONS = 'maxEvaluations'
MIN_STEP_LENGTH = 'minStepLength'
MIN_FUNCTION_VALUE = 'minFunctionValue'
OTHER = 'other'
UNKNOWN = 'unknown'
SOLVER_FAILURE = 'solverFailure'
INTERNAL_SOLVER_ERROR = 'internalSolverError'
ERROR = 'error'
USER_INTERRUPT = 'userInterrupt'
RESOURCE_INTERRUPT = 'resourceInterrupt'
LICENSING_PROBLEMS = 'licensingProblems'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify TerminationCondition enum completeness and accuracy.

The enum defines 17 termination conditions with mixed-case string values (e.g., 'optimal', 'maxTimeLimit'). These string literals must exactly match Pyomo's actual TerminationCondition enum values to ensure type stubs accurately model runtime behavior.

Run the following script to verify the TerminationCondition enum:

Additionally, please verify against the Pyomo 6.9.2 documentation to ensure no commonly used termination conditions are missing:


🏁 Script executed:

#!/bin/bash
# Description: Verify TerminationCondition enum values match Pyomo

# Search for TerminationCondition enum definition
ast-grep --pattern $'class TerminationCondition($$$):
  $$$'

# Also search with ripgrep
rg -nP --type=py -C20 'class TerminationCondition'

# Look for all enum member definitions
rg -nP --type=py 'TerminationCondition\.[A-Z_]+\s*='

Length of output: 3746


🌐 Web query:

Pyomo 6.9.2 TerminationCondition enum values

💡 Result:

Here are the TerminationCondition enum members in Pyomo 6.9.2 (name = value, with short description):

  • convergenceCriteriaSatisfied = 0 — convergence criteria satisfied. [1]
  • maxTimeLimit = 1 — reached time limit. [1]
  • iterationLimit = 2 — reached iteration limit. [1]
  • objectiveLimit = 3 — reached objective cutoff/limit. [1]
  • minStepLength = 4 — minimum step length reached. [1]
  • unbounded = 5 — problem found unbounded. [1]
  • provenInfeasible = 6 — problem proven infeasible. [1]
  • locallyInfeasible = 7 — locally infeasible (used in some interfaces). [2]
  • infeasibleOrUnbounded = 8 — infeasible or unbounded. [2]
  • error = 9 — generic error. [2]
  • interrupted = 10 — solver was interrupted. [2]
  • licensingProblems = 11 — licensing problems. [2]
  • unknown = (default) — unknown / default value. [2]

Sources: Pyomo 6.9.2 API/docs. [1] pyomo.contrib.solver.common.results module (TerminationCondition). [2] pyomo.opt.results.solver TerminationCondition listing.


Stub TerminationCondition enum does not match Pyomo 6.9.2 implementation.

The actual Pyomo 6.9.2 TerminationCondition enum uses integer-valued members (convergenceCriteriaSatisfied=0, maxTimeLimit=1, iterationLimit=2, objectiveLimit=3, minStepLength=4, unbounded=5, provenInfeasible=6, locallyInfeasible=7, infeasibleOrUnbounded=8, error=9, interrupted=10, licensingProblems=11, unknown as default), not string values. The stub incorrectly defines 17 string-valued members with names like OPTIMAL='optimal' and MAX_TIME_LIMIT='maxTimeLimit', which do not correspond to Pyomo's actual member names and types. Key misalignments:

  • Value type: Stub uses strings; Pyomo uses integers
  • Missing: convergenceCriteriaSatisfied, objectiveLimit, provenInfeasible, locallyInfeasible, iterationLimit
  • Incorrect: OPTIMAL, USER_INTERRUPT, RESOURCE_INTERRUPT, INTERNAL_SOLVER_ERROR, SOLVER_FAILURE, OTHER are not in Pyomo
  • Renamed: MAX_ITERATIONS should be iterationLimit; MAX_EVALUATIONS has no Pyomo equivalent

Redefine the enum to match Pyomo's actual integer-based structure with correct member names.

🤖 Prompt for AI Agents
In temoa/types/pyomo_opt_stubs.pyi around lines 28-47, the TerminationCondition
enum is incorrect: it defines many string-valued members that don't match Pyomo
6.9.2. Replace the current members with the Pyomo 6.9.2 integer-valued members
(e.g. convergenceCriteriaSatisfied=0, maxTimeLimit=1, iterationLimit=2,
objectiveLimit=3, minStepLength=4, unbounded=5, provenInfeasible=6,
locallyInfeasible=7, infeasibleOrUnbounded=8, error=9, interrupted=10,
licensingProblems=11, unknown as the default), remove the non-Pyomo names
(OPTIMAL, USER_INTERRUPT, RESOURCE_INTERRUPT, INTERNAL_SOLVER_ERROR,
SOLVER_FAILURE, OTHER, MAX_EVALUATIONS, etc.), and ensure the enum members use
integer values and correct names to match Pyomo 6.9.2.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (3)
temoa/components/operations.py (2)

302-314: Fix denominator: compute hours for each slice separately.

Both ramp-up/down day and season constraints divide the next-slice activity by the current slice’s hours, violating the documented equations and mis-scaling when SegFrac differs.

@@ def RampUpDay_Constraint(...
-    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
@@
-    hourly_activity_sd_next = (
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )
@@ def RampDownDay_Constraint(...
-    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
@@
-    hourly_activity_sd_next = (
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )
@@ def RampUpSeason_Constraint(...
-    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
@@
-    hourly_activity_sd_next = (
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )
@@ def RampDownSeason_Constraint(...
-    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust = value(M.SegFrac[p, s, d]) * value(M.DaysPerPeriod) * 24
+    hours_adjust_next = value(M.SegFrac[p, s_next, d_next]) * value(M.DaysPerPeriod) * 24
@@
-    hourly_activity_sd_next = (
+    hourly_activity_sd_next = (
         sum(
             M.V_FlowOut[r, p, s_next, d_next, S_i, t, v, S_o]
             for S_i in M.processInputs[r, p, t, v]
             for S_o in M.processOutputsByInput[r, p, t, v, S_i]
         )
-        / hours_adjust
+        / hours_adjust_next
     )

Also applies to: 399-406, 466-473, 533-540


103-115: Critical: Missing _p == p filter allows cross-period indices.

RampDownSeasonConstraintIndices omits the period filter present in RampUpSeasonConstraintIndices (line 86). This can create invalid indices with mismatched periods (_p ≠ p), causing KeyErrors or cross-period references.

Add the missing filter at line 109:

     indices = {
         (r, p, s, s_next, t, v)
         for r, p, t in M.rampDownVintages
         for v in M.rampDownVintages[r, p, t]
         for _p, s_seq, s in M.ordered_season_sequential
+        if _p == p
         for s_seq_next in (M.time_next_sequential[p, s_seq],)  # next sequential season
         for s_next in (
             M.sequential_to_season[p, s_seq_next],
         )  # next sequential season's matching season
         if s_next != M.time_next[p, s, M.time_of_day.last()][0]
     }
temoa/components/technology.py (1)

96-122: Cast PeriodLength to int for range.

Ensure range() receives ints; also return 1.0 for float consistency.

-    period_length = value(M.PeriodLength[p])
+    period_length = int(value(M.PeriodLength[p]))
@@
-        return 1
+        return 1.0
♻️ Duplicate comments (15)
temoa/components/time.py (2)

194-196: Return precise types: Season, TimeOfDay (not str).

The function returns Season/TimeOfDay instances. Tighten the annotation.

-def loop_period_next_timeslice(
-    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
-) -> tuple[str, str]:
+def loop_period_next_timeslice(
+    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
+) -> tuple['Season', 'TimeOfDay']:

221-223: Mark unused parameter and tighten return types.

Rename p to _p (ARG001) and return Season/TimeOfDay.

-def loop_season_next_timeslice(
-    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
-) -> tuple[str, str]:
+def loop_season_next_timeslice(
+    M: 'TemoaModel', _p: 'Period', s: 'Season', d: 'TimeOfDay'
+) -> tuple['Season', 'TimeOfDay']:
temoa/components/technology.py (2)

66-74: Guard against KeyError and silence unused p.

M.isSurvivalCurveProcess[r, t, v] may be unset during Param default evaluation; use .get. Also rename p to _p.

-def get_default_survival(
-    M: 'TemoaModel', r: 'Region', p: 'Period', t: 'Technology', v: 'Vintage'
-) -> float:
+def get_default_survival(
+    M: 'TemoaModel', r: 'Region', _p: 'Period', t: 'Technology', v: 'Vintage'
+) -> float:
@@
-    return 0.0 if M.isSurvivalCurveProcess[r, t, v] else 1.0
+    return 0.0 if getattr(M, "isSurvivalCurveProcess", {}).get((r, t, v), False) else 1.0
#!/bin/bash
# Verify initialization order and presence of isSurvivalCurveProcess before LifetimeSurvivalCurve
rg -nC3 "LifetimeSurvivalCurve.*default=.*get_default_survival" temoa | cat
rg -nC3 "\bisSurvivalCurveProcess\b" temoa/core temoa/temoa_model | cat

77-79: Silence unused v in initializer.

Rename to _v to satisfy ARG001 without behavior change.

-def get_default_process_lifetime(
-    M: 'TemoaModel', r: 'Region', t: 'Technology', v: 'Vintage'
+def get_default_process_lifetime(
+    M: 'TemoaModel', r: 'Region', t: 'Technology', _v: 'Vintage'
 ) -> int:
temoa/components/capacity.py (11)

89-97: Underscore unused comprehension vars.

Avoid unused i, o in unpacking.

-    processes = {(r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()}
+    processes = {(r, t, v) for r, _i, t, v, _o in M.Efficiency.sparse_iterkeys()}

119-122: Silence unused v in default getter.

Rename v to _v.

-def get_default_capacity_factor(
-    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage
+def get_default_capacity_factor(
+    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, _v: Vintage
 ) -> float:

154-157: Remove incorrect | None.

M.newCapacity_rtv is always a set. Align type.

-def CapacityVariableIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Technology, Vintage]] | None:
+def CapacityVariableIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Technology, Vintage]]:

178-181: Remove incorrect | None.

M.activeCapacityAvailable_rpt is a set.

-def CapacityAvailableVariableIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Technology]] | None:
+def CapacityAvailableVariableIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Technology]]:

184-192: Use a set comprehension.

More concise and faster.

-def RegionalExchangeCapacityConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Region, Period, Technology, Vintage]]:
-    indices: set[tuple[Region, Region, Period, Technology, Vintage]] = set()
-    for r_e, p, i in M.exportRegions:
-        for r_i, t, v, _o in M.exportRegions[r_e, p, i]:
-            indices.add((r_e, r_i, p, t, v))
-
-    return indices
+def RegionalExchangeCapacityConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Region, Period, Technology, Vintage]]:
+    return {
+        (r_e, r_i, p, t, v)
+        for r_e, p, i in M.exportRegions
+        for r_i, t, v, _o in M.exportRegions[r_e, p, i]
+    }

195-207: Simplify to a comprehension; drop redundant else.

Current else: return set() is unnecessary.

-def CapacityAnnualConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Technology, Vintage]]:
-    capacity_indices: set[tuple[Region, Period, Technology, Vintage]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t in M.tech_annual and t not in M.tech_demand:
-                if t not in M.tech_uncap:
-                    capacity_indices.add((r, p, t, v))
-    else:
-        return set()
-
-    return capacity_indices
+def CapacityAnnualConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Technology, Vintage]]:
+    return {
+        (r, p, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if t in M.tech_annual and t not in M.tech_demand and t not in M.tech_uncap
+    }

210-225: Same simplification applies here.

Use one comprehension; remove else.

-def CapacityConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]:
-    capacity_indices: set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t not in M.tech_annual or t in M.tech_demand:
-                if t not in M.tech_uncap:
-                    if t not in M.tech_storage:
-                        for s in M.TimeSeason[p]:
-                            for d in M.time_of_day:
-                                capacity_indices.add((r, p, s, d, t, v))
-    else:
-        return set()
-
-    return capacity_indices
+def CapacityConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]:
+    return {
+        (r, p, s, d, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if (t not in M.tech_annual or t in M.tech_demand)
+        if t not in M.tech_uncap
+        if t not in M.tech_storage
+        for s in M.TimeSeason[p]
+        for d in M.time_of_day
+    }

229-238: Fix inconsistent return annotation in deprecated function.

Align return type with actual tuple contents.

-def CapacityFactorProcessIndices(
-    M: TemoaModel,
-) -> set[tuple[str, str, str, str, int]]:
-    indices: set[tuple[Region, Season, TimeOfDay, Technology, Vintage]] = set()
+def CapacityFactorProcessIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Season, TimeOfDay, Technology, Vintage]]:
+    indices: set[tuple[Region, Season, TimeOfDay, Technology, Vintage]] = set()

241-252: Use a comprehension; drop redundant else.

Consistent with other index builders.

-def CapacityFactorTechIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Season, TimeOfDay, Technology]]:
-    all_cfs: set[tuple[Region, Period, Season, TimeOfDay, Technology]] = set()
-    if M.activeCapacityAvailable_rpt:
-        for r, p, t in M.activeCapacityAvailable_rpt:
-            for s in M.TimeSeason[p]:
-                for d in M.time_of_day:
-                    all_cfs.add((r, p, s, d, t))
-    else:
-        return set()
-    return all_cfs
+def CapacityFactorTechIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Season, TimeOfDay, Technology]]:
+    return {
+        (r, p, s, d, t)
+        for r, p, t in (M.activeCapacityAvailable_rpt or set())
+        for s in M.TimeSeason[p]
+        for d in M.time_of_day
+    }

255-258: Remove incorrect | None.

M.activeCapacityAvailable_rptv is a set.

-def CapacityAvailableVariableIndicesVintage(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Technology, Vintage]] | None:
+def CapacityAvailableVariableIndicesVintage(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Technology, Vintage]]:

266-268: Replace -> Any with a constraint-expression alias.

Adopt a ConstraintExpression alias from temoa/types (or add one if missing) and use it for all constraint rule return types to satisfy ANN401.

-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any
+# If available:
+from temoa.types import ConstraintExpression  # type: ignore[unused-ignore]  # alias to Pyomo expression/Any
@@
-def AnnualRetirement_Constraint(
-    M: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage
-) -> Any:
+def AnnualRetirement_Constraint(
+    M: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage
+) -> ConstraintExpression:
@@
-def CapacityAvailableByPeriodAndTech_Constraint(
-    M: TemoaModel, r: Region, p: Period, t: Technology
-) -> Any:
+def CapacityAvailableByPeriodAndTech_Constraint(
+    M: TemoaModel, r: Region, p: Period, t: Technology
+) -> ConstraintExpression:
@@
-def CapacityAnnual_Constraint(
-    M: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage
-) -> Any:
+def CapacityAnnual_Constraint(
+    M: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage
+) -> ConstraintExpression:
@@
-def Capacity_Constraint(
-    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage
-) -> Any:
+def Capacity_Constraint(
+    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage
+) -> ConstraintExpression:
@@
-def AdjustedCapacity_Constraint(
-    M: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage
-) -> Any:
+def AdjustedCapacity_Constraint(
+    M: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage
+) -> ConstraintExpression:
#!/bin/bash
# Check for an existing alias to use
rg -n "ConstraintExpression|ConstraintExpr" temoa/types | cat

Also applies to: 348-351, 370-372, 403-405, 477-479

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01ba8c9 and 98d4dce.

📒 Files selected for processing (7)
  • temoa/components/capacity.py (15 hunks)
  • temoa/components/operations.py (10 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/components/time.py (13 hunks)
  • temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1 hunks)
  • temoa/types/pyomo_opt_stubs.pyi (1 hunks)
  • temoa/types/solver_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
temoa/components/time.py (1)
temoa/components/commodities.py (1)
  • get_str_padding (757-758)
temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1)
temoa/types/pyomo_opt_stubs.pyi (1)
  • TerminationCondition (28-44)
temoa/types/solver_types.py (1)
temoa/types/pyomo_opt_stubs.pyi (4)
  • SolverResults (11-17)
  • SolverStatus (19-26)
  • TerminationCondition (28-44)
  • solver (17-17)
temoa/components/capacity.py (2)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/components/technology.py (2)
temoa/temoa_model/temoa_model.py (1)
  • TemoaModel (96-1106)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
🪛 Ruff (0.14.0)
temoa/components/time.py

121-121: Use explicit conversion flag

Replace with conversion flag

(RUF010)


222-222: Unused function argument: p

(ARG001)

temoa/components/operations.py

132-132: Dynamically typed expressions (typing.Any) are disallowed in BaseloadDiurnal_Constraint

(ANN401)


250-250: Dynamically typed expressions (typing.Any) are disallowed in RampUpDay_Constraint

(ANN401)


359-359: Dynamically typed expressions (typing.Any) are disallowed in RampDownDay_Constraint

(ANN401)


442-442: Dynamically typed expressions (typing.Any) are disallowed in RampUpSeason_Constraint

(ANN401)


509-509: Dynamically typed expressions (typing.Any) are disallowed in RampDownSeason_Constraint

(ANN401)

temoa/components/capacity.py

120-120: Unused function argument: v

(ARG001)


268-268: Dynamically typed expressions (typing.Any) are disallowed in AnnualRetirement_Constraint

(ANN401)


350-350: Dynamically typed expressions (typing.Any) are disallowed in CapacityAvailableByPeriodAndTech_Constraint

(ANN401)


372-372: Dynamically typed expressions (typing.Any) are disallowed in CapacityAnnual_Constraint

(ANN401)


405-405: Dynamically typed expressions (typing.Any) are disallowed in Capacity_Constraint

(ANN401)


479-479: Dynamically typed expressions (typing.Any) are disallowed in AdjustedCapacity_Constraint

(ANN401)

temoa/components/technology.py

67-67: Unused function argument: p

(ARG001)


78-78: Unused function argument: v

(ARG001)

🔇 Additional comments (11)
temoa/types/pyomo_opt_stubs.pyi (1)

28-44: Stub enum matches Pyomo 6.9.2; LGTM.

TerminationCondition members and integer values align with the target version. Based on learnings.

temoa/types/solver_types.py (1)

61-88: Typed protocol and aliases look good.

Protocol uses object; options use Mapping[str, object]; TYPE_CHECKING stubs avoid runtime dependency. LGTM.

temoa/components/technology.py (4)

13-22: Imports tidy and modernized.

Using collections.abc.Iterable and pyomo.environ.value is correct. TYPE_CHECKING block looks good.


217-233: LGTM: survival-curve scaffolding.

Clear initialization and sorted period handling with explicit typing.


331-342: LGTM: clearer set diffs and messages.

Set construction and stringification look correct and readable.

Also applies to: 350-364


380-391: LGTM: variable efficiency bookkeeping.

Defaulting flags and counters is correct and efficient.

temoa/components/capacity.py (5)

16-16: Imports and TYPE_CHECKING are consistent.

Public types imported under TYPE_CHECKING; good.

Also applies to: 25-32


140-147: LGTM: runtime selector for capacity factor.

Correctly defers to per-process when flagged.


160-169: LGTM: retirement indices logic.

Bounds check with LifetimeProcess and PeriodLength looks correct.


172-176: LGTM: annual retirement indices.

Straightforward set comprehension.


572-634: LGTM: precomputed sets built with comprehensions.

The three indices are constructed efficiently and with clear predicates.

Comment on lines +45 to 48
def ModelProcessLifeIndices(
M: 'TemoaModel',
) -> set[tuple['Region', 'Period', 'Technology', 'Vintage']] | None:
"""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return type should not include | None.

ModelProcessLifeIndices returns M.activeActivity_rptv (a set). Remove | None to match reality and Pyomo expectations.

-def ModelProcessLifeIndices(
-    M: 'TemoaModel',
-) -> set[tuple['Region', 'Period', 'Technology', 'Vintage']] | None:
+def ModelProcessLifeIndices(
+    M: 'TemoaModel',
+) -> set[tuple['Region', 'Period', 'Technology', 'Vintage']]:
🤖 Prompt for AI Agents
In temoa/components/technology.py around lines 45 to 48, the return type
annotation for ModelProcessLifeIndices incorrectly includes "| None"; change the
annotation to return only set[tuple['Region','Period','Technology','Vintage']]
(i.e., remove the "| None") so it matches the actual return value
M.activeActivity_rptv and Pyomo expectations; update any docstring/type hints
accordingly to reflect the non-optional set return.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

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

⚠️ Outside diff range comments (9)
pyproject.toml (1)

12-39: Move pytest to dev dependencies.

pytest is a test-only tool; avoid shipping it as a runtime dep.

 dependencies = [
@@
-    "pytest>=8.3.2",
@@
 ]
@@
 [dependency-groups]
 dev = [
+    "pytest>=8.3.2",
temoa/components/geography.py (1)

49-63: LGTM with tiny polish opportunity.

Logic OK; consider using a single formatted string for both log and exception to avoid duplication.

temoa/data_io/hybrid_loader.py (1)

28-28: Prefer pyomo.environ imports for stability.

Upstream best practice uses pyomo.environ; consider standardizing imports here after adding the environ stub.

-from pyomo.core import Param, Set
+from pyomo.environ import Param, Set
temoa/components/capacity.py (2)

43-50: Use precise key type for count_rptv.

Keys are (r,p,t,v). Tighten the type to improve mypy coverage.

-    count_rptv: dict[tuple[Any, Any, Any, Any], int] = {}
+    count_rptv: dict[tuple[Region, Period, Technology, Vintage], int] = {}

266-346: Unify constraint rule return typing via alias.

Multiple rules return Any (ANN401). Introduce and use a ConstraintExpression alias from temoa.types.

Example:

-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING
+from temoa.types import ConstraintExpression
...
-def AnnualRetirement_Constraint(...) -> Any:
+def AnnualRetirement_Constraint(...) -> ConstraintExpression:

Apply to: AnnualRetirement_Constraint, CapacityAvailableByPeriodAndTech_Constraint, CapacityAnnual_Constraint, Capacity_Constraint, AdjustedCapacity_Constraint.

Also applies to: 348-368, 370-401, 403-475, 477-565

temoa/components/limits.py (4)

215-222: Guard against missing reserve entries.

M.processReservePeriods[r, p] can be absent; use .get like you did above to avoid KeyError.

-    total_inp = quicksum(
+    total_inp = quicksum(
         M.V_FlowOut[r, p, s, d, S_i, t, v, S_o]
-        for (t, v) in M.processReservePeriods[r, p]
+        for (t, v) in M.processReservePeriods.get((r, p), [])
         for s in M.TimeSeason[p]
         for d in M.time_of_day
         for S_i in M.processInputs[r, p, t, v]
         for S_o in M.processOutputsByInput[r, p, t, v, S_i]
     )

620-636: Fix input-split annual-average denominator; wrong indices used.

total_inp should divide each term by its own (S_i, S_o) efficiency and iterate outputs keyed by S_i, not the fixed i. Current code biases the denominator.

-    total_inp = quicksum(
-        M.V_FlowOut[r, p, S_s, S_d, S_i, t, v, S_o]
-        / get_variable_efficiency(M, r, p, S_s, S_d, i, t, v, S_o)
-        for S_s in M.TimeSeason[p]
-        for S_d in M.time_of_day
-        for S_i in M.processInputs[r, p, t, v]
-        for S_o in M.processOutputsByInput[r, p, t, v, i]
-    )
+    total_inp = quicksum(
+        M.V_FlowOut[r, p, S_s, S_d, S_i, t, v, S_o]
+        / get_variable_efficiency(M, r, p, S_s, S_d, S_i, t, v, S_o)
+        for S_s in M.TimeSeason[p]
+        for S_d in M.time_of_day
+        for S_i in M.processInputs[r, p, t, v]
+        for S_o in M.processOutputsByInput[r, p, t, v, S_i]
+    )

516-524: Avoid KeyError when no vintages are present.

Use .get((...), []) for M.processVintages[...] in both branches to match surrounding usage.

-    if t not in M.tech_annual:
-        activity_rpst = quicksum(
-            M.V_FlowOut[_r, p, s, d, S_i, t, S_v, S_o]
-            for _r in regions
-            for S_v in M.processVintages[_r, p, t]
+    if t not in M.tech_annual:
+        activity_rpst = quicksum(
+            M.V_FlowOut[_r, p, s, d, S_i, t, S_v, S_o]
+            for _r in regions
+            for S_v in M.processVintages.get((_r, p, t), [])
             for S_i in M.processInputs[_r, p, t, S_v]
             for S_o in M.processOutputsByInput[_r, p, t, S_v, S_i]
             for d in M.time_of_day
         )
     else:
-        activity_rpst = quicksum(
+        activity_rpst = quicksum(
             M.V_FlowOutAnnual[_r, p, S_i, t, S_v, S_o] * M.SegFracPerSeason[p, s]
-            for _r in regions
-            for S_v in M.processVintages[_r, p, t]
+            for _r in regions
+            for S_v in M.processVintages.get((_r, p, t), [])
             for S_i in M.processInputs[_r, p, t, S_v]
             for S_o in M.processOutputsByInput[_r, p, t, S_v, S_i]
         )

Also applies to: 526-532


841-847: Use the actual region when checking retirement periods.

(r, t, v) should be (reg, t, v) inside the loop. Using the group token r (e.g., 'global' or 'A+B') is incorrect.

-    retirement_emissions = quicksum(
+    retirement_emissions = quicksum(
         M.V_AnnualRetirement[reg, p, t, v] * value(M.EmissionEndOfLife[reg, e, t, v])
         for reg in regions
         for (S_r, S_e, t, v) in M.EmissionEndOfLife.sparse_iterkeys()
-        if (r, t, v) in M.retirementPeriods and p in M.retirementPeriods[r, t, v]
+        if (reg, t, v) in M.retirementPeriods and p in M.retirementPeriods[reg, t, v]
         if S_r == reg and S_e == e
     )
♻️ Duplicate comments (17)
pyproject.toml (1)

36-36: gurobipy version mismatch noted (tracked separately).

Runtime is >=11.0.3 while dev is >=12.0.3. Since this is already being handled in a follow-up issue, no action here.

Also applies to: 199-201

temoa/types/model_types.py (2)

162-171: Mirror TimeSeason in the TYPE_CHECKING TemoaModel stub.

Keep the stub consistent with runtime attributes used by components.

         vintage_all: Set
         time_season: Set
         time_of_day: Set
+        # Indexed seasonal set by period
+        TimeSeason: Set

98-106: Add missing TimeSeason to the Protocol.

M.TimeSeason is used throughout (e.g., components/time.py). Include it for structural typing parity.

     # Time-related sets
     time_exist: Set
     time_future: Set
     time_optimize: Set
     vintage_exist: Set
     vintage_optimize: Set
     time_season: Set
     time_of_day: Set
+    # Indexed seasonal set by period (e.g., M.TimeSeason[p])
+    TimeSeason: Set
temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1)

354-354: Use check_optimal_termination for success detection.

Current check can misclassify successful solves.

-        return status == pyo.TerminationCondition.convergenceCriteriaSatisfied
+        return check_optimal_termination(res)
temoa/components/time.py (1)

124-128: Use generator + explicit conversion flag in join.

Removes an intermediate list and satisfies RUF010.

-            items_list: list[tuple[tuple[object, object, object], object]] = sorted(
-                [(k, M.SegFrac[k]) for k in keys]
-            )
-            items: str = '\n   '.join(f'{str(k):<{key_padding}} = {v}' for k, v in items_list)
+            items_list: list[tuple[tuple[object, object, object], object]] = sorted(
+                (k, M.SegFrac[k]) for k in keys
+            )
+            items: str = '\n   '.join(f'{k!s:<{key_padding}} = {v}' for k, v in items_list)
temoa/components/operations.py (2)

123-131: Return object for Pyomo constraint rules (repeat).

These rules should return a Pyomo relational expr or Constraint.Skip; annotate as object to avoid ANN401.

-) -> Any:
+) -> object:

Apply to:

  • BaseloadDiurnal_Constraint
  • RampUpDay_Constraint
  • RampDownDay_Constraint
  • RampUpSeason_Constraint
  • RampDownSeason_Constraint

Also applies to: 241-249, 350-358, 433-441, 500-509


14-14: Drop unused Any import.

After fixing signatures to return object, Any isn’t needed here.

-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING
temoa/types/__init__.py (1)

6-6: Either sort all or suppress RUF022 to keep semantic grouping.

Add a local noqa if you want to preserve the grouped layout.

-__all__ = [
+# ruff: noqa: RUF022
+__all__ = [
temoa/data_io/hybrid_loader.py (2)

587-588: SegFrac fallback: drop ignore after casting.

-            time_optimize = data.get('time_optimize', [])
-            fallback = [(p, 'S', 'D', 1.0) for p in time_optimize]  # type: ignore[attr-defined]
+            time_optimize = cast(list[int], data.get('time_optimize', []))
+            fallback = [(p, 'S', 'D', 1.0) for p in time_optimize]

516-519: Avoid # type: ignore; use a typed accumulator.

Cast the backing dict and use setdefault for clarity.

-            store = data.get(M.tech_group_members.name, defaultdict(list))
-            store[group_name].append(tech)  # type: ignore[index]
-            data[M.tech_group_members.name] = store
+            store = cast(dict[str, list[str]], data.get(M.tech_group_members.name, {}))
+            store.setdefault(group_name, []).append(tech)
+            data[M.tech_group_members.name] = store
temoa/components/capacity.py (7)

89-97: Prefix unused unpacked vars in comprehension.

i and o are unused; underscore them to silence ARG001 and clarify intent. This was flagged previously.

-    processes = {(r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()}
+    processes = {(r, t, v) for r, _i, t, v, _o in M.Efficiency.sparse_iterkeys()}

119-122: Silence unused parameter warning.

v isn’t used. Prefix with _ to satisfy ARG001.

-def get_default_capacity_factor(
-    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage
+def get_default_capacity_factor(
+    M: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, _v: Vintage
 ) -> float:

154-158: Drop unnecessary | None from return types.

These functions return concrete sets; Optional adds noise and breaks Pyomo initializer expectations.

-def CapacityVariableIndices(...) -> set[tuple[Region, Technology, Vintage]] | None:
+def CapacityVariableIndices(...) -> set[tuple[Region, Technology, Vintage]]:
 ...
-def CapacityAvailableVariableIndices(...) -> set[tuple[Region, Period, Technology]] | None:
+def CapacityAvailableVariableIndices(...) -> set[tuple[Region, Period, Technology]]:
 ...
-def CapacityAvailableVariableIndicesVintage(...) -> set[tuple[Region, Period, Technology, Vintage]] | None:
+def CapacityAvailableVariableIndicesVintage(...) -> set[tuple[Region, Period, Technology, Vintage]]:

Also applies to: 178-182, 255-259


184-193: Prefer a set comprehension for indices.

Cleaner and faster; no need for a temporary set.

-def RegionalExchangeCapacityConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Region, Period, Technology, Vintage]]:
-    indices: set[tuple[Region, Region, Period, Technology, Vintage]] = set()
-    for r_e, p, i in M.exportRegions:
-        for r_i, t, v, _o in M.exportRegions[r_e, p, i]:
-            indices.add((r_e, r_i, p, t, v))
-    return indices
+def RegionalExchangeCapacityConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Region, Period, Technology, Vintage]]:
+    return {
+        (r_e, r_i, p, t, v)
+        for r_e, p, i in M.exportRegions
+        for r_i, t, v, _o in M.exportRegions[r_e, p, i]
+    }

195-207: Simplify to a single set comprehension and drop redundant else.

Avoid mutation and redundant else: return set().

-def CapacityAnnualConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Technology, Vintage]]:
-    capacity_indices: set[tuple[Region, Period, Technology, Vintage]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t in M.tech_annual and t not in M.tech_demand:
-                if t not in M.tech_uncap:
-                    capacity_indices.add((r, p, t, v))
-    else:
-        return set()
-    return capacity_indices
+def CapacityAnnualConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Technology, Vintage]]:
+    return {
+        (r, p, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if t in M.tech_annual and t not in M.tech_demand and t not in M.tech_uncap
+    }

210-225: Same refactor: comprehension and no redundant else.

Match the pattern used above for clarity and performance.

-def CapacityConstraintIndices(
-    M: TemoaModel,
-) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]:
-    capacity_indices: set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] = set()
-    if M.activeActivity_rptv:
-        for r, p, t, v in M.activeActivity_rptv:
-            if t not in M.tech_annual or t in M.tech_demand:
-                if t not in M.tech_uncap:
-                    if t not in M.tech_storage:
-                        for s in M.TimeSeason[p]:
-                            for d in M.time_of_day:
-                                capacity_indices.add((r, p, s, d, t, v))
-    else:
-        return set()
-    return capacity_indices
+def CapacityConstraintIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]:
+    return {
+        (r, p, s, d, t, v)
+        for r, p, t, v in (M.activeActivity_rptv or set())
+        if t not in M.tech_annual or t in M.tech_demand
+        if t not in M.tech_uncap
+        if t not in M.tech_storage
+        for s in M.TimeSeason[p]
+        for d in M.time_of_day
+    }

228-239: Fix type annotation mismatch in deprecated function.

Signature and local indices type disagree. Align with actual tuple types (Region, Season, TimeOfDay, Technology, Vintage) or relax both to Any.

-@deprecated('switched over to validator... this set is typically VERY empty')
-def CapacityFactorProcessIndices(
-    M: TemoaModel,
-) -> set[tuple[str, str, str, str, int]]:
-    indices: set[tuple[Region, Season, TimeOfDay, Technology, Vintage]] = set()
+@deprecated('switched over to validator... this set is typically VERY empty')
+def CapacityFactorProcessIndices(
+    M: TemoaModel,
+) -> set[tuple[Region, Season, TimeOfDay, Technology, Vintage]]:
+    indices: set[tuple[Region, Season, TimeOfDay, Technology, Vintage]] = set()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98d4dce and 1bc7aaf.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • .gitignore (1 hunks)
  • pyproject.toml (1 hunks)
  • stubs/pyomo/core.pyi (1 hunks)
  • stubs/pyomo/opt/__init__.pyi (1 hunks)
  • stubs/pyomo/opt/results.pyi (1 hunks)
  • temoa/_internal/table_data_puller.py (17 hunks)
  • temoa/components/capacity.py (15 hunks)
  • temoa/components/commodities.py (15 hunks)
  • temoa/components/costs.py (22 hunks)
  • temoa/components/geography.py (6 hunks)
  • temoa/components/limits.py (53 hunks)
  • temoa/components/operations.py (10 hunks)
  • temoa/components/reserves.py (6 hunks)
  • temoa/components/storage.py (15 hunks)
  • temoa/components/technology.py (14 hunks)
  • temoa/components/time.py (15 hunks)
  • temoa/data_io/hybrid_loader.py (23 hunks)
  • temoa/data_io/loader_manifest.py (2 hunks)
  • temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
  • temoa/types/solver_types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (16)
temoa/types/model_types.py (2)
stubs/pyomo/core.pyi (9)
  • AbstractModel (27-35)
  • BuildAction (106-109)
  • Constraint (89-94)
  • Objective (96-104)
  • Param (63-73)
  • Set (37-61)
  • Var (75-87)
  • name (44-44)
  • name (70-70)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/components/time.py (1)
stubs/pyomo/core.pyi (4)
  • keys (86-86)
  • difference (59-59)
  • last (53-53)
  • first (52-52)
temoa/components/operations.py (1)
stubs/pyomo/core.pyi (5)
  • value (68-68)
  • value (80-80)
  • value (128-128)
  • first (52-52)
  • last (53-53)
stubs/pyomo/opt/__init__.pyi (1)
stubs/pyomo/opt/results.pyi (1)
  • SolverResults (11-17)
temoa/data_io/hybrid_loader.py (5)
stubs/pyomo/core.pyi (8)
  • data (42-42)
  • Set (37-61)
  • Param (63-73)
  • name (44-44)
  • name (70-70)
  • value (68-68)
  • value (80-80)
  • value (128-128)
temoa/extensions/myopic/myopic_index.py (1)
  • MyopicIndex (37-57)
temoa/data_io/loader_manifest.py (1)
  • LoadItem (26-59)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/model_checking/element_checker.py (1)
  • members (126-131)
temoa/components/geography.py (3)
stubs/pyomo/core.pyi (3)
  • value (68-68)
  • value (80-80)
  • value (128-128)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py (1)
stubs/pyomo/opt/results.pyi (1)
  • TerminationCondition (28-44)
temoa/components/technology.py (3)
stubs/pyomo/core.pyi (3)
  • value (68-68)
  • value (80-80)
  • value (128-128)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/components/costs.py (4)
stubs/pyomo/core.pyi (6)
  • Expression (116-125)
  • Var (75-87)
  • quicksum (130-130)
  • value (68-68)
  • value (80-80)
  • value (128-128)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/_internal/table_data_puller.py (1)
  • loan_costs (459-502)
temoa/data_io/loader_manifest.py (1)
stubs/pyomo/core.pyi (2)
  • Param (63-73)
  • Set (37-61)
temoa/types/solver_types.py (1)
stubs/pyomo/opt/results.pyi (4)
  • SolverResults (11-17)
  • SolverStatus (19-26)
  • TerminationCondition (28-44)
  • solver (17-17)
temoa/components/storage.py (1)
stubs/pyomo/core.pyi (1)
  • Constraint (89-94)
temoa/components/commodities.py (2)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/components/time.py (1)
  • get_str_padding (117-118)
temoa/_internal/table_data_puller.py (4)
temoa/types/model_types.py (5)
  • EI (295-302)
  • FI (305-315)
  • SLI (318-326)
  • CapData (329-334)
  • FlowType (338-345)
temoa/_internal/exchange_tech_cost_ledger.py (3)
  • CostType (22-30)
  • add_cost_record (41-52)
  • get_entries (119-143)
stubs/pyomo/core.pyi (3)
  • value (68-68)
  • value (80-80)
  • value (128-128)
temoa/components/costs.py (1)
  • pv_to_annuity (59-70)
temoa/components/capacity.py (2)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/components/limits.py (3)
stubs/pyomo/core.pyi (6)
  • Constraint (89-94)
  • quicksum (130-130)
  • value (68-68)
  • value (80-80)
  • value (128-128)
  • expr (101-101)
temoa/components/utils.py (3)
  • Operator (24-27)
  • get_variable_efficiency (41-70)
  • operator_expression (30-38)
temoa/components/technology.py (1)
  • gather_group_techs (30-37)
🪛 Ruff (0.14.0)
temoa/components/time.py

102-106: Use f-string instead of format call

Convert to f-string

(UP032)


127-127: Use explicit conversion flag

Replace with conversion flag

(RUF010)


230-230: Unused function argument: p

(ARG001)

temoa/components/operations.py

131-131: Dynamically typed expressions (typing.Any) are disallowed in BaseloadDiurnal_Constraint

(ANN401)


249-249: Dynamically typed expressions (typing.Any) are disallowed in RampUpDay_Constraint

(ANN401)


358-358: Dynamically typed expressions (typing.Any) are disallowed in RampDownDay_Constraint

(ANN401)


441-441: Dynamically typed expressions (typing.Any) are disallowed in RampUpSeason_Constraint

(ANN401)


508-508: Dynamically typed expressions (typing.Any) are disallowed in RampDownSeason_Constraint

(ANN401)

temoa/data_io/hybrid_loader.py

323-323: Boolean-typed positional argument in function definition

(FBT001)


478-478: Unused method argument: raw_data

(ARG002)


479-479: Unused method argument: filtered_data

(ARG002)


508-508: Unused method argument: raw_data

(ARG002)


525-525: Unused method argument: filtered_data

(ARG002)


549-549: Unnecessary list() call within sorted()

Remove the inner list() call

(C414)


560-560: Unused method argument: raw_data

(ARG002)


571-571: Unnecessary list() call within sorted()

Remove the inner list() call

(C414)


577-577: Unused method argument: raw_data

(ARG002)


594-594: Unused method argument: raw_data

(ARG002)


595-595: Unused method argument: filtered_data

(ARG002)


629-629: Unused method argument: raw_data

(ARG002)


641-641: Unused method argument: raw_data

(ARG002)


654-654: Unused method argument: raw_data

(ARG002)


669-669: Unused method argument: raw_data

(ARG002)


677-679: Avoid specifying long messages outside the exception class

(TRY003)


684-684: Unused method argument: raw_data

(ARG002)


696-696: Unused method argument: raw_data

(ARG002)


697-697: Unused method argument: filtered_data

(ARG002)


728-728: Unused method argument: raw_data

(ARG002)


744-744: Unused method argument: raw_data

(ARG002)


760-760: Unused method argument: raw_data

(ARG002)

temoa/components/technology.py

67-67: Unused function argument: p

(ARG001)


78-78: Unused function argument: v

(ARG001)

temoa/components/costs.py

35-35: Dynamically typed expressions (typing.Any) are disallowed in *_

(ANN401)

temoa/data_io/loader_manifest.py

20-20: Type alias ComponentType uses TypeAlias annotation instead of the type keyword

Use the type keyword

(UP040)

temoa/components/storage.py

70-70: Dynamically typed expressions (typing.Any) are disallowed in StorageEnergy_Constraint

(ANN401)


126-126: Dynamically typed expressions (typing.Any) are disallowed in SeasonalStorageEnergy_Constraint

(ANN401)


217-217: Dynamically typed expressions (typing.Any) are disallowed in StorageEnergyUpperBound_Constraint

(ANN401)


274-274: Dynamically typed expressions (typing.Any) are disallowed in SeasonalStorageEnergyUpperBound_Constraint

(ANN401)


352-352: Dynamically typed expressions (typing.Any) are disallowed in StorageChargeRate_Constraint

(ANN401)


390-390: Dynamically typed expressions (typing.Any) are disallowed in StorageDischargeRate_Constraint

(ANN401)


426-426: Dynamically typed expressions (typing.Any) are disallowed in StorageThroughput_Constraint

(ANN401)


476-476: Dynamically typed expressions (typing.Any) are disallowed in LimitStorageFraction_Constraint

(ANN401)

temoa/components/commodities.py

33-33: Dynamically typed expressions (typing.Any) are disallowed in supplied

(ANN401)


33-33: Dynamically typed expressions (typing.Any) are disallowed in demanded

(ANN401)


56-56: Dynamically typed expressions (typing.Any) are disallowed in supplied

(ANN401)


56-56: Dynamically typed expressions (typing.Any) are disallowed in demanded

(ANN401)


78-78: Dynamically typed expressions (typing.Any) are disallowed in supply

(ANN401)


151-151: Dynamically typed expressions (typing.Any) are disallowed in Demand_Constraint

(ANN401)


199-199: Dynamically typed expressions (typing.Any) are disallowed in DemandActivity_Constraint

(ANN401)


243-243: Dynamically typed expressions (typing.Any) are disallowed in CommodityBalance_Constraint

(ANN401)


477-477: Dynamically typed expressions (typing.Any) are disallowed in AnnualCommodityBalance_Constraint

(ANN401)


764-764: Dynamically typed expressions (typing.Any) are disallowed in obj

(ANN401)


769-769: Use format specifiers instead of percent format

(UP031)

temoa/_internal/table_data_puller.py

469-469: Unused function argument: kwargs

(ARG001)


517-517: Unused function argument: vintage

(ARG001)


518-518: Unused function argument: kwargs

(ARG001)

temoa/components/capacity.py

120-120: Unused function argument: v

(ARG001)


268-268: Dynamically typed expressions (typing.Any) are disallowed in AnnualRetirement_Constraint

(ANN401)


350-350: Dynamically typed expressions (typing.Any) are disallowed in CapacityAvailableByPeriodAndTech_Constraint

(ANN401)


372-372: Dynamically typed expressions (typing.Any) are disallowed in CapacityAnnual_Constraint

(ANN401)


405-405: Dynamically typed expressions (typing.Any) are disallowed in Capacity_Constraint

(ANN401)


479-479: Dynamically typed expressions (typing.Any) are disallowed in AdjustedCapacity_Constraint

(ANN401)

temoa/types/__init__.py

6-73: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)


153-156: Use X | Y for type annotations

(UP007)

temoa/components/limits.py

195-195: Dynamically typed expressions (typing.Any) are disallowed in RenewablePortfolioStandard_Constraint

(ANN401)


228-228: Dynamically typed expressions (typing.Any) are disallowed in LimitResource_Constraint

(ANN401)


285-285: Dynamically typed expressions (typing.Any) are disallowed in LimitActivityShare_Constraint

(ANN401)


365-365: Dynamically typed expressions (typing.Any) are disallowed in LimitCapacityShare_Constraint

(ANN401)


398-398: Dynamically typed expressions (typing.Any) are disallowed in LimitNewCapacityShare_Constraint

(ANN401)


431-431: Dynamically typed expressions (typing.Any) are disallowed in LimitAnnualCapacityFactor_Constraint

(ANN401)


489-489: Dynamically typed expressions (typing.Any) are disallowed in LimitSeasonalCapacityFactor_Constraint

(ANN401)


558-558: Dynamically typed expressions (typing.Any) are disallowed in LimitTechInputSplit_Constraint

(ANN401)


585-585: Dynamically typed expressions (typing.Any) are disallowed in LimitTechInputSplitAnnual_Constraint

(ANN401)


611-611: Dynamically typed expressions (typing.Any) are disallowed in LimitTechInputSplitAverage_Constraint

(ANN401)


653-653: Dynamically typed expressions (typing.Any) are disallowed in LimitTechOutputSplit_Constraint

(ANN401)


706-706: Dynamically typed expressions (typing.Any) are disallowed in LimitTechOutputSplitAnnual_Constraint

(ANN401)


738-738: Dynamically typed expressions (typing.Any) are disallowed in LimitTechOutputSplitAverage_Constraint

(ANN401)


768-768: Dynamically typed expressions (typing.Any) are disallowed in LimitEmission_Constraint

(ANN401)


869-869: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthCapacityConstraint_rule

(ANN401)


871-871: Boolean positional value in function call

(FBT003)


876-876: Dynamically typed expressions (typing.Any) are disallowed in LimitDegrowthCapacityConstraint_rule

(ANN401)


878-878: Boolean positional value in function call

(FBT003)


882-882: Boolean-typed positional argument in function definition

(FBT001)


882-882: Boolean default positional argument in function definition

(FBT002)


883-883: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthCapacity

(ANN401)


985-985: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacityConstraint_rule

(ANN401)


987-987: Boolean positional value in function call

(FBT003)


992-992: Dynamically typed expressions (typing.Any) are disallowed in LimitDegrowthNewCapacityConstraint_rule

(ANN401)


994-994: Boolean positional value in function call

(FBT003)


998-998: Boolean-typed positional argument in function definition

(FBT001)


998-998: Boolean default positional argument in function definition

(FBT002)


999-999: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacity

(ANN401)


1100-1100: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacityDeltaConstraint_rule

(ANN401)


1102-1102: Boolean positional value in function call

(FBT003)


1107-1107: Dynamically typed expressions (typing.Any) are disallowed in LimitDegrowthNewCapacityDeltaConstraint_rule

(ANN401)


1109-1109: Boolean positional value in function call

(FBT003)


1113-1113: Boolean-typed positional argument in function definition

(FBT001)


1113-1113: Boolean default positional argument in function definition

(FBT002)


1114-1114: Dynamically typed expressions (typing.Any) are disallowed in LimitGrowthNewCapacityDelta

(ANN401)


1238-1238: Dynamically typed expressions (typing.Any) are disallowed in LimitActivity_Constraint

(ANN401)


1298-1298: Dynamically typed expressions (typing.Any) are disallowed in LimitNewCapacity_Constraint

(ANN401)


1319-1319: Dynamically typed expressions (typing.Any) are disallowed in LimitCapacity_Constraint

(ANN401)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (11)
temoa/types/model_types.py (1)

229-232: Runtime alias for TemoaModel looks good.

temoa/components/time.py (1)

33-34: Heads-up: relies on M.TimeSeason being typed.

Ensure the types module exposes TimeSeason: Set on the model Protocol/stub; see suggested change in temoa/types/model_types.py.

.gitignore (1)

12-12: LGTM: keep stubs/ tracked.

temoa/data_io/loader_manifest.py (1)

50-59: LoadItem typing looks good; clear surface and runtime-safety preserved.

temoa/components/geography.py (5)

13-13: LGTM: modern import.

Using Iterable from collections.abc follows UP035.


33-41: LGTM: clear region grouping helper.

Return type and branching read well.


66-77: LGTM: deprecated wrapper retained with clear message.

Decorator and return type are fine.


85-87: LGTM: constraint rule returns object, not Any.

Matches Pyomo expectations and silences ANN401.


140-144: Code correctly uses sparse_iterkeys as part of exchange technology processing loop; type stub dependency already noted as addressed in separate comment.

The code at lines 140-144 forms part of the loop initiated by M.Efficiency.sparse_iterkeys() at line 129. The lifetime calculation and export/import region dictionary population are correctly implemented and follow the same pattern as 80+ other sparse_iterkeys usages throughout the codebase. The review's note about requiring a stub for Param.sparse_iterkeys is a valid type-checking dependency that you indicated was already added in a companion comment.

temoa/data_io/hybrid_loader.py (2)

860-893: LGTM: param index-set builder.

Clean detection of indexed Params and extraction of keys.


1-800: No issues found in verification sweep.

The file correctly imports Param and Set from pyomo.core (consistent with the rest of the codebase), and appropriately avoids Constraint.Skip, Set.Skip, and sparse_iterkeys() since it operates as a data loader that populates a DataPortal before model construction rather than iterating over instantiated Pyomo components.

pyproject.toml Outdated
Comment on lines +92 to +94
# Exclude specific directories from type checking
exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^docs/)"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix conflicting mypy config: exclude vs overrides.

You exclude tests/ and temoa/model_checking/ but also configure overrides for them; excludes win, making overrides inert. Remove them from exclude so overrides apply.

-[tool.mypy]
-# Exclude specific directories from type checking
-exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^docs/)"
+[tool.mypy]
+# Exclude specific directories from type checking
+exclude = "(?x)(^temoa/data_processing/|^temoa/extensions/|^docs/)"

Also applies to: 151-154, 167-174

🤖 Prompt for AI Agents
In pyproject.toml around lines 92-94, the mypy `exclude` pattern currently lists
directories (tests/ and temoa/model_checking/) that you also have per-path
`overrides` for, which prevents those overrides from ever being applied; update
the `exclude` regex to remove those specific paths so mypy will include them for
override handling (and repeat the same correction for the other mentioned
ranges: lines 151-154 and 167-174) — ensure the final exclude still excludes
only directories you truly want omitted and that the paths targeted by overrides
are not present in the exclude pattern.

Comment on lines +8 to +13
from abc import ABC
from typing import Any, Optional, Protocol, Union, runtime_checkable

# Type aliases for common Pyomo patterns in Temoa
PyomoIndex = Union[str, int, float, tuple[Any, ...]]
PyomoExpression = Any # Complex expressions are hard to type statically
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Provide a pyomo.environ stub (code imports use pyomo.environ).

Core code imports Constraint/value from pyomo.environ; only stubbing pyomo.core leaves those as Any. Add stubs/pyomo/environ.pyi re‑exporting core to align with recommended import style.

Add new file:

# stubs/pyomo/environ.pyi
from .core import *  # re-export core API used by TEMOA
🤖 Prompt for AI Agents
In stubs/pyomo/environ.pyi lines 1-3, add a new stub file that re-exports the
core API so imports using pyomo.environ resolve to the same types as pyomo.core;
create stubs/pyomo/environ.pyi and put a single import that re-exports
everything from the local core stub (i.e., import and re-export the core
module’s public names) so code doing "from pyomo.environ import Constraint,
value" will get the core types.

Comment on lines +195 to 196
def RenewablePortfolioStandard_Constraint(M: 'TemoaModel', r: Region, p: Period, g: str) -> Any:
r"""
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Adopt a ConstraintExpression alias instead of Any for rule return types.

Standardize return typing across limits to satisfy ANN401.

  • Define ConstraintExpression in temoa/types and import it here.
  • Change -> Any to -> ConstraintExpression for all constraint functions, including wrappers.

Also applies to: 228-229, 283-286, 363-366, 396-399, 429-432, 487-490, 548-559, 583-586, 609-612, 643-654, 704-707, 736-739, 768-769, 867-870, 874-877, 983-986, 990-993, 1098-1101, 1105-1108, 1238-1239, 1296-1299, 1319-1320

🧰 Tools
🪛 Ruff (0.14.0)

195-195: Dynamically typed expressions (typing.Any) are disallowed in RenewablePortfolioStandard_Constraint

(ANN401)

Comment on lines 659 to 665
value = 365
if filtered_data:
value = int(filtered_data[0][0])
value = int(filtered_data[0][0]) # type: ignore[call-overload]
else:
logger.info('No value for days_per_period found. Assuming 365 days per period.')
data[M.DaysPerPeriod.name] = {None: value}

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: avoid unnecessary ignores; parse explicitly.

-        value = 365
-        if filtered_data:
-            value = int(filtered_data[0][0])  # type: ignore[call-overload]
-        ...
-        data[M.DaysPerPeriod.name] = {None: value}
+        days = 365
+        if filtered_data:
+            days = int(filtered_data[0][0])
+        ...
+        data[M.DaysPerPeriod.name] = {None: days}
@@
-            data[M.GlobalDiscountRate.name] = {None: float(filtered_data[0][0])}  # type: ignore[arg-type]
+            data[M.GlobalDiscountRate.name] = {None: float(filtered_data[0][0])}
@@
-            data[M.DefaultLoanRate.name] = {None: float(filtered_data[0][0])}  # type: ignore[arg-type]
+            data[M.DefaultLoanRate.name] = {None: float(filtered_data[0][0])}

Also applies to: 675-691

🤖 Prompt for AI Agents
In temoa/data_io/hybrid_loader.py around lines 659-665 (and similarly at
675-691), the code uses a broad "# type: ignore[call-overload]" when assigning
value = int(filtered_data[0][0]); replace this with explicit, safe parsing:
retrieve the cell into a local variable, handle bytes by decoding, coerce to str
then to int (e.g. v = filtered_data[0][0]; if isinstance(v, bytes): v =
v.decode(); value = int(str(v).strip())), and optionally catch ValueError to log
a clearer error and fall back to the default; remove the type: ignore comment
after implementing the explicit conversion.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (5)
temoa/core/model.py (1)

14-24: Unify imports: import Set/Var/BuildCheck from pyomo.environ for consistency with project guidance.

-from pyomo.core import BuildCheck, Set, Var
-from pyomo.environ import (
-    AbstractModel,
-    BuildAction,
-    Constraint,
-    Integers,
-    NonNegativeReals,
-    Objective,
-    Param,
-    minimize,
-)
+from pyomo.environ import (
+    AbstractModel,
+    BuildAction,
+    BuildCheck,
+    Constraint,
+    Integers,
+    NonNegativeReals,
+    Objective,
+    Param,
+    Set,
+    Var,
+    minimize,
+)
temoa/components/time.py (1)

78-85: Use f-strings instead of building msg and calling .format twice.

-            msg = (
-                'All items in time_future must be larger than in time_exist.'
-                '\ntime_exist max:   {}'
-                '\ntime_future min: {}'
-            )
-            logger.error(msg.format(max_exist, min_horizon))
-            raise Exception(msg.format(max_exist, min_horizon))
+            msg = (
+                'All items in time_future must be larger than in time_exist.'
+                f'\ntime_exist max:   {max_exist}'
+                f'\ntime_future min: {min_horizon}'
+            )
+            logger.error(msg)
+            raise Exception(msg)
temoa/components/storage.py (1)

256-265: Consistency: use value(M.DaysPerPeriod) here too.
Elsewhere you call value(...) on Params in this expression; keep it consistent to avoid mixing symbolic and numeric forms.

-        * (value(M.StorageDuration[r, t]) / (24 * value(M.DaysPerPeriod)))
+        * (value(M.StorageDuration[r, t]) / (24 * value(M.DaysPerPeriod)))
         * value(M.SegFracPerSeason[p, s])
-        * M.DaysPerPeriod  # adjust for days in season
+        * value(M.DaysPerPeriod)  # adjust for days in season
temoa/_internal/table_writer.py (2)

124-126: Standardize None checks for tech_sectors.
Empty dicts shouldn’t trigger init; check is None explicitly.

-        if not self.tech_sectors:
+        if self.tech_sectors is None:
             self._set_tech_sectors()

Apply at all three locations above.

Also applies to: 159-161, 176-178


480-485: Harden flow balance: avoid KeyError on missing types.
Use .get(..., 0) to guard against absent flow categories.

-            fin = flows[fi][FlowType.IN]
-            fout = flows[fi][FlowType.OUT]
-            fcurt = flows[fi][FlowType.CURTAIL]
-            fflex = flows[fi][FlowType.FLEX]
-            flost = flows[fi][FlowType.LOST]
+            fin   = flows[fi].get(FlowType.IN, 0.0)
+            fout  = flows[fi].get(FlowType.OUT, 0.0)
+            fcurt = flows[fi].get(FlowType.CURTAIL, 0.0)
+            fflex = flows[fi].get(FlowType.FLEX, 0.0)
+            flost = flows[fi].get(FlowType.LOST, 0.0)

Also applies to: 496-516

♻️ Duplicate comments (12)
temoa/temoa_model/temoa_model.py (4)

14-24: Consolidate on pyomo.environ imports (Set/Var/BuildCheck) for consistency.

-from pyomo.core import BuildCheck, Set, Var
-from pyomo.environ import (
+from pyomo.environ import (
     AbstractModel,
     BuildAction,
+    BuildCheck,
     Constraint,
     Integers,
     NonNegativeReals,
     Objective,
     Param,
+    Set,
+    Var,
     minimize,
 )

97-97: Remove unnecessary type: ignore on inheritance; alias already handles typing.

-class TemoaModel(PyomoAbstractModel):  # type: ignore[no-any-unimported]
+class TemoaModel(PyomoAbstractModel):

1110-1112: Mark the unused parameter in progress_check to satisfy ARG001 (optional this PR).

-def progress_check(M: TemoaModel, checkpoint: str) -> None:
+def progress_check(_M: TemoaModel, checkpoint: str) -> None:

141-143: commodityBalance_rpc should be a set; fix type and initializer to match actual usage and comments.

-        M.commodityBalance_rpc: t.CommodityBalanceDict = {}  # Set of valid region-period-commodity indices to balance
+        M.commodityBalance_rpc: set[tuple[str, int, str]] = set()  # Set of valid (region, period, commodity) indices

Update any type: ignore tied to this misannotation. Based on learnings.

temoa/core/model.py (2)

1110-1112: Silence ARG001: mark the unused model param as intentionally unused.

-def progress_check(M: TemoaModel, checkpoint: str) -> None:
+def progress_check(_M: TemoaModel, checkpoint: str) -> None:

97-97: Remove stale type: ignore on base class; the alias makes it unnecessary.

-class TemoaModel(PyomoAbstractModel):  # type: ignore[no-any-unimported]
+class TemoaModel(PyomoAbstractModel):
temoa/components/time.py (3)

202-205: Tighten return type to match actual values (Season, TimeOfDay).

-def loop_period_next_timeslice(
-    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
-) -> tuple[str, str]:
+def loop_period_next_timeslice(
+    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
+) -> tuple['Season', 'TimeOfDay']:

229-235: Same: return Season/TimeOfDay; keep parameter names unchanged per PR scope.

-def loop_season_next_timeslice(
-    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
-) -> tuple[str, str]:
+def loop_season_next_timeslice(
+    M: 'TemoaModel', p: 'Period', s: 'Season', d: 'TimeOfDay'
+) -> tuple['Season', 'TimeOfDay']:
@@
-    s_next: str = s
+    s_next: 'Season' = s

124-128: Minor: avoid intermediate list and use explicit conversion flag in f-string.

-            items_list: list[tuple[tuple[object, object, object], object]] = sorted(
-                [(k, M.SegFrac[k]) for k in keys]
-            )
-            items: str = '\n   '.join(f'{str(k):<{key_padding}} = {v}' for k, v in items_list)
+            items_list: list[tuple[tuple[object, object, object], object]] = sorted(
+                (k, M.SegFrac[k]) for k in keys
+            )
+            items: str = '\n   '.join(f'{k!s:<{key_padding}} = {v}' for k, v in items_list)
temoa/types/model_types.py (3)

151-171: Add TimeSeason to the TemoaModel stub.
Mirror the Protocol so structural typing works across modules using M.TimeSeason.

         vintage_all: Set
         time_season: Set
         time_of_day: Set
+        # Indexed seasonal set by period
+        TimeSeason: Set

91-106: Add TimeSeason to the Protocol.
Consumers call M.TimeSeason[p]. Add the attribute to the protocol to align with usage.
Apply:

 class TemoaModelProtocol(Protocol):
@@
-    time_season: Set
-    time_of_day: Set
+    time_season: Set
+    time_of_day: Set
+    # Indexed seasonal set by period
+    TimeSeason: Set

219-225: Make lazily-initialized active sets Optional in the stub too.*
They’re Optional in the Protocol; align the stub to avoid false positives.

-        activeFlow_rpsditvo: set[RegionPeriodSeasonTimeInputTechVintageOutput]
-        activeActivity_rptv: set[RegionPeriodTechVintage]
+        activeFlow_rpsditvo: set[RegionPeriodSeasonTimeInputTechVintageOutput] | None
+        activeActivity_rptv: set[RegionPeriodTechVintage] | None
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6122e8d and 5eed595.

📒 Files selected for processing (12)
  • stubs/pyomo/core.pyi (1 hunks)
  • temoa/_internal/table_writer.py (20 hunks)
  • temoa/components/emissions.py (3 hunks)
  • temoa/components/operations.py (10 hunks)
  • temoa/components/storage.py (15 hunks)
  • temoa/components/time.py (15 hunks)
  • temoa/components/utils.py (3 hunks)
  • temoa/core/model.py (7 hunks)
  • temoa/data_io/loader_manifest.py (2 hunks)
  • temoa/temoa_model/temoa_model.py (7 hunks)
  • temoa/types/__init__.py (1 hunks)
  • temoa/types/model_types.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-18T15:54:24.826Z
Learnt from: ParticularlyPythonicBS
PR: TemoaProject/temoa#165
File: temoa/components/time.py:229-236
Timestamp: 2025-10-18T15:54:24.826Z
Learning: In PR #165 (typing first pass), unused parameter renaming (e.g., `p` → `_p`) is intentionally deferred to a future PR. The current PR focuses exclusively on adding type annotations, not on parameter naming conventions or marking unused parameters.

Applied to files:

  • temoa/components/time.py
🧬 Code graph analysis (9)
temoa/components/emissions.py (1)
stubs/pyomo/core.pyi (1)
  • sparse_iterkeys (69-69)
temoa/data_io/loader_manifest.py (1)
stubs/pyomo/core.pyi (2)
  • Param (58-69)
  • Set (32-56)
temoa/core/model.py (2)
stubs/pyomo/core.pyi (1)
  • AbstractModel (27-30)
temoa/temoa_model/temoa_model.py (3)
  • CreateSparseDicts (61-94)
  • TemoaModel (97-1107)
  • progress_check (1110-1112)
temoa/components/time.py (1)
stubs/pyomo/core.pyi (8)
  • keys (82-82)
  • sparse_iterkeys (69-69)
  • difference (54-54)
  • value (63-63)
  • value (76-76)
  • value (126-126)
  • last (48-48)
  • first (47-47)
temoa/components/utils.py (2)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
stubs/pyomo/core.pyi (1)
  • Expression (114-123)
temoa/temoa_model/temoa_model.py (2)
stubs/pyomo/core.pyi (2)
  • AbstractModel (27-30)
  • Param (58-69)
temoa/types/model_types.py (1)
  • TemoaModel (152-226)
temoa/components/operations.py (1)
stubs/pyomo/core.pyi (2)
  • first (47-47)
  • last (48-48)
temoa/types/model_types.py (2)
stubs/pyomo/core.pyi (9)
  • AbstractModel (27-30)
  • BuildAction (104-107)
  • Constraint (85-92)
  • Objective (94-102)
  • Param (58-69)
  • Set (32-56)
  • Var (71-83)
  • name (39-39)
  • name (65-65)
temoa/core/model.py (1)
  • TemoaModel (97-1107)
temoa/_internal/table_writer.py (6)
temoa/core/config.py (1)
  • TemoaConfig (32-261)
temoa/types/model_types.py (3)
  • TemoaModel (152-226)
  • FlowType (277-284)
  • FI (244-254)
temoa/_internal/data_brick.py (2)
  • DataBrick (21-76)
  • flow_data (59-60)
temoa/_internal/table_data_puller.py (2)
  • poll_objective (271-281)
  • poll_capacity_results (67-111)
temoa/_internal/exchange_tech_cost_ledger.py (1)
  • CostType (22-30)
temoa/extensions/myopic/myopic_sequencer.py (1)
  • execute_script (506-515)
🪛 Ruff (0.14.0)
temoa/core/model.py

1110-1110: Unused function argument: M

(ARG001)

temoa/components/time.py

102-106: Use f-string instead of format call

Convert to f-string

(UP032)


127-127: Use explicit conversion flag

Replace with conversion flag

(RUF010)


209-209: Remove quotes from type annotation

Remove quotes

(UP037)


210-210: Remove quotes from type annotation

Remove quotes

(UP037)


230-230: Unused function argument: p

(ARG001)

temoa/temoa_model/temoa_model.py

1110-1110: Unused function argument: M

(ARG001)

temoa/_internal/table_writer.py

111-111: Boolean-typed positional argument in function definition

(FBT001)


111-111: Boolean default positional argument in function definition

(FBT002)


246-246: Avoid specifying long messages outside the exception class

(TRY003)


278-278: Avoid specifying long messages outside the exception class

(TRY003)


280-280: Avoid specifying long messages outside the exception class

(TRY003)


362-362: Avoid specifying long messages outside the exception class

(TRY003)


364-364: Avoid specifying long messages outside the exception class

(TRY003)


576-576: Avoid specifying long messages outside the exception class

(TRY003)

temoa/types/__init__.py

5-72: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (22)
temoa/components/emissions.py (2)

16-16: Good: switched to a shared ExprLike for rule return type.

Confirm temoa.types exports ExprLike and it maps to your Pyomo expression/relational type union.


38-49: Guarding with M.activeActivity_rptv prevents unnecessary iterations; nice.

temoa/components/utils.py (4)

16-16: Centralize expression typing import looks good.
Using a single ExprLike alias improves consistency across components.


20-28: TYPE_CHECKING-only domain imports are appropriate.
Keeps runtime lean; no objections.


40-48: Return type for operator_expression is appropriate.
Relational expressions fit ExprLike; match-case with explicit ValueError is clear.


51-61: Minor: confirm EfficiencyVariable semantics.
If EfficiencyVariable is a Param (not a Var), value(...) is correct; if it can be a Var, value(...) at build-time would collapse symbolic math. Please confirm it’s always a Param.

temoa/components/operations.py (3)

74-92: Guard logic LGTM; request small verification.
The s_next != M.time_next[p, s, M.time_of_day.last()][0] filter avoids duplication; ensure M.time_next returns a 2-tuple (Season, TimeOfDay) in all configs so index [0] is always Season.

Also applies to: 97-115


124-132: Constraint returns use ExprLike: confirm it includes Constraint.Skip.
Bodies return Constraint.Skip in some branches. Please ensure ExprLike includes the sentinel type (or use object) to keep mypy happy. If not, consider a project alias like ConstraintRuleReturn = object and use that here.

Also applies to: 242-250, 351-359, 434-442, 501-509


206-206: Explicit None return is fine.
create_operational_vintage_sets returns None; clear and typed.

temoa/types/model_types.py (1)

302-345: all looks coherent.
No action; grouping kept with RUF022 suppression.

temoa/components/storage.py (3)

30-39: Return Optional sets only if truly needed.
If these model-backed sets are always initialized to set(), prefer returning set[...] (not | None) to simplify Pyomo Set initializers. If mypy currently requires | None, fine to defer.

Also applies to: 55-58


42-52: Set comprehension is clear and efficient.
Good refactor; no issues.


68-70: Constraint returns use ExprLike: confirm it admits Constraint.Skip.
These rules may return Constraint.Skip via helpers/branches; ensure ExprLike accommodates that sentinel.

Also applies to: 124-126, 215-217, 272-275, 350-353, 388-391, 424-427, 466-476

temoa/_internal/table_writer.py (1)

106-113: Revert the diff; the proposed change removes a required parameter.

The M: TemoaModel parameter cannot be removed—all call sites depend on it. The correct approach to make flags keyword-only is to place *, after M, not replace it:

 def write_results(
     self,
     M: TemoaModel,
+    *,
     results_with_duals: SolverResults | None = None,
     save_storage_levels: bool = False,
     append: bool = False,
     iteration: int | None = None,
 ) -> None:

This preserves M as a positional-or-keyword parameter while enforcing keyword-only for the optional flags.

Likely an incorrect or invalid review comment.

temoa/types/__init__.py (5)

74-87: LGTM!

Core type imports are complete and correctly match the exported names in __all__.


89-125: LGTM!

Dictionary type imports are complete, well-organized, and correctly match the exported names in __all__.


127-131: LGTM!

Index type imports are complete and correctly match the exported names in __all__.


133-148: LGTM!

Set type imports are complete, well-organized, and correctly match the exported names in __all__.


150-152: LGTM!

The ExprLike type alias uses modern PEP 604 union syntax and pragmatically includes object to cover Pyomo's expression types and sentinel values (like Constraint.Skip). The comment clearly documents the intent.

temoa/data_io/loader_manifest.py (3)

12-12: Good use of __future__.annotations and TYPE_CHECKING pattern.

The addition of from __future__ import annotations enables forward references and resolves the nonstandard ignore codes flagged in past reviews. The ComponentType annotation on line 50 is now properly supported through the TYPE_CHECKING pattern.

Also applies to: 50-50


53-59: LGTM! Modern union syntax is clean and correct.

The use of | None syntax instead of Optional is more readable and works correctly with the from __future__ import annotations import on line 12.


17-22: No issues found—PEP 695 syntax is appropriate for Python 3.12+ project requirement.

The project requires Python 3.12+ as specified in pyproject.toml (requires-python = ">=3.12"), making the PEP 695 type statement on line 20 fully compatible and correct. The TYPE_CHECKING pattern and modern union syntax are appropriate for this codebase.

@@ -0,0 +1,147 @@
"""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Provide pyomo.environ re-export stub to match import style across the codebase.

Please add a new file stubs/pyomo/environ.pyi:

from .core import *  # re-export core API for 'pyomo.environ'

This avoids mypy falling back to Any for environ imports. Based on learnings.


I'll verify this review comment by examining the repository structure and checking for relevant imports and stub files.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check stubs directory structure
echo "=== Checking stubs directory structure ==="
find stubs -type f -name "*.pyi" 2>/dev/null | head -20

echo -e "\n=== Checking if stubs/pyomo/environ.pyi exists ==="
ls -la stubs/pyomo/environ.pyi 2>&1

echo -e "\n=== Content of stubs/pyomo/core.pyi ==="
cat stubs/pyomo/core.pyi 2>&1

echo -e "\n=== Searching for pyomo.environ imports ==="
rg -n "from pyomo\.environ|import.*pyomo\.environ" --type=py --type=pyi -C2

Length of output: 5253


🌐 Web query:

pyomo.environ module purpose re-export

💡 Result:

Short answer: pyomo.environ is a convenience (umbrella) module that imports and initializes the core Pyomo subpackages and re‑exports the common modeling symbols (ConcreteModel, Var, Param, Constraint, Objective, SolverFactory, units, etc.) so users can write "from pyomo.environ import ..." instead of importing many submodules individually. It also performs package initialization to ensure Pyomo features/plugins are loaded. [1][2]

Sources:

  • Pyomo project: recommending import pyomo.environ as the way to get core functionality. [1]
  • Pyomo documentation/examples showing usage (symbols like units and ConcreteModel are exposed via pyomo.environ). [2]

I need to search more carefully for pyomo.environ imports in the actual Python code to determine if this stub is necessary.


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for pyomo.environ imports in Python files only
echo "=== Searching for pyomo.environ usage in .py files ==="
rg -n "pyomo\.environ|from pyomo import environ" --type=py -C3 2>&1 | head -50

echo -e "\n=== Searching for pyomo imports (any variant) ==="
rg -n "import.*pyomo|from.*pyomo" --type=py -C1 2>&1 | head -50

echo -e "\n=== Checking stubs/pyomo/__init__.pyi if exists ==="
cat stubs/pyomo/__init__.pyi 2>&1

Length of output: 5060


🌐 Web query:

Does pyomo.environ need type stub for mypy type checking?

💡 Result:

Short answer: Yes — unless Pyomo ships type information (py.typed or .pyi stubs) for pyomo.environ, mypy will report "library stubs not installed" and you will need stubs (or to silence/check around it).

Details and options:

  • mypy expects either inline annotations, a py.typed marker, or a stub package; otherwise it will complain about missing library stubs. [1][2]
  • If Pyomo doesn't provide types, you can:
    • install a third‑party stub package if one exists (pip install types-), or
    • create .pyi stubs (manually or with mypy's stubgen) and add them via MYPYPATH or ship them with the package, or
    • tell mypy to ignore the import (ignore_missing_imports or per-import # type: ignore). [1][3]

References:
[1] mypy — Getting started / Stub files (install types-... and stubgen).
[2] PEP 561 — py.typed marker and stub resolution order.
[3] Python typing docs — Using external libraries / stub packages.


Create stubs/pyomo/environ.pyi to enable type checking for pyomo.environ imports.

The codebase actively imports from pyomo.environ across multiple files (test_validators.py, test_set_consistency.py, hybrid_loader.py, commodities.py, limits.py, etc.), yet the stub file does not exist. Without it, mypy cannot resolve types for symbols imported from pyomo.environ and will fall back to Any. The proposed re-export from stubs/pyomo/core.pyi is the correct approach, as pyomo.environ is designed as a convenience module that re-exports core Pyomo functionality.

from .core import *  # re-export core API for 'pyomo.environ'
🤖 Prompt for AI Agents
In stubs/pyomo/core.pyi around lines 1-1, add a new file stubs/pyomo/environ.pyi
that re-exports the core API so imports from pyomo.environ are type-checkable;
the file should contain a single re-export of core (i.e., from .core import *),
placed alongside core.pyi so mypy resolves symbols imported from pyomo.environ
instead of treating them as Any.

Comment on lines +104 to +108
class BuildAction:
"""Pyomo BuildAction class for model construction actions."""

def __init__(self, rule: Any, **kwargs: Any) -> None: ...

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

BuildAction should accept positional args (e.g., messages) and rule kw; widen signature.

-class BuildAction:
+class BuildAction:
@@
-    def __init__(self, rule: Any, **kwargs: Any) -> None: ...
+    def __init__(self, *args: Any, **kwargs: Any) -> None: ...
📝 Committable suggestion

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

Suggested change
class BuildAction:
"""Pyomo BuildAction class for model construction actions."""
def __init__(self, rule: Any, **kwargs: Any) -> None: ...
class BuildAction:
"""Pyomo BuildAction class for model construction actions."""
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
🤖 Prompt for AI Agents
In stubs/pyomo/core.pyi around lines 104 to 108, the BuildAction.__init__ type
signature is too narrow — it currently only accepts rule as a positional
argument and kwargs, but callers may pass positional args (e.g., messages) and
supply rule as a keyword; update the stub to broaden the signature to accept
arbitrary positional arguments and allow rule as a keyword-only or optional
keyword parameter while keeping **kwargs for other options, so the initializer
type reflects callers that pass positional messages plus a rule kw.

Comment on lines +125 to +147
# Common Pyomo function stubs
def value(obj: Any) -> Any: ...
def sum_product(*args: Any, **kwargs: Any) -> Any: ...
def quicksum(args: Any, **kwargs: Any) -> Expression: ...

# Export all stubs
__all__ = [
'AbstractModel',
'Set',
'Param',
'Var',
'Constraint',
'Objective',
'BuildAction',
'BuildCheck',
'Expression',
'PyomoIndex',
'PyomoExpression',
'PyomoComponent',
'value',
'sum_product',
'quicksum',
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add missing symbols used by the model: minimize, Integers, NonNegativeReals; export them.

 # Common Pyomo function stubs
 def value(obj: Any) -> Any: ...
 def sum_product(*args: Any, **kwargs: Any) -> Any: ...
 def quicksum(args: Any, **kwargs: Any) -> Expression: ...
+
+# Common sets/domains and sense constants
+Integers: Any
+NonNegativeReals: Any
+minimize: Any
@@
     'PyomoComponent',
     'value',
     'sum_product',
     'quicksum',
+    'Integers',
+    'NonNegativeReals',
+    'minimize',
 ]
🤖 Prompt for AI Agents
In stubs/pyomo/core.pyi around lines 125 to 147, the file is missing declared
symbols used by the model: add type stubs for minimize, Integers, and
NonNegativeReals and export them in __all__. Specifically, declare a minimal
stub signature or alias for minimize (e.g., a callable that accepts an
expression and returns an Objective-like value) and add type
aliases/placeholders for Integers and NonNegativeReals (e.g., Any or appropriate
type), then include the three names ('minimize', 'Integers', 'NonNegativeReals')
in the __all__ list so they are exported.

Comment on lines 363 to 365
if not self.flow_register:
raise RuntimeError('flow_register not available... code error')
# sort the flows
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prefer explicit None/emptiness checks.
Keep as-is if you intentionally treat empty flow_register as an error; otherwise consider a clearer message or early-return.

🧰 Tools
🪛 Ruff (0.14.0)

364-364: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In temoa/_internal/table_writer.py around lines 363 to 365, the current check
uses "if not self.flow_register" which conflates None and empty containers; make
the intent explicit: if you want to treat a missing register as an error, change
to "if self.flow_register is None:" and raise a RuntimeError with a clearer
message; if an empty register is acceptable and should short-circuit, use "if
not self.flow_register:" then either return early (no-op) or raise a ValueError
with a message indicating the register is empty; update the message to be
descriptive of the condition chosen.

Comment on lines +651 to 659
def execute_script(self, script_file: str | Path) -> None:
"""
A utility to execute a sql script on the current db connection
:return:
"""
with open(script_file, 'r') as table_script:
with open(script_file) as table_script:
sql_commands = table_script.read()
logger.debug('Executing sql from file: %s ', script_file)

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: specify encoding when reading SQL scripts.
Prevents platform-specific decode issues.

-        with open(script_file) as table_script:
+        with open(script_file, encoding="utf-8") as table_script:
📝 Committable suggestion

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

Suggested change
def execute_script(self, script_file: str | Path) -> None:
"""
A utility to execute a sql script on the current db connection
:return:
"""
with open(script_file, 'r') as table_script:
with open(script_file) as table_script:
sql_commands = table_script.read()
logger.debug('Executing sql from file: %s ', script_file)
def execute_script(self, script_file: str | Path) -> None:
"""
A utility to execute a sql script on the current db connection
:return:
"""
with open(script_file, encoding="utf-8") as table_script:
sql_commands = table_script.read()
logger.debug('Executing sql from file: %s ', script_file)
🤖 Prompt for AI Agents
In temoa/_internal/table_writer.py around lines 651 to 659, the file open call
for reading SQL scripts does not specify an encoding which can lead to
platform-specific decode issues; change the open(...) call to explicitly set
encoding='utf-8' (and keep it in text read mode) so the script is read
consistently across platforms, e.g. open(script_file, 'r', encoding='utf-8') and
leave the rest of the logic unchanged.

Comment on lines +32 to +41
def BaseloadDiurnalConstraintIndices(
M: 'TemoaModel',
) -> set[RegionPeriodSeasonTimeOfDayTechVintage]:
indices = {
(r, p, s, d, t, v)
for r, p, t in M.baseloadVintages
for v in M.baseloadVintages[r, p, t]
for s in M.TimeSeason[p]
for d in M.time_of_day
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Expose M.TimeSeason in the type surface.
These comprehensions index M.TimeSeason[p]; add TimeSeason to TemoaModelProtocol and the TemoaModel TYPE_CHECKING stub so mypy/IDE users don’t see attribute errors. See suggested diff in temoa/types/model_types.py comment.

Also applies to: 46-55, 60-69

Comment on lines +33 to +43
# Import Pyomo stub types
if TYPE_CHECKING:
from pyomo.core import (
AbstractModel,
BuildAction,
Constraint,
Objective,
Param,
Set,
Var,
)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: prefer local stubs for isolation.
Importing from pyomo.core under TYPE_CHECKING is okay if your stubs are discoverable; otherwise, consider importing from your local stub module to decouple from an installed Pyomo.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eed595 and 72ebbfe.

⛔ Files ignored due to path filters (293)
  • stubs/pyomo/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/chull.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/component_map.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/component_set.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/current.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/plugin.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/rangeset.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/register_numpy_types.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/sets.pyi is excluded by !stubs/**
  • stubs/pyomo/_archive/template_expr.pyi is excluded by !stubs/**
  • stubs/pyomo/common/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/common/_command.pyi is excluded by !stubs/**
  • stubs/pyomo/common/_common.pyi is excluded by !stubs/**
  • stubs/pyomo/common/autoslots.pyi is excluded by !stubs/**
  • stubs/pyomo/common/backports.pyi is excluded by !stubs/**
  • stubs/pyomo/common/cmake_builder.pyi is excluded by !stubs/**
  • stubs/pyomo/common/collections/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/common/collections/bunch.pyi is excluded by !stubs/**
  • stubs/pyomo/common/collections/component_map.pyi is excluded by !stubs/**
  • stubs/pyomo/common/collections/component_set.pyi is excluded by !stubs/**
  • stubs/pyomo/common/collections/orderedset.pyi is excluded by !stubs/**
  • stubs/pyomo/common/config.pyi is excluded by !stubs/**
  • stubs/pyomo/common/dependencies.pyi is excluded by !stubs/**
  • stubs/pyomo/common/deprecation.pyi is excluded by !stubs/**
  • stubs/pyomo/common/download.pyi is excluded by !stubs/**
  • stubs/pyomo/common/enums.pyi is excluded by !stubs/**
  • stubs/pyomo/common/env.pyi is excluded by !stubs/**
  • stubs/pyomo/common/envvar.pyi is excluded by !stubs/**
  • stubs/pyomo/common/errors.pyi is excluded by !stubs/**
  • stubs/pyomo/common/extensions.pyi is excluded by !stubs/**
  • stubs/pyomo/common/factory.pyi is excluded by !stubs/**
  • stubs/pyomo/common/fileutils.pyi is excluded by !stubs/**
  • stubs/pyomo/common/flags.pyi is excluded by !stubs/**
  • stubs/pyomo/common/formatting.pyi is excluded by !stubs/**
  • stubs/pyomo/common/gc_manager.pyi is excluded by !stubs/**
  • stubs/pyomo/common/gsl.pyi is excluded by !stubs/**
  • stubs/pyomo/common/log.pyi is excluded by !stubs/**
  • stubs/pyomo/common/modeling.pyi is excluded by !stubs/**
  • stubs/pyomo/common/multithread.pyi is excluded by !stubs/**
  • stubs/pyomo/common/numeric_types.pyi is excluded by !stubs/**
  • stubs/pyomo/common/plugin_base.pyi is excluded by !stubs/**
  • stubs/pyomo/common/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/common/pyomo_typing.pyi is excluded by !stubs/**
  • stubs/pyomo/common/shutdown.pyi is excluded by !stubs/**
  • stubs/pyomo/common/sorting.pyi is excluded by !stubs/**
  • stubs/pyomo/common/tee.pyi is excluded by !stubs/**
  • stubs/pyomo/common/tempfiles.pyi is excluded by !stubs/**
  • stubs/pyomo/common/timing.pyi is excluded by !stubs/**
  • stubs/pyomo/common/unittest.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/aos_utils.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/balas.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/lp_enum.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/lp_enum_solnpool.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/obbt.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/shifted_lp.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/solnpool.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/alternative_solutions/solution.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/ampl_function_demo/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/ampl_function_demo/build.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/ampl_function_demo/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/base.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/build.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/cmodel/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/cmodel/appsi_cmodel.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/examples/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/examples/getting_started.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/fbbt.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/cbc.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/cplex.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/gurobi.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/highs.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/ipopt.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/maingo.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/maingo_solvermodel.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/solvers/wntr.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/utils/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/utils/get_objective.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/writers/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/writers/config.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/writers/lp_writer.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/appsi/writers/nl_writer.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/benders/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/benders/benders_cuts.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/benders/examples/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/benders/examples/farmer.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/benders/examples/grothey_ex.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/community_detection/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/community_detection/community_graph.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/community_detection/detection.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/community_detection/event_log.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/community_detection/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/interval_var.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/repn/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/repn/docplex_writer.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/scheduling_expr/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/scheduling_expr/precedence_expressions.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/scheduling_expr/scheduling_logic.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/scheduling_expr/sequence_expressions.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/scheduling_expr/step_function_expressions.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/sequence_var.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/transform/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_program.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cspline_external/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cspline_external/build.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cspline_external/cspline_parameters.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/cspline_external/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/doe.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/examples/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/examples/reactor_compute_factorial_FIM.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/examples/reactor_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/examples/reactor_experiment.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/doe/utils.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/example/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/example/bar.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/example/foo.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/example/plugins/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/example/plugins/ex_plugin.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fbbt/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fbbt/expression_bounds_walker.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fbbt/fbbt.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fbbt/interval.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fme/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fme/fourier_motzkin_elimination.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/fme/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdp_bounds/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdp_bounds/compute_bounds.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdp_bounds/info.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdp_bounds/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/GDPopt.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/algorithm_base_class.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/branch_and_bound.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/config_options.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/create_oa_subproblems.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/cut_generation.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/discrete_problem_initialize.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/enumerate.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/gloa.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/ldsda.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/loa.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/nlp_initialization.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/oa_algorithm_utils.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/ric.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/solve_discrete_problem.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/solve_subproblem.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gdpopt/util.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gjh/GJH.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gjh/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gjh/getGJH.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/gjh/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/iis/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/iis/iis.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/iis/mis.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/common/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/config.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/connected.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/incidence.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/matching.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/scc_solver.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/triangularize.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/incidence_analysis/visualize.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/examples/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/examples/ex1.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/interior_point.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/inverse_reduced_hessian.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/linalg/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/linalg/ma27_interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/linalg/mumps_interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/interior_point/linalg/scipy_interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/latex_printer/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/latex_printer/latex_printer.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mcpp/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mcpp/build.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mcpp/getMCPP.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mcpp/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mcpp/pyomo_mcpp.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/MindtPy.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/algorithm_base_class.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/config_options.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/cut_generation.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/extended_cutting_plane.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/feasibility_pump.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/global_outer_approximation.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/outer_approximation.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/single_tree.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/tabu_list.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mindtpy/util.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/convert.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/dynamic_data_base.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/find_nearest_index.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/get_cuid.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/interval_data.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/scalar_data.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/data/series_data.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/examples/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/examples/cstr/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/examples/cstr/model.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/examples/cstr/run_mpc.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/examples/cstr/run_openloop.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/interfaces/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/interfaces/copy_values.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/interfaces/load_data.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/interfaces/model_interface.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/interfaces/var_linker.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/modeling/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/modeling/constraints.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/modeling/cost_expressions.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/mpc/modeling/terminal.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/multistart/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/multistart/high_conf_stop.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/multistart/multi.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/multistart/plugins.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/multistart/reinit.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/datarec_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/reactor_design.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/rooney_biegler/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/semibatch/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/semibatch/parallel_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/semibatch/scenario_example.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/examples/semibatch/semibatch.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/experiment.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/graphics.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/parmest.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/scenariocreator.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/utils/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/utils/create_ef.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/utils/model_utils.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/utils/mpi_utils.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/parmest/utils/scenario_tree.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/ordered_3d_j1_triangulation_data.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/piecewise_linear_expression.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/piecewise_linear_function.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/convex_combination.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/incremental.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/inner_representation_gdp.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/multiple_choice.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/nested_inner_repn.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/outer_representation_gdp.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/piecewise_linear_transformation_base.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/piecewise/triangulations.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/preprocessing/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/preprocessing/plugins/__init__.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/preprocessing/plugins/bounds_to_vars.pyi is excluded by !stubs/**
  • stubs/pyomo/contrib/preprocessing/plugins/constraint_tightener.pyi is excluded by !stubs/**
📒 Files selected for processing (2)
  • .coderabbit.yaml (1 hunks)
  • pyproject.toml (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: setup and test (3.12)
🔇 Additional comments (3)
.coderabbit.yaml (1)

132-132: ✓ Correct exclusion of stubs directory from review.

The path filter properly excludes the new stubs/** directory from CodeRabbit reviews using valid glob syntax. This aligns with the PR's introduction of a typing subdirectory for type stubs and type definitions.

pyproject.toml (2)

144-151: Audit override block for excluded modules.

The override block at lines 144–151 applies follow_imports = "silent" to modules that are already listed in the exclude pattern (line 106). Since excluded paths are not type-checked by mypy, this override has no effect. Once the exclude/override conflict is resolved, revisit this block to ensure it targets the correct paths and achieves its intended purpose.


98-175: Comprehensive mypy configuration is well-structured for typing first pass.

The mypy setup is appropriate for an initial strict typing pass:

  • Correctly targets Python 3.12 and points to the new stubs directory (lines 100, 103)
  • Enables strict flags (lines 109–128) with pragmatic exception for disallow_any_expr = false to ease transition (line 126) ✓
  • Fixed: ignore_missing_imports = false (line 121) to maintain strictness for first-party code ✓
  • Provides sensible per-module relaxations for external dependencies (pyomo), tests, and type definitions

The configuration demonstrates thoughtful planning for incremental typing improvements.

Comment on lines +42 to +51
[dependency-groups]
dev = [
"gurobipy>=12.0.3",
"types-deprecated>=1.2.15.20250304",
"ruff>=0.2.0",
"pre-commit",
"pytest",
"pytest-cov",
"pandas-stubs>=2.3.2.250926",
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move pytest and pytest-cov from dev group or remove from runtime dependencies.

pytest (line 30) and pytest-cov are listed in the runtime dependencies array but only needed during development. Either:

  1. Remove them from runtime dependencies (line 30) and keep only in the dev group (lines 48–49), or
  2. Move them to an optional dependency group if they are truly needed for some runtime use case

For a library, test runners should typically not be in production dependencies.

🤖 Prompt for AI Agents
In pyproject.toml around lines 30 and 42–51, pytest and pytest-cov are currently
present in the runtime dependencies (line ~30) while also listed in the dev
dependency-group (lines 42–51); remove pytest and pytest-cov from the top-level
dependencies array so they remain only in the dev group, or alternatively move
them into an optional/extra dependency group if there is a valid runtime use
case — update the pyproject.toml accordingly and run a quick install to verify
no production dependency includes test runners.

mypy_path = "stubs"

# Exclude specific directories from type checking will try to add them back gradually
exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^temoa/data_processing/|^temoa/utilities/|^docs/)"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove duplicate temoa/data_processing/ entry in mypy exclude pattern.

The temoa/data_processing/ path appears twice in the exclude regex (line 106). Deduplicate to reduce maintenance burden and clarify intent:

-exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^temoa/data_processing/|^temoa/utilities/|^docs/)"
+exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^temoa/utilities/|^docs/)"
📝 Committable suggestion

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

Suggested change
exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^temoa/data_processing/|^temoa/utilities/|^docs/)"
exclude = "(?x)(^tests/|^temoa/model_checking/|^temoa/data_processing/|^temoa/extensions/|^temoa/utilities/|^docs/)"
🤖 Prompt for AI Agents
In pyproject.toml around line 106, the mypy exclude regex contains a duplicate
entry for "temoa/data_processing/"; remove the redundant
"temoa/data_processing/" segment so the exclude pattern lists each path only
once, ensuring the regex remains valid and behavior unchanged.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Resolve conflicting mypy exclude vs override blocks.

The exclude pattern (line 106) lists tests/ and temoa/model_checking/, but these same paths have override blocks that will never apply because excluded paths are skipped entirely by mypy. This creates maintenance confusion and inert overrides.

Specifically:

  • tests/ is excluded (line 106) but has an override at lines 138–142
  • temoa/model_checking/ is excluded (line 106) but has overrides at lines 149–150 and 160–164

To fix, either:

  1. Remove these paths from the exclude pattern if you intend the overrides to apply, or
  2. Remove the corresponding override blocks if the paths should remain excluded

Since you want stricter checking for temoa.model_checking.* (line 154–164), consider removing it from the exclude pattern. For tests/, the relaxed override (lines 138–142) suggests you want type checking to apply; remove tests/ from exclude if so.

This issue was flagged in previous review feedback and should be resolved before merge.

Also applies to: 138-142, 144-151, 153-164



---

🏁 Script executed:

```shell
#!/bin/bash
# Retrieve pyproject.toml to verify the mypy configuration sections
fd -e toml -name "pyproject.toml" --exec cat {} \;

Length of output: 230


🌐 Web query:

mypy exclude pattern override blocks precedence behavior

💡 Result:

Short answer: Global exclude patterns (the [mypy] exclude setting or --exclude) are applied during recursive file discovery and can prevent files/paths from being found and checked at all; they live only in the global section. Overrides (per-module / [[tool.mypy.overrides]] or [mypy-...] sections) are per‑module configuration that only take effect for modules that mypy actually considers. Therefore an exclude that prevents discovery will effectively prevent the corresponding override from ever being used (unless you explicitly pass the file/module on the command line). Command-line --exclude and config exclude behave the same way for discovery, and command-line options can override config. [1][2]

Sources:

  • mypy config file docs (exclude is a global-only regex; per-module overrides precedence rules). [1]
  • mypy command-line docs (what --exclude does: affects recursive discovery only; explicitly passed files are still checked). [2]

References:
[1] mypy configuration file documentation.
[2] mypy command-line / --exclude documentation.



---

🏁 Script executed:

```shell
#!/bin/bash
# Find and display pyproject.toml
find . -name "pyproject.toml" -type f | head -1 | xargs cat

Length of output: 4106


Resolve conflicting mypy exclude vs override blocks.

The review comment is accurate. The exclude pattern (line 106) lists tests/ and temoa/model_checking/, but corresponding override blocks exist for these same paths:

  • tests/ is excluded but has an override (lines 138–142) making it less strict
  • temoa/model_checking/ is excluded but appears in overrides (lines 147–150 and 153–164), with the latter explicitly enabling strict checking

Since mypy applies exclude patterns during file discovery and prevents files from being found and checked, the corresponding overrides never take effect. This creates inert configuration and maintenance confusion.

Recommended fix:

  • For temoa/model_checking/: Remove from exclude (line 106) since the override at lines 153–164 explicitly requires strict type checking.
  • For tests/: Remove from exclude (line 106) if the relaxed override at lines 138–142 should apply; keep excluded if strict checking is not intended.
🤖 Prompt for AI Agents
In pyproject.toml at line ~106 the mypy exclude regex currently lists tests/ and
temoa/model_checking/, which prevents file discovery and makes the override
blocks later in the file inert; remove temoa/model_checking/ from the exclude so
the strict override at lines 153–164 can take effect, and for tests/ either
remove it from the exclude so the relaxed override at lines 138–142 is applied
or keep it excluded and delete/disable the tests override—update the exclude
pattern accordingly to reflect the chosen behavior.

@ParticularlyPythonicBS
Copy link
Member Author

using the learning here to create a more polished pr at #171

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

Labels

Maintenance Code quality fixes and deprecation management refactor type-safety

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant