From 57848d85ba17baa1d99ba5aad2d71e1f32ace20f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:25:01 +0000 Subject: [PATCH 1/4] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.22 β†’ v0.16.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.22...v0.16.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa71e0e..34453a0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: args: ["--maxkb=800"] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.22 + rev: v0.16.2 hooks: - id: ruff-check args: [--fix] From 38ebf1a97851a00a5dd9132cc9386abcdc609fff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:27:29 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- wasm/marimo/plant-explorer.py | 12 ++++-------- wasm/marimo/rate-base-sales-explorer.py | 11 +---------- wasm/marimo/utility-explorer.py | 11 ----------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/wasm/marimo/plant-explorer.py b/wasm/marimo/plant-explorer.py index e9843d1..44321a0 100644 --- a/wasm/marimo/plant-explorer.py +++ b/wasm/marimo/plant-explorer.py @@ -21,13 +21,11 @@ def _(mo, selection): ] ) ) - return @app.cell(hide_code=True) def _(mo, this_plant): mo.md(f"# {this_plant.name} (EIA id={this_plant.plant_id_eia})") - return @app.cell @@ -37,12 +35,12 @@ def _(): with mo.status.progress_bar( total=1, title="Loading subroutines", remove_on_exit=True ) as do_imports: - import itertools import functools + import itertools + import altair as alt import pandas as pd import pyarrow as pa - import altair as alt do_imports.update(subtitle="Done!") return alt, functools, itertools, mo, pd @@ -239,9 +237,10 @@ def reset_params(**kwargs): @app.cell def _(Options, mo, query_params, reset_params): - from pydantic import BaseModel, Field, computed_field from functools import cached_property + from pydantic import BaseModel, Field, computed_field + class Selection(BaseModel): """Store/represent the user's current plant selection. @@ -573,7 +572,6 @@ def _(alt, mo, this_plant, this_plant__monthly_generation_fuel_combined): f"via {table_preview_href('out_eia923__monthly_generation_fuel_combined')}" ) ) - return @app.cell @@ -625,7 +623,6 @@ def _( mo.output.append( mo.md(f"via {table_preview_href('out_eia923__monthly_generation')}") ) - return @app.cell @@ -799,7 +796,6 @@ def _( ) ) mo.output.append(mo.md(f"via {table_preview_href('out_eia__yearly_generators')}")) - return if __name__ == "__main__": diff --git a/wasm/marimo/rate-base-sales-explorer.py b/wasm/marimo/rate-base-sales-explorer.py index 86089de..59884cd 100644 --- a/wasm/marimo/rate-base-sales-explorer.py +++ b/wasm/marimo/rate-base-sales-explorer.py @@ -15,7 +15,6 @@ def header(mo): These tables provide clues about how rates have changed over time, the primary drivers of that change and who bears the impact of that change. It’s important to note that, while informative, the data provide an incomplete and imperfect picture. The rate making process is complex and this dashboard only gives us a snapshot. Nonetheless, we think this information is useful and we encourage you to explore. For more background materials, see the Additional Resources at the bottom of the page. """) - return @app.cell @@ -222,7 +221,6 @@ def add_columns( pd.StringDtype() ) ) - return @app.cell @@ -524,7 +522,6 @@ def sidebar(mark_type_selector, mo, selection): ), ] ) - return @app.cell @@ -535,7 +532,6 @@ def no_utilities_1_stop(mo, selection): "## πŸ›‘ **Choose a utilities the sidebar.** ⬅️\nWe really want to show you some pretty graphs, but you have to select utilities to show. If you happen to choose utilities in the 'To compare' section, you still need to choose utilities above that." ), ) - return @app.cell @@ -562,7 +558,7 @@ class GraphInputs(BaseModel): graph_inputs = {"opt_1": {}, "opt_2": {}} - for opt_n in graph_inputs.keys(): + for opt_n in graph_inputs: utility_selection = selection.model_dump().get( f"utilities_{opt_n.removeprefix('opt_')}" ) @@ -820,7 +816,6 @@ def chart_ferc1(ColumToChart, graph_inputs, make_comparison_charts, mo): ] make_comparison_charts(cols_to_chart_ferc1, graph_inputs, "filtered_rate_base") - return @app.cell @@ -895,7 +890,6 @@ def chart_eia861(ColumToChart, graph_inputs, make_comparison_charts, mo): ] make_comparison_charts(cols_to_chart_eia861, graph_inputs, "filtered_sales") - return @app.cell(hide_code=True) @@ -903,7 +897,6 @@ def _(mo): mo.md(r""" ## πŸ“š Additional Resources """) - return @app.cell @@ -979,7 +972,6 @@ def materials_accordion(mo): * RAP's Improving Utility Performance Incentives in the United States"""), } ) - return @app.cell(hide_code=True) @@ -987,7 +979,6 @@ def contact_us(mo): mo.md(r""" If you see anything odd in the data, find a bug or just have a question, feel free to reach out to us by emailing us at hello@catalyst.coop or write up a github issue. Heck, if you just found this helpful, let us know! As an open-source project we love to hear about your energy data needs. """) - return if __name__ == "__main__": diff --git a/wasm/marimo/utility-explorer.py b/wasm/marimo/utility-explorer.py index 81474c7..ce6f42f 100644 --- a/wasm/marimo/utility-explorer.py +++ b/wasm/marimo/utility-explorer.py @@ -12,7 +12,6 @@ def _(mo): 'Explore attributes of any utility that reports to EIA-861. Select a state and specific utility to explore its attributes and its electricity sources, sales, and reliability over time.' ) ) - return @app.cell @@ -49,7 +48,6 @@ def _(mo, selection): ] ) ) - return @app.cell @@ -57,7 +55,6 @@ def _(mo, selection): mo.md(f""" #**{selection.util_name}** """) - return @app.cell @@ -662,7 +659,6 @@ def _(fips_set, mo, selection, st_chart, stats_table, table_preview_href): ] ) util_stats - return @app.cell @@ -780,7 +776,6 @@ def _(mo, selection, status_df, table_preview_href): mo.md(f"via {table_preview_href('out_eia__yearly_generators')}"), ] ) - return @app.cell @@ -970,7 +965,6 @@ def _(fuel_chart, fuel_long, mo, selection, source_chart, table_preview_href): ] ) electricity_source - return @app.cell @@ -998,7 +992,6 @@ def _(util_mfrc_df): fuel_cost_df["fuel_cost_received"] = ( fuel_cost_df["fuel_cost_per_mmbtu"] * fuel_cost_df["fuel_received_mmbtu"] ) - return @app.cell @@ -1073,7 +1066,6 @@ def _(fuel_cost_chart, mo, table_preview_href): ] ) fuel_cost - return @app.cell @@ -1380,7 +1372,6 @@ def _( ) customer_facing - return @app.cell @@ -1435,7 +1426,6 @@ def _(mo, summer_v_winter_demand_chart, table_preview_href): ) demand - return @app.cell(hide_code=True) @@ -1443,7 +1433,6 @@ def _(mo): mo.md(r""" If you see anything odd in the data, find a bug or just have a question, feel free to reach out to us by emailing us at hello@catalyst.coop or write up a github issue. Heck, if you just found this helpful, let us know! As an open-source project we love to hear about your energy data needs. """) - return if __name__ == "__main__": From 859c8fc2fc3e52840585feba3601980808bf3764 Mon Sep 17 00:00:00 2001 From: Zane Selvans Date: Sat, 15 Aug 2026 19:02:10 -0600 Subject: [PATCH 3/4] Exclude notebooks from ruff, ignore B018 in marimo cells, fix remaining lint Jupyter notebooks are synced from Kaggle and get overwritten without running our tooling, so linting them is pointless; exclude *.ipynb via a new root ruff.toml. Marimo notebook cells idiomatically end with a bare expression to display it, which ruff flags as B018 (useless expression), so ignore that rule under wasm/marimo/*.py. Fix the remaining real lint findings (dict.keys() iteration, TypeVar name mismatch, list-slice vs next(iter()), dict lookup in loop) rather than suppressing them. --- .pre-commit-config.yaml | 2 +- ruff.toml | 4 ++++ scripts/export_marimo_notebooks.py | 0 scripts/sync_kaggle_deps.py | 4 ++-- wasm/marimo/plant-explorer.py | 6 +++--- wasm/marimo/rate-base-sales-explorer.py | 4 ++-- wasm/ruff.toml | 9 +++++++++ 7 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 ruff.toml mode change 100644 => 100755 scripts/export_marimo_notebooks.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34453a0..3da1d39 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: args: ["--maxkb=800"] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.16.2 + rev: v0.16.3 hooks: - id: ruff-check args: [--fix] diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..d7bd7cb --- /dev/null +++ b/ruff.toml @@ -0,0 +1,4 @@ +# NOTE (2026-08-15): Jupyter notebooks are synced from Kaggle, which will +# overwrite any lint fixes we make without running our tooling, so there's no +# point linting them here. See .pre-commit-config.yaml for the same rationale. +extend-exclude = ["*.ipynb"] diff --git a/scripts/export_marimo_notebooks.py b/scripts/export_marimo_notebooks.py old mode 100644 new mode 100755 diff --git a/scripts/sync_kaggle_deps.py b/scripts/sync_kaggle_deps.py index 70466eb..f32c057 100755 --- a/scripts/sync_kaggle_deps.py +++ b/scripts/sync_kaggle_deps.py @@ -73,7 +73,7 @@ def main() -> None: package_names = sorted( str(name) - for name in deps_table.keys() + for name in deps_table if name not in exempt_from_sync and name != "python" ) probe_result = probe_kaggle_runtime_dependencies( @@ -153,7 +153,7 @@ def apply_kaggle_pins_to_pixi_deps( kaggle_pins = probe_result["pins"] kaggle_pins["python"] = f"=={probe_result['python_version']}" - for dep_name in deps_table.keys(): + for dep_name in deps_table: if dep_name in exempt_from_sync: continue diff --git a/wasm/marimo/plant-explorer.py b/wasm/marimo/plant-explorer.py index 44321a0..4641c4f 100644 --- a/wasm/marimo/plant-explorer.py +++ b/wasm/marimo/plant-explorer.py @@ -644,7 +644,7 @@ def _(mo, this_plant__generators): only_option = set() filter_options = {} filter_defaults = {} - for k, v in filter_counts.to_dict().items(): + for k in filter_counts.to_dict(): available = this_plant__generators[k].value_counts(dropna=False) available = available.loc[available > 0].index if available.shape[0] == 1: @@ -663,7 +663,7 @@ def _(mo, this_plant__generators): max_input = max(max(v) for v in option_lengths.values()) columns = math.ceil(80 / max_column) - for k in filter_options: + for k, options in filter_options.items(): filters[k] = mo.Html( f"""
@@ -671,7 +671,7 @@ def _(mo, this_plant__generators):
""" ).batch( multiselect=mo.ui.multiselect( - options=filter_options[k], + options=options, value=filter_defaults[k], # label=k, ) diff --git a/wasm/marimo/rate-base-sales-explorer.py b/wasm/marimo/rate-base-sales-explorer.py index 59884cd..87cf264 100644 --- a/wasm/marimo/rate-base-sales-explorer.py +++ b/wasm/marimo/rate-base-sales-explorer.py @@ -543,7 +543,7 @@ def filter_dfs( ): from typing import TypeVar - PandasDataFrame = TypeVar("pandas.core.frame.DataFrame") + PandasDataFrame = TypeVar("PandasDataFrame") class GraphInput(BaseModel): filtered_rate_base: PandasDataFrame @@ -610,7 +610,7 @@ class GraphInputs(BaseModel): utility_selection_title_part = f"{util_len} Utilities" utils_subtitle = " & ".join(utility_selection) else: - utility_selection_title_part = f"{list(utility_selection)[0]}" + utility_selection_title_part = f"{next(iter(utility_selection))}" filtered_rate_base = out_ferc1__yearly_rate_base[rate_mask] filtered_sales = core_eia861__yearly_sales[sales_mask] graph_inputs[opt_n] = GraphInput( diff --git a/wasm/ruff.toml b/wasm/ruff.toml index 0d04f1c..5ba5238 100644 --- a/wasm/ruff.toml +++ b/wasm/ruff.toml @@ -1,4 +1,13 @@ +extend = "../ruff.toml" + [lint.pyflakes] # NOTE 2026-02-27: these need to be imported so that Pandas can read Parquet # files in pyodide, but are not explicitly used in the code. allowed-unused-imports = ["fastparquet", "pyarrow"] + +[lint.per-file-ignores] +# NOTE (2026-08-15): marimo notebook cells commonly end with a bare +# expression, which marimo treats as the cell's displayed output (mirroring +# Jupyter's "last line is shown" behavior). Ruff sees this as a useless +# expression, but it's an intentional and idiomatic part of marimo notebooks. +"marimo/*.py" = ["B018"] From 9203d8729f3f5ba77eddbd6fa2338982c69e7fc8 Mon Sep 17 00:00:00 2001 From: Zane Selvans Date: Sat, 15 Aug 2026 19:08:24 -0600 Subject: [PATCH 4/4] Bring over modernized bot-auto-merge.yml --- .github/actionlint.yaml | 17 ++++++++++ .github/workflows/bot-auto-merge.yml | 46 +++++++++++++++------------- .pre-commit-config.yaml | 6 ++++ 3 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 .github/actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000..6daa50b --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,17 @@ +paths: + .github/workflows/*.yml: + ignore: + # actionlint's bundled metadata for actions/create-github-app-token@v3 + # predates the "client-id" input added upstream in v3.1.0 (2026-04-11), + # so it still reports app-id as required and client-id as unknown. + # Remove once actionlint ships a release newer than v1.7.12 with + # updated action metadata for this action. + - 'missing input "app-id" which is required by action "actions/create-github-app-token@v3"' + - 'input "client-id" is not defined in action "actions/create-github-app-token@v3"' +# these are GitHub-hosted but for some reason actionlint couldn't find them +self-hosted-runner: + labels: + - ubuntu-latest-4core + - ubuntu-latest-8core + - blacksmith-4vcpu-ubuntu-2404 + - blacksmith-8vcpu-ubuntu-2404 diff --git a/.github/workflows/bot-auto-merge.yml b/.github/workflows/bot-auto-merge.yml index 56f5fbb..2001c7e 100644 --- a/.github/workflows/bot-auto-merge.yml +++ b/.github/workflows/bot-auto-merge.yml @@ -1,34 +1,38 @@ --- name: bot-auto-merge +# Runs on pull_request_target (not pull_request) so it has access to secrets even for +# PRs opened by Dependabot, which run with a read-only token by default. This is safe +# here because we never check out or execute any code from the PR -- we only call the +# GitHub API by PR URL to approve it and enable auto-merge. on: - workflow_run: - types: [completed] - workflows: ["tox-pytest"] + pull_request_target: + types: [opened, reopened, synchronize] + +permissions: {} jobs: bot-auto-merge: name: Auto-merge passing bot PRs runs-on: ubuntu-latest + # Gate on who *opened* the PR, not github.actor -- for a `synchronize` event, + # github.actor is whoever pushed the latest commit (e.g. pre-commit-ci fixing up + # a human-authored PR), which must not by itself trigger auto-merge. + if: >- + github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.user.login == 'pre-commit-ci[bot]' steps: - name: Impersonate auto merge PR bot - uses: tibdex/github-app-token@v1 + uses: actions/create-github-app-token@v3 id: generate-token with: - app_id: ${{ secrets.BOT_AUTO_MERGE_PRS_APP_ID }} - private_key: ${{ secrets.BOT_AUTO_MERGE_PRS_APP_KEY }} - - name: Auto-merge passing dependabot PRs - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ridedott/merge-me-action@v2 - with: - # For clarity only. dependabot is default login. - GITHUB_LOGIN: dependabot - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - ENABLED_FOR_MANUAL_CHANGES: "true" - - name: Auto-merge passing pre-commit-ci PRs - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ridedott/merge-me-action@v2 - with: - GITHUB_LOGIN: pre-commit-ci - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - ENABLED_FOR_MANUAL_CHANGES: "true" + client-id: ${{ secrets.BOT_AUTO_MERGE_PRS_CLIENT_ID }} + private-key: ${{ secrets.BOT_AUTO_MERGE_PRS_APP_KEY }} + + - name: Approve and enable auto-merge + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR_URL" + gh pr merge --merge --auto "$PR_URL" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3da1d39..0a97055 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,3 +25,9 @@ repos: types_or: [python, pyi] - id: ruff-format types_or: [python, pyi] + + # Check Github Actions + - repo: https://github.com/rhysd/actionlint + rev: v1.7.12 + hooks: + - id: actionlint