mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-05-28 18:43:40 +00:00
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.
94 lines
3 KiB
Python
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
|