Refactor energy validation issue reporting (#85523)

* Refactor energy validation issue reporting

* Update English translations

* Adjust translations
This commit is contained in:
Erik Montnemery 2023-01-12 12:50:43 +01:00 committed by GitHub
parent e4e96d3394
commit 8418a30cc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 311 additions and 173 deletions

View file

@ -1,3 +1,61 @@
{
"title": "Energy"
"title": "Energy",
"issues": {
"entity_not_defined": {
"title": "Entity not defined",
"description": "Check the integration or your configuration that provides:"
},
"recorder_untracked": {
"title": "Entity not tracked",
"description": "The recorder has been configured to exclude these configured entities:"
},
"entity_unavailable": {
"title": "Entity unavailable",
"description": "The state of these configured entities are currently not available:"
},
"entity_state_non_numeric": {
"title": "Entity has non-numeric state",
"description": "The following entities have a state that cannot be parsed as a number:"
},
"entity_negative_state": {
"title": "Entity has a negative state",
"description": "The following entities have a negative state while a positive state is expected:"
},
"entity_unexpected_unit_energy": {
"title": "Unexpected unit of measurement",
"description": "The following entities do not have an expected unit of measurement (either of {energy_units}):"
},
"entity_unexpected_unit_gas": {
"title": "[%key:component::energy::issues::entity_unexpected_unit_energy::title%]",
"description": "The following entities do not have an expected unit of measurement (either of {energy_units} for an energy sensor or either of {gas_units} for a gas sensor:)"
},
"entity_unexpected_unit_water": {
"title": "[%key:component::energy::issues::entity_unexpected_unit_energy::title%]",
"description": "The following entities do not have the expected unit of measurement (either of {water_units}):"
},
"entity_unexpected_unit_energy_price": {
"title": "[%key:component::energy::issues::entity_unexpected_unit_energy::title%]",
"description": "The following entities do not have an expected unit of measurement {price_units}:"
},
"entity_unexpected_unit_gas_price": {
"title": "[%key:component::energy::issues::entity_unexpected_unit_energy_price::title%]",
"description": "[%key:component::energy::issues::entity_unexpected_unit_energy::description%]"
},
"entity_unexpected_unit_water_price": {
"title": "[%key:component::energy::issues::entity_unexpected_unit_energy::title%]",
"description": "[%key:component::energy::issues::entity_unexpected_unit_energy::description%]"
},
"entity_unexpected_state_class": {
"title": "Unexpected state class",
"description": "The following entities do not have the expected state class:"
},
"entity_unexpected_device_class": {
"title": "Unexpected device class",
"description": "The following entities do not have the expected device class:"
},
"entity_state_class_measurement_no_last_reset": {
"title": "Last reset missing",
"description": "The following entities have state class 'measurement' but 'last_reset' is missing:"
}
}
}

View file

@ -1,3 +1,61 @@
{
"issues": {
"entity_negative_state": {
"description": "The following entities have a negative state while a positive state is expected:",
"title": "Entity has a negative state"
},
"entity_not_defined": {
"description": "Check the integration or your configuration that provides:",
"title": "Entity not defined"
},
"entity_state_class_measurement_no_last_reset": {
"description": "The following entities have state class 'measurement' but 'last_reset' is missing:",
"title": "Last reset missing"
},
"entity_state_non_numeric": {
"description": "The following entities have a state that cannot be parsed as a number:",
"title": "Entity has non-numeric state"
},
"entity_unavailable": {
"description": "The state of these configured entities are currently not available:",
"title": "Entity unavailable"
},
"entity_unexpected_device_class": {
"description": "The following entities do not have the expected device class:",
"title": "Unexpected device class"
},
"entity_unexpected_state_class": {
"description": "The following entities do not have the expected state class:",
"title": "Unexpected state class"
},
"entity_unexpected_unit_energy": {
"description": "The following entities do not have an expected unit of measurement (either of {energy_units}):",
"title": "Unexpected unit of measurement"
},
"entity_unexpected_unit_energy_price": {
"description": "The following entities do not have an expected unit of measurement {price_units}:",
"title": "Unexpected unit of measurement"
},
"entity_unexpected_unit_gas": {
"description": "The following entities do not have an expected unit of measurement (either of {energy_units} for an energy sensor or either of {gas_units} for a gas sensor:)",
"title": "Unexpected unit of measurement"
},
"entity_unexpected_unit_gas_price": {
"description": "The following entities do not have an expected unit of measurement (either of {energy_units}):",
"title": "Unexpected unit of measurement"
},
"entity_unexpected_unit_water": {
"description": "The following entities do not have the expected unit of measurement (either of {water_units}):",
"title": "Unexpected unit of measurement"
},
"entity_unexpected_unit_water_price": {
"description": "The following entities do not have an expected unit of measurement (either of {energy_units}):",
"title": "Unexpected unit of measurement"
},
"recorder_untracked": {
"description": "The recorder has been configured to exclude these configured entities:",
"title": "Entity not tracked"
}
},
"title": "Energy"
}

View file

@ -4,7 +4,6 @@ from __future__ import annotations
from collections.abc import Mapping, Sequence
import dataclasses
import functools
from typing import Any
from homeassistant.components import recorder, sensor
from homeassistant.const import (
@ -72,29 +71,94 @@ WATER_UNIT_ERROR = "entity_unexpected_unit_water"
WATER_PRICE_UNIT_ERROR = "entity_unexpected_unit_water_price"
def _get_placeholders(hass: HomeAssistant, issue_type: str) -> dict[str, str] | None:
currency = hass.config.currency
if issue_type == ENERGY_UNIT_ERROR:
return {
"energy_units": ", ".join(
ENERGY_USAGE_UNITS[sensor.SensorDeviceClass.ENERGY]
),
}
if issue_type == ENERGY_PRICE_UNIT_ERROR:
return {
"price_units": ", ".join(
f"{currency}{unit}" for unit in ENERGY_PRICE_UNITS
),
}
if issue_type == GAS_UNIT_ERROR:
return {
"energy_units": ", ".join(GAS_USAGE_UNITS[sensor.SensorDeviceClass.ENERGY]),
"gas_units": ", ".join(GAS_USAGE_UNITS[sensor.SensorDeviceClass.GAS]),
}
if issue_type == GAS_PRICE_UNIT_ERROR:
return {
"price_units": ", ".join(f"{currency}{unit}" for unit in GAS_PRICE_UNITS),
}
if issue_type == WATER_UNIT_ERROR:
return {
"water_units": ", ".join(WATER_USAGE_UNITS[sensor.SensorDeviceClass.WATER]),
}
if issue_type == WATER_PRICE_UNIT_ERROR:
return {
"price_units": ", ".join(f"{currency}{unit}" for unit in WATER_PRICE_UNITS),
}
return None
@dataclasses.dataclass
class ValidationIssue:
"""Error or warning message."""
type: str
identifier: str
value: Any | None = None
affected_entities: set[tuple[str, float | str | None]] = dataclasses.field(
default_factory=set
)
translation_placeholders: dict[str, str] | None = None
@dataclasses.dataclass
class ValidationIssues:
"""Container for validation issues."""
issues: dict[str, ValidationIssue] = dataclasses.field(default_factory=dict)
def __init__(self) -> None:
"""Container for validiation issues."""
self.issues = {}
def add_issue(
self,
hass: HomeAssistant,
issue_type: str,
affected_entity: str,
detail: float | str | None = None,
) -> None:
"""Add an issue for an entity."""
if not (issue := self.issues.get(issue_type)):
self.issues[issue_type] = issue = ValidationIssue(issue_type)
issue.translation_placeholders = _get_placeholders(hass, issue_type)
issue.affected_entities.add((affected_entity, detail))
@dataclasses.dataclass
class EnergyPreferencesValidation:
"""Dictionary holding validation information."""
energy_sources: list[list[ValidationIssue]] = dataclasses.field(
default_factory=list
)
device_consumption: list[list[ValidationIssue]] = dataclasses.field(
default_factory=list
)
energy_sources: list[ValidationIssues] = dataclasses.field(default_factory=list)
device_consumption: list[ValidationIssues] = dataclasses.field(default_factory=list)
def as_dict(self) -> dict:
"""Return dictionary version."""
return dataclasses.asdict(self)
return {
"energy_sources": [
[dataclasses.asdict(issue) for issue in issues.issues.values()]
for issues in self.energy_sources
],
"device_consumption": [
[dataclasses.asdict(issue) for issue in issues.issues.values()]
for issues in self.device_consumption
],
}
@callback
@ -105,11 +169,11 @@ def _async_validate_usage_stat(
allowed_device_classes: Sequence[str],
allowed_units: Mapping[str, Sequence[str]],
unit_error: str,
result: list[ValidationIssue],
issues: ValidationIssues,
) -> None:
"""Validate a statistic."""
if stat_id not in metadata:
result.append(ValidationIssue("statistics_not_defined", stat_id))
issues.add_issue(hass, "statistics_not_defined", stat_id)
has_entity_source = valid_entity_id(stat_id)
@ -119,54 +183,36 @@ def _async_validate_usage_stat(
entity_id = stat_id
if not recorder.is_entity_recorded(hass, entity_id):
result.append(
ValidationIssue(
"recorder_untracked",
entity_id,
)
)
issues.add_issue(hass, "recorder_untracked", entity_id)
return
if (state := hass.states.get(entity_id)) is None:
result.append(
ValidationIssue(
"entity_not_defined",
entity_id,
)
)
issues.add_issue(hass, "entity_not_defined", entity_id)
return
if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
result.append(ValidationIssue("entity_unavailable", entity_id, state.state))
issues.add_issue(hass, "entity_unavailable", entity_id, state.state)
return
try:
current_value: float | None = float(state.state)
except ValueError:
result.append(
ValidationIssue("entity_state_non_numeric", entity_id, state.state)
)
issues.add_issue(hass, "entity_state_non_numeric", entity_id, state.state)
return
if current_value is not None and current_value < 0:
result.append(
ValidationIssue("entity_negative_state", entity_id, current_value)
)
issues.add_issue(hass, "entity_negative_state", entity_id, current_value)
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
if device_class not in allowed_device_classes:
result.append(
ValidationIssue(
"entity_unexpected_device_class",
entity_id,
device_class,
)
issues.add_issue(
hass, "entity_unexpected_device_class", entity_id, device_class
)
else:
unit = state.attributes.get("unit_of_measurement")
if device_class and unit not in allowed_units.get(device_class, []):
result.append(ValidationIssue(unit_error, entity_id, unit))
issues.add_issue(hass, unit_error, entity_id, unit)
state_class = state.attributes.get(sensor.ATTR_STATE_CLASS)
@ -176,20 +222,14 @@ def _async_validate_usage_stat(
sensor.SensorStateClass.TOTAL_INCREASING,
]
if state_class not in allowed_state_classes:
result.append(
ValidationIssue(
"entity_unexpected_state_class",
entity_id,
state_class,
)
)
issues.add_issue(hass, "entity_unexpected_state_class", entity_id, state_class)
if (
state_class == sensor.SensorStateClass.MEASUREMENT
and sensor.ATTR_LAST_RESET not in state.attributes
):
result.append(
ValidationIssue("entity_state_class_measurement_no_last_reset", entity_id)
issues.add_issue(
hass, "entity_state_class_measurement_no_last_reset", entity_id
)
@ -197,32 +237,25 @@ def _async_validate_usage_stat(
def _async_validate_price_entity(
hass: HomeAssistant,
entity_id: str,
result: list[ValidationIssue],
issues: ValidationIssues,
allowed_units: tuple[str, ...],
unit_error: str,
) -> None:
"""Validate that the price entity is correct."""
if (state := hass.states.get(entity_id)) is None:
result.append(
ValidationIssue(
"entity_not_defined",
entity_id,
)
)
issues.add_issue(hass, "entity_not_defined", entity_id)
return
try:
float(state.state)
except ValueError:
result.append(
ValidationIssue("entity_state_non_numeric", entity_id, state.state)
)
issues.add_issue(hass, "entity_state_non_numeric", entity_id, state.state)
return
unit = state.attributes.get("unit_of_measurement")
if unit is None or not unit.endswith(allowed_units):
result.append(ValidationIssue(unit_error, entity_id, unit))
issues.add_issue(hass, unit_error, entity_id, unit)
@callback
@ -230,11 +263,11 @@ def _async_validate_cost_stat(
hass: HomeAssistant,
metadata: dict[str, tuple[int, recorder.models.StatisticMetaData]],
stat_id: str,
result: list[ValidationIssue],
issues: ValidationIssues,
) -> None:
"""Validate that the cost stat is correct."""
if stat_id not in metadata:
result.append(ValidationIssue("statistics_not_defined", stat_id))
issues.add_issue(hass, "statistics_not_defined", stat_id)
has_entity = valid_entity_id(stat_id)
@ -242,10 +275,10 @@ def _async_validate_cost_stat(
return
if not recorder.is_entity_recorded(hass, stat_id):
result.append(ValidationIssue("recorder_untracked", stat_id))
issues.add_issue(hass, "recorder_untracked", stat_id)
if (state := hass.states.get(stat_id)) is None:
result.append(ValidationIssue("entity_not_defined", stat_id))
issues.add_issue(hass, "entity_not_defined", stat_id)
return
state_class = state.attributes.get("state_class")
@ -256,22 +289,18 @@ def _async_validate_cost_stat(
sensor.SensorStateClass.TOTAL_INCREASING,
]
if state_class not in supported_state_classes:
result.append(
ValidationIssue("entity_unexpected_state_class", stat_id, state_class)
)
issues.add_issue(hass, "entity_unexpected_state_class", stat_id, state_class)
if (
state_class == sensor.SensorStateClass.MEASUREMENT
and sensor.ATTR_LAST_RESET not in state.attributes
):
result.append(
ValidationIssue("entity_state_class_measurement_no_last_reset", stat_id)
)
issues.add_issue(hass, "entity_state_class_measurement_no_last_reset", stat_id)
@callback
def _async_validate_auto_generated_cost_entity(
hass: HomeAssistant, energy_entity_id: str, result: list[ValidationIssue]
hass: HomeAssistant, energy_entity_id: str, issues: ValidationIssues
) -> None:
"""Validate that the auto generated cost entity is correct."""
if energy_entity_id not in hass.data[DOMAIN]["cost_sensors"]:
@ -280,7 +309,7 @@ def _async_validate_auto_generated_cost_entity(
cost_entity_id = hass.data[DOMAIN]["cost_sensors"][energy_entity_id]
if not recorder.is_entity_recorded(hass, cost_entity_id):
result.append(ValidationIssue("recorder_untracked", cost_entity_id))
issues.add_issue(hass, "recorder_untracked", cost_entity_id)
async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
@ -297,7 +326,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
# Create a list of validation checks
for source in manager.data["energy_sources"]:
source_result: list[ValidationIssue] = []
source_result = ValidationIssues()
result.energy_sources.append(source_result)
if source["type"] == "grid":
@ -550,7 +579,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
)
for device in manager.data["device_consumption"]:
device_result: list[ValidationIssue] = []
device_result = ValidationIssues()
result.device_consumption.append(device_result)
wanted_statistics_metadata.add(device["stat_consumption"])
validate_calls.append(

View file

@ -118,13 +118,13 @@ async def test_validation_device_consumption_entity_missing(hass, mock_energy_ma
[
{
"type": "statistics_not_defined",
"identifier": "sensor.not_exist",
"value": None,
"affected_entities": {("sensor.not_exist", None)},
"translation_placeholders": None,
},
{
"type": "entity_not_defined",
"identifier": "sensor.not_exist",
"value": None,
"affected_entities": {("sensor.not_exist", None)},
"translation_placeholders": None,
},
]
],
@ -142,8 +142,8 @@ async def test_validation_device_consumption_stat_missing(hass, mock_energy_mana
[
{
"type": "statistics_not_defined",
"identifier": "external:not_exist",
"value": None,
"affected_entities": {("external:not_exist", None)},
"translation_placeholders": None,
}
]
],
@ -165,8 +165,8 @@ async def test_validation_device_consumption_entity_unavailable(
[
{
"type": "entity_unavailable",
"identifier": "sensor.unavailable",
"value": "unavailable",
"affected_entities": {("sensor.unavailable", "unavailable")},
"translation_placeholders": None,
}
]
],
@ -188,8 +188,8 @@ async def test_validation_device_consumption_entity_non_numeric(
[
{
"type": "entity_state_non_numeric",
"identifier": "sensor.non_numeric",
"value": "123,123.10",
"affected_entities": {("sensor.non_numeric", "123,123.10")},
"translation_placeholders": None,
},
]
],
@ -219,8 +219,8 @@ async def test_validation_device_consumption_entity_unexpected_unit(
[
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.unexpected_unit",
"value": "beers",
"affected_entities": {("sensor.unexpected_unit", "beers")},
"translation_placeholders": {"energy_units": "GJ, kWh, MWh, Wh"},
}
]
],
@ -242,8 +242,8 @@ async def test_validation_device_consumption_recorder_not_tracked(
[
{
"type": "recorder_untracked",
"identifier": "sensor.not_recorded",
"value": None,
"affected_entities": {("sensor.not_recorded", None)},
"translation_placeholders": None,
}
]
],
@ -273,8 +273,8 @@ async def test_validation_device_consumption_no_last_reset(
[
{
"type": "entity_state_class_measurement_no_last_reset",
"identifier": "sensor.no_last_reset",
"value": None,
"affected_entities": {("sensor.no_last_reset", None)},
"translation_placeholders": None,
}
]
],
@ -305,8 +305,8 @@ async def test_validation_solar(hass, mock_energy_manager, mock_get_metadata):
[
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.solar_production",
"value": "beers",
"affected_entities": {("sensor.solar_production", "beers")},
"translation_placeholders": {"energy_units": "GJ, kWh, MWh, Wh"},
}
]
],
@ -351,13 +351,11 @@ async def test_validation_battery(hass, mock_energy_manager, mock_get_metadata):
[
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.battery_import",
"value": "beers",
},
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.battery_export",
"value": "beers",
"affected_entities": {
("sensor.battery_import", "beers"),
("sensor.battery_export", "beers"),
},
"translation_placeholders": {"energy_units": "GJ, kWh, MWh, Wh"},
},
]
],
@ -422,43 +420,35 @@ async def test_validation_grid(
[
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.grid_consumption_1",
"value": "beers",
"affected_entities": {
("sensor.grid_consumption_1", "beers"),
("sensor.grid_production_1", "beers"),
},
"translation_placeholders": {"energy_units": "GJ, kWh, MWh, Wh"},
},
{
"type": "statistics_not_defined",
"identifier": "sensor.grid_cost_1",
"value": None,
"affected_entities": {
("sensor.grid_cost_1", None),
("sensor.grid_compensation_1", None),
},
"translation_placeholders": None,
},
{
"type": "recorder_untracked",
"identifier": "sensor.grid_cost_1",
"value": None,
"affected_entities": {
("sensor.grid_cost_1", None),
("sensor.grid_compensation_1", None),
},
"translation_placeholders": None,
},
{
"type": "entity_not_defined",
"identifier": "sensor.grid_cost_1",
"value": None,
},
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.grid_production_1",
"value": "beers",
},
{
"type": "statistics_not_defined",
"identifier": "sensor.grid_compensation_1",
"value": None,
},
{
"type": "recorder_untracked",
"identifier": "sensor.grid_compensation_1",
"value": None,
},
{
"type": "entity_not_defined",
"identifier": "sensor.grid_compensation_1",
"value": None,
"affected_entities": {
("sensor.grid_cost_1", None),
("sensor.grid_compensation_1", None),
},
"translation_placeholders": None,
},
]
],
@ -517,23 +507,19 @@ async def test_validation_grid_external_cost_compensation(
[
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.grid_consumption_1",
"value": "beers",
"affected_entities": {
("sensor.grid_consumption_1", "beers"),
("sensor.grid_production_1", "beers"),
},
"translation_placeholders": {"energy_units": "GJ, kWh, MWh, Wh"},
},
{
"type": "statistics_not_defined",
"identifier": "external:grid_cost_1",
"value": None,
},
{
"type": "entity_unexpected_unit_energy",
"identifier": "sensor.grid_production_1",
"value": "beers",
},
{
"type": "statistics_not_defined",
"identifier": "external:grid_compensation_1",
"value": None,
"affected_entities": {
("external:grid_cost_1", None),
("external:grid_compensation_1", None),
},
"translation_placeholders": None,
},
]
],
@ -599,18 +585,16 @@ async def test_validation_grid_price_not_exist(
[
{
"type": "entity_not_defined",
"identifier": "sensor.grid_price_1",
"value": None,
"affected_entities": {("sensor.grid_price_1", None)},
"translation_placeholders": None,
},
{
"type": "recorder_untracked",
"identifier": "sensor.grid_consumption_1_cost",
"value": None,
},
{
"type": "recorder_untracked",
"identifier": "sensor.grid_production_1_compensation",
"value": None,
"affected_entities": {
("sensor.grid_consumption_1_cost", None),
("sensor.grid_production_1_compensation", None),
},
"translation_placeholders": None,
},
]
],
@ -683,8 +667,8 @@ async def test_validation_grid_auto_cost_entity_errors(
"$/kWh",
{
"type": "entity_state_non_numeric",
"identifier": "sensor.grid_price_1",
"value": "123,123.12",
"affected_entities": {("sensor.grid_price_1", "123,123.12")},
"translation_placeholders": None,
},
),
(
@ -692,8 +676,10 @@ async def test_validation_grid_auto_cost_entity_errors(
"$/Ws",
{
"type": "entity_unexpected_unit_energy_price",
"identifier": "sensor.grid_price_1",
"value": "$/Ws",
"affected_entities": {("sensor.grid_price_1", "$/Ws")},
"translation_placeholders": {
"price_units": "EUR/GJ, EUR/kWh, EUR/MWh, EUR/Wh"
},
},
),
),
@ -834,18 +820,21 @@ async def test_validation_gas(
[
{
"type": "entity_unexpected_unit_gas",
"identifier": "sensor.gas_consumption_1",
"value": "beers",
"affected_entities": {("sensor.gas_consumption_1", "beers")},
"translation_placeholders": {
"energy_units": "GJ, kWh, MWh, Wh",
"gas_units": "CCF, ft³, m³",
},
},
{
"type": "recorder_untracked",
"identifier": "sensor.gas_cost_1",
"value": None,
"affected_entities": {("sensor.gas_cost_1", None)},
"translation_placeholders": None,
},
{
"type": "entity_not_defined",
"identifier": "sensor.gas_cost_1",
"value": None,
"affected_entities": {("sensor.gas_cost_1", None)},
"translation_placeholders": None,
},
],
[],
@ -853,15 +842,17 @@ async def test_validation_gas(
[
{
"type": "entity_unexpected_device_class",
"identifier": "sensor.gas_consumption_4",
"value": None,
"affected_entities": {("sensor.gas_consumption_4", None)},
"translation_placeholders": None,
},
],
[
{
"type": "entity_unexpected_unit_gas_price",
"identifier": "sensor.gas_price_2",
"value": "EUR/invalid",
"affected_entities": {("sensor.gas_price_2", "EUR/invalid")},
"translation_placeholders": {
"price_units": "EUR/GJ, EUR/kWh, EUR/MWh, EUR/Wh, EUR/CCF, EUR/ft³, EUR/m³"
},
},
],
],
@ -1039,18 +1030,18 @@ async def test_validation_water(
[
{
"type": "entity_unexpected_unit_water",
"identifier": "sensor.water_consumption_1",
"value": "beers",
"affected_entities": {("sensor.water_consumption_1", "beers")},
"translation_placeholders": {"water_units": "CCF, ft³, m³, gal, L"},
},
{
"type": "recorder_untracked",
"identifier": "sensor.water_cost_1",
"value": None,
"affected_entities": {("sensor.water_cost_1", None)},
"translation_placeholders": None,
},
{
"type": "entity_not_defined",
"identifier": "sensor.water_cost_1",
"value": None,
"affected_entities": {("sensor.water_cost_1", None)},
"translation_placeholders": None,
},
],
[],
@ -1058,15 +1049,17 @@ async def test_validation_water(
[
{
"type": "entity_unexpected_device_class",
"identifier": "sensor.water_consumption_4",
"value": None,
"affected_entities": {("sensor.water_consumption_4", None)},
"translation_placeholders": None,
},
],
[
{
"type": "entity_unexpected_unit_water_price",
"identifier": "sensor.water_price_2",
"value": "EUR/invalid",
"affected_entities": {("sensor.water_price_2", "EUR/invalid")},
"translation_placeholders": {
"price_units": "EUR/CCF, EUR/ft³, EUR/m³, EUR/gal, EUR/L"
},
},
],
],