From 8418a30cc043207ec1dde7322367a34f26669169 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 12 Jan 2023 12:50:43 +0100 Subject: [PATCH] Refactor energy validation issue reporting (#85523) * Refactor energy validation issue reporting * Update English translations * Adjust translations --- homeassistant/components/energy/strings.json | 60 +++++- .../components/energy/translations/en.json | 58 ++++++ homeassistant/components/energy/validate.py | 173 +++++++++------- tests/components/energy/test_validate.py | 193 +++++++++--------- 4 files changed, 311 insertions(+), 173 deletions(-) diff --git a/homeassistant/components/energy/strings.json b/homeassistant/components/energy/strings.json index 6cdcd827633..62888e6ecc0 100644 --- a/homeassistant/components/energy/strings.json +++ b/homeassistant/components/energy/strings.json @@ -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:" + } + } } diff --git a/homeassistant/components/energy/translations/en.json b/homeassistant/components/energy/translations/en.json index 109e1bd5af8..63b4148522b 100644 --- a/homeassistant/components/energy/translations/en.json +++ b/homeassistant/components/energy/translations/en.json @@ -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" } \ No newline at end of file diff --git a/homeassistant/components/energy/validate.py b/homeassistant/components/energy/validate.py index 55d11f5f04d..f8f276f6438 100644 --- a/homeassistant/components/energy/validate.py +++ b/homeassistant/components/energy/validate.py @@ -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( diff --git a/tests/components/energy/test_validate.py b/tests/components/energy/test_validate.py index f1e626c24d5..706e10c87aa 100644 --- a/tests/components/energy/test_validate.py +++ b/tests/components/energy/test_validate.py @@ -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" + }, }, ], ],