mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-05-28 18:43:40 +00:00
Add energy_price and tax attributes to interval and daily stat sensors: - Interval sensors (current/next/previous): energy_price and tax from the specific 15-minute interval - Daily min/max sensors: energy_price and tax from the extreme interval - Daily average sensors: energy_price_mean, energy_price_median, tax_mean, tax_median — matching the existing mean/median pattern used for the main price attribute Calculator caches both mean and median for energy/tax using calculate_median() from utils/average. All new attributes are excluded from Recorder to prevent database bloat. Impact: Users can see price composition (spot price vs. taxes) on all major price sensors. Enables solar feed-in and net metering automations based on raw energy prices.
306 lines
11 KiB
Python
306 lines
11 KiB
Python
"""Interval attribute builders for Tibber Prices sensors."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from custom_components.tibber_prices.const import (
|
|
PRICE_LEVEL_MAPPING,
|
|
PRICE_RATING_MAPPING,
|
|
get_display_unit_factor,
|
|
)
|
|
from custom_components.tibber_prices.entity_utils import add_icon_color_attribute
|
|
from custom_components.tibber_prices.utils.price import find_price_data_for_interval
|
|
|
|
if TYPE_CHECKING:
|
|
from custom_components.tibber_prices.coordinator.core import (
|
|
TibberPricesDataUpdateCoordinator,
|
|
)
|
|
from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService
|
|
from custom_components.tibber_prices.data import TibberPricesConfigEntry
|
|
|
|
from .helpers import add_alternate_average_attribute
|
|
from .metadata import get_current_interval_data
|
|
|
|
|
|
def _get_interval_data_for_attributes(
|
|
key: str,
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
attributes: dict,
|
|
*,
|
|
time: TibberPricesTimeService,
|
|
) -> dict | None:
|
|
"""
|
|
Get interval data and set timestamp based on sensor type.
|
|
|
|
Refactored to reduce branch complexity in main function.
|
|
|
|
Args:
|
|
key: The sensor entity key
|
|
coordinator: The data update coordinator
|
|
attributes: Attributes dict to update with timestamp if needed
|
|
time: TibberPricesTimeService instance
|
|
|
|
Returns:
|
|
Interval data if found, None otherwise
|
|
|
|
"""
|
|
now = time.now()
|
|
|
|
# Current/next price sensors - override timestamp with interval's startsAt
|
|
next_sensors = ["next_interval_price", "next_interval_price_level", "next_interval_price_rating"]
|
|
prev_sensors = ["previous_interval_price", "previous_interval_price_level", "previous_interval_price_rating"]
|
|
next_hour = ["next_hour_average_price", "next_hour_price_level", "next_hour_price_rating"]
|
|
curr_interval = ["current_interval_price", "current_interval_price_base"]
|
|
curr_hour = ["current_hour_average_price", "current_hour_price_level", "current_hour_price_rating"]
|
|
|
|
if key in next_sensors:
|
|
target_time = time.get_next_interval_start()
|
|
interval_data = find_price_data_for_interval(coordinator.data, target_time, time=time)
|
|
if interval_data:
|
|
attributes["timestamp"] = interval_data["startsAt"]
|
|
return interval_data
|
|
|
|
if key in prev_sensors:
|
|
target_time = time.get_interval_offset_time(-1)
|
|
interval_data = find_price_data_for_interval(coordinator.data, target_time, time=time)
|
|
if interval_data:
|
|
attributes["timestamp"] = interval_data["startsAt"]
|
|
return interval_data
|
|
|
|
if key in next_hour:
|
|
target_time = now + timedelta(hours=1)
|
|
interval_data = find_price_data_for_interval(coordinator.data, target_time, time=time)
|
|
if interval_data:
|
|
attributes["timestamp"] = interval_data["startsAt"]
|
|
return interval_data
|
|
|
|
# Current interval sensors (both variants)
|
|
if key in curr_interval:
|
|
interval_data = get_current_interval_data(coordinator, time=time)
|
|
if interval_data and "startsAt" in interval_data:
|
|
attributes["timestamp"] = interval_data["startsAt"]
|
|
return interval_data
|
|
|
|
# Current hour sensors - keep default timestamp
|
|
if key in curr_hour:
|
|
return get_current_interval_data(coordinator, time=time)
|
|
|
|
return None
|
|
|
|
|
|
def _add_energy_tax_attributes(
|
|
attributes: dict,
|
|
interval_data: dict | None,
|
|
*,
|
|
config_entry: TibberPricesConfigEntry,
|
|
) -> None:
|
|
"""
|
|
Add energy_price and tax attributes from interval data.
|
|
|
|
The API provides `energy` (raw spot price) and `tax` (tax component) in base
|
|
currency. These are converted to display units using the same factor as `total`.
|
|
|
|
Args:
|
|
attributes: Dictionary to add attributes to
|
|
interval_data: Price interval data dict (may contain energy/tax fields)
|
|
config_entry: Config entry for display unit preference
|
|
|
|
"""
|
|
if not interval_data:
|
|
return
|
|
|
|
factor = get_display_unit_factor(config_entry)
|
|
|
|
energy = interval_data.get("energy")
|
|
if energy is not None:
|
|
attributes["energy_price"] = round(float(energy) * factor, 2)
|
|
|
|
tax = interval_data.get("tax")
|
|
if tax is not None:
|
|
attributes["tax"] = round(float(tax) * factor, 2)
|
|
|
|
|
|
def add_current_interval_price_attributes( # noqa: PLR0913
|
|
attributes: dict,
|
|
key: str,
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
native_value: Any,
|
|
cached_data: dict,
|
|
*,
|
|
time: TibberPricesTimeService,
|
|
config_entry: TibberPricesConfigEntry,
|
|
) -> None:
|
|
"""
|
|
Add attributes for current interval price sensors.
|
|
|
|
Args:
|
|
attributes: Dictionary to add attributes to
|
|
key: The sensor entity key
|
|
coordinator: The data update coordinator
|
|
native_value: The current native value of the sensor
|
|
cached_data: Dictionary containing cached sensor data
|
|
time: TibberPricesTimeService instance (required)
|
|
config_entry: Config entry for user preferences
|
|
|
|
"""
|
|
# Get interval data and handle timestamp overrides
|
|
interval_data = _get_interval_data_for_attributes(key, coordinator, attributes, time=time)
|
|
|
|
# Add icon_color for price sensors (based on their price level)
|
|
if key in [
|
|
"current_interval_price",
|
|
"current_interval_price_base",
|
|
"next_interval_price",
|
|
"previous_interval_price",
|
|
]:
|
|
# For interval-based price sensors, get level from interval_data
|
|
if interval_data and "level" in interval_data:
|
|
level = interval_data["level"]
|
|
add_icon_color_attribute(attributes, key="price_level", state_value=level)
|
|
|
|
# Add energy_price and tax attributes from raw API data
|
|
_add_energy_tax_attributes(attributes, interval_data, config_entry=config_entry)
|
|
elif key in ["current_hour_average_price", "next_hour_average_price"]:
|
|
# For hour-based price sensors, get level from cached_data
|
|
level = cached_data.get("rolling_hour_level")
|
|
if level:
|
|
add_icon_color_attribute(attributes, key="price_level", state_value=level)
|
|
|
|
# Add alternate average attribute for rolling hour average price sensors
|
|
base_key = "rolling_hour_0" if key == "current_hour_average_price" else "rolling_hour_1"
|
|
add_alternate_average_attribute(
|
|
attributes,
|
|
cached_data,
|
|
base_key,
|
|
config_entry=config_entry,
|
|
)
|
|
|
|
# Add price level attributes for all level sensors
|
|
add_level_attributes_for_sensor(
|
|
attributes=attributes,
|
|
key=key,
|
|
interval_data=interval_data,
|
|
coordinator=coordinator,
|
|
native_value=native_value,
|
|
time=time,
|
|
)
|
|
|
|
# Add price rating attributes for all rating sensors
|
|
add_rating_attributes_for_sensor(
|
|
attributes=attributes,
|
|
key=key,
|
|
interval_data=interval_data,
|
|
coordinator=coordinator,
|
|
native_value=native_value,
|
|
time=time,
|
|
)
|
|
|
|
|
|
def add_level_attributes_for_sensor( # noqa: PLR0913
|
|
attributes: dict,
|
|
key: str,
|
|
interval_data: dict | None,
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
native_value: Any,
|
|
*,
|
|
time: TibberPricesTimeService,
|
|
) -> None:
|
|
"""
|
|
Add price level attributes based on sensor type.
|
|
|
|
Args:
|
|
attributes: Dictionary to add attributes to
|
|
key: The sensor entity key
|
|
interval_data: Interval data for next/previous sensors
|
|
coordinator: The data update coordinator
|
|
native_value: The current native value of the sensor
|
|
time: TibberPricesTimeService instance (required)
|
|
|
|
"""
|
|
# For interval-based level sensors (next/previous), use interval data
|
|
if key in ["next_interval_price_level", "previous_interval_price_level"]:
|
|
if interval_data and "level" in interval_data:
|
|
add_price_level_attributes(attributes, interval_data["level"])
|
|
# For hour-aggregated level sensors, use native_value
|
|
elif key in ["current_hour_price_level", "next_hour_price_level"]:
|
|
level_value = native_value
|
|
if level_value and isinstance(level_value, str):
|
|
add_price_level_attributes(attributes, level_value.upper())
|
|
# For current price level sensor
|
|
elif key == "current_interval_price_level":
|
|
current_interval_data = get_current_interval_data(coordinator, time=time)
|
|
if current_interval_data and "level" in current_interval_data:
|
|
add_price_level_attributes(attributes, current_interval_data["level"])
|
|
|
|
|
|
def add_price_level_attributes(attributes: dict, level: str) -> None:
|
|
"""
|
|
Add price level specific attributes.
|
|
|
|
Args:
|
|
attributes: Dictionary to add attributes to
|
|
level: The price level value (e.g., VERY_CHEAP, NORMAL, etc.)
|
|
|
|
"""
|
|
if level in PRICE_LEVEL_MAPPING:
|
|
attributes["level_value"] = PRICE_LEVEL_MAPPING[level]
|
|
attributes["level_id"] = level
|
|
|
|
# Add icon_color for dynamic styling
|
|
add_icon_color_attribute(attributes, key="price_level", state_value=level)
|
|
|
|
|
|
def add_rating_attributes_for_sensor( # noqa: PLR0913
|
|
attributes: dict,
|
|
key: str,
|
|
interval_data: dict | None,
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
native_value: Any,
|
|
*,
|
|
time: TibberPricesTimeService,
|
|
) -> None:
|
|
"""
|
|
Add price rating attributes based on sensor type.
|
|
|
|
Args:
|
|
attributes: Dictionary to add attributes to
|
|
key: The sensor entity key
|
|
interval_data: Interval data for next/previous sensors
|
|
coordinator: The data update coordinator
|
|
native_value: The current native value of the sensor
|
|
time: TibberPricesTimeService instance (required)
|
|
|
|
"""
|
|
# For interval-based rating sensors (next/previous), use interval data
|
|
if key in ["next_interval_price_rating", "previous_interval_price_rating"]:
|
|
if interval_data and "rating_level" in interval_data:
|
|
add_price_rating_attributes(attributes, interval_data["rating_level"])
|
|
# For hour-aggregated rating sensors, use native_value
|
|
elif key in ["current_hour_price_rating", "next_hour_price_rating"]:
|
|
rating_value = native_value
|
|
if rating_value and isinstance(rating_value, str):
|
|
add_price_rating_attributes(attributes, rating_value.upper())
|
|
# For current price rating sensor
|
|
elif key == "current_interval_price_rating":
|
|
current_interval_data = get_current_interval_data(coordinator, time=time)
|
|
if current_interval_data and "rating_level" in current_interval_data:
|
|
add_price_rating_attributes(attributes, current_interval_data["rating_level"])
|
|
|
|
|
|
def add_price_rating_attributes(attributes: dict, rating: str) -> None:
|
|
"""
|
|
Add price rating specific attributes.
|
|
|
|
Args:
|
|
attributes: Dictionary to add attributes to
|
|
rating: The price rating value (e.g., LOW, NORMAL, HIGH)
|
|
|
|
"""
|
|
if rating in PRICE_RATING_MAPPING:
|
|
attributes["rating_value"] = PRICE_RATING_MAPPING[rating]
|
|
attributes["rating_id"] = rating
|
|
|
|
# Add icon_color for dynamic styling
|
|
add_icon_color_attribute(attributes, key="price_rating", state_value=rating)
|