core/homeassistant/components/iqvia/sensor.py

217 lines
6.7 KiB
Python
Raw Normal View History

"""Support for IQVIA sensors."""
from statistics import mean
import numpy as np
from homeassistant.const import ATTR_STATE
from homeassistant.core import callback
from . import IQVIAEntity
from .const import (
DATA_COORDINATOR,
2019-07-31 19:25:30 +00:00
DOMAIN,
SENSORS,
2019-07-31 19:25:30 +00:00
TYPE_ALLERGY_FORECAST,
TYPE_ALLERGY_INDEX,
TYPE_ALLERGY_OUTLOOK,
2019-07-31 19:25:30 +00:00
TYPE_ALLERGY_TODAY,
TYPE_ALLERGY_TOMORROW,
TYPE_ASTHMA_FORECAST,
TYPE_ASTHMA_INDEX,
TYPE_ASTHMA_TODAY,
TYPE_ASTHMA_TOMORROW,
TYPE_DISEASE_FORECAST,
TYPE_DISEASE_INDEX,
TYPE_DISEASE_TODAY,
)
2019-07-31 19:25:30 +00:00
ATTR_ALLERGEN_AMOUNT = "allergen_amount"
ATTR_ALLERGEN_GENUS = "allergen_genus"
ATTR_ALLERGEN_NAME = "allergen_name"
ATTR_ALLERGEN_TYPE = "allergen_type"
ATTR_CITY = "city"
ATTR_OUTLOOK = "outlook"
ATTR_RATING = "rating"
ATTR_SEASON = "season"
ATTR_TREND = "trend"
ATTR_ZIP_CODE = "zip_code"
API_CATEGORY_MAPPING = {
TYPE_ALLERGY_TODAY: TYPE_ALLERGY_INDEX,
TYPE_ALLERGY_TOMORROW: TYPE_ALLERGY_INDEX,
TYPE_ALLERGY_TOMORROW: TYPE_ALLERGY_INDEX,
TYPE_ASTHMA_TODAY: TYPE_ASTHMA_INDEX,
TYPE_ASTHMA_TOMORROW: TYPE_ASTHMA_INDEX,
TYPE_DISEASE_TODAY: TYPE_DISEASE_INDEX,
}
2019-07-31 19:25:30 +00:00
RATING_MAPPING = [
{"label": "Low", "minimum": 0.0, "maximum": 2.4},
{"label": "Low/Medium", "minimum": 2.5, "maximum": 4.8},
{"label": "Medium", "minimum": 4.9, "maximum": 7.2},
{"label": "Medium/High", "minimum": 7.3, "maximum": 9.6},
{"label": "High", "minimum": 9.7, "maximum": 12},
]
TREND_FLAT = "Flat"
TREND_INCREASING = "Increasing"
TREND_SUBSIDING = "Subsiding"
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up IQVIA sensors based on a config entry."""
sensor_class_mapping = {
TYPE_ALLERGY_FORECAST: ForecastSensor,
TYPE_ALLERGY_TODAY: IndexSensor,
TYPE_ALLERGY_TOMORROW: IndexSensor,
TYPE_ASTHMA_FORECAST: ForecastSensor,
TYPE_ASTHMA_TODAY: IndexSensor,
TYPE_ASTHMA_TOMORROW: IndexSensor,
TYPE_DISEASE_FORECAST: ForecastSensor,
TYPE_DISEASE_TODAY: IndexSensor,
}
sensors = []
for sensor_type, (name, icon) in SENSORS.items():
api_category = API_CATEGORY_MAPPING.get(sensor_type, sensor_type)
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][api_category]
sensor_class = sensor_class_mapping[sensor_type]
sensors.append(sensor_class(coordinator, entry, sensor_type, name, icon))
async_add_entities(sensors)
def calculate_trend(indices):
"""Calculate the "moving average" of a set of indices."""
index_range = np.arange(0, len(indices))
index_array = np.array(indices)
linear_fit = np.polyfit(index_range, index_array, 1)
slope = round(linear_fit[0], 2)
if slope > 0:
return TREND_INCREASING
if slope < 0:
return TREND_SUBSIDING
return TREND_FLAT
class ForecastSensor(IQVIAEntity):
"""Define sensor related to forecast data."""
@callback
def update_from_latest_data(self):
"""Update the sensor."""
if not self.available:
return
data = self.coordinator.data.get("Location", {})
if not data.get("periods"):
return
2019-07-31 19:25:30 +00:00
indices = [p["Index"] for p in data["periods"]]
average = round(mean(indices), 1)
[rating] = [
2019-07-31 19:25:30 +00:00
i["label"]
for i in RATING_MAPPING
if i["minimum"] <= average <= i["maximum"]
]
self._attr_state = average
self._attr_extra_state_attributes.update(
2019-07-31 19:25:30 +00:00
{
ATTR_CITY: data["City"].title(),
ATTR_RATING: rating,
ATTR_STATE: data["State"],
ATTR_TREND: calculate_trend(indices),
ATTR_ZIP_CODE: data["ZIP"],
}
)
if self._type == TYPE_ALLERGY_FORECAST:
outlook_coordinator = self.hass.data[DOMAIN][DATA_COORDINATOR][
self._entry.entry_id
][TYPE_ALLERGY_OUTLOOK]
if not outlook_coordinator.last_update_success:
return
self._attr_extra_state_attributes[
ATTR_OUTLOOK
] = outlook_coordinator.data.get("Outlook")
self._attr_extra_state_attributes[
ATTR_SEASON
] = outlook_coordinator.data.get("Season")
class IndexSensor(IQVIAEntity):
"""Define sensor related to indices."""
@callback
def update_from_latest_data(self):
"""Update the sensor."""
if not self.coordinator.last_update_success:
return
try:
if self._type in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW):
data = self.coordinator.data.get("Location")
elif self._type in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW):
data = self.coordinator.data.get("Location")
elif self._type == TYPE_DISEASE_TODAY:
data = self.coordinator.data.get("Location")
except KeyError:
return
2019-07-31 19:25:30 +00:00
key = self._type.split("_")[-1].title()
try:
[period] = [p for p in data["periods"] if p["Type"] == key]
except ValueError:
return
[rating] = [
2019-07-31 19:25:30 +00:00
i["label"]
for i in RATING_MAPPING
if i["minimum"] <= period["Index"] <= i["maximum"]
]
self._attr_extra_state_attributes.update(
2019-07-31 19:25:30 +00:00
{
ATTR_CITY: data["City"].title(),
ATTR_RATING: rating,
ATTR_STATE: data["State"],
ATTR_ZIP_CODE: data["ZIP"],
}
)
if self._type in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW):
2019-07-31 19:25:30 +00:00
for idx, attrs in enumerate(period["Triggers"]):
index = idx + 1
self._attr_extra_state_attributes.update(
2019-07-31 19:25:30 +00:00
{
f"{ATTR_ALLERGEN_GENUS}_{index}": attrs["Genus"],
f"{ATTR_ALLERGEN_NAME}_{index}": attrs["Name"],
f"{ATTR_ALLERGEN_TYPE}_{index}": attrs["PlantType"],
2019-07-31 19:25:30 +00:00
}
)
elif self._type in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW):
2019-07-31 19:25:30 +00:00
for idx, attrs in enumerate(period["Triggers"]):
index = idx + 1
self._attr_extra_state_attributes.update(
2019-07-31 19:25:30 +00:00
{
f"{ATTR_ALLERGEN_NAME}_{index}": attrs["Name"],
f"{ATTR_ALLERGEN_AMOUNT}_{index}": attrs["PPM"],
2019-07-31 19:25:30 +00:00
}
)
elif self._type == TYPE_DISEASE_TODAY:
2019-07-31 19:25:30 +00:00
for attrs in period["Triggers"]:
self._attr_extra_state_attributes[
f"{attrs['Name'].lower()}_index"
] = attrs["Index"]
self._attr_state = period["Index"]