hass.tibber_prices/custom_components/tibber_prices/entity_utils/attributes.py
Julian Pawlowski 4876a2cc29 refactor(entity_utils): extract shared helpers from sensor platform
Created entity_utils/helpers.py with platform-agnostic utility functions:
- get_price_value(): Price unit conversion (major/minor currency)
- translate_level(): Price level translation
- translate_rating_level(): Rating level translation
- find_rolling_hour_center_index(): Rolling hour window calculations

These functions moved from sensor/helpers.py as they are used by both
sensor and binary_sensor platforms. Remaining sensor/helpers.py now
contains only sensor-specific helpers (aggregate_price_data, etc.).

Updated imports:
- sensor/core.py: Import from entity_utils instead of sensor.helpers
- entity_utils/icons.py: Fixed find_rolling_hour_center_index import
- binary_sensor platforms: Can now use shared helpers

Added clear docstrings explaining:
- entity_utils/helpers.py: Platform-agnostic utilities
- sensor/helpers.py: Sensor-specific aggregation functions

Impact: Better code reuse, clearer responsibility boundaries between
platform-specific and shared utilities.
2025-11-18 20:07:17 +00:00

237 lines
7.3 KiB
Python

"""Common attribute utilities for Tibber Prices entities."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
from ..data import TibberPricesConfigEntry # noqa: TID252
def build_timestamp_attribute(interval_data: dict | None) -> str | None:
"""
Build timestamp attribute from interval data.
Extracts startsAt field consistently across all sensors.
Args:
interval_data: Interval data dictionary containing startsAt field
Returns:
ISO format timestamp string or None
"""
if not interval_data:
return None
return interval_data.get("startsAt")
def build_period_attributes(period_data: dict) -> dict:
"""
Build common period attributes (start, end, duration, timestamp).
Used by binary sensors for period-based entities.
Args:
period_data: Period data dictionary
Returns:
Dictionary with common period attributes
"""
return {
"start": period_data.get("start"),
"end": period_data.get("end"),
"duration_minutes": period_data.get("duration_minutes"),
"timestamp": period_data.get("start"), # Timestamp = period start
}
def add_description_attributes( # noqa: PLR0913
attributes: dict,
platform: str,
translation_key: str | None,
hass: HomeAssistant,
config_entry: TibberPricesConfigEntry,
*,
position: str = "end",
) -> None:
"""
Add description attributes from custom translations to an existing attributes dict.
Adds description (always), and optionally long_description and usage_tips if
CONF_EXTENDED_DESCRIPTIONS is enabled in config.
This function modifies the attributes dict in-place. By default, descriptions are
added at the END of the dict (after all other attributes). For special cases like
chart_data_export, use position="before_service_data" to add descriptions before
service data attributes.
Args:
attributes: Existing attributes dict to modify (in-place)
platform: Platform name ("sensor" or "binary_sensor")
translation_key: Translation key for entity
hass: Home Assistant instance
config_entry: Config entry with options
position: Where to add descriptions:
- "end" (default): Add at the very end
- "before_service_data": Add before service data (for chart_data_export)
"""
if not translation_key or not hass:
return
# Import here to avoid circular dependency
from ..const import ( # noqa: PLC0415, TID252
CONF_EXTENDED_DESCRIPTIONS,
DEFAULT_EXTENDED_DESCRIPTIONS,
get_entity_description,
)
language = hass.config.language if hass.config.language else "en"
# Build description dict
desc_attrs: dict[str, str] = {}
description = get_entity_description(platform, translation_key, language, "description")
if description:
desc_attrs["description"] = description
extended_descriptions = config_entry.options.get(
CONF_EXTENDED_DESCRIPTIONS,
config_entry.data.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS),
)
if extended_descriptions:
long_desc = get_entity_description(platform, translation_key, language, "long_description")
if long_desc:
desc_attrs["long_description"] = long_desc
usage_tips = get_entity_description(platform, translation_key, language, "usage_tips")
if usage_tips:
desc_attrs["usage_tips"] = usage_tips
# Add descriptions at appropriate position
if position == "end":
# Default: Add at the very end
attributes.update(desc_attrs)
elif position == "before_service_data":
# Special case: Insert before service data
# This is used by chart_data_export to keep our attributes before foreign data
# We need to rebuild the dict to maintain order
temp_attrs = dict(attributes)
attributes.clear()
# Add everything except service data
for key, value in temp_attrs.items():
if key not in ("timestamp", "error"):
continue
attributes[key] = value
# Add descriptions here (before service data)
attributes.update(desc_attrs)
# Add service data last
for key, value in temp_attrs.items():
if key in ("timestamp", "error"):
continue
attributes[key] = value
async def async_add_description_attributes( # noqa: PLR0913
attributes: dict,
platform: str,
translation_key: str | None,
hass: HomeAssistant,
config_entry: TibberPricesConfigEntry,
*,
position: str = "end",
) -> None:
"""
Async version of add_description_attributes.
Adds description attributes from custom translations to an existing attributes dict.
Uses async translation loading (calls async_get_entity_description).
Args:
attributes: Existing attributes dict to modify (in-place)
platform: Platform name ("sensor" or "binary_sensor")
translation_key: Translation key for entity
hass: Home Assistant instance
config_entry: Config entry with options
position: Where to add descriptions ("end" or "before_service_data")
"""
if not translation_key or not hass:
return
# Import here to avoid circular dependency
from ..const import ( # noqa: PLC0415, TID252
CONF_EXTENDED_DESCRIPTIONS,
DEFAULT_EXTENDED_DESCRIPTIONS,
async_get_entity_description,
)
language = hass.config.language if hass.config.language else "en"
# Build description dict
desc_attrs: dict[str, str] = {}
description = await async_get_entity_description(
hass,
platform,
translation_key,
language,
"description",
)
if description:
desc_attrs["description"] = description
extended_descriptions = config_entry.options.get(
CONF_EXTENDED_DESCRIPTIONS,
config_entry.data.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS),
)
if extended_descriptions:
long_desc = await async_get_entity_description(
hass,
platform,
translation_key,
language,
"long_description",
)
if long_desc:
desc_attrs["long_description"] = long_desc
usage_tips = await async_get_entity_description(
hass,
platform,
translation_key,
language,
"usage_tips",
)
if usage_tips:
desc_attrs["usage_tips"] = usage_tips
# Add descriptions at appropriate position
if position == "end":
# Default: Add at the very end
attributes.update(desc_attrs)
elif position == "before_service_data":
# Special case: Insert before service data (same logic as sync version)
temp_attrs = dict(attributes)
attributes.clear()
for key, value in temp_attrs.items():
if key not in ("timestamp", "error"):
continue
attributes[key] = value
attributes.update(desc_attrs)
for key, value in temp_attrs.items():
if key in ("timestamp", "error"):
continue
attributes[key] = value