feat(demand): CPUC SERVM 2026 demand profile with selectable weather year (CPUC stack 2/5) - #798
Closed
ktehranchi wants to merge 1 commit into
Closed
ktehranchi wants to merge 1 commit into
ktehranchi wants to merge 1 commit into
Conversation
Adds `electricity.demand.profile: servm`, wiring CPUC SERVM 2026 hourly load
for the six California load regions (IID, LADWP, NCNC, PGE, SCE, SDGE) through
the existing read/write demand strategies.
Retrieve: `retrieve_cpuc_servm_load` pulls one ~118 MB CSV per forecast year
(2026-2045) from files.cpuc.ca.gov, and `retrieve_cpuc_baseline_generators`
pulls the CAISO baseline generator list, both through a thin
`retrieve_cpuc_data.py` downloader patterned on `retrieve_eer_data.py`.
ReadServm parses the three-row header positionally: the seventh index column
("Hour of Day") carries the stray ('Region', 'Unit Type') labels, so the
calendar block cannot be found by testing the upper header levels for
blankness. Every published component is kept on the `subsector` index level so
the zonal artifact stays component-resolved; only `Net Load` reaches the model,
selected by a new `ReadStrategy.default_subsector` hook that `Context` applies
so `main()` stays profile-agnostic. Files are indexed by the forecast year
parsed from each basename rather than by list order. Strips are fixed PST
(UTC-8, no DST -- verified against the BTMPV solar-noon centroid, Dec 12.52 vs
Jul 12.68) and are rolled to UTC with the same `np.roll` convention ReadEer
uses for CST.
Two calendar misalignments are accepted and documented on
`ReadServm._assign_snapshots`:
1. SERVM lays its hours on a synthetic Monday-start calendar, so weekday and
weekend hours do not line up with the real weekdays of the planning horizon.
2. For a leap *weather* year the strip contains February 29 and omits
December 31, while the model snapshots do the opposite, so every hour after
February lands one calendar day earlier than it sat in the source file.
The 8760-hour strip is therefore mapped positionally onto the network's own
per-period snapshots rather than onto a synthesised `date_range`, which would
run a day short of December 31 for the leap planning horizons (2028/2032/2040)
because `get_snapshots` drops February 29 from them.
WriteServm disaggregates by matrix product against the PR-1 weights table
pivoted to (region x bus), rather than the base class's one-zone-per-bus
mapping: a cluster bus can straddle two SERVM regions (LA County holds both
LDWP and CISO-SCE buses), and the product gives such a bus the sum of its share
of every region it overlaps.
`build_electrical_demand` gains a second output, the reader's zonal
component-resolved demand written before disaggregation. It is produced for
every profile (for efs/eer it is simply the single subsector="all" slice), and
is captured off the `Context` rather than re-read.
Config: `electricity.demand.scenario.servm_weather_years` selects the weather
year from the stacked 2000-2024 record. A single-entry list is deterministic;
multiple entries raise NotImplementedError pending stochastic scenarios. A
value differing from the top-level `renewable_weather_years` logs a warning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 29, 2026
Collaborator
Author
|
Superseded by #801 — the five-PR stack was collapsed into a single PR at the author's request. All commits (plus Tier B fixes) are on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacks on #796 (base is set to
feat/servm-load-weights; retarget todevelopafter #796 merges).Changes proposed in this Pull Request
New electricity demand profile
servm: hourly load for the six CPUC SERVM California regions, per-forecast-year files (2026–2045, nine vintages), 25 selectable weather years (2000–2024), medians consistent with the 2025 IEPR Planning Scenario.retrieve_cpuc_servm_load(per-forecast-year CSVs, ~118 MB each) andretrieve_cpuc_baseline_generatorsfrom files.cpuc.ca.gov.ReadServm(build_demand.py): parses the 3-row-header CSVs (incl. the stray('Region','Unit Type','Hour of Day')label quirk); validatesplanning_horizons ⊆ {2026,2028,2030,2032,2035,2037,2040,2042,2045}(hard error) and Net Load presence for all six regions (loud failure on CPUC format drift); filters to the configured weather year; shifts fixed PST→UTC (+8 h — no DST in the data, verified empirically via the BTM-PV solar-noon centroid Dec vs Jul); maps the 8760-hour strip positionally onto the network's actual per-period snapshots (leap-safe — avoids the syntheticdate_rangepattern that breaks for leap horizons).electricity.demand.scenario.servm_weather_years: [2019]— a list; a single entry is deterministic, multiple entries raiseNotImplementedError(reserved for stochastic scenarios); mismatch withrenewable_weather_yearswarns and is documented as not recommended.WriteServm: disaggregates region demand to buses as a matrix product against the feat(demand): SERVM region load-allocation weights for California (CPUC stack 1/5) #796 weights (exact for clusters straddling regions).Net Load, but every SERVM component (BTMPV, EV, BTM storage, AAEE/AAFS, climate, data-center) is retained in a new zonal artifact{end_use}_zonal_components_s{simpl}.parquet(written for all profiles) as the feedstock for later component-level modeling.Two accepted calendar caveats, documented in code and docs: SERVM strips are Monday-start synthetic calendars (day-of-week misalignment vs real calendars), and leap weather years include Feb 29 / drop Dec 31 while model snapshots do the opposite (post-February hours shift one calendar day).
Testing
pytest -m fast: 195 passed, 1 pre-existing skip (combined stack).test_build_demand_servm.py(16) +test_build_demand_servm_write.py(5) — 21 passed, including a leap-horizon (2028) snapshot-alignment test; existing demand-reader tests unaffected (38 passed across servm+eer+weights).profile: servm,planning_horizons: [2030, 2035]resolves (11 jobs incl. both retrieves and the weights rule). Real-data spot check: 2026 forecast × weather-year 2019 totals 267 TWh CA net load (PGE 100.5, SCE 97.8, LADWP 26.5, NCNC 20.2, SDGE 18.1, IID 4.2 TWh).Checklist
pre-commitandpytest -m fastpass locally.workflow/envs/environment.yaml(no dependency changes; parquet uses the already-pinned pyarrow).workflow/repo_data/config/config.default.yaml.docs/source/configtables/*.csv.🤖 Generated with Claude Code