hass.tibber_prices/custom_components/tibber_prices/sensor/attributes/helpers.py
Julian Pawlowski 51a99980df feat(sensors)!: add configurable median/mean display for average sensors
Add user-configurable option to choose between median and arithmetic mean
as the displayed value for all 14 average price sensors, with the alternate
value exposed as attribute.

BREAKING CHANGE: Average sensor default changed from arithmetic mean to
median. Users who rely on arithmetic mean behavior may use the price_mean attribue now, or must manually reconfigure
via Settings → Devices & Services → Tibber Prices → Configure → General
Settings → "Average Sensor Display" → Select "Arithmetic Mean" to get this as sensor state.

Affected sensors (14 total):
- Daily averages: average_price_today, average_price_tomorrow
- 24h windows: trailing_price_average, leading_price_average
- Rolling hour: current_hour_average_price, next_hour_average_price
- Future forecasts: next_avg_3h, next_avg_6h, next_avg_9h, next_avg_12h

Implementation:
- All average calculators now return (mean, median) tuples
- User preference controls which value appears in sensor state
- Alternate value automatically added to attributes
- Period statistics (best_price/peak_price) extended with both values

Technical changes:
- New config option: CONF_AVERAGE_SENSOR_DISPLAY (default: "median")
- Calculator functions return tuples: (avg, median)
- Attribute builders: add_alternate_average_attribute() helper function
- Period statistics: price_avg → price_mean + price_median
- Translations: Updated all 5 languages (de, en, nb, nl, sv)
- Documentation: AGENTS.md, period-calculation.md, recorder-optimization.md

Migration path:
Users can switch back to arithmetic mean via:
Settings → Integrations → Tibber Prices → Configure
→ General Settings → "Average Sensor Display" → "Arithmetic Mean"

Impact: Median is more resistant to price spikes, providing more stable
automation triggers. Statistical analysis from coordinator still uses
arithmetic mean (e.g., trailing_avg_24h for rating calculations).

Co-developed-with: GitHub Copilot <copilot@github.com>
2025-12-08 17:53:40 +00:00

52 lines
1.7 KiB
Python

"""Helper functions for sensor attributes."""
from __future__ import annotations
from typing import TYPE_CHECKING
from custom_components.tibber_prices.const import (
CONF_AVERAGE_SENSOR_DISPLAY,
DEFAULT_AVERAGE_SENSOR_DISPLAY,
)
if TYPE_CHECKING:
from custom_components.tibber_prices.data import TibberPricesConfigEntry
def add_alternate_average_attribute(
attributes: dict,
cached_data: dict,
base_key: str,
*,
config_entry: TibberPricesConfigEntry,
) -> None:
"""
Add the alternate average value (mean or median) as attribute.
If user selected "median" as state display, adds "price_mean" as attribute.
If user selected "mean" as state display, adds "price_median" as attribute.
Args:
attributes: Dictionary to add attribute to
cached_data: Cached calculation data containing mean/median values
base_key: Base key for cached values (e.g., "average_price_today", "rolling_hour_0")
config_entry: Config entry for user preferences
"""
# Get user preference for which value to display in state
display_mode = config_entry.options.get(
CONF_AVERAGE_SENSOR_DISPLAY,
DEFAULT_AVERAGE_SENSOR_DISPLAY,
)
# Add the alternate value as attribute
if display_mode == "median":
# State shows median → add mean as attribute
mean_value = cached_data.get(f"{base_key}_mean")
if mean_value is not None:
attributes["price_mean"] = mean_value
else:
# State shows mean → add median as attribute
median_value = cached_data.get(f"{base_key}_median")
if median_value is not None:
attributes["price_median"] = median_value