mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-07-27 17:26:48 +00:00
check_min_distance_from_avg() computed the distance threshold as range_avg * (1 ± ratio). Tibber prices can go negative during grid oversupply, and multiplying a negative range_avg directly flips the intended direction: e.g. avg * 1.05 makes a negative average MORE negative (i.e. cheaper), which is the wrong direction for a "most expensive" threshold check, and analogously wrong for "cheapest" checks. Compute the threshold as range_avg ± abs(range_avg) * ratio instead, matching the sign-safe normalization pattern already used for min_distance_from_avg in the period system (coordinator/period_handlers/level_filtering.py). Behavior for positive averages (the common case) is unchanged. Used by find_cheapest_block, find_cheapest_hours, and plan_charging. Impact: min_distance_from_avg now correctly filters cheapest/most expensive windows during negative-price periods instead of silently accepting windows in the wrong direction relative to the search range average.
71 lines
3.4 KiB
Python
71 lines
3.4 KiB
Python
"""Tests for small pure helper functions in services/helpers.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from custom_components.tibber_prices.services.helpers import check_min_distance_from_avg
|
|
|
|
|
|
class TestCheckMinDistanceFromAvgPositiveAverage:
|
|
"""Regression coverage for the common case: positive range average."""
|
|
|
|
def test_cheapest_passes_when_far_enough_below(self) -> None:
|
|
"""Window 10% below a positive average passes a 5% requirement."""
|
|
assert check_min_distance_from_avg(18.0, 20.0, 5.0, reverse=False) is True
|
|
|
|
def test_cheapest_fails_when_too_close(self) -> None:
|
|
"""Window only 2% below a positive average fails a 5% requirement."""
|
|
assert check_min_distance_from_avg(19.6, 20.0, 5.0, reverse=False) is False
|
|
|
|
def test_most_expensive_passes_when_far_enough_above(self) -> None:
|
|
"""Window 10% above a positive average passes a 5% requirement."""
|
|
assert check_min_distance_from_avg(22.0, 20.0, 5.0, reverse=True) is True
|
|
|
|
def test_most_expensive_fails_when_too_close(self) -> None:
|
|
"""Window only 2% above a positive average fails a 5% requirement."""
|
|
assert check_min_distance_from_avg(20.4, 20.0, 5.0, reverse=True) is False
|
|
|
|
|
|
class TestCheckMinDistanceFromAvgNegativeAverage:
|
|
"""Regression coverage for GH-reported sign bug: negative range average.
|
|
|
|
Tibber prices can go negative during grid oversupply. The threshold must
|
|
be computed as `avg ± abs(avg) * ratio` (not `avg * (1 ± ratio)`), since
|
|
multiplying a negative average directly flips the intended direction.
|
|
"""
|
|
|
|
def test_cheapest_passes_when_more_negative_than_avg(self) -> None:
|
|
"""A window that is 10% cheaper (more negative) than a -5 avg passes 5% req."""
|
|
# -5 - abs(-5)*0.05 = -5.25 → window must be <= -5.25
|
|
assert check_min_distance_from_avg(-5.6, -5.0, 5.0, reverse=False) is True
|
|
|
|
def test_cheapest_fails_when_less_negative_than_avg(self) -> None:
|
|
"""A window that is actually more expensive (less negative) than avg must fail.
|
|
|
|
Regression: the old buggy formula (avg * (1 - ratio)) computed a
|
|
threshold of -4.75 here, incorrectly letting -5.1 pass even though
|
|
it is only ~2% below avg (needs 5%).
|
|
"""
|
|
assert check_min_distance_from_avg(-5.1, -5.0, 5.0, reverse=False) is False
|
|
|
|
def test_most_expensive_passes_when_less_negative_than_avg(self) -> None:
|
|
"""A window 10% above (less negative than) a -5 avg passes a 5% req."""
|
|
# -5 + abs(-5)*0.05 = -4.75 → window must be >= -4.75
|
|
assert check_min_distance_from_avg(-4.4, -5.0, 5.0, reverse=True) is True
|
|
|
|
def test_most_expensive_fails_when_actually_cheaper_than_avg(self) -> None:
|
|
"""A window that is cheaper (more negative) than avg must fail the most-expensive check.
|
|
|
|
Regression: the old buggy formula (avg * (1 + ratio)) computed a
|
|
threshold of -5.25 here, incorrectly letting -5.1 pass even though
|
|
it is actually cheaper than the average, not more expensive.
|
|
"""
|
|
assert check_min_distance_from_avg(-5.1, -5.0, 5.0, reverse=True) is False
|
|
|
|
|
|
class TestCheckMinDistanceFromAvgEdgeCases:
|
|
"""Edge cases: zero average."""
|
|
|
|
def test_zero_average_always_passes(self) -> None:
|
|
"""A zero average makes percentage distance undefined; always pass."""
|
|
assert check_min_distance_from_avg(5.0, 0.0, 5.0, reverse=False) is True
|
|
assert check_min_distance_from_avg(-5.0, 0.0, 5.0, reverse=True) is True
|