mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Let the statistics component calculate changes in fossil energy consumption calculation (#101557)
This commit is contained in:
parent
235a3486ee
commit
017c699e19
@ -274,10 +274,10 @@ async def ws_get_fossil_energy_consumption(
|
||||
statistic_ids,
|
||||
"hour",
|
||||
{"energy": UnitOfEnergy.KILO_WATT_HOUR},
|
||||
{"mean", "sum"},
|
||||
{"mean", "change"},
|
||||
)
|
||||
|
||||
def _combine_sum_statistics(
|
||||
def _combine_change_statistics(
|
||||
stats: dict[str, list[StatisticsRow]], statistic_ids: list[str]
|
||||
) -> dict[float, float]:
|
||||
"""Combine multiple statistics, returns a dict indexed by start time."""
|
||||
@ -287,21 +287,12 @@ async def ws_get_fossil_energy_consumption(
|
||||
if statistics_id not in statistic_ids:
|
||||
continue
|
||||
for period in stat:
|
||||
if period["sum"] is None:
|
||||
if period["change"] is None:
|
||||
continue
|
||||
result[period["start"]] += period["sum"]
|
||||
result[period["start"]] += period["change"]
|
||||
|
||||
return {key: result[key] for key in sorted(result)}
|
||||
|
||||
def _calculate_deltas(sums: dict[float, float]) -> dict[float, float]:
|
||||
prev: float | None = None
|
||||
result: dict[float, float] = {}
|
||||
for period, sum_ in sums.items():
|
||||
if prev is not None:
|
||||
result[period] = sum_ - prev
|
||||
prev = sum_
|
||||
return result
|
||||
|
||||
def _reduce_deltas(
|
||||
stat_list: list[dict[str, Any]],
|
||||
same_period: Callable[[float, float], bool],
|
||||
@ -334,10 +325,9 @@ async def ws_get_fossil_energy_consumption(
|
||||
|
||||
return result
|
||||
|
||||
merged_energy_statistics = _combine_sum_statistics(
|
||||
merged_energy_statistics = _combine_change_statistics(
|
||||
statistics, msg["energy_statistic_ids"]
|
||||
)
|
||||
energy_deltas = _calculate_deltas(merged_energy_statistics)
|
||||
indexed_co2_statistics = cast(
|
||||
dict[float, float],
|
||||
{
|
||||
@ -349,7 +339,7 @@ async def ws_get_fossil_energy_consumption(
|
||||
# Calculate amount of fossil based energy, assume 100% fossil if missing
|
||||
fossil_energy = [
|
||||
{"start": start, "delta": delta * indexed_co2_statistics.get(start, 100) / 100}
|
||||
for start, delta in energy_deltas.items()
|
||||
for start, delta in merged_energy_statistics.items()
|
||||
]
|
||||
|
||||
if msg["period"] == "hour":
|
||||
|
@ -423,6 +423,7 @@ async def test_fossil_energy_consumption_no_co2(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx(22.0),
|
||||
period2.isoformat(): pytest.approx(33.0 - 22.0),
|
||||
period3.isoformat(): pytest.approx(55.0 - 33.0),
|
||||
period4.isoformat(): pytest.approx(88.0 - 55.0),
|
||||
@ -445,6 +446,7 @@ async def test_fossil_energy_consumption_no_co2(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx(22.0),
|
||||
period2_day_start.isoformat(): pytest.approx(33.0 - 22.0),
|
||||
period3.isoformat(): pytest.approx(55.0 - 33.0),
|
||||
period4_day_start.isoformat(): pytest.approx(88.0 - 55.0),
|
||||
@ -467,7 +469,7 @@ async def test_fossil_energy_consumption_no_co2(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx(33.0 - 22.0),
|
||||
period1.isoformat(): pytest.approx(33.0),
|
||||
period3.isoformat(): pytest.approx((55.0 - 33.0) + (88.0 - 55.0)),
|
||||
}
|
||||
|
||||
@ -586,8 +588,9 @@ async def test_fossil_energy_consumption_hole(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period2.isoformat(): pytest.approx(3.0 - 20.0),
|
||||
period3.isoformat(): pytest.approx(55.0 - 3.0),
|
||||
period1.isoformat(): pytest.approx(20.0),
|
||||
period2.isoformat(): pytest.approx(3.0),
|
||||
period3.isoformat(): pytest.approx(32.0),
|
||||
period4.isoformat(): pytest.approx(88.0 - 55.0),
|
||||
}
|
||||
|
||||
@ -608,8 +611,9 @@ async def test_fossil_energy_consumption_hole(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period2_day_start.isoformat(): pytest.approx(3.0 - 20.0),
|
||||
period3.isoformat(): pytest.approx(55.0 - 3.0),
|
||||
period1.isoformat(): pytest.approx(20.0),
|
||||
period2_day_start.isoformat(): pytest.approx(3.0),
|
||||
period3.isoformat(): pytest.approx(32.0),
|
||||
period4_day_start.isoformat(): pytest.approx(88.0 - 55.0),
|
||||
}
|
||||
|
||||
@ -630,8 +634,8 @@ async def test_fossil_energy_consumption_hole(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx(3.0 - 20.0),
|
||||
period3.isoformat(): pytest.approx((55.0 - 3.0) + (88.0 - 55.0)),
|
||||
period1.isoformat(): pytest.approx(23.0),
|
||||
period3.isoformat(): pytest.approx((55.0 - 3.0) + (88.0 - 55.0) - 20.0),
|
||||
}
|
||||
|
||||
|
||||
@ -930,6 +934,7 @@ async def test_fossil_energy_consumption(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx(11.0 * 0.2),
|
||||
period2.isoformat(): pytest.approx((33.0 - 22.0) * 0.3),
|
||||
period3.isoformat(): pytest.approx((44.0 - 33.0) * 0.6),
|
||||
period4.isoformat(): pytest.approx((55.0 - 44.0) * 0.9),
|
||||
@ -952,6 +957,7 @@ async def test_fossil_energy_consumption(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx(11.0 * 0.2),
|
||||
period2_day_start.isoformat(): pytest.approx((33.0 - 22.0) * 0.3),
|
||||
period3.isoformat(): pytest.approx((44.0 - 33.0) * 0.6),
|
||||
period4_day_start.isoformat(): pytest.approx((55.0 - 44.0) * 0.9),
|
||||
@ -974,7 +980,7 @@ async def test_fossil_energy_consumption(
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {
|
||||
period1.isoformat(): pytest.approx((33.0 - 22.0) * 0.3),
|
||||
period1.isoformat(): pytest.approx(11.0 * 0.5),
|
||||
period3.isoformat(): pytest.approx(
|
||||
((44.0 - 33.0) * 0.6) + ((55.0 - 44.0) * 0.9)
|
||||
),
|
||||
@ -1032,3 +1038,120 @@ async def test_fossil_energy_consumption_checks(
|
||||
assert msg["id"] == 2
|
||||
assert not msg["success"]
|
||||
assert msg["error"] == {"code": "invalid_end_time", "message": "Invalid end_time"}
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2021-08-01 01:00:00+00:00")
|
||||
async def test_fossil_energy_consumption_check_missing_hour(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test explicitly if the API keeps the first hour of data for the requested time frame."""
|
||||
|
||||
now = dt_util.utcnow()
|
||||
later = dt_util.as_utc(dt_util.parse_datetime("2021-08-01 05:00:00"))
|
||||
|
||||
await async_setup_component(hass, "history", {})
|
||||
await async_setup_component(hass, "sensor", {})
|
||||
await async_recorder_block_till_done(hass)
|
||||
|
||||
hour1 = dt_util.as_utc(dt_util.parse_datetime("2021-08-01 01:00:00"))
|
||||
hour2 = dt_util.as_utc(dt_util.parse_datetime("2021-08-01 02:00:00"))
|
||||
hour3 = dt_util.as_utc(dt_util.parse_datetime("2021-08-01 03:00:00"))
|
||||
hour4 = dt_util.as_utc(dt_util.parse_datetime("2021-08-01 04:00:00"))
|
||||
|
||||
# add energy statistics for 4 hours
|
||||
energy_statistics_1 = (
|
||||
{
|
||||
"start": hour1,
|
||||
"last_reset": None,
|
||||
"state": 0,
|
||||
"sum": 1,
|
||||
},
|
||||
{
|
||||
"start": hour2,
|
||||
"last_reset": None,
|
||||
"state": 1,
|
||||
"sum": 3,
|
||||
},
|
||||
{
|
||||
"start": hour3,
|
||||
"last_reset": None,
|
||||
"state": 2,
|
||||
"sum": 5,
|
||||
},
|
||||
{
|
||||
"start": hour4,
|
||||
"last_reset": None,
|
||||
"state": 3,
|
||||
"sum": 8,
|
||||
},
|
||||
)
|
||||
energy_metadata_1 = {
|
||||
"has_mean": False,
|
||||
"has_sum": True,
|
||||
"name": "Total imported energy",
|
||||
"source": "test",
|
||||
"statistic_id": "test:total_energy_import",
|
||||
"unit_of_measurement": "kWh",
|
||||
}
|
||||
|
||||
async_add_external_statistics(hass, energy_metadata_1, energy_statistics_1)
|
||||
|
||||
# add co2 statistics for 4 hours
|
||||
co2_statistics = (
|
||||
{
|
||||
"start": hour1,
|
||||
"last_reset": None,
|
||||
"mean": 10,
|
||||
},
|
||||
{
|
||||
"start": hour2,
|
||||
"last_reset": None,
|
||||
"mean": 30,
|
||||
},
|
||||
{
|
||||
"start": hour3,
|
||||
"last_reset": None,
|
||||
"mean": 60,
|
||||
},
|
||||
{
|
||||
"start": hour4,
|
||||
"last_reset": None,
|
||||
"mean": 90,
|
||||
},
|
||||
)
|
||||
co2_metadata = {
|
||||
"has_mean": True,
|
||||
"has_sum": False,
|
||||
"name": "Fossil percentage",
|
||||
"source": "test",
|
||||
"statistic_id": "test:fossil_percentage",
|
||||
"unit_of_measurement": "%",
|
||||
}
|
||||
|
||||
async_add_external_statistics(hass, co2_metadata, co2_statistics)
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
client = await hass_ws_client()
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "energy/fossil_energy_consumption",
|
||||
"start_time": now.isoformat(),
|
||||
"end_time": later.isoformat(),
|
||||
"energy_statistic_ids": [
|
||||
"test:total_energy_import",
|
||||
],
|
||||
"co2_statistic_id": "test:fossil_percentage",
|
||||
"period": "hour",
|
||||
}
|
||||
)
|
||||
|
||||
# check if we received deltas for the requested time frame
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert list(response["result"].keys()) == [
|
||||
hour1.isoformat(),
|
||||
hour2.isoformat(),
|
||||
hour3.isoformat(),
|
||||
hour4.isoformat(),
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user