hass.tibber_prices/tests/test_period_statistics.py
Julian Pawlowski bbcfdd4443 fix(periods): stabilize best and peak period outputs
Recompute merged relaxed periods from raw intervals, harden numeric period option normalization, update day-volatility handling for zero or negative averages, and expose day context on period binary sensors.

Add focused regressions for overlap merges, cache invalidation, day statistics, and visible binary sensor attributes.

Impact: Best and peak period entities stay consistent on negative-price days, refresh correctly when same-day prices change, and expose the documented day context attributes.
2026-04-25 22:46:38 +00:00

94 lines
3 KiB
Python

"""Regression tests for period summary day statistics."""
from __future__ import annotations
from datetime import datetime
import pytest
from custom_components.tibber_prices.coordinator.period_handlers.period_statistics import build_period_summary_dict
from custom_components.tibber_prices.coordinator.period_handlers.types import (
TibberPricesPeriodData,
TibberPricesPeriodStatistics,
)
def _build_stats() -> TibberPricesPeriodStatistics:
"""Create minimal summary stats for period-summary tests."""
return TibberPricesPeriodStatistics(
aggregated_level="cheap",
aggregated_rating="low",
rating_difference_pct=-10.0,
price_mean=-0.2,
price_median=-0.2,
price_min=-0.3,
price_max=0.1,
price_spread=0.4,
volatility="moderate",
coefficient_of_variation=12.3,
period_price_diff=0.0,
period_price_diff_pct=0.0,
)
def _build_period_data(day: datetime) -> TibberPricesPeriodData:
"""Create minimal period timing data for summary tests."""
return TibberPricesPeriodData(
start_time=day.replace(hour=1),
end_time=day.replace(hour=2),
period_length=4,
period_idx=1,
total_periods=1,
)
@pytest.mark.unit
class TestPeriodSummaryDayVolatility:
"""Validate day_volatility_% semantics on extreme price days."""
def test_day_volatility_uses_absolute_average_for_negative_price_days(self) -> None:
"""Negative-average days should still report meaningful volatility percentage."""
day = datetime(2025, 11, 22)
summary = build_period_summary_dict(
_build_period_data(day),
_build_stats(),
reverse_sort=False,
price_context={
"intervals_by_day": {
day.date(): [
{"total": -0.30},
{"total": -0.10},
{"total": 0.10},
]
},
"avg_prices": {day.date(): -0.10},
},
)
assert summary["day_volatility_%"] == 400.0
assert summary["day_price_min"] == -30.0
assert summary["day_price_max"] == 10.0
assert summary["day_price_span"] == 40.0
def test_day_volatility_is_none_when_day_average_is_zero(self) -> None:
"""Zero-average days should avoid reporting a misleading 0% volatility."""
day = datetime(2025, 11, 23)
summary = build_period_summary_dict(
_build_period_data(day),
_build_stats(),
reverse_sort=False,
price_context={
"intervals_by_day": {
day.date(): [
{"total": -0.20},
{"total": 0.20},
]
},
"avg_prices": {day.date(): 0.0},
},
)
assert summary["day_volatility_%"] is None
assert summary["day_price_min"] == -20.0
assert summary["day_price_max"] == 20.0
assert summary["day_price_span"] == 40.0