1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Fix handling of missing period statistics in Anglian Water coordinator (#167427)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Jordan Harvey
2026-04-06 11:28:35 +01:00
committed by GitHub
parent 4849dc0eb9
commit dfaed39a01
2 changed files with 120 additions and 9 deletions
@@ -92,6 +92,7 @@ class AnglianWaterUpdateCoordinator(DataUpdateCoordinator[None]):
_LOGGER.debug("Updating statistics for the first time")
usage_sum = 0.0
last_stats_time = None
allow_update_last_stored_hour = False
else:
if not meter.readings or len(meter.readings) == 0:
_LOGGER.debug("No recent usage statistics found, skipping update")
@@ -107,6 +108,7 @@ class AnglianWaterUpdateCoordinator(DataUpdateCoordinator[None]):
continue
start = dt_util.as_local(parsed_read_at) - timedelta(hours=1)
_LOGGER.debug("Getting statistics at %s", start)
stats: dict[str, list[Any]] = {}
for end in (start + timedelta(seconds=1), None):
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
@@ -127,15 +129,28 @@ class AnglianWaterUpdateCoordinator(DataUpdateCoordinator[None]):
"Not found, trying to find oldest statistic after %s",
start,
)
assert stats
def _safe_get_sum(records: list[Any]) -> float:
if records and "sum" in records[0]:
return float(records[0]["sum"])
return 0.0
if not stats or not stats.get(usage_statistic_id):
_LOGGER.debug(
"Could not find existing statistics during period lookup for %s, "
"falling back to last stored statistic",
usage_statistic_id,
)
allow_update_last_stored_hour = True
last_records = last_stat[usage_statistic_id]
usage_sum = float(last_records[0].get("sum") or 0.0)
last_stats_time = last_records[0]["start"]
else:
allow_update_last_stored_hour = False
records = stats[usage_statistic_id]
usage_sum = _safe_get_sum(stats.get(usage_statistic_id, []))
last_stats_time = stats[usage_statistic_id][0]["start"]
def _safe_get_sum(records: list[Any]) -> float:
if records and "sum" in records[0]:
return float(records[0]["sum"])
return 0.0
usage_sum = _safe_get_sum(records)
last_stats_time = records[0]["start"]
usage_statistics = []
@@ -148,7 +163,13 @@ class AnglianWaterUpdateCoordinator(DataUpdateCoordinator[None]):
)
continue
start = dt_util.as_local(parsed_read_at) - timedelta(hours=1)
if last_stats_time is not None and start.timestamp() <= last_stats_time:
if last_stats_time is not None and (
start.timestamp() < last_stats_time
or (
start.timestamp() == last_stats_time
and not allow_update_last_stored_hour
)
):
continue
usage_state = max(0, read["consumption"] / 1000)
usage_sum = max(0, read["read"])
@@ -1,6 +1,7 @@
"""Tests for the Anglian Water coordinator."""
from unittest.mock import AsyncMock
from datetime import timedelta
from unittest.mock import AsyncMock, patch
from pyanglianwater.meter import SmartMeter
import pytest
@@ -162,3 +163,92 @@ async def test_coordinator_invalid_readings(
"Could not parse read_at time also-invalid-date, skipping reading"
in caplog.text
)
async def test_coordinator_subsequent_run_missing_period_statistics(
recorder_mock: Recorder,
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smart_meter: SmartMeter,
mock_anglian_water_client: AsyncMock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the coordinator handles missing period lookup statistics."""
coordinator = AnglianWaterUpdateCoordinator(
hass, mock_anglian_water_client, mock_config_entry
)
await coordinator._async_update_data()
await async_wait_recording_done(hass)
# Correct the latest already-stored reading. Fallback should still update
# this hour instead of skipping it.
mock_smart_meter.readings[-1] = {
"read_at": "2024-06-01T14:00:00",
"consumption": 35,
"read": 70,
}
# Add a new later reading to ensure fallback also accepts newer entries.
mock_smart_meter.readings.append(
{"read_at": "2024-06-01T15:00:00", "consumption": 20, "read": 90}
)
with patch(
"homeassistant.components.anglian_water.coordinator.statistics_during_period",
return_value={},
):
await coordinator._async_update_data()
await async_wait_recording_done(hass)
assert "Could not find existing statistics during period lookup" in caplog.text
statistic_id = f"anglian_water:{ACCOUNT_NUMBER}_testsn_usage"
stats = await hass.async_add_executor_job(
get_last_statistics, hass, 1, statistic_id, True, {"sum"}
)
assert stats[statistic_id][0]["sum"] >= 70
parsed_read_at = dt_util.parse_datetime("2024-06-01T14:00:00")
assert parsed_read_at is not None
corrected_start = dt_util.as_local(parsed_read_at) - timedelta(hours=1)
corrected_stats = await hass.async_add_executor_job(
statistics_during_period,
hass,
corrected_start,
corrected_start + timedelta(seconds=1),
{
statistic_id,
},
"hour",
None,
{"sum"},
)
assert corrected_stats[statistic_id][0]["sum"] == 70
async def test_coordinator_period_statistics_without_sum(
recorder_mock: Recorder,
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_anglian_water_client: AsyncMock,
) -> None:
"""Test period lookup records without sum are handled safely."""
coordinator = AnglianWaterUpdateCoordinator(
hass, mock_anglian_water_client, mock_config_entry
)
await coordinator._async_update_data()
await async_wait_recording_done(hass)
statistic_id = f"anglian_water:{ACCOUNT_NUMBER}_testsn_usage"
with patch(
"homeassistant.components.anglian_water.coordinator.statistics_during_period",
return_value={statistic_id: [{"start": 0.0}]},
):
await coordinator._async_update_data()
await async_wait_recording_done(hass)
stats = await hass.async_add_executor_job(
get_last_statistics, hass, 1, statistic_id, True, {"sum"}
)
assert stats[statistic_id]