11from __future__ import annotations
22
33from collections import defaultdict
4+ from decimal import Decimal
45
56from homeassistant .components .sensor import SensorDeviceClass , SensorEntity , SensorStateClass
67from homeassistant .components import persistent_notification
1920 ATTR_MONTHLY_COMPONENTS ,
2021 ATTR_PERIOD ,
2122 ATTR_CURRENT_RATE_NAME ,
23+ ATTR_EXPORT_RATE_SOURCE ,
24+ ATTR_EXPORT_RATE_WARNING ,
2225 ATTR_NEXT_RATE_CHANGE ,
2326 ATTR_NEXT_RATE_NAME ,
2427 ATTR_NEXT_RATE_VALUE ,
28+ ATTR_PSCR_CENTS ,
29+ ATTR_PSCR_RATE_CODE ,
30+ ATTR_PSCR_RATES ,
31+ ATTR_PSCR_SOURCE_URL ,
2532 ATTR_RATE_CODE ,
2633 ATTR_RATE_NAME ,
34+ ATTR_RIDER18_EXPORT_AVAILABLE ,
2735 ATTR_SCHEDULE_BY_SEASON ,
2836 ATTR_SCHEDULE_TEXT ,
2937 ATTR_SEASON ,
4149 get_active_period ,
4250 get_next_rate_change ,
4351 period_display_name ,
52+ rider18_export_formula_available ,
4453)
4554
4655
@@ -103,6 +112,36 @@ def _active_period(self) -> SeasonalPeriodRate | None:
103112 return None
104113 return get_active_period (rate , dt_util .now ())
105114
115+ def _pscr_cents_for_rate (self , rate : RatePlan | None = None ) -> Decimal | None :
116+ target_rate = rate or self ._selected_rate ()
117+ if target_rate is None :
118+ return None
119+ return getattr (self .coordinator .data , "pscr_rates" , {}).get (target_rate .code )
120+
121+ def _export_rate_cents (self , period : SeasonalPeriodRate ):
122+ return current_export_rate_cents (
123+ period ,
124+ self ._entry .data .get (CONF_NET_METERING , False ),
125+ self ._pscr_cents_for_rate (),
126+ )
127+
128+ def _export_rate_source (self , period : SeasonalPeriodRate ) -> str :
129+ if self ._entry .data .get (CONF_NET_METERING , False ):
130+ return "net_metering"
131+ if rider18_export_formula_available (period , self ._pscr_cents_for_rate ()):
132+ return "rider18_formula"
133+ return "rider18_formula_incomplete"
134+
135+ def _export_rate_warning (self , period : SeasonalPeriodRate ) -> str | None :
136+ if self ._entry .data .get (CONF_NET_METERING , False ):
137+ return None
138+ if rider18_export_formula_available (period , self ._pscr_cents_for_rate ()):
139+ return None
140+ return (
141+ "Rider 18 formula is missing a generation component or PSCR value "
142+ "for the active period; using the available formula components only."
143+ )
144+
106145 def _warning (self ) -> str | None :
107146 selected = self ._entry .data [CONF_SELECTED_RATE ]
108147 if selected not in self .coordinator .data .rates :
@@ -118,6 +157,16 @@ def _base_attributes(self) -> dict:
118157 ATTR_SOURCE_URL : self .coordinator .data .source_url ,
119158 ATTR_CARD_EFFECTIVE_DATE : self .coordinator .data .effective_date ,
120159 }
160+ pscr_cents = self ._pscr_cents_for_rate ()
161+ if pscr_cents is not None :
162+ attrs [ATTR_PSCR_CENTS ] = float (pscr_cents )
163+ rate = self ._selected_rate ()
164+ if rate is not None :
165+ attrs [ATTR_PSCR_RATE_CODE ] = rate .code
166+ if self .coordinator .data .pscr_rates :
167+ attrs [ATTR_PSCR_RATES ] = {code : float (value ) for code , value in self .coordinator .data .pscr_rates .items ()}
168+ if self .coordinator .data .pscr_source_url :
169+ attrs [ATTR_PSCR_SOURCE_URL ] = self .coordinator .data .pscr_source_url
121170
122171 rate = self ._selected_rate ()
123172 period = self ._active_period ()
@@ -260,13 +309,19 @@ def native_value(self) -> float | None:
260309 def extra_state_attributes (self ) -> dict :
261310 attrs = self ._base_attributes ()
262311 attrs [CONF_NET_METERING ] = self ._entry .data .get (CONF_NET_METERING , False )
312+ period = self ._active_period ()
313+ if period is not None :
314+ attrs [ATTR_EXPORT_RATE_SOURCE ] = self ._export_rate_source (period )
315+ attrs [ATTR_RIDER18_EXPORT_AVAILABLE ] = rider18_export_formula_available (period , self ._pscr_cents_for_rate ())
316+ warning = self ._export_rate_warning (period )
317+ if warning is not None :
318+ attrs [ATTR_EXPORT_RATE_WARNING ] = warning
263319 return attrs
264320
265321 def _period_value_usd (self , period : SeasonalPeriodRate | None ) -> float | None :
266322 if period is None :
267323 return None
268- cents = current_export_rate_cents (period , self ._entry .data .get (CONF_NET_METERING , False ))
269- return float (cents / 100 )
324+ return float (self ._export_rate_cents (period ) / 100 )
270325
271326
272327class DteCurrentRateNameSensor (_DteBaseRateSensor ):
@@ -328,7 +383,6 @@ def extra_state_attributes(self) -> dict:
328383
329384 def _schedule_rows (self , rate : RatePlan ) -> list [dict ]:
330385 rows : list [dict ] = []
331- net_metering = self ._entry .data .get (CONF_NET_METERING , False )
332386 for period in sorted (rate .periods , key = lambda p : (p .season_name , p .period_name )):
333387 rows .append (
334388 {
@@ -337,7 +391,7 @@ def _schedule_rows(self, rate: RatePlan) -> list[dict]:
337391 "name" : period_display_name (period ),
338392 "time_window" : self ._window_summary (period ),
339393 "import_usd_per_kwh" : round (float (current_import_rate_cents (period ) / 100 ), 6 ),
340- "export_usd_per_kwh" : round (float (current_export_rate_cents (period , net_metering ) / 100 ), 6 ),
394+ "export_usd_per_kwh" : round (float (self . _export_rate_cents (period ) / 100 ), 6 ),
341395 }
342396 )
343397 return rows
0 commit comments