@@ -130,10 +130,56 @@ Output schema:
130130Utility aggregation maps each utility to its zone(s) via
131131` data/pjm/zone_mapping/csv/pjm_utility_zone_mapping.csv ` and sums zone loads by
132132timestamp. Each MD utility maps to exactly one zone (bge→BC, pepco→PEP, dpl→DPL,
133- potomac-edison →AP), so a utility series equals its zone series. The
133+ poted →AP), so a utility series equals its zone series. The
134134` capacity_weight ` column is for capacity-cost allocation and is ** not** applied
135135to load.
136136
137+ ## Zone vs utility load (shared zones, scale-invariance, multi-state zones)
138+
139+ PJM zones and retail utilities are not 1:1, and the aggregation deliberately
140+ treats them as distinct things. Three consequences are worth spelling out, since
141+ they look surprising at first glance.
142+
143+ ** Utilities that share a zone get identical load profiles.** A co-op or
144+ municipal that sits inside a host IOU's transmission zone (e.g. ` smeco ` inside
145+ PEPCO; ` choptank ` , ` easton_muni ` , ` berlin_muni ` inside DPL; ` poted ` ,
146+ ` somerset_rec ` , ` hagerstown_muni ` inside APS) is mapped to that zone and receives
147+ the ** full zone load series** — ` capacity_weight ` is for capacity-cost
148+ allocation and is never applied to split load. So every utility in a zone gets
149+ the same hourly shape. Each utility is also computed independently, so
150+ adding or dropping a co-op/municipal in a zone does ** not** change any other
151+ utility's load — dropping the phantom slugs that were never assigned in ResStock
152+ (` an-electric ` , ` thurmont ` , ` williamsport ` ) left ` poted ` /` dpl ` etc. unchanged.
153+
154+ ** Why identical (and full-zone) profiles are acceptable: the MC allocation is
155+ scale-invariant.** The sub-TX/DX marginal-cost methods that consume these loads
156+ (` generate_utility_tx_dx_mc.py ` : peak-of-peak weighting, exceedance weighting)
157+ allocate a fixed ` $/kW-yr ` cost across hours using only the ** shape** of the load
158+ profile — they normalize by the profile's own peak/exceedance, so the absolute
159+ magnitude divides out. Two utilities with the same shape but different absolute
160+ MW get the same hourly MC allocation; a utility assigned its whole zone's MW
161+ rather than just its retail slice gets the same allocation it would from a scaled
162+ copy of that shape. Magnitude is not over- or under-attributed because magnitude
163+ does not enter the allocation.
164+
165+ ** Multi-state zones are a shape proxy, not a magnitude error.** Several PJM zones
166+ extend well beyond Maryland — APS (Allegheny Power Systems) covers parts of WV,
167+ PA, and VA; the PEPCO and DPL zones include DC and DE. So ` poted ` 's load shape is
168+ the whole APS zone's shape, which mixes non-MD load. This is an acknowledged
169+ approximation: we use the zone shape as a ** proxy** for the MD retail territory's
170+ shape, accepting that the wider footprint can smear the timing of the local peak.
171+ Because the allocation is scale-invariant, the concern is purely "is the _ shape_
172+ representative?", not "are we attributing too many MW?". Customer filtering to the
173+ MD retail territory happens upstream in ResStock utility assignment, not here.
174+
175+ ** Consistent with the NY (NYISO) treatment.** NYISO load aggregation
176+ (` data/nyiso/hourly_demand/aggregate_nyiso_utility_loads.py ` ) follows the same
177+ rule: it sums full zone loads for each utility and uses ` capacity_weight ` only
178+ for capacity allocation, not to split load. NY utilities that cover the same
179+ zone(s) likewise end up with identical load profiles. The MD approach (shared
180+ APS/PEPCO/DPL profiles, full-zone load) is the PJM analogue of that established
181+ NY precedent.
182+
137183## Running it
138184
139185From the repo root (scripts run as modules because they import ` data.pjm ` ):
0 commit comments