@@ -271,13 +271,14 @@ def _parse_rate_section(section: str) -> tuple[list[SeasonalPeriodRate], list[st
271271 component_label = _normalize_key (value_match .group ("label" ))
272272 value = _to_decimal (value_match .group ("value" ))
273273 unit = value_match .group ("unit" ).lower ().replace (" " , "" )
274+ target_season = "year_round" if "year_round" in component_label else season
274275
275276 if "¢perkwh" in unit :
276277 period_name = _period_from_component_label (component_label ) or current_period
277- key = (season , period_name )
278+ key = (target_season , period_name )
278279 per_kwh [key ][component_label ] = value
279280 else :
280- monthly_by_season [season ][component_label ] = value
281+ monthly_by_season [target_season ][component_label ] = value
281282
282283 periods : list [SeasonalPeriodRate ] = []
283284 for (period_season , period_name ), kwh_components in per_kwh .items ():
@@ -292,6 +293,7 @@ def _parse_rate_section(section: str) -> tuple[list[SeasonalPeriodRate], list[st
292293 )
293294 )
294295
296+ periods = _merge_base_components (periods , monthly_by_season )
295297 return periods , notes
296298
297299
@@ -308,3 +310,56 @@ def _window_for_period(period_name: str, season: str, specs: dict[str, _WindowSp
308310 hour_ranges = list (spec .hour_ranges ),
309311 months = _months_for (season ),
310312 )
313+
314+
315+ def _merge_base_components (
316+ periods : list [SeasonalPeriodRate ],
317+ monthly_by_season : dict [str , dict [str , Decimal ]],
318+ ) -> list [SeasonalPeriodRate ]:
319+ """Apply base all_kwh/year_round components to specific TOU periods.
320+
321+ Example: D1.11 has Distribution kWh (Year-round). That value should be part
322+ of both peak and off-peak totals instead of existing in a separate all_kwh-only
323+ period that is not the active TOU period.
324+ """
325+ by_key : dict [tuple [str , str ], SeasonalPeriodRate ] = {
326+ (p .season_name , p .period_name ): p for p in periods
327+ }
328+ has_specific_periods = any (p .period_name != "all_kwh" for p in periods )
329+ if not has_specific_periods :
330+ return periods
331+
332+ year_round_base = by_key .get (("year_round" , "all_kwh" ))
333+
334+ merged : list [SeasonalPeriodRate ] = []
335+ for period in periods :
336+ if period .period_name == "all_kwh" :
337+ continue
338+
339+ base_for_season = by_key .get ((period .season_name , "all_kwh" ))
340+ merged_kwh = dict (period .components .per_kwh )
341+
342+ for source in (year_round_base , base_for_season ):
343+ if source is None :
344+ continue
345+ for key , value in source .components .per_kwh .items ():
346+ merged_kwh .setdefault (key , value )
347+
348+ merged_monthly : dict [str , Decimal ] = {}
349+ for source_monthly in (
350+ monthly_by_season .get ("year_round" , {}),
351+ monthly_by_season .get (period .season_name , {}),
352+ ):
353+ for key , value in source_monthly .items ():
354+ merged_monthly .setdefault (key , value )
355+
356+ merged .append (
357+ SeasonalPeriodRate (
358+ season_name = period .season_name ,
359+ period_name = period .period_name ,
360+ components = PriceComponents (per_kwh = merged_kwh , monthly = merged_monthly ),
361+ window = period .window ,
362+ )
363+ )
364+
365+ return merged
0 commit comments