You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bulk code cleanliness review — general (non-sector)
A full clean-code review of the repository turned up 144 verified findings across 12 domains and 47,895
lines of Python. This issue tracks the 63 of them that are outside sector coupling and carry some
behavioural risk.
Raw defect density is almost flat across the codebase — 2.4 to 3.3 findings per thousand lines — so no area is
simply "messier". What the review found is that five habits repeat everywhere, and they have stopped being
stylistic:
Returning where it should raise. Functions degrade instead of failing. Because Snakemake caches whatever
lands on disk, a function that degrades doesn't produce an error — it produces a wrong artifact the DAG then
treats as a valid, up-to-date input for every downstream rule.
The __main__ block is the real program.add_extra_components.py (230 lines), build_demand.py (225), build_renewable_profiles.py (350) keep their orchestration inside the guard, where nothing can be imported
or tested. Functions below reach past their own parameters to module globals.
Copy-paste, then one copy drifts. The damage is never the redundancy — it's the divergence.
Dead code at scale. ~2,900 lines are provably unreachable, about 6% of the Python in the repo.
The safety nets have holes. The mechanisms meant to catch all of the above are themselves compromised.
Critical in this scope
tests/equivalence/report_sections/maps.py:40 — maps.render() is a 567-line function with 12 nested closures and 13 guarded call sites (complexity 54)
This is the reference implementation of the project's own equivalence evidence, and it is the least readable code in the tree. Every helper (three_panel, differing_carriers, mean_pu_by_carrier_bus) is a pure function of pandas/numpy inputs that could be unit-tested in milliseconds, but because they are closures over ctx, nc, na, regs_c, regs_a, labels and norm none of them can be imported or called without a fully built two-sided equivalence run on disk. A change to the diff-panel color scale requires reading 570 lines to find where dlim is set. The _simplified cache at line 84 keys on id(regs), which is only safe because every GeoDataFrame it sees happens to stay alive for the whole call — an invariant nothing states or enforces.
Evidence
line 40: def render(ctx) -> str:
line 41: import sys
line 43: sys.path.insert(0, str(ctx["repo"] / "workflow" / "scripts"))
...
line 606: return "".join(parts)
Verified: `wc -l` = 606, so render() spans lines 40-606 (567 lines). `awk 'NR>=40 && NR<=606 && /^ def /'` counts 12 nested function definitions (img, plot_regions, three_panel, guarded, per_bus_load, pnom_by_carrier_bus, ext_pnom_max_by_carrier_bus, mean_pu_by_carrier_bus, carrier_vec, carriers_by_total, differing_carriers, plus the section closures _demand/_pnom/_pnom_max/_mean_pu/_profiles/_solved). `grep -c 'guarded('` = 13.
Fix: Lift the pure quantity extractors (per_bus_load, pnom_by_carrier_bus, ext_pnom_max_by_carrier_bus, mean_pu_by_carrier_bus, carrier_vec, carriers_by_total, differing_carriers) to module level taking explicit n / norm arguments, and lift three_panel/plot_regions/img into a small _panels.py taking (plt, np, labels) explicitly. render() then becomes a ~60-line sequence of parts.append(guarded(_section, ...)) calls over module-level section functions _section_demand(ctx, nc, na, regs_c, regs_a) etc. Key _simplified on id(regs) only after documenting the lifetime invariant, or better on a stable attribute the caller supplies. Move the sys.path.insert at line 43 into a module-level import guard so it happens once, not on every render.
Risk: low
workflow/scripts/_helpers.py:363 — is_transport_model returns a ValueError instead of raising it; callers treat it as truthy
A ValueError instance is truthy. Both call sites bind the result and branch on it directly — workflow/scripts/cluster_network.py:937 then if transport_model: at lines 1026, 1114 and if not transport_model: at 1000, 1131; workflow/scripts/prepare_network.py:290 then set_line_s_max_pu(n, transport_model, ...) at 308, whose body is if not transport_model:. So any transmission_network value that is not exactly "reeds" or "tamu" (a typo, a renamed option, a new backend) silently selects the full transport-model branch: line impedances are dropped, s_max_pu is left unset, and clustering takes the reeds path. The run completes, the log is clean, and the numbers are wrong. This is the single highest-cost defect in the domain because it converts a config typo into a silently different physical model.
Evidence
363: def is_transport_model(transmission_network):
364: match transmission_network:
365: case "reeds":
366: return True
367: case "tamu":
368: return False
369: case _:
370: return ValueError(
371: "transmission network not specified correctly. Check config",
372: )
Fix: Raise instead of return, and make the message name the offending value:
No call site needs to change — both already assume a bool.
Risk: low
Three further criticals in this scope are one-line, no-behaviour-change fixes and are tracked in #782: build_renewable_profiles.py:280, workflow/scripts/test/test_land.py:46, and the
repo-root directory literally named " " that holds 100 KB of committed forked code.
Related existing issues
Accidental addition of workflow/config/config.yaml #771 already covers the accidental workflow/config/config.common.yaml commit. This review adds context:
because that file sits in the destination init_pypsa_usa.sh refuses to overwrite, init_pypsa_usa.sh is
permanently broken for every fresh clone — it always prints "Existing config files found… Delete the
following files and rerun." and seeds nothing.
Missing top level config values #770 ("Missing top level config values", renewable_land_access) is a symptom of the same problem. The
two config.common.yaml copies have diverged bidirectionally: the tracked runtime copy has renewable_land_access, nrel_caps_reassign and godeeep_wind_height; the repo_data template that init_pypsa_usa.sh actually copies from does not. It also pins PUDL v2025.5.0 vs the runtime copy's v2025.2.0, and drilling_cost: advanced vs base. Since docs/source/config-configuration.md literalincludes the template, the published docs describe a configuration the model does not run.
Findings
Error handling (22)
workflow/scripts/_helpers.py:363 — is_transport_model returns a ValueError instead of raising it; callers treat it as truthy CRITICAL · whole modeling domain · Error handling · risk: low
tests/equivalence/report_sections/maps.py:176 — guarded() turns every map failure into HTML prose, so a report "succeeds" with evidence missing high · whole modeling domain · Error handling · risk: low
tests/equivalence/report_sections/stages.py:399 — The report hardcodes benign conclusions about findings it never inspected, in three places high · whole modeling domain · Error handling · risk: low
workflow/scripts/cluster_network.py:1021 — custom_busmap path uses pd.read_csv(squeeze=True), removed in pandas 2.0 — TypeError on pinned 2.2.2 high · power system model · Error handling · risk: low
workflow/scripts/opts/policy.py:413 — return instead of continue aborts the whole RPS loop on the first zone with no eligible generators high · power system model · Error handling · risk: numerics
workflow/scripts/plot_validation_production.py:578 — plot_state_emissions_historical_bar raises NameError on historical when the EIA API key is unset high · power system model · Error handling · risk: low
workflow/scripts/retrieve_caiso_data.py:71 — Failed OASIS requests are only printed, yet the missing filename is still queued for reading high · whole modeling domain · Error handling · risk: low
workflow/scripts/retrieve_gridemissions_data.py:32 — Partial download prints and returns; extraction is skipped and the run continues on stale data high · whole modeling domain · Error handling · risk: low
workflow/scripts/solve_network.py:244 — Iterative transmission-expansion branch cannot execute: 1-tuples passed as ints, None return unpacked high · power system model · Error handling · risk: low
workflow/scripts/summary.py:120 — get_energy_timeseries writes into the live links_t frame with a link-name index, raising KeyError high · power system model · Error handling · risk: low
workflow/scripts/zenodo_downloader.py:190 — Downloads stream straight to the final output path and resume on bare exists() high · whole modeling domain · Error handling · risk: low
.github/workflows/main.yml:44 — CI "Setup secrets" step writes an empty ~/.cdsapirc — no secrets are wired into the job medium · whole modeling domain · Error handling · risk: low
workflow/scripts/_helpers.py:699 — CF+ wildcard handling calls packaging.version.parse on a list — always raises TypeError medium · whole modeling domain · Error handling · risk: low
workflow/scripts/add_demand.py:29 — attach_demand checks only row count before overwriting the demand index with network snapshots medium · power system model · Error handling · risk: numerics
workflow/scripts/nrel_exclusion/aggregate_godeeep_weighted.py:104 — Nearest-neighbour bus fill indexes points with positions computed against valid medium · power system model · Error handling · risk: low
workflow/scripts/nrel_exclusion/build_nrel_bus_capacities.py:374 — Output NetCDF attrs record filters as applied even when the tech gate skipped them medium · power system model · Error handling · risk: low
workflow/scripts/opts/policy.py:314 — pd.concat without reset_index makes .loc[row.name] write RPS targets into the wrong rows medium · power system model · Error handling · risk: numerics
workflow/scripts/opts/reserves.py:39 — Three-way except swallow silently restores full ERM capacity credit to fossil in zero-carbon years medium · power system model · Error handling · risk: numerics
workflow/scripts/_helpers.py:548 — mock_snakemake can raise NameError on unbound snakefile and leaves cwd mutated on failure low · whole modeling domain · Error handling · risk: low
workflow/scripts/add_electricity.py:1258 — Dynamic fuel pricing swallows KeyError twice and continues with static costs low · power system model · Error handling · risk: low
workflow/scripts/add_extra_components.py:563 — .mean() or 1 fallback never fires: an empty generator set yields NaN ramp limits, not 1 low · power system model · Error handling · risk: numerics
workflow/scripts/opts/bidirectional_link.py:46 — Orphaned fwd/rev links are only reported when no complete pair exists at all low · power system model · Error handling · risk: low
Duplication (13)
workflow/scripts/plot_statistics.py:60 — create_title, save_fig and format_sankey_data are duplicated across modules, largely byte-for-byte high · power system model · Duplication · risk: low
workflow/scripts/test/conftest.py:309 — multi_period_base_network is a ~210-line copy of base_network with three small deltas high · whole modeling domain · Duplication · risk: numerics
tests/integration/conftest.py:46 — _seed_runtime_configs is duplicated in two test roots and both mutate the developer's checkout medium · whole modeling domain · Duplication · risk: low
workflow/rules/postprocess.smk:45 — export_statistics's custom-files branch drops the {simpl} wildcard its sibling rules keep medium · power system model · Duplication · risk: low
workflow/rules/validate.smk:15 — solve_network_validation clones solve_network and declares identical config/log/benchmark paths medium · power system model · Duplication · risk: low
workflow/scripts/_helpers.py:731 — The 15-line carrier-adjustment attr_lookup block is pasted twice, differing in one dict key medium · whole modeling domain · Duplication · risk: low
workflow/scripts/build_fuel_prices.py:155 — generator_name key plus z-score/IQR pipeline is copy-pasted verbatim across two files, comments included medium · power system model · Duplication · risk: low
workflow/scripts/cluster_network.py:673 — The topological_boundaries to bus-region field ladder is written twice inside one function medium · power system model · Duplication · risk: low
workflow/scripts/nrel_exclusion/plot_caps_summary.py:168 — main() inlines a 38-line near-verbatim copy of plot_pnom_map instead of calling it medium · power system model · Duplication · risk: low
workflow/scripts/opts/_helpers.py:58 — filter_components duplicates its four-clause mask and silently returns empty on horizon miss medium · whole modeling domain · Duplication · risk: low
workflow/scripts/opts/reserves.py:45 — Three pypsa constraint builders vendored verbatim with a _RESERVES suffix, dropping upstream terms medium · power system model · Duplication · risk: numerics
workflow/scripts/retrieve_eer_data.py:19 — Two retrieve scripts are the same six statements; one hardcodes its URL, the other takes it from the rule medium · whole modeling domain · Duplication · risk: low
workflow/scripts/scenario_comparison.py:251 — scenario_comparison() re-implements the three modular functions defined above it in 145 lines medium · whole modeling domain · Duplication · risk: numerics
Function size (6)
tests/equivalence/report_sections/maps.py:40 — maps.render() is a 567-line function with 12 nested closures and 13 guarded call sites (complexity 54) CRITICAL · whole modeling domain · Function size · risk: low
workflow/scripts/build_powerplants.py:242 — merge_ads_data: 172-line function that takes a DataFrame but reads its file inputs from the snakemake global high · power system model · Function size · risk: low
workflow/scripts/nrel_exclusion/build_nrel_bus_capacities.py:167 — rollup_supply_curve is a 214-line, complexity-14 function that builds its output Dataset twice high · power system model · Function size · risk: numerics
workflow/scripts/_helpers.py:697 — update_config_from_wildcards is 166 lines, complexity 46, with two unrelated responsibilities medium · whole modeling domain · Function size · risk: numerics
workflow/scripts/add_extra_components.py:958 — add_elec_imports_exports: 179 lines, six nested closures, and a cache whose lookup key never matches medium · power system model · Function size · risk: low
workflow/scripts/cluster_network.py:603 — calibrate_tamu_transmission_capacity is 297 lines mixing file I/O, region resolution and mutation medium · power system model · Function size · risk: numerics
Testability (6)
workflow/scripts/build_renewable_profiles.py:238 — 350 lines of the renewable-profile pipeline live in main with no extractable function high · power system model · Testability · risk: numerics
workflow/scripts/nrel_exclusion/build_nrel_bus_capacities.py:404 — Every nrel_exclusion entrypoint defaults its input paths to one developer's Sherlock home directory high · power system model · Testability · risk: low
workflow/scripts/plot_validation_production.py:259 — Plotting functions read the module-global snakemake inside their bodies, defeating the main() seam high · power system model · Testability · risk: low
workflow/scripts/build_demand.py:2440 — 225-line main is the only wiring layer and none of it can be unit-tested medium · power system model · Testability · risk: low
workflow/scripts/build_demand.py:261 — ReadFERC714 reaches through the class boundary into the global snakemake object medium · power system model · Testability · risk: low
workflow/scripts/cluster_network.py:47 — weighting_for_region mutates n.generators and computes work the population strategy discards medium · power system model · Testability · risk: low
Dead code (5)
workflow/repo_data/config/config.common.yaml:159 — Config keys deleted as dead in PR Change plant/fueltype naming scheme to match pypsa or eia data #10 are back, plus four more no code reads, with false comments high · whole modeling domain · Dead code · risk: low
workflow/scripts/nrel_exclusion/compare_legacy_vs_nrel.py:1 — Three nrel_exclusion modules (573 lines) have zero references anywhere in the repo high · power system model · Dead code · risk: low
workflow/scripts/plot_validation_production.py:282 — Six functions (~290 lines, 28% of the file) are reachable only through commented-out call sites high · power system model · Dead code · risk: low
workflow/rules/retrieve.smk:213 — rule retrieve_ship_raster downloads a file no rule consumes medium · whole modeling domain · Dead code · risk: low
workflow/scripts/_helpers.py:772 — EQ-constraint loop breaks unconditionally, so only opts[0] is ever inspected medium · whole modeling domain · Dead code · risk: numerics
Config coupling (5)
.gitignore:15 — Bare config/ and notebooks/ patterns gitignore the tracked config mirror and all 10 notebooks high · whole modeling domain · Config coupling · risk: low
conftest.py:15 — Root conftest blanket-marks 17 GLPK LP solves as fast, violating the marker's documented contract high · whole modeling domain · Config coupling · risk: low
.pre-commit-config.yaml:1 — Lint config is largely inert: large-file guard excluded where large files live, ruff pins diverge medium · whole modeling domain · Config coupling · risk: low
workflow/scripts/build_demand.py:2342 — Profile/disaggregation/scaler are bare strings dispatched in three unrelated chains low · power system model · Config coupling · risk: low
Naming (3)
workflow/scripts/build_demand.py:1731 — _disaggregate_demand_to_buses rebinds load three ways and multiplies by the wrong laf frame low · power system model · Naming · risk: low
workflow/scripts/constants.py:35 — MMBTU_MWHthemal is misspelled, named backwards, and duplicates NG_Dol_MMBTU_2_MWH low · whole modeling domain · Naming · risk: low
workflow/scripts/opts/policy.py:179 — target.name reads the row index, not the CSV name column, so TCT constraints are named by row number low · power system model · Naming · risk: low
API surface (2)
workflow/scripts/add_extra_components.py:1723 — Exports call omits zone_col, silently defaulting to reeds_zone after state/county conversion high · power system model · API surface · risk: numerics
workflow/scripts/_helpers.py:1 — _helpers.py is a junk drawer: 999 lines, nine responsibilities, 45 importers medium · whole modeling domain · API surface · risk: low
Magic values (1)
workflow/scripts/test/fixtures/build_test_network.py:210 — Every wind site's capacity factor is squared — cf_scale is multiplied in twice in p_max_pu high · whole modeling domain · Magic values · risk: numerics
How this list was produced
A ruff sweep with an expanded rule set (F,C90,ARG,ERA,SIM,RET,PLR,B,PIE,TRY,PTH,DTZ) established an
objective baseline, then twelve reviewers each read one domain of the codebase in full rather than sampling.
Eight of the twelve domains went through an adversarial verification pass instructed to refute: open the file
at the line, confirm the quoted evidence is verbatim, grep the whole repo before accepting any dead-code claim,
and diff both sites before accepting any duplication claim. That pass adjusted 64 findings and confirmed 32.
The remaining four domains self-verified under the same instructions, and the critical findings were re-checked
by hand.
Every item below was confirmed by opening the file at the line. Line numbers are against develop as of
2026-08-24. No code has been changed — this is a survey only.
Each finding carries a risk label:
safe — provably no behaviour change (mostly deletions)
low — small blast radius, no numerics
numerics — touches model results; should not land without a validation run
Filed from an automated clean-code review. Items are unchecked survey results, not agreed work — triage
before acting. Ping if you want any line promoted into its own issue.
Bulk code cleanliness review — general (non-sector)
A full clean-code review of the repository turned up 144 verified findings across 12 domains and 47,895
lines of Python. This issue tracks the 63 of them that are outside sector coupling and carry some
behavioural risk.
The other two buckets are tracked separately:
63 findings — 2 critical, 24 high, 29 medium, 8 low
Why this is worth doing
Raw defect density is almost flat across the codebase — 2.4 to 3.3 findings per thousand lines — so no area is
simply "messier". What the review found is that five habits repeat everywhere, and they have stopped being
stylistic:
lands on disk, a function that degrades doesn't produce an error — it produces a wrong artifact the DAG then
treats as a valid, up-to-date input for every downstream rule.
__main__block is the real program.add_extra_components.py(230 lines),build_demand.py(225),build_renewable_profiles.py(350) keep their orchestration inside the guard, where nothing can be importedor tested. Functions below reach past their own parameters to module globals.
Critical in this scope
tests/equivalence/report_sections/maps.py:40— maps.render() is a 567-line function with 12 nested closures and 13 guarded call sites (complexity 54)This is the reference implementation of the project's own equivalence evidence, and it is the least readable code in the tree. Every helper (three_panel, differing_carriers, mean_pu_by_carrier_bus) is a pure function of pandas/numpy inputs that could be unit-tested in milliseconds, but because they are closures over
ctx,nc,na,regs_c,regs_a,labelsandnormnone of them can be imported or called without a fully built two-sided equivalence run on disk. A change to the diff-panel color scale requires reading 570 lines to find wheredlimis set. The_simplifiedcache at line 84 keys onid(regs), which is only safe because every GeoDataFrame it sees happens to stay alive for the whole call — an invariant nothing states or enforces.Evidence
Fix: Lift the pure quantity extractors (per_bus_load, pnom_by_carrier_bus, ext_pnom_max_by_carrier_bus, mean_pu_by_carrier_bus, carrier_vec, carriers_by_total, differing_carriers) to module level taking explicit
n/normarguments, and lift three_panel/plot_regions/img into a small_panels.pytaking (plt, np, labels) explicitly. render() then becomes a ~60-line sequence ofparts.append(guarded(_section, ...))calls over module-level section functions_section_demand(ctx, nc, na, regs_c, regs_a)etc. Key_simplifiedonid(regs)only after documenting the lifetime invariant, or better on a stable attribute the caller supplies. Move thesys.path.insertat line 43 into a module-level import guard so it happens once, not on every render.Risk: low
workflow/scripts/_helpers.py:363— is_transport_model returns a ValueError instead of raising it; callers treat it as truthyA
ValueErrorinstance is truthy. Both call sites bind the result and branch on it directly —workflow/scripts/cluster_network.py:937thenif transport_model:at lines 1026, 1114 andif not transport_model:at 1000, 1131;workflow/scripts/prepare_network.py:290thenset_line_s_max_pu(n, transport_model, ...)at 308, whose body isif not transport_model:. So anytransmission_networkvalue that is not exactly "reeds" or "tamu" (a typo, a renamed option, a new backend) silently selects the full transport-model branch: line impedances are dropped,s_max_puis left unset, and clustering takes the reeds path. The run completes, the log is clean, and the numbers are wrong. This is the single highest-cost defect in the domain because it converts a config typo into a silently different physical model.Evidence
Fix: Raise instead of return, and make the message name the offending value:
No call site needs to change — both already assume a bool.
Risk: low
Related existing issues
workflow/config/config.yaml#771 already covers the accidentalworkflow/config/config.common.yamlcommit. This review adds context:because that file sits in the destination
init_pypsa_usa.shrefuses to overwrite,init_pypsa_usa.shispermanently broken for every fresh clone — it always prints "Existing config files found… Delete the
following files and rerun." and seeds nothing.
renewable_land_access) is a symptom of the same problem. Thetwo
config.common.yamlcopies have diverged bidirectionally: the tracked runtime copy hasrenewable_land_access,nrel_caps_reassignandgodeeep_wind_height; therepo_datatemplate thatinit_pypsa_usa.shactually copies from does not. It also pins PUDLv2025.5.0vs the runtime copy'sv2025.2.0, anddrilling_cost: advancedvsbase. Sincedocs/source/config-configuration.mdliteralincludes the template, the published docs describe a configuration the model does not run.Findings
Error handling (22)
workflow/scripts/_helpers.py:363— is_transport_model returns a ValueError instead of raising it; callers treat it as truthyCRITICAL · whole modeling domain · Error handling · risk: low
tests/equivalence/report_sections/maps.py:176—guarded()turns every map failure into HTML prose, so a report "succeeds" with evidence missinghigh · whole modeling domain · Error handling · risk: low
tests/equivalence/report_sections/stages.py:399— The report hardcodes benign conclusions about findings it never inspected, in three placeshigh · whole modeling domain · Error handling · risk: low
workflow/scripts/cluster_network.py:1021— custom_busmap path uses pd.read_csv(squeeze=True), removed in pandas 2.0 — TypeError on pinned 2.2.2high · power system model · Error handling · risk: low
workflow/scripts/opts/policy.py:413—returninstead ofcontinueaborts the whole RPS loop on the first zone with no eligible generatorshigh · power system model · Error handling · risk: numerics
workflow/scripts/plot_validation_production.py:578— plot_state_emissions_historical_bar raises NameError onhistoricalwhen the EIA API key is unsethigh · power system model · Error handling · risk: low
workflow/scripts/retrieve_caiso_data.py:71— Failed OASIS requests are only printed, yet the missing filename is still queued for readinghigh · whole modeling domain · Error handling · risk: low
workflow/scripts/retrieve_gridemissions_data.py:32— Partial download prints and returns; extraction is skipped and the run continues on stale datahigh · whole modeling domain · Error handling · risk: low
workflow/scripts/solve_network.py:244— Iterative transmission-expansion branch cannot execute: 1-tuples passed as ints, None return unpackedhigh · power system model · Error handling · risk: low
workflow/scripts/summary.py:120— get_energy_timeseries writes into the live links_t frame with a link-name index, raising KeyErrorhigh · power system model · Error handling · risk: low
workflow/scripts/zenodo_downloader.py:190— Downloads stream straight to the final output path and resume on bare exists()high · whole modeling domain · Error handling · risk: low
.github/workflows/main.yml:44— CI "Setup secrets" step writes an empty ~/.cdsapirc — no secrets are wired into the jobmedium · whole modeling domain · Error handling · risk: low
workflow/scripts/_helpers.py:699—CF+wildcard handling calls packaging.version.parse on a list — always raises TypeErrormedium · whole modeling domain · Error handling · risk: low
workflow/scripts/add_demand.py:29—attach_demandchecks only row count before overwriting the demand index with network snapshotsmedium · power system model · Error handling · risk: numerics
workflow/scripts/nrel_exclusion/aggregate_godeeep_weighted.py:104— Nearest-neighbour bus fill indexespointswith positions computed againstvalidmedium · power system model · Error handling · risk: low
workflow/scripts/nrel_exclusion/build_nrel_bus_capacities.py:374— Output NetCDF attrs record filters as applied even when the tech gate skipped themmedium · power system model · Error handling · risk: low
workflow/scripts/opts/policy.py:314— pd.concat without reset_index makes .loc[row.name] write RPS targets into the wrong rowsmedium · power system model · Error handling · risk: numerics
workflow/scripts/opts/reserves.py:39— Three-way except swallow silently restores full ERM capacity credit to fossil in zero-carbon yearsmedium · power system model · Error handling · risk: numerics
workflow/scripts/_helpers.py:548— mock_snakemake can raise NameError on unboundsnakefileand leaves cwd mutated on failurelow · whole modeling domain · Error handling · risk: low
workflow/scripts/add_electricity.py:1258— Dynamic fuel pricing swallows KeyError twice and continues with static costslow · power system model · Error handling · risk: low
workflow/scripts/add_extra_components.py:563—.mean() or 1fallback never fires: an empty generator set yields NaN ramp limits, not 1low · power system model · Error handling · risk: numerics
workflow/scripts/opts/bidirectional_link.py:46— Orphaned fwd/rev links are only reported when no complete pair exists at alllow · power system model · Error handling · risk: low
Duplication (13)
workflow/scripts/plot_statistics.py:60— create_title, save_fig and format_sankey_data are duplicated across modules, largely byte-for-bytehigh · power system model · Duplication · risk: low
workflow/scripts/test/conftest.py:309—multi_period_base_networkis a ~210-line copy ofbase_networkwith three small deltashigh · whole modeling domain · Duplication · risk: numerics
tests/integration/conftest.py:46—_seed_runtime_configsis duplicated in two test roots and both mutate the developer's checkoutmedium · whole modeling domain · Duplication · risk: low
workflow/rules/postprocess.smk:45—export_statistics's custom-files branch drops the{simpl}wildcard its sibling rules keepmedium · power system model · Duplication · risk: low
workflow/rules/validate.smk:15—solve_network_validationclonessolve_networkand declares identical config/log/benchmark pathsmedium · power system model · Duplication · risk: low
workflow/scripts/_helpers.py:731— The 15-line carrier-adjustment attr_lookup block is pasted twice, differing in one dict keymedium · whole modeling domain · Duplication · risk: low
workflow/scripts/build_fuel_prices.py:155— generator_name key plus z-score/IQR pipeline is copy-pasted verbatim across two files, comments includedmedium · power system model · Duplication · risk: low
workflow/scripts/cluster_network.py:673— The topological_boundaries to bus-region field ladder is written twice inside one functionmedium · power system model · Duplication · risk: low
workflow/scripts/nrel_exclusion/plot_caps_summary.py:168— main() inlines a 38-line near-verbatim copy of plot_pnom_map instead of calling itmedium · power system model · Duplication · risk: low
workflow/scripts/opts/_helpers.py:58— filter_components duplicates its four-clause mask and silently returns empty on horizon missmedium · whole modeling domain · Duplication · risk: low
workflow/scripts/opts/reserves.py:45— Three pypsa constraint builders vendored verbatim with a _RESERVES suffix, dropping upstream termsmedium · power system model · Duplication · risk: numerics
workflow/scripts/retrieve_eer_data.py:19— Two retrieve scripts are the same six statements; one hardcodes its URL, the other takes it from the rulemedium · whole modeling domain · Duplication · risk: low
workflow/scripts/scenario_comparison.py:251— scenario_comparison() re-implements the three modular functions defined above it in 145 linesmedium · whole modeling domain · Duplication · risk: numerics
Function size (6)
tests/equivalence/report_sections/maps.py:40— maps.render() is a 567-line function with 12 nested closures and 13 guarded call sites (complexity 54)CRITICAL · whole modeling domain · Function size · risk: low
workflow/scripts/build_powerplants.py:242— merge_ads_data: 172-line function that takes a DataFrame but reads its file inputs from the snakemake globalhigh · power system model · Function size · risk: low
workflow/scripts/nrel_exclusion/build_nrel_bus_capacities.py:167— rollup_supply_curve is a 214-line, complexity-14 function that builds its output Dataset twicehigh · power system model · Function size · risk: numerics
workflow/scripts/_helpers.py:697— update_config_from_wildcards is 166 lines, complexity 46, with two unrelated responsibilitiesmedium · whole modeling domain · Function size · risk: numerics
workflow/scripts/add_extra_components.py:958—add_elec_imports_exports: 179 lines, six nested closures, and a cache whose lookup key never matchesmedium · power system model · Function size · risk: low
workflow/scripts/cluster_network.py:603— calibrate_tamu_transmission_capacity is 297 lines mixing file I/O, region resolution and mutationmedium · power system model · Function size · risk: numerics
Testability (6)
workflow/scripts/build_renewable_profiles.py:238— 350 lines of the renewable-profile pipeline live in main with no extractable functionhigh · power system model · Testability · risk: numerics
workflow/scripts/nrel_exclusion/build_nrel_bus_capacities.py:404— Every nrel_exclusion entrypoint defaults its input paths to one developer's Sherlock home directoryhigh · power system model · Testability · risk: low
workflow/scripts/plot_validation_production.py:259— Plotting functions read the module-globalsnakemakeinside their bodies, defeating the main() seamhigh · power system model · Testability · risk: low
workflow/scripts/build_demand.py:2440— 225-line main is the only wiring layer and none of it can be unit-testedmedium · power system model · Testability · risk: low
workflow/scripts/build_demand.py:261— ReadFERC714 reaches through the class boundary into the globalsnakemakeobjectmedium · power system model · Testability · risk: low
workflow/scripts/cluster_network.py:47— weighting_for_region mutates n.generators and computes work the population strategy discardsmedium · power system model · Testability · risk: low
Dead code (5)
workflow/repo_data/config/config.common.yaml:159— Config keys deleted as dead in PR Change plant/fueltype naming scheme to match pypsa or eia data #10 are back, plus four more no code reads, with false commentshigh · whole modeling domain · Dead code · risk: low
workflow/scripts/nrel_exclusion/compare_legacy_vs_nrel.py:1— Three nrel_exclusion modules (573 lines) have zero references anywhere in the repohigh · power system model · Dead code · risk: low
workflow/scripts/plot_validation_production.py:282— Six functions (~290 lines, 28% of the file) are reachable only through commented-out call siteshigh · power system model · Dead code · risk: low
workflow/rules/retrieve.smk:213—rule retrieve_ship_rasterdownloads a file no rule consumesmedium · whole modeling domain · Dead code · risk: low
workflow/scripts/_helpers.py:772— EQ-constraint loop breaks unconditionally, so only opts[0] is ever inspectedmedium · whole modeling domain · Dead code · risk: numerics
Config coupling (5)
.gitignore:15— Bareconfig/andnotebooks/patterns gitignore the tracked config mirror and all 10 notebookshigh · whole modeling domain · Config coupling · risk: low
conftest.py:15— Root conftest blanket-marks 17 GLPK LP solves asfast, violating the marker's documented contracthigh · whole modeling domain · Config coupling · risk: low
.pre-commit-config.yaml:1— Lint config is largely inert: large-file guard excluded where large files live, ruff pins divergemedium · whole modeling domain · Config coupling · risk: low
workflow/Snakefile:115—POWERPLANTSconstant unused; rules hard-coderesources/powerplants/and bypassCOSTSmedium · whole modeling domain · Config coupling · risk: low
workflow/scripts/build_demand.py:2342— Profile/disaggregation/scaler are bare strings dispatched in three unrelated chainslow · power system model · Config coupling · risk: low
Naming (3)
workflow/scripts/build_demand.py:1731— _disaggregate_demand_to_buses rebindsloadthree ways and multiplies by the wrong laf framelow · power system model · Naming · risk: low
workflow/scripts/constants.py:35— MMBTU_MWHthemal is misspelled, named backwards, and duplicates NG_Dol_MMBTU_2_MWHlow · whole modeling domain · Naming · risk: low
workflow/scripts/opts/policy.py:179—target.namereads the row index, not the CSVnamecolumn, so TCT constraints are named by row numberlow · power system model · Naming · risk: low
API surface (2)
workflow/scripts/add_extra_components.py:1723— Exports call omitszone_col, silently defaulting toreeds_zoneafter state/county conversionhigh · power system model · API surface · risk: numerics
workflow/scripts/_helpers.py:1— _helpers.py is a junk drawer: 999 lines, nine responsibilities, 45 importersmedium · whole modeling domain · API surface · risk: low
Magic values (1)
workflow/scripts/test/fixtures/build_test_network.py:210— Every wind site's capacity factor is squared —cf_scaleis multiplied in twice in p_max_puhigh · whole modeling domain · Magic values · risk: numerics
How this list was produced
A
ruffsweep with an expanded rule set (F,C90,ARG,ERA,SIM,RET,PLR,B,PIE,TRY,PTH,DTZ) established anobjective baseline, then twelve reviewers each read one domain of the codebase in full rather than sampling.
Eight of the twelve domains went through an adversarial verification pass instructed to refute: open the file
at the line, confirm the quoted evidence is verbatim, grep the whole repo before accepting any dead-code claim,
and diff both sites before accepting any duplication claim. That pass adjusted 64 findings and confirmed 32.
The remaining four domains self-verified under the same instructions, and the critical findings were re-checked
by hand.
Every item below was confirmed by opening the file at the line. Line numbers are against
developas of2026-08-24. No code has been changed — this is a survey only.
Each finding carries a risk label:
Filed from an automated clean-code review. Items are unchecked survey results, not agreed work — triage
before acting. Ping if you want any line promoted into its own issue.