Skip to content

Add type annotations, remove a few redundant casts, remove old prices.py#26

Merged
rbx merged 13 commits intomasterfrom
types
Feb 25, 2026
Merged

Add type annotations, remove a few redundant casts, remove old prices.py#26
rbx merged 13 commits intomasterfrom
types

Conversation

@rbx
Copy link
Member

@rbx rbx commented Feb 24, 2026

No description provided.

@rbx rbx changed the title TypesAdd type annotations, remove a few redundant casts, remove old prices.py Add type annotations, remove a few redundant casts, remove old prices.py Feb 24, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds comprehensive static typing across the codebase: annotated public APIs, refactored Prices to be stateful, introduced a typed MetricsTracker, and updated job/backlog and plotting call-sites to use typed signatures. No major behavioral logic rewrites reported; most edits are signature/typing, small API renames, and test call-site updates.

Changes

Cohort / File(s) Summary
Core Environment & Config
src/environment.py, src/config.py
Added/strengthened type annotations on ComputeClusterEnv constructor and public methods (render, reset, step, etc.) and made configuration constants explicitly typed. No control-flow changes.
Job & Baseline Management
src/job_management.py, src/baseline.py
Typed job/backlog APIs (age_backlog_queue, fill_queue_from_backlog, add_new_jobs, assign_jobs_to_available_nodes, validate/process helpers) and expanded baseline_step signature to accept typed collections, MetricsTracker, env_print and a deque backlog.
Metrics & Callbacks
src/metrics_tracker.py, src/callbacks.py
Introduced a typed MetricsTracker (timeline- and episode-scoped attributes/methods). callbacks.py only had formatting whitespace changes.
Node Management & Reward
src/node_management.py, src/reward_calculation.py
adjust_nodes annotated; reward logic converted to typed interfaces (power_cost, RewardCalculator, normalization and penalty helpers) and a typed calculate API.
Pricing
src/prices.py, src/prices_deterministic.py, test/test_price_history.py, test/test_prices_cycling.py
Reworked Prices to be stateful (price_index, history, predicted_prices), added reset/advance/stat helpers, and applied strong typing; tests updated to import Prices from src.prices.
Plotting & Visualization
src/plot.py, src/plotter.py, test/test_plot_dummy.py, train.py
Added future-annotations/TYPE_CHECKING and typed plotting APIs (plot, plot_dashboard, plot_cumulative_savings, helpers); removed episode_costs argument from several plot_dashboard call sites and tests.
Sampling & Workload
src/sampler_duration.py, src/sampler_hourly.py, src/sampler_jobs.py, src/workload_generator.py, src/workloadgen.py, src/workloadgen_cli.py
Extensive typing of samplers and workload-generator APIs and internals (new Job alias, typed return structures, .tolist() conversions for durations); behavior preserved with minor data-shape adjustments.
Utilities & Weights
src/weights.py
Small typing additions for Weights.__str__() and Weights.sum() signatures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Next empty slot bump #2: Modifies add_new_jobs and assign_jobs_to_available_nodes return shapes and propagates changes into baseline/step signatures — directly related to job/backlog API updates.
  • RewardCalculator #6: Refactors reward logic toward a RewardCalculator-style interface and updates callers — related to typed reward_calculation changes.
  • Rework of Rewards and Queueing + Small hotfixes #17: Changes backlog/queue aging and job assignment helpers and integrates metrics — overlaps with job_management and baseline backlog handling.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to understand the motivation or context for the extensive type annotation additions. Add a brief description explaining the rationale for type annotations, which files are most affected, and any breaking changes or migration notes needed for users.
Docstring Coverage ⚠️ Warning Docstring coverage is 61.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary objective of the changeset: adding type annotations throughout the codebase, removing redundant casts, and consolidating the pricing module.

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

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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
Contributor

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

Caution

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

⚠️ Outside diff range comments (4)
src/sampler_jobs.py (2)

36-45: ⚠️ Potential issue | 🟠 Major

Fix bin stepping to honor bin_minutes (multi‑hour bins are mis-binned).

Line 41–45 sets time_delta to 1 hour (or 1 day) even when bin_minutes is 120, 180, 2880, etc. This generates period keys at the wrong cadence and inserts extra empty bins, which breaks deterministic binning. Use bin_minutes consistently for time_delta, and make the format reflect minutes when needed.

✅ Suggested fix
-        if bin_minutes >= 1440:  # Daily or longer (≥ 24 hours)
-            time_format = "%Y-%m-%d"
-            time_delta = datetime.timedelta(days=1)
-        elif bin_minutes >= 60:  # Hourly or longer
-            time_format = "%Y-%m-%d %H:00"
-            time_delta = datetime.timedelta(hours=1)
-        else:  # Less than an hour
-            time_format = "%Y-%m-%d %H:%M"
-            time_delta = datetime.timedelta(minutes=bin_minutes)
+        if bin_minutes >= 1440 and bin_minutes % 1440 == 0:  # Daily-aligned bins
+            time_format = "%Y-%m-%d"
+        elif bin_minutes >= 60 and bin_minutes % 60 == 0:  # Hour-aligned bins
+            time_format = "%Y-%m-%d %H:00"
+        else:  # Minute-aligned bins
+            time_format = "%Y-%m-%d %H:%M"
+        time_delta = datetime.timedelta(minutes=bin_minutes)

As per coding guidelines: Jobs sampler (src/sampler_jobs.py) must parse Slurm logs, bin jobs by time period (default hourly), and support aggregation that groups similar jobs by (nodes, cores, duration) while converting sub-hour jobs to hourly equivalents by adjusting resource requirements to preserve core-hours.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sampler_jobs.py` around lines 36 - 45, The bin stepping currently
hardcodes 1-hour/1-day deltas; change the logic in the time_format/time_delta
assignment (the block that sets time_format and time_delta) to compute
time_delta from bin_minutes (time_delta =
datetime.timedelta(minutes=bin_minutes)) and choose the string format based on
divisibility: if bin_minutes % 1440 == 0 use "%Y-%m-%d", elif bin_minutes % 60
== 0 use "%Y-%m-%d %H:00", else use "%Y-%m-%d %H:%M"; update the code that
builds period keys to use this computed time_delta so multi-hour and multi-day
bins advance correctly and avoid extra empty bins.

326-346: ⚠️ Potential issue | 🟠 Major

Preserve core-hours when converting sub-hour jobs (flooring loses work).

Line 330 floors total_core_hours via int(...), which undercounts core-hours (e.g., 89 core‑minutes → 1 core‑hour). That violates the requirement to preserve core-hours when converting sub-hour jobs. At minimum, avoid truncation and carry the exact core‑minutes forward for downstream accounting.

✅ Suggested fix
-                equivalent_cores = max(1, int(total_core_hours))
+                equivalent_cores = max(1, math.ceil(total_core_hours))
...
-                hourly_job: Job = {
+                hourly_job: Job = {
                     'nnodes': equivalent_nodes,
                     'cores_per_node': cores_per_node_needed,
                     'duration_hours': 1,  # 1 hour
+                    'total_core_minutes': total_core_minutes,
                     'original_job_count': agg_job['count']
                 }

As per coding guidelines: Jobs sampler (src/sampler_jobs.py) must parse Slurm logs, bin jobs by time period (default hourly), and support aggregation that groups similar jobs by (nodes, cores, duration) while converting sub-hour jobs to hourly equivalents by adjusting resource requirements to preserve core-hours.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sampler_jobs.py` around lines 326 - 346, The code truncates total
core-hours by using int(total_core_hours) to set equivalent_cores, which loses
fractional core-hours; change equivalent_cores to use the full float (e.g.,
equivalent_cores = max(1.0, total_core_hours)) so you don't undercount, keep
using math.ceil when converting cores to integer nodes/cores per node
(equivalent_nodes = max(1, math.ceil(equivalent_cores / cores_per_node)) and
cores_per_node_needed = min(cores_per_node, math.ceil(cores_needed /
equivalent_nodes))) and add the precise total_core_hours value into the created
hourly_job (for example as 'total_core_hours' or 'core_hours') so downstream
accounting can preserve exact core-hours while allocation remains integer.
src/prices_deterministic.py (1)

1-12: ⚠️ Potential issue | 🟡 Minor

Add from __future__ import annotations for consistency with other files in the project.

While this file uses PEP 604 union syntax (X | Y) in type annotations and does not have from __future__ import annotations, this is not a runtime issue since the project targets Python 3.10+ (confirmed in .github/workflows/tests.yml). However, other files in the codebase—such as src/sampler_duration.py, src/plotter.py, and src/workloadgen_cli.py—consistently include this import. Adding it here maintains uniformity across the codebase.

Proposed fix
+from __future__ import annotations
+
 import numpy as np
 import matplotlib.pyplot as plt
 from collections import deque
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/prices_deterministic.py` around lines 1 - 12, Add the line "from
__future__ import annotations" at the top of this module before any other
imports to match other files and ensure forward-compatible, deferred evaluation
of type annotations used in the Prices class and its __init__ signature (which
uses PEP 604 union syntax like np.ndarray | list[float] | None); no other code
changes are needed.
src/baseline.py (1)

33-54: ⚠️ Potential issue | 🟡 Minor

Docstring is missing the baseline_backlog_queue parameter.

The Args section documents parameters up to env_print but omits baseline_backlog_queue: deque, which was added to the signature on line 31.

📝 Proposed fix
         metrics: MetricsTracker object to update with baseline job metrics
         env_print: Print function for logging
+        baseline_backlog_queue: Backlog queue for overflow jobs
 
     Returns:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/baseline.py` around lines 33 - 54, Update the function docstring for the
baseline simulation (the triple-quoted block in baseline.py above the function
signature that includes baseline_backlog_queue) to document the missing
parameter baseline_backlog_queue: deque; add a brief description like
"baseline_backlog_queue: deque of jobs waiting for scheduling (holds job IDs or
job dicts)" alongside the other Args entries and keep the same formatting style
as the existing Args lines so the docstring accurately reflects the function
signature and parameters such as baseline_state, baseline_cores_available,
baseline_running_jobs, current_price, new_jobs_count, new_jobs_durations,
new_jobs_nodes, new_jobs_cores, baseline_next_empty_slot, next_job_id, metrics,
env_print.
🧹 Nitpick comments (7)
src/sampler_hourly.py (2)

22-22: Nit: return Nonereturn.

For a -> None function, bare return is the idiomatic form.

♻️ Proposed fix
-            return None
+            return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sampler_hourly.py` at line 22, Replace the non-idiomatic "return None"
with a bare "return" in the function that is annotated with "-> None" (the
function containing the current "return None" at line 22) so the function uses
the idiomatic form for returning from a None-annotated function; update the
single "return None" statement to "return" in that function (no other logic
changes).

142-147: get_stats() return-type annotation is inaccurate for numpy scalars.

The inner dict values include np.mean(...)np.floating[Any], np.max(...)np.intp, and len(...)int. None of these satisfy float in numpy's type stubs — strict type checkers (mypy/Pylance with numpy stubs) will flag the annotation dict[int, dict[str, float]] as a type error.

Although np.float_ is a subtype of Python's native float at runtime, type checkers like Pylance report "float_" is incompatible with "float" when numpy stubs are active. The np.intp return of np.max and the int return of len compound the problem further.

Consider loosening the inner dict's value type to Any (consistent with how hourly_templates is typed elsewhere in this class), or use a more precise union:

♻️ Proposed fix
-    def get_stats(self) -> dict[int, dict[str, float]]:
+    def get_stats(self) -> dict[int, dict[str, Any]]:
         """Return summary statistics of the sampler."""
         if not self.initialized:
             raise RuntimeError("Sampler not initialized. Call parse_jobs() first.")

-        stats: dict[int, dict[str, float]] = {}
+        stats: dict[int, dict[str, Any]] = {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sampler_hourly.py` around lines 142 - 147, The return-type annotation of
get_stats is too strict for numpy scalar results; change the signature to return
dict[int, dict[str, Any]] (import Any from typing) or otherwise widen the
inner-value type to accept numpy scalars (e.g., Union[int, float, np.floating,
np.integer]) so mypy/Pylance stop flagging np.mean/np.max/len returns; update
the get_stats function annotation and any related usages to use the same relaxed
type (consistent with hourly_templates typing).
src/plotter.py (1)

88-88: Consider prefixing the unused episode_costs parameter to silence the linter.

Ruff flags this as unused (ARG001). Since it's kept for backward compatibility (documented in the docstring), prefixing with _ would communicate intent and silence the warning.

Suggested fix
-def plot_dashboard(env: ComputeClusterEnv, num_hours: int, max_nodes: int, episode_costs: list[dict[str, float | int]] | None = None, save: bool = True, show: bool = True, suffix: int | str = "") -> None:
+def plot_dashboard(env: ComputeClusterEnv, num_hours: int, max_nodes: int, _episode_costs: list[dict[str, float | int]] | None = None, save: bool = True, show: bool = True, suffix: int | str = "") -> None:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plotter.py` at line 88, The parameter episode_costs in function
plot_dashboard is currently unused and triggers Ruff ARG001; rename it to
_episode_costs in the plot_dashboard signature to indicate it's intentionally
unused (keeping its docstring note for backward compatibility) and update any
internal references to use the new name if present; no other behavior changes
should be made so callers remain compatible with the parameter name while the
linter warning is silenced.
src/sampler_duration.py (1)

62-76: Stale docstring contradicts the return type annotation.

The docstring says "Single float if n=1, otherwise array of floats" but the method always returns an NDArray[np.intp] (via .astype(int)), matching the type hint. Consider updating the docstring to match.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sampler_duration.py` around lines 62 - 76, The docstring for sample() is
stale and describes floats/scalar behavior that doesn't match the annotated
return type NDArray[np.intp] and the actual implementation (which always returns
a numpy array cast to int via .astype(int)); update the docstring in sample() to
state it returns a numpy.ndarray of integers (NDArray[np.intp]) with shape (n,)
and that when n==1 it yields a single-element array rather than a plain
float/scalar; ensure the Args/Returns section mentions integers and the array
shape to match sample_values/probabilities usage.
src/workloadgen_cli.py (1)

22-31: Type-checker note: tuple() from a generator yields tuple[float, ...], not the fixed-length tuple.

The return type says tuple[float, float, float, float] but tuple(float(p) for p in parts) produces tuple[float, ...] as far as type checkers are concerned. Same pattern on parse_quad_ints (line 41) and parse_quad_ranges (line 65). Runtime behavior is correct since validation ensures exactly 4 elements.

If you want strict type-checker compliance, you could unpack explicitly, though this is entirely optional:

Example for parse_quad_floats
     try:
-        return tuple(float(p) for p in parts)
+        a, b, c, d = (float(p) for p in parts)
+        return (a, b, c, d)
     except ValueError as exc:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/workloadgen_cli.py` around lines 22 - 31, The type error arises because
using tuple(generator) yields tuple[float, ...] for parse_quad_floats (and
similarly for parse_quad_ints and parse_quad_ranges); to satisfy the declared
fixed-length return types, explicitly convert parts to the target types, unpack
into four named variables (e.g., a,b,c,d) after validating len(parts)==4, then
return (a, b, c, d) so the type checker sees a fixed 4-tuple rather than a
variadic tuple.
src/reward_calculation.py (1)

181-181: env_print typed as Callable[[str], None] — inconsistent with Callable[..., None] in baseline.py.

Here env_print is typed Callable[[str], None] (exactly one str argument), but in src/baseline.py line 30 the same callback is typed Callable[..., None]. The actual method signature is env_print(self, *args: Any) -> None. Using Callable[..., None] consistently would be more accurate and avoid strict type-checker complaints about passing a variadic function where a single-argument callable is expected.

Proposed fix
-                  env_print: Callable[[str], None]) -> tuple[float, float, float, float, float, float]:
+                  env_print: Callable[..., None]) -> tuple[float, float, float, float, float, float]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/reward_calculation.py` at line 181, The env_print parameter in the
function signature is currently typed as Callable[[str], None] which conflicts
with the variadic env_print used elsewhere (e.g., the env_print method in
baseline.py typed as Callable[..., None]); change the type annotation for
env_print in the function (the signature that returns tuple[float, float, float,
float, float, float]) from Callable[[str], None] to Callable[..., None] so
callers passing env_print(self, *args: Any) match the type; update only the type
annotation for the env_print parameter (and any similar signatures in this
module) to Callable[..., None].
src/environment.py (1)

49-50: Nit: render parameter name shadows Gymnasium convention.

Gymnasium's render method signature is render(self) -> RenderFrame | list[RenderFrame] | None (no mode parameter — the mode is set via render_mode at construction). This implementation uses render to mutate self.render_mode at runtime, which deviates from the Gym API. This is pre-existing behavior, not introduced by this PR, but the new annotation surfaces the deviation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/environment.py` around lines 49 - 50, The render method currently accepts
a mode parameter and mutates self.render_mode, which diverges from Gymnasium
conventions; change the signature of render to no-argument (def render(self):)
and remove any in-method mutation of self.render_mode in the render method in
Environment (or the class containing render), and instead provide a separate
setter (e.g., set_render_mode or a render_mode property setter) or require
render_mode to be set at construction so that render only performs rendering and
follows Gymnasium's API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/environment.py`:
- Line 74: The constructor parameter workload_gen is incorrectly typed as
WorkloadGenConfig; change its annotation to WorkloadGenerator to match actual
callers and usage (e.g. generate_jobs expects WorkloadGenerator). Update the
signature that contains "workload_gen: WorkloadGenConfig | None" to
"workload_gen: WorkloadGenerator | None" and ensure the module imports
WorkloadGenerator from src.workload_generator (or the correct symbol) instead
of/alongside WorkloadGenConfig so type checking and callers like train.py and
test_sanity_env.py align.

In `@src/plot.py`:
- Line 14: The plot function's signature allows suffix to be a str or empty
string but the code formats it with {suffix:09d}, which requires an integer;
update the plot function signature to use an integer type and a sensible integer
default (e.g., change suffix: int | str = "" to suffix: int = 0) so runtime
ValueError is avoided, and ensure any callers (e.g., places passing
self.current_step) continue to pass integers consistently.

In `@src/sampler_hourly.py`:
- Line 33: Remove the dead local variable hourly_job_counts from parse_jobs:
delete the declaration "hourly_job_counts: defaultdict[int, list[int]] =
defaultdict(list)" and, if defaultdict is now unused in the module, remove its
import as well to avoid unused-import lints; ensure no other references to
hourly_job_counts exist before deleting.

In `@src/workload_generator.py`:
- Line 29: The declared return type tuple[int, list[int], list[int], list[int]]
is wrong for code paths where new_jobs_durations is filled from numpy arrays;
change the code that assigns from durations_sampler.sample() and
np_random.integers(...) to convert those NDArray results to Python lists (call
.tolist()) so new_jobs_durations and the related arrays match list[int], leaving
the append-based paths unchanged; specifically update the assignments that call
durations_sampler.sample() and np_random.integers(...) to use .tolist() so the
function signature can remain as declared.

---

Outside diff comments:
In `@src/baseline.py`:
- Around line 33-54: Update the function docstring for the baseline simulation
(the triple-quoted block in baseline.py above the function signature that
includes baseline_backlog_queue) to document the missing parameter
baseline_backlog_queue: deque; add a brief description like
"baseline_backlog_queue: deque of jobs waiting for scheduling (holds job IDs or
job dicts)" alongside the other Args entries and keep the same formatting style
as the existing Args lines so the docstring accurately reflects the function
signature and parameters such as baseline_state, baseline_cores_available,
baseline_running_jobs, current_price, new_jobs_count, new_jobs_durations,
new_jobs_nodes, new_jobs_cores, baseline_next_empty_slot, next_job_id, metrics,
env_print.

In `@src/prices_deterministic.py`:
- Around line 1-12: Add the line "from __future__ import annotations" at the top
of this module before any other imports to match other files and ensure
forward-compatible, deferred evaluation of type annotations used in the Prices
class and its __init__ signature (which uses PEP 604 union syntax like
np.ndarray | list[float] | None); no other code changes are needed.

In `@src/sampler_jobs.py`:
- Around line 36-45: The bin stepping currently hardcodes 1-hour/1-day deltas;
change the logic in the time_format/time_delta assignment (the block that sets
time_format and time_delta) to compute time_delta from bin_minutes (time_delta =
datetime.timedelta(minutes=bin_minutes)) and choose the string format based on
divisibility: if bin_minutes % 1440 == 0 use "%Y-%m-%d", elif bin_minutes % 60
== 0 use "%Y-%m-%d %H:00", else use "%Y-%m-%d %H:%M"; update the code that
builds period keys to use this computed time_delta so multi-hour and multi-day
bins advance correctly and avoid extra empty bins.
- Around line 326-346: The code truncates total core-hours by using
int(total_core_hours) to set equivalent_cores, which loses fractional
core-hours; change equivalent_cores to use the full float (e.g.,
equivalent_cores = max(1.0, total_core_hours)) so you don't undercount, keep
using math.ceil when converting cores to integer nodes/cores per node
(equivalent_nodes = max(1, math.ceil(equivalent_cores / cores_per_node)) and
cores_per_node_needed = min(cores_per_node, math.ceil(cores_needed /
equivalent_nodes))) and add the precise total_core_hours value into the created
hourly_job (for example as 'total_core_hours' or 'core_hours') so downstream
accounting can preserve exact core-hours while allocation remains integer.

---

Nitpick comments:
In `@src/environment.py`:
- Around line 49-50: The render method currently accepts a mode parameter and
mutates self.render_mode, which diverges from Gymnasium conventions; change the
signature of render to no-argument (def render(self):) and remove any in-method
mutation of self.render_mode in the render method in Environment (or the class
containing render), and instead provide a separate setter (e.g., set_render_mode
or a render_mode property setter) or require render_mode to be set at
construction so that render only performs rendering and follows Gymnasium's API.

In `@src/plotter.py`:
- Line 88: The parameter episode_costs in function plot_dashboard is currently
unused and triggers Ruff ARG001; rename it to _episode_costs in the
plot_dashboard signature to indicate it's intentionally unused (keeping its
docstring note for backward compatibility) and update any internal references to
use the new name if present; no other behavior changes should be made so callers
remain compatible with the parameter name while the linter warning is silenced.

In `@src/reward_calculation.py`:
- Line 181: The env_print parameter in the function signature is currently typed
as Callable[[str], None] which conflicts with the variadic env_print used
elsewhere (e.g., the env_print method in baseline.py typed as Callable[...,
None]); change the type annotation for env_print in the function (the signature
that returns tuple[float, float, float, float, float, float]) from
Callable[[str], None] to Callable[..., None] so callers passing env_print(self,
*args: Any) match the type; update only the type annotation for the env_print
parameter (and any similar signatures in this module) to Callable[..., None].

In `@src/sampler_duration.py`:
- Around line 62-76: The docstring for sample() is stale and describes
floats/scalar behavior that doesn't match the annotated return type
NDArray[np.intp] and the actual implementation (which always returns a numpy
array cast to int via .astype(int)); update the docstring in sample() to state
it returns a numpy.ndarray of integers (NDArray[np.intp]) with shape (n,) and
that when n==1 it yields a single-element array rather than a plain
float/scalar; ensure the Args/Returns section mentions integers and the array
shape to match sample_values/probabilities usage.

In `@src/sampler_hourly.py`:
- Line 22: Replace the non-idiomatic "return None" with a bare "return" in the
function that is annotated with "-> None" (the function containing the current
"return None" at line 22) so the function uses the idiomatic form for returning
from a None-annotated function; update the single "return None" statement to
"return" in that function (no other logic changes).
- Around line 142-147: The return-type annotation of get_stats is too strict for
numpy scalar results; change the signature to return dict[int, dict[str, Any]]
(import Any from typing) or otherwise widen the inner-value type to accept numpy
scalars (e.g., Union[int, float, np.floating, np.integer]) so mypy/Pylance stop
flagging np.mean/np.max/len returns; update the get_stats function annotation
and any related usages to use the same relaxed type (consistent with
hourly_templates typing).

In `@src/workloadgen_cli.py`:
- Around line 22-31: The type error arises because using tuple(generator) yields
tuple[float, ...] for parse_quad_floats (and similarly for parse_quad_ints and
parse_quad_ranges); to satisfy the declared fixed-length return types,
explicitly convert parts to the target types, unpack into four named variables
(e.g., a,b,c,d) after validating len(parts)==4, then return (a, b, c, d) so the
type checker sees a fixed 4-tuple rather than a variadic tuple.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 814dd8e and e5adf00.

📒 Files selected for processing (19)
  • src/baseline.py
  • src/callbacks.py
  • src/config.py
  • src/environment.py
  • src/job_management.py
  • src/metrics_tracker.py
  • src/node_management.py
  • src/plot.py
  • src/plotter.py
  • src/prices.py
  • src/prices_deterministic.py
  • src/reward_calculation.py
  • src/sampler_duration.py
  • src/sampler_hourly.py
  • src/sampler_jobs.py
  • src/weights.py
  • src/workload_generator.py
  • src/workloadgen.py
  • src/workloadgen_cli.py
💤 Files with no reviewable changes (1)
  • src/prices.py

Copy link
Contributor

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

Caution

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

⚠️ Outside diff range comments (1)
src/workload_generator.py (1)

52-55: ⚠️ Potential issue | 🟠 Major

Guard optional jobs_sampler before use.

At Line 54, jobs_sampler is optional in the signature but used unconditionally. If external_jobs is configured and sampler wiring is missing, this fails with AttributeError instead of a controlled error.

Proposed fix
     if external_jobs and not workload_gen:
+        if jobs_sampler is None:
+            raise RuntimeError("jobs_sampler is required when external_jobs is enabled.")
         # Use jobs sampler for pattern-based replay
         jobs = jobs_sampler.sample_one_hourly(wrap=True)["hourly_jobs"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/workload_generator.py` around lines 52 - 55, The code uses jobs_sampler
unconditionally in the external-jobs branch (when external_jobs and not
workload_gen) by calling jobs_sampler.sample_one_hourly; guard the optional
jobs_sampler (e.g., check jobs_sampler is not None) before calling
sample_one_hourly and if it is missing raise a clear, controlled exception
(ValueError or custom) explaining that jobs_sampler must be provided when
external_jobs is enabled, or alternatively fall back to an appropriate default
behavior; update the logic around jobs_sampler.sample_one_hourly and the jobs
assignment to only execute when jobs_sampler is present.
🧹 Nitpick comments (4)
src/environment.py (1)

268-268: Minor: unparameterized dict in reset return type.

step consistently uses dict[str, Any] for the info dict; reset should too.

♻️ Proposed fix
-    def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[dict[str, np.ndarray], dict]:
+    def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[dict[str, np.ndarray], dict[str, Any]]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/environment.py` at line 268, The reset method's return type uses an
unparameterized dict for the info return; update the signature of reset (def
reset) to return tuple[dict[str, np.ndarray], dict[str, Any]] to match step's
info typing. Locate the reset function declaration and change its second element
of the tuple return annotation from dict to dict[str, Any], ensuring imports for
Any are present if needed and that any docstring/type hints align with the new
annotation.
src/metrics_tracker.py (1)

128-137: Inconsistent guard style: if self.episode_jobs_submitted vs > 0 used elsewhere.

Lines 130 and 135 use a bare truthiness test while lines 119, 124, and the rest of the method use > 0. Both are equivalent for int, but aligning them improves readability.

♻️ Proposed fix
-        drop_rate: float = (
-            (self.episode_jobs_dropped / self.episode_jobs_submitted * 100)
-            if self.episode_jobs_submitted
-            else 0.0
-        )
-        baseline_drop_rate: float = (
-            (self.episode_baseline_jobs_dropped / self.episode_baseline_jobs_submitted * 100)
-            if self.episode_baseline_jobs_submitted
-            else 0.0
-        )
+        drop_rate: float = (
+            (self.episode_jobs_dropped / self.episode_jobs_submitted * 100)
+            if self.episode_jobs_submitted > 0
+            else 0.0
+        )
+        baseline_drop_rate: float = (
+            (self.episode_baseline_jobs_dropped / self.episode_baseline_jobs_submitted * 100)
+            if self.episode_baseline_jobs_submitted > 0
+            else 0.0
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/metrics_tracker.py` around lines 128 - 137, The guard expressions for
computing drop_rate and baseline_drop_rate use bare truthiness checks; change
the conditions to explicit comparisons (use > 0) to match the rest of the
method. Update the ternary guards in the drop_rate calculation (referencing
drop_rate and self.episode_jobs_submitted) and in the baseline_drop_rate
calculation (referencing baseline_drop_rate and
self.episode_baseline_jobs_submitted) to use "if self.episode_jobs_submitted >
0" and "if self.episode_baseline_jobs_submitted > 0" respectively so the style
is consistent.
src/job_management.py (1)

13-13: Parameterize deque type hint for precision.

All three signatures declare backlog_queue: deque (unparameterized). Queue items are list[int] (4-element [duration, age, nodes, cores]); annotating as deque[list[int]] makes the element type explicit and catches misuse earlier under strict type-checkers.

♻️ Proposed refactor
-def age_backlog_queue(backlog_queue: deque, _metrics: MetricsTracker, _is_baseline: bool = False) -> int:
+def age_backlog_queue(backlog_queue: deque[list[int]], _metrics: MetricsTracker, _is_baseline: bool = False) -> int:
-def fill_queue_from_backlog(job_queue_2d: np.ndarray, backlog_queue: deque, next_empty_slot: int) -> tuple[int, int]:
+def fill_queue_from_backlog(job_queue_2d: np.ndarray, backlog_queue: deque[list[int]], next_empty_slot: int) -> tuple[int, int]:
-        backlog_queue: deque | None = None,
+        backlog_queue: deque[list[int]] | None = None,

Also applies to: 47-47, 127-127

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/job_management.py` at line 13, Update the unparameterized deque type
annotations to be precise: change occurrences like backlog_queue: deque to
backlog_queue: deque[list[int]] (items are 4-element lists of ints: [duration,
age, nodes, cores]) so static type checkers catch misuse; specifically update
the signature of age_backlog_queue(backlog_queue: deque, ...) to backlog_queue:
deque[list[int]] and make the same replacement for the other functions in this
file that currently use backlog_queue: deque.
src/reward_calculation.py (1)

79-88: Remove dead code: _reward_efficiency_normalized is unreachable and its bounds are unused.

_reward_efficiency_normalized is never called. The calculate() method uses _reward_energy_efficiency_normalized() + _blackout_term() instead (line 205). The efficiency bounds _min_efficiency_reward and _max_efficiency_reward are only referenced within this dead method, making them unreachable from all live code paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/reward_calculation.py` around lines 79 - 88, The
_reward_efficiency_normalized method and its associated bounds
(_min_efficiency_reward, _max_efficiency_reward) are dead code: remove the
_reward_efficiency_normalized function and eliminate the unused
attributes/variables _min_efficiency_reward and _max_efficiency_reward from the
class (and from __init__ or any defaults) so they are not kept around; ensure no
remaining references to _reward_efficiency_normalized, _min_efficiency_reward or
_max_efficiency_reward remain (search for those symbols) and run tests to
confirm behavior still relies on _reward_energy_efficiency_normalized() +
_blackout_term().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/prices_deterministic.py`:
- Line 19: predicted_prices is declared as np.ndarray | None but used
unconditionally in get_price_context (np.mean(self.predicted_prices)), causing
type-checker errors; fix by either narrowing the attribute type to np.ndarray
and initializing it (e.g., assign an empty np.ndarray placeholder) before
reset() in __init__ so reset can overwrite it, or keep the union type and add a
None-guard in get_price_context (raise/assert or return a sensible default)
before calling np.mean; update the annotation on self.predicted_prices and
modify __init__/reset or add the guard in get_price_context accordingly,
referencing self.predicted_prices, __init__, reset, and get_price_context.

In `@src/reward_calculation.py`:
- Around line 178-181: The calculate method currently accepts num_node_changes
but never uses it; either remove the unused parameter from the calculate
signature (and update any callers) or keep it as a planned feature by adding a
local noqa to silence ARG002 and a short TODO; specifically, if you intend to
implement node-change penalties later, keep num_node_changes in def
calculate(...) and add a comment like "# noqa: ARG002  TODO: apply node-change
penalty when implementing transition costs" next to the parameter or at the top
of the function; otherwise delete num_node_changes from the calculate signature
and adjust all callers (and any type hints) accordingly.

---

Outside diff comments:
In `@src/workload_generator.py`:
- Around line 52-55: The code uses jobs_sampler unconditionally in the
external-jobs branch (when external_jobs and not workload_gen) by calling
jobs_sampler.sample_one_hourly; guard the optional jobs_sampler (e.g., check
jobs_sampler is not None) before calling sample_one_hourly and if it is missing
raise a clear, controlled exception (ValueError or custom) explaining that
jobs_sampler must be provided when external_jobs is enabled, or alternatively
fall back to an appropriate default behavior; update the logic around
jobs_sampler.sample_one_hourly and the jobs assignment to only execute when
jobs_sampler is present.

---

Nitpick comments:
In `@src/environment.py`:
- Line 268: The reset method's return type uses an unparameterized dict for the
info return; update the signature of reset (def reset) to return tuple[dict[str,
np.ndarray], dict[str, Any]] to match step's info typing. Locate the reset
function declaration and change its second element of the tuple return
annotation from dict to dict[str, Any], ensuring imports for Any are present if
needed and that any docstring/type hints align with the new annotation.

In `@src/job_management.py`:
- Line 13: Update the unparameterized deque type annotations to be precise:
change occurrences like backlog_queue: deque to backlog_queue: deque[list[int]]
(items are 4-element lists of ints: [duration, age, nodes, cores]) so static
type checkers catch misuse; specifically update the signature of
age_backlog_queue(backlog_queue: deque, ...) to backlog_queue: deque[list[int]]
and make the same replacement for the other functions in this file that
currently use backlog_queue: deque.

In `@src/metrics_tracker.py`:
- Around line 128-137: The guard expressions for computing drop_rate and
baseline_drop_rate use bare truthiness checks; change the conditions to explicit
comparisons (use > 0) to match the rest of the method. Update the ternary guards
in the drop_rate calculation (referencing drop_rate and
self.episode_jobs_submitted) and in the baseline_drop_rate calculation
(referencing baseline_drop_rate and self.episode_baseline_jobs_submitted) to use
"if self.episode_jobs_submitted > 0" and "if
self.episode_baseline_jobs_submitted > 0" respectively so the style is
consistent.

In `@src/reward_calculation.py`:
- Around line 79-88: The _reward_efficiency_normalized method and its associated
bounds (_min_efficiency_reward, _max_efficiency_reward) are dead code: remove
the _reward_efficiency_normalized function and eliminate the unused
attributes/variables _min_efficiency_reward and _max_efficiency_reward from the
class (and from __init__ or any defaults) so they are not kept around; ensure no
remaining references to _reward_efficiency_normalized, _min_efficiency_reward or
_max_efficiency_reward remain (search for those symbols) and run tests to
confirm behavior still relies on _reward_energy_efficiency_normalized() +
_blackout_term().

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5adf00 and a3954bf.

📒 Files selected for processing (20)
  • src/baseline.py
  • src/callbacks.py
  • src/config.py
  • src/environment.py
  • src/job_management.py
  • src/metrics_tracker.py
  • src/node_management.py
  • src/plot.py
  • src/plotter.py
  • src/prices_deterministic.py
  • src/reward_calculation.py
  • src/sampler_duration.py
  • src/sampler_hourly.py
  • src/sampler_jobs.py
  • src/weights.py
  • src/workload_generator.py
  • src/workloadgen.py
  • src/workloadgen_cli.py
  • test/test_plot_dummy.py
  • train.py
💤 Files with no reviewable changes (2)
  • train.py
  • test/test_plot_dummy.py
✅ Files skipped from review due to trivial changes (1)
  • src/callbacks.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/weights.py
  • src/node_management.py
  • src/sampler_jobs.py

Copy link
Contributor

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

♻️ Duplicate comments (1)
src/reward_calculation.py (1)

178-181: noqa: ARG002 on num_node_changes now correctly suppresses the unused-argument warning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/reward_calculation.py` around lines 178 - 181, The calculate method
declares num_node_changes as a reserved-but-unused parameter and you should
either keep the existing "# noqa: ARG002" comment on that parameter (ensure it's
exactly after num_node_changes in the signature) or explicitly consume it to
silence linters by adding a short no-op assignment in the function body (e.g., _
= num_node_changes) inside calculate; reference the calculate(...) signature and
the num_node_changes parameter when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/prices_deterministic.py`:
- Around line 5-11: The PR deleted src/prices.py which violates the guideline
that pricing models live in both src/prices.py and src/prices_deterministic.py;
restore compliance by either re-adding a minimal src/prices.py that imports and
re-exports the deterministic implementation (e.g., from src.prices_deterministic
import Prices and expose Prices) or update the project guideline to state that
src/prices_deterministic.py is now the canonical implementation; ensure the
restored src/prices.py references the Prices class from prices_deterministic.py
so consumers still import from src.prices while keeping the existing Prices
class intact.

In `@src/reward_calculation.py`:
- Around line 116-120: Wrap the np.clip return values with float(...) to match
the -> float annotations: change the return in _penalty_idle_normalized to
return float(np.clip(...)), do the same in _penalty_job_age_normalized, and
ensure the branch in _reward_efficiency_normalized that currently returns an
unwrapped np.clip is also wrapped with float(...); keep other branches that
already return Python floats unchanged so all code paths return a Python float.

---

Duplicate comments:
In `@src/reward_calculation.py`:
- Around line 178-181: The calculate method declares num_node_changes as a
reserved-but-unused parameter and you should either keep the existing "# noqa:
ARG002" comment on that parameter (ensure it's exactly after num_node_changes in
the signature) or explicitly consume it to silence linters by adding a short
no-op assignment in the function body (e.g., _ = num_node_changes) inside
calculate; reference the calculate(...) signature and the num_node_changes
parameter when making the change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3954bf and c0504ee.

📒 Files selected for processing (2)
  • src/prices_deterministic.py
  • src/reward_calculation.py

Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLAUDE.md`:
- Line 24: The documentation change removed the documented core pricing module
reference to prices_deterministic.py while the repository rule still requires
both pricing modules; either restore the missing mention or update the rule.
Revert or re-add the entry for prices_deterministic.py in CLAUDE.md so both
prices.py and prices_deterministic.py are listed under pricing, or if
consolidation is intentional, update the repository guideline text to reflect a
single prices.py implementation; ensure the change references the module names
prices.py and prices_deterministic.py and the CLAUDE.md docs section
consistently.

In `@src/environment.py`:
- Line 67: ComputeClusterEnv.__init__ currently accepts external_prices: str |
None and passes it directly to Prices, but Prices expects numeric sequences
(np.ndarray | list[float] | None); update ComputeClusterEnv.__init__ to detect
when external_prices is a str (file path) and load/parse it into a numeric
sequence (e.g., use numpy.loadtxt or json/CSV parsing to produce a list[float]
or np.ndarray) before constructing Prices, validate the parsed data is numeric
(or raise a clear ValueError), and otherwise accept None or already-numeric
inputs unchanged; ensure you reference ComputeClusterEnv.__init__ and Prices
when making the change so callers can still pass arrays or file paths safely.

In `@src/prices.py`:
- Around line 71-76: The synthetic-mode branch of reset ignores the start_index
parameter: update the synthetic reset logic in the reset(start_index=...) path
so self.price_index is initialized to start_index (not always to
self.PREDICTION_WINDOW) and build self.predicted_prices by calling
self._synthetic_price_at(i) for i in range(start_index, start_index +
self.PREDICTION_WINDOW) (or an equivalent offset loop) while preserving
dtype=np.float32; ensure references to self.PREDICTION_WINDOW,
self._synthetic_price_at, self.price_index, and self.predicted_prices are used
so synthetic timeline positioning matches external mode.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0504ee and 0c69f5b.

📒 Files selected for processing (6)
  • CLAUDE.md
  • src/environment.py
  • src/prices.py
  • src/reward_calculation.py
  • test/test_price_history.py
  • test/test_prices_cycling.py
✅ Files skipped from review due to trivial changes (1)
  • test/test_price_history.py

Copy link
Contributor

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

♻️ Duplicate comments (4)
src/prices.py (1)

71-77: start_index is now correctly applied in synthetic mode.

The previous review flagged that start_index was ignored in the synthetic branch. The current code correctly uses start (derived from start_index) for both self.price_index and the _synthetic_price_at calls. This is resolved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/prices.py` around lines 71 - 77, The synthetic branch now correctly
applies start_index by setting start = int(start_index), updating
self.price_index, and generating self.predicted_prices via
self._synthetic_price_at over self.PREDICTION_WINDOW; no code changes
required—just remove the duplicate/obsolete review comment and mark the change
approved so the PR can be merged (references: start, self.price_index,
self.predicted_prices, _synthetic_price_at, PREDICTION_WINDOW).
src/environment.py (3)

67-67: Previously flagged external_prices: str | None type mismatch is now resolved.

The parameter is correctly typed as list[float] | np.ndarray | None, which is consistent with the Prices constructor signature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/environment.py` at line 67, The type mismatch for the parameter
external_prices has been corrected to list[float] | np.ndarray | None to match
the Prices constructor signature; no further code changes are necessary—verify
the signature in the Prices class and ensure any callers passing external_prices
conform to the updated type.

63-75: ⚠️ Potential issue | 🟠 Major

X | Y union syntax in __init__ signature requires Python ≥ 3.10.

Same concern as flagged in src/prices.py: the list[float] | np.ndarray | None annotation on Line 67 and WorkloadGenerator | None on Line 74 will raise TypeError at import time on Python < 3.10 without from __future__ import annotations. Since the Python version concern is project-wide, please also address it here if the project requires supporting Python < 3.10.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/environment.py` around lines 63 - 75, The __init__ signature in
Environment uses Python 3.10 union syntax (e.g., list[float] | np.ndarray | None
and WorkloadGenerator | None) which will raise on older Python versions; either
add "from __future__ import annotations" at the top of src/environment.py to
defer evaluation of annotations, or replace those unions with
typing.Union/Optional (e.g., Optional[Union[list[float], np.ndarray]]) and
typing.Optional[WorkloadGenerator] to be compatible with Python <3.10; update
the annotations in the __init__ definition (and any other annotations in this
file) accordingly to match the project's supported Python version.

74-74: Previously flagged workload_gen type mismatch is now resolved.

The parameter is correctly typed as WorkloadGenerator | None, matching the actual callers and the generate_jobs signature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/environment.py` at line 74, The workload_gen parameter typing has been
corrected to WorkloadGenerator | None; keep this updated annotation on the
function/class where workload_gen is declared, ensure the generate_jobs
signature uses the same WorkloadGenerator | None type, remove any
stale/duplicate type comments or alternate annotations that previously flagged a
mismatch, and run mypy/unit tests to verify all callers (references to
workload_gen and generate_jobs) type-check correctly.
🧹 Nitpick comments (3)
src/environment.py (1)

268-268: Unparameterized dict in reset return type.

The return type is annotated as tuple[dict[str, np.ndarray], dict]. The second element — returned as {} — should be dict[str, Any] for consistency with step's dict[str, Any] and for stricter type checking.

🧹 Proposed fix
-    def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[dict[str, np.ndarray], dict]:
+    def reset(self, seed: int | None = None, options: dict[str, Any] | None = None) -> tuple[dict[str, np.ndarray], dict[str, Any]]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/environment.py` at line 268, Change the unparameterized dict in the reset
return type to match step's metadata typing: update the signature of reset
(function reset) from returning tuple[dict[str, np.ndarray], dict] to
tuple[dict[str, np.ndarray], dict[str, Any]] so the second element (currently
returned as {}) is properly typed for stricter type checking and consistency
with step's dict[str, Any].
src/prices.py (2)

34-35: Redundant self.external_prices = None assignment.

self.external_prices is already initialized to None on Line 14. The reassignment on Line 35 inside the else branch is dead code.

🧹 Proposed fix
         else:
-            self.external_prices = None
             # Keep your defaults for normalization bounds
             self.MAX_PRICE = 24.0
             self.MIN_PRICE = 16.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/prices.py` around lines 34 - 35, The else-branch contains a redundant
reassignment of the instance attribute self.external_prices to None (it was
already initialized as None earlier); remove the redundant assignment in that
else block so the attribute set at construction (self.external_prices) remains
the single source of truth and avoid dead code in the method that contains the
else branch.

97-101: advance_and_get_predicted_prices shifts the window using the next external price, not the PREDICTION_WINDOW-ahead price.

get_next_price() fetches external_prices[price_index], which is the element immediately after the initial window. After the first call it advances price_index by 1. This means the new tail appended to predicted_prices is the price just beyond the current window, which is the correct sliding-window behaviour. However, the docstring comment on Line 97 ("Changed name for readability") is a stale note from development — it adds no informational value to readers and should be cleaned up.

🧹 Proposed fix
-    def advance_and_get_predicted_prices(self) -> np.ndarray:  # Changed name for readability
+    def advance_and_get_predicted_prices(self) -> np.ndarray:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/prices.py` around lines 97 - 101, The inline comment "Changed name for
readability" in the advance_and_get_predicted_prices method is stale and should
be removed; update the function's docstring (or add one) to concisely state that
advance_and_get_predicted_prices calls get_next_price() to pull
external_prices[price_index], advances price_index, shifts the predicted_prices
window (using np.roll) and appends that next external price as the new tail;
reference the symbols advance_and_get_predicted_prices, get_next_price,
predicted_prices, price_index, and external_prices when editing so the
behavioral note replaces the stale development comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/environment.py`:
- Line 67: The type mismatch for the parameter external_prices has been
corrected to list[float] | np.ndarray | None to match the Prices constructor
signature; no further code changes are necessary—verify the signature in the
Prices class and ensure any callers passing external_prices conform to the
updated type.
- Around line 63-75: The __init__ signature in Environment uses Python 3.10
union syntax (e.g., list[float] | np.ndarray | None and WorkloadGenerator |
None) which will raise on older Python versions; either add "from __future__
import annotations" at the top of src/environment.py to defer evaluation of
annotations, or replace those unions with typing.Union/Optional (e.g.,
Optional[Union[list[float], np.ndarray]]) and typing.Optional[WorkloadGenerator]
to be compatible with Python <3.10; update the annotations in the __init__
definition (and any other annotations in this file) accordingly to match the
project's supported Python version.
- Line 74: The workload_gen parameter typing has been corrected to
WorkloadGenerator | None; keep this updated annotation on the function/class
where workload_gen is declared, ensure the generate_jobs signature uses the same
WorkloadGenerator | None type, remove any stale/duplicate type comments or
alternate annotations that previously flagged a mismatch, and run mypy/unit
tests to verify all callers (references to workload_gen and generate_jobs)
type-check correctly.

In `@src/prices.py`:
- Around line 71-77: The synthetic branch now correctly applies start_index by
setting start = int(start_index), updating self.price_index, and generating
self.predicted_prices via self._synthetic_price_at over self.PREDICTION_WINDOW;
no code changes required—just remove the duplicate/obsolete review comment and
mark the change approved so the PR can be merged (references: start,
self.price_index, self.predicted_prices, _synthetic_price_at,
PREDICTION_WINDOW).

---

Nitpick comments:
In `@src/environment.py`:
- Line 268: Change the unparameterized dict in the reset return type to match
step's metadata typing: update the signature of reset (function reset) from
returning tuple[dict[str, np.ndarray], dict] to tuple[dict[str, np.ndarray],
dict[str, Any]] so the second element (currently returned as {}) is properly
typed for stricter type checking and consistency with step's dict[str, Any].

In `@src/prices.py`:
- Around line 34-35: The else-branch contains a redundant reassignment of the
instance attribute self.external_prices to None (it was already initialized as
None earlier); remove the redundant assignment in that else block so the
attribute set at construction (self.external_prices) remains the single source
of truth and avoid dead code in the method that contains the else branch.
- Around line 97-101: The inline comment "Changed name for readability" in the
advance_and_get_predicted_prices method is stale and should be removed; update
the function's docstring (or add one) to concisely state that
advance_and_get_predicted_prices calls get_next_price() to pull
external_prices[price_index], advances price_index, shifts the predicted_prices
window (using np.roll) and appends that next external price as the new tail;
reference the symbols advance_and_get_predicted_prices, get_next_price,
predicted_prices, price_index, and external_prices when editing so the
behavioral note replaces the stale development comment.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c69f5b and 2846e62.

📒 Files selected for processing (2)
  • src/environment.py
  • src/prices.py

@rbx rbx merged commit 70b4aa5 into master Feb 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant