1 """Support for IQVIA sensors."""
3 from __future__
import annotations
5 from statistics
import mean
6 from typing
import Any, NamedTuple, cast
12 SensorEntityDescription,
22 TYPE_ALLERGY_FORECAST,
26 TYPE_ALLERGY_TOMORROW,
31 TYPE_DISEASE_FORECAST,
35 from .entity
import IQVIAEntity
37 ATTR_ALLERGEN_AMOUNT =
"allergen_amount"
38 ATTR_ALLERGEN_GENUS =
"allergen_genus"
39 ATTR_ALLERGEN_NAME =
"allergen_name"
40 ATTR_ALLERGEN_TYPE =
"allergen_type"
42 ATTR_OUTLOOK =
"outlook"
43 ATTR_RATING =
"rating"
44 ATTR_SEASON =
"season"
46 ATTR_ZIP_CODE =
"zip_code"
48 API_CATEGORY_MAPPING = {
49 TYPE_ALLERGY_TODAY: TYPE_ALLERGY_INDEX,
50 TYPE_ALLERGY_TOMORROW: TYPE_ALLERGY_INDEX,
51 TYPE_ASTHMA_TODAY: TYPE_ASTHMA_INDEX,
52 TYPE_ASTHMA_TOMORROW: TYPE_ASTHMA_INDEX,
53 TYPE_DISEASE_TODAY: TYPE_DISEASE_INDEX,
58 """Assign label to value range."""
65 RATING_MAPPING: list[Rating] = [
66 Rating(label=
"Low", minimum=0.0, maximum=2.4),
67 Rating(label=
"Low/Medium", minimum=2.5, maximum=4.8),
68 Rating(label=
"Medium", minimum=4.9, maximum=7.2),
69 Rating(label=
"Medium/High", minimum=7.3, maximum=9.6),
70 Rating(label=
"High", minimum=9.7, maximum=12),
75 TREND_INCREASING =
"Increasing"
76 TREND_SUBSIDING =
"Subsiding"
79 FORECAST_SENSOR_DESCRIPTIONS = (
81 key=TYPE_ALLERGY_FORECAST,
82 name=
"Allergy index: forecasted average",
86 key=TYPE_ASTHMA_FORECAST,
87 name=
"Asthma index: forecasted average",
91 key=TYPE_DISEASE_FORECAST,
92 name=
"Cold & flu: forecasted average",
97 INDEX_SENSOR_DESCRIPTIONS = (
99 key=TYPE_ALLERGY_TODAY,
100 name=
"Allergy index: today",
102 state_class=SensorStateClass.MEASUREMENT,
105 key=TYPE_ALLERGY_TOMORROW,
106 name=
"Allergy index: tomorrow",
110 key=TYPE_ASTHMA_TODAY,
111 name=
"Asthma index: today",
113 state_class=SensorStateClass.MEASUREMENT,
116 key=TYPE_ASTHMA_TOMORROW,
117 name=
"Asthma index: tomorrow",
121 key=TYPE_DISEASE_TODAY,
122 name=
"Cold & flu index: today",
124 state_class=SensorStateClass.MEASUREMENT,
130 hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
132 """Set up IQVIA sensors based on a config entry."""
133 sensors: list[ForecastSensor | IndexSensor] = [
135 hass.data[DOMAIN][entry.entry_id][
136 API_CATEGORY_MAPPING.get(description.key, description.key)
141 for description
in FORECAST_SENSOR_DESCRIPTIONS
146 hass.data[DOMAIN][entry.entry_id][
147 API_CATEGORY_MAPPING.get(description.key, description.key)
152 for description
in INDEX_SENSOR_DESCRIPTIONS
161 """Calculate the "moving average" of a set of indices."""
162 index_range = np.arange(0, len(indices))
163 index_array = np.array(indices)
164 linear_fit = np.polyfit(index_range, index_array, 1)
165 slope = round(linear_fit[0], 2)
168 return TREND_INCREASING
171 return TREND_SUBSIDING
177 """Define sensor related to forecast data."""
181 """Update the sensor."""
185 data = self.coordinator.data.get(
"Location", {})
187 if not data.get(
"periods"):
190 indices = [p[
"Index"]
for p
in data[
"periods"]]
191 average = round(
mean(indices), 1)
193 i.label
for i
in RATING_MAPPING
if i.minimum <= average <= i.maximum
199 ATTR_CITY: data[
"City"].title(),
201 ATTR_STATE: data[
"State"],
203 ATTR_ZIP_CODE: data[
"ZIP"],
208 outlook_coordinator = self.
hasshasshass.data[DOMAIN][self.
_entry_entry.entry_id][
212 if not outlook_coordinator.last_update_success:
216 outlook_coordinator.data.get(
"Outlook")
219 outlook_coordinator.data.get(
"Season")
224 """Define sensor related to indices."""
228 """Update the sensor."""
229 if not self.coordinator.last_update_success:
235 TYPE_ALLERGY_TOMORROW,
237 TYPE_ASTHMA_TOMORROW,
240 data = self.coordinator.data.get(
"Location")
247 period = next(p
for p
in data[
"periods"]
if p[
"Type"] == key)
248 except StopIteration:
251 data = cast(dict[str, Any], data)
253 i.label
for i
in RATING_MAPPING
if i.minimum <= period[
"Index"] <= i.maximum
258 ATTR_CITY: data[
"City"].title(),
260 ATTR_STATE: data[
"State"],
261 ATTR_ZIP_CODE: data[
"ZIP"],
265 if self.
entity_descriptionentity_description.key
in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW):
266 for idx, attrs
in enumerate(period[
"Triggers"]):
270 f
"{ATTR_ALLERGEN_GENUS}_{index}": attrs[
"Genus"],
271 f
"{ATTR_ALLERGEN_NAME}_{index}": attrs[
"Name"],
272 f
"{ATTR_ALLERGEN_TYPE}_{index}": attrs[
"PlantType"],
275 elif self.
entity_descriptionentity_description.key
in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW):
276 for idx, attrs
in enumerate(period[
"Triggers"]):
280 f
"{ATTR_ALLERGEN_NAME}_{index}": attrs[
"Name"],
281 f
"{ATTR_ALLERGEN_AMOUNT}_{index}": attrs[
"PPM"],
285 for attrs
in period[
"Triggers"]:
_attr_extra_state_attributes
None update_from_latest_data(self)
None update_from_latest_data(self)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
str calculate_trend(list[float] indices)
IssData update(pyiss.ISS iss)
float|None mean(list[float] values)