hass.tibber_prices/custom_components/tibber_prices/sensor/definitions.py
Julian Pawlowski 75da094c81
Some checks are pending
Auto-Tag on Version Bump / Check and create version tag (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
refactor(day_patterns): rename double valley/peak to double dip/duck curve
Updated pattern names for clarity and consistency in the codebase. The changes include renaming constants and updating related logic to reflect the new terminology.

Impact: Improved readability and understanding of day pattern classifications for developers.
2026-04-17 14:37:17 +00:00

1382 lines
57 KiB
Python

"""
Sensor entity definitions for Tibber Prices.
This module contains all SensorEntityDescription definitions organized by
calculation method. Sensor definitions are declarative and independent of
the implementation logic.
Organization by calculation pattern:
1. Interval-based: Time offset from current interval
2. Rolling hour: 5-interval aggregation windows
3. Daily statistics: Calendar day min/max/avg
4. 24h windows: Trailing/leading statistics
5. Future forecast: N-hour windows from next interval
6. Volatility: Price variation analysis
7. Best/Peak Price timing: Period-based time tracking
8. Diagnostic: System metadata
"""
from __future__ import annotations
from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription, SensorStateClass
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfArea, UnitOfElectricCurrent, UnitOfEnergy, UnitOfTime
# ============================================================================
# SENSOR DEFINITIONS - Grouped by calculation method
# ============================================================================
#
# Sensors are organized by HOW they calculate values, not WHAT they display.
# This groups sensors that share common logic and enables code reuse through
# unified handler methods.
#
# Calculation patterns:
# 1. Interval-based: Use time offset from current interval
# 2. Rolling hour: Aggregate 5-interval window (2 before + center + 2 after)
# 3. Daily statistics: Min/max/avg within calendar day boundaries
# 4. 24h windows: Trailing/leading from current interval
# 5. Future forecast: N-hour windows starting from next interval
# 6. Volatility: Statistical analysis of price variation
# 7. Best/Peak Price timing: Period-based time tracking (requires minute updates)
# 8. Diagnostic: System information and metadata
# ============================================================================
# ----------------------------------------------------------------------------
# 1. INTERVAL-BASED SENSORS (offset: -1, 0, +1 from current interval)
# ----------------------------------------------------------------------------
# All use find_price_data_for_interval() with time offset
# Shared handler: _get_interval_value(interval_offset, value_type)
INTERVAL_PRICE_SENSORS = (
SensorEntityDescription(
key="current_interval_price",
translation_key="current_interval_price",
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on price level
device_class=SensorDeviceClass.MONETARY,
state_class=SensorStateClass.TOTAL, # MONETARY requires TOTAL or None
suggested_display_precision=2,
),
SensorEntityDescription(
key="current_interval_price_base",
translation_key="current_interval_price_base",
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on price level
device_class=SensorDeviceClass.MONETARY,
state_class=SensorStateClass.TOTAL, # MONETARY requires TOTAL or None for Energy Dashboard
suggested_display_precision=4, # More precision for base currency (e.g., 0.2534 EUR/kWh)
),
SensorEntityDescription(
key="next_interval_price",
translation_key="next_interval_price",
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on price level
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future value: historical chart not useful
suggested_display_precision=2,
),
SensorEntityDescription(
key="previous_interval_price",
translation_key="previous_interval_price",
icon="mdi:cash-refund", # Static: arrow back indicates "past"
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Past snapshot: historical chart not useful
entity_registry_enabled_default=False,
suggested_display_precision=2,
),
)
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_LEVEL_* constants in const.py!
INTERVAL_LEVEL_SENSORS = (
SensorEntityDescription(
key="current_interval_price_level",
translation_key="current_interval_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="next_interval_price_level",
translation_key="next_interval_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="previous_interval_price_level",
translation_key="previous_interval_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on level value
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
)
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_RATING_* constants in const.py!
INTERVAL_RATING_SENSORS = (
SensorEntityDescription(
key="current_interval_price_rating",
translation_key="current_interval_price_rating",
icon="mdi:thumbs-up-down", # Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on rating value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False, # Level is more commonly used
),
SensorEntityDescription(
key="next_interval_price_rating",
translation_key="next_interval_price_rating",
icon="mdi:thumbs-up-down", # Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on rating value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False, # Level is more commonly used
),
SensorEntityDescription(
key="previous_interval_price_rating",
translation_key="previous_interval_price_rating",
icon="mdi:thumbs-up-down", # Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on rating value
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
),
)
# ----------------------------------------------------------------------------
# 2. ROLLING HOUR SENSORS (5-interval window: 2 before + center + 2 after)
# ----------------------------------------------------------------------------
# All aggregate data from rolling 5-interval window around a specific hour
# Shared handler: _get_rolling_hour_value(hour_offset, value_type)
ROLLING_HOUR_PRICE_SENSORS = (
SensorEntityDescription(
key="current_hour_average_price",
translation_key="current_hour_average_price",
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on aggregated price level
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling derived value: historical chart not useful
suggested_display_precision=2,
),
SensorEntityDescription(
key="next_hour_average_price",
translation_key="next_hour_average_price",
icon="mdi:cash-fast", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on aggregated price level
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future derived value: historical chart not useful
suggested_display_precision=2,
),
)
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_LEVEL_* constants in const.py!
ROLLING_HOUR_LEVEL_SENSORS = (
SensorEntityDescription(
key="current_hour_price_level",
translation_key="current_hour_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on aggregated level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="next_hour_price_level",
translation_key="next_hour_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on aggregated level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
)
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_RATING_* constants in const.py!
ROLLING_HOUR_RATING_SENSORS = (
SensorEntityDescription(
key="current_hour_price_rating",
translation_key="current_hour_price_rating",
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on aggregated rating value
icon="mdi:thumbs-up-down",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False, # Level is more commonly used
),
SensorEntityDescription(
key="next_hour_price_rating",
translation_key="next_hour_price_rating",
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on aggregated rating value
icon="mdi:thumbs-up-down",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False, # Level is more commonly used
),
)
# ----------------------------------------------------------------------------
# 3. DAILY STATISTICS SENSORS (min/max/avg for calendar day boundaries)
# ----------------------------------------------------------------------------
# Calculate statistics for specific calendar days (today/tomorrow)
DAILY_STAT_SENSORS = (
SensorEntityDescription(
key="lowest_price_today",
translation_key="lowest_price_today",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Daily snapshot: stays constant most of the day
suggested_display_precision=2,
),
SensorEntityDescription(
key="highest_price_today",
translation_key="highest_price_today",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Daily snapshot: stays constant most of the day
suggested_display_precision=2,
),
SensorEntityDescription(
key="average_price_today",
translation_key="average_price_today",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=SensorStateClass.TOTAL, # Keep TOTAL: useful to track daily avg over weeks/months
suggested_display_precision=2,
),
SensorEntityDescription(
key="lowest_price_tomorrow",
translation_key="lowest_price_tomorrow",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future data: historical chart not useful
suggested_display_precision=2,
),
SensorEntityDescription(
key="highest_price_tomorrow",
translation_key="highest_price_tomorrow",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future data: historical chart not useful
suggested_display_precision=2,
),
SensorEntityDescription(
key="average_price_tomorrow",
translation_key="average_price_tomorrow",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future data: historical chart not useful
suggested_display_precision=2,
),
)
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_LEVEL_* constants in const.py!
DAILY_LEVEL_SENSORS = (
SensorEntityDescription(
key="yesterday_price_level",
translation_key="yesterday_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on daily level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="today_price_level",
translation_key="today_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on daily level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="tomorrow_price_level",
translation_key="tomorrow_price_level",
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on daily level value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
)
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_RATING_* constants in const.py!
DAILY_RATING_SENSORS = (
SensorEntityDescription(
key="yesterday_price_rating",
translation_key="yesterday_price_rating",
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on daily rating value
icon="mdi:thumbs-up-down",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="today_price_rating",
translation_key="today_price_rating",
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on daily rating value
icon="mdi:thumbs-up-down",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False, # Level is more commonly used
),
SensorEntityDescription(
key="tomorrow_price_rating",
translation_key="tomorrow_price_rating",
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on daily rating value
icon="mdi:thumbs-up-down",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "normal", "high"],
entity_registry_enabled_default=False, # Level is more commonly used
),
)
# ----------------------------------------------------------------------------
# 4. 24H WINDOW SENSORS (trailing/leading from current interval)
# ----------------------------------------------------------------------------
# Calculate statistics over sliding 24-hour windows
WINDOW_24H_SENSORS = (
SensorEntityDescription(
key="trailing_price_average",
translation_key="trailing_price_average",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
entity_registry_enabled_default=False,
suggested_display_precision=2,
),
SensorEntityDescription(
key="leading_price_average",
translation_key="leading_price_average",
icon="mdi:chart-line-variant",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
entity_registry_enabled_default=False, # Advanced use case
suggested_display_precision=2,
),
SensorEntityDescription(
key="trailing_price_min",
translation_key="trailing_price_min",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
entity_registry_enabled_default=False,
suggested_display_precision=2,
),
SensorEntityDescription(
key="trailing_price_max",
translation_key="trailing_price_max",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
entity_registry_enabled_default=False,
suggested_display_precision=2,
),
SensorEntityDescription(
key="leading_price_min",
translation_key="leading_price_min",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
entity_registry_enabled_default=False, # Advanced use case
suggested_display_precision=2,
),
SensorEntityDescription(
key="leading_price_max",
translation_key="leading_price_max",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
entity_registry_enabled_default=False, # Advanced use case
suggested_display_precision=2,
),
)
# ----------------------------------------------------------------------------
# 5. FUTURE FORECAST SENSORS (N-hour windows starting from next interval)
# ----------------------------------------------------------------------------
# Calculate averages and trends for upcoming time windows
FUTURE_MEAN_SENSORS = (
# Default enabled: 1h-5h
SensorEntityDescription(
key="next_avg_1h",
translation_key="next_avg_1h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_2h",
translation_key="next_avg_2h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_3h",
translation_key="next_avg_3h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_4h",
translation_key="next_avg_4h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_5h",
translation_key="next_avg_5h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=True,
),
# Disabled by default: 6h, 8h, 12h (advanced use cases)
SensorEntityDescription(
key="next_avg_6h",
translation_key="next_avg_6h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_avg_8h",
translation_key="next_avg_8h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_avg_12h",
translation_key="next_avg_12h",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
state_class=None, # Future forecast: historical chart not useful
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
)
FUTURE_TREND_SENSORS = (
# Current trend sensor (what is the trend right now, valid until next change?)
SensorEntityDescription(
key="current_price_trend",
translation_key="current_price_trend",
icon="mdi:trending-up", # Dynamic: trending-up/trending-down/trending-neutral based on current trend
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
# Next trend change sensor (when will trend change?)
SensorEntityDescription(
key="next_price_trend_change",
translation_key="next_price_trend_change",
icon="mdi:clock-alert", # Dynamic: trending-up/trending-down/trending-neutral based on direction
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None, # Timestamp: no statistics
entity_registry_enabled_default=True,
),
# Trend change countdown sensor (how long until trend changes?)
SensorEntityDescription(
key="next_price_trend_change_in",
translation_key="next_price_trend_change_in",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Countdown timer: no statistics
suggested_display_precision=2,
entity_registry_enabled_default=True,
),
# Price outlook forecast sensors (is the average of the next Xh cheaper/more expensive than now?)
# Default enabled: 1h-5h
SensorEntityDescription(
key="price_outlook_1h",
translation_key="price_outlook_1h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_outlook_2h",
translation_key="price_outlook_2h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_outlook_3h",
translation_key="price_outlook_3h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_outlook_4h",
translation_key="price_outlook_4h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_outlook_5h",
translation_key="price_outlook_5h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
# Disabled by default: 6h, 8h, 12h
SensorEntityDescription(
key="price_outlook_6h",
translation_key="price_outlook_6h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_outlook_8h",
translation_key="price_outlook_8h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_outlook_12h",
translation_key="price_outlook_12h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
)
# ----------------------------------------------------------------------------
# 5b. PRICE TRAJECTORY SENSORS (first-half vs second-half window comparison)
# ----------------------------------------------------------------------------
# These sensors reveal turning points: is the price rising or falling WITHIN
# the window? Complements price_outlook_Xh sensors.
#
# Example at a price minimum (12:00):
# - price_outlook_4h: "strongly_falling" (Ø next 4h is below current high)
# - price_trajectory_4h: "rising" (second half avg > first half avg)
# → Combined: act now, reversal is coming within the window.
#
# Coverage starts at 2h (minimum for meaningful first/second half split).
# Default enabled: 2h-5h
PRICE_TRAJECTORY_SENSORS = (
SensorEntityDescription(
key="price_trajectory_2h",
translation_key="price_trajectory_2h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trajectory_3h",
translation_key="price_trajectory_3h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trajectory_4h",
translation_key="price_trajectory_4h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trajectory_5h",
translation_key="price_trajectory_5h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=True,
),
# Disabled by default: 6h, 8h, 12h
SensorEntityDescription(
key="price_trajectory_6h",
translation_key="price_trajectory_6h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_trajectory_8h",
translation_key="price_trajectory_8h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_trajectory_12h",
translation_key="price_trajectory_12h",
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
entity_registry_enabled_default=False,
),
)
# ----------------------------------------------------------------------------
# 6. VOLATILITY SENSORS (coefficient of variation analysis)
# ----------------------------------------------------------------------------
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
# import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with VOLATILITY_* constants in const.py!
VOLATILITY_SENSORS = (
SensorEntityDescription(
key="today_volatility",
translation_key="today_volatility",
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "moderate", "high", "very_high"],
),
SensorEntityDescription(
key="tomorrow_volatility",
translation_key="tomorrow_volatility",
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "moderate", "high", "very_high"],
entity_registry_enabled_default=False, # Today's volatility is usually sufficient
),
SensorEntityDescription(
key="next_24h_volatility",
translation_key="next_24h_volatility",
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "moderate", "high", "very_high"],
entity_registry_enabled_default=False, # Advanced use case
),
SensorEntityDescription(
key="today_tomorrow_volatility",
translation_key="today_tomorrow_volatility",
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
state_class=None, # Enum values: no statistics
options=["low", "moderate", "high", "very_high"],
entity_registry_enabled_default=False, # Advanced use case
),
)
# ----------------------------------------------------------------------------
# 6b. PRICE PERCENTILE RANK SENSORS
# ----------------------------------------------------------------------------
# These sensors show where the current price ranks within a reference period.
# The state (0-100%) answers: "What percentage of reference prices are cheaper
# than the current price?"
#
# 0% = current price is the cheapest in the reference period
# 50% = half the prices are cheaper (current price at median level)
# ~99% = almost everything is cheaper (current price near the maximum)
#
# Reference periods:
# - today: 96 intervals of today (local calendar day)
# - tomorrow: 96 intervals of tomorrow (once data is available)
# - today_tomorrow: 192 combined intervals when tomorrow is available
#
# Use case: "Is now the right time to run a large appliance?"
# - current_interval_price_rank_today < 25 → bottom quartile, great time to use energy
# - current_interval_price_rank_today > 75 → top quartile, consider delaying consumption
PERCENTILE_RANK_SENSORS = (
# ----------------------------------------------------------------
# Current interval rank sensors
# ----------------------------------------------------------------
SensorEntityDescription(
key="current_interval_price_rank_today",
translation_key="current_interval_price_rank_today",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None, # Position metric: no statistics
suggested_display_precision=0,
),
SensorEntityDescription(
key="current_interval_price_rank_tomorrow",
translation_key="current_interval_price_rank_tomorrow",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None, # Position metric: no statistics
suggested_display_precision=0,
entity_registry_enabled_default=False, # Available once tomorrow's data arrives
),
SensorEntityDescription(
key="current_interval_price_rank_today_tomorrow",
translation_key="current_interval_price_rank_today_tomorrow",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None, # Position metric: no statistics
suggested_display_precision=0,
entity_registry_enabled_default=False, # Advanced overview use case
),
# ----------------------------------------------------------------
# Next interval rank sensors
# ----------------------------------------------------------------
SensorEntityDescription(
key="next_interval_price_rank_today",
translation_key="next_interval_price_rank_today",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_interval_price_rank_today_tomorrow",
translation_key="next_interval_price_rank_today_tomorrow",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
# ----------------------------------------------------------------
# Previous interval rank sensors
# ----------------------------------------------------------------
SensorEntityDescription(
key="previous_interval_price_rank_today",
translation_key="previous_interval_price_rank_today",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="previous_interval_price_rank_today_tomorrow",
translation_key="previous_interval_price_rank_today_tomorrow",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
# ----------------------------------------------------------------
# Rolling-hour rank sensors (rank of 1h rolling average)
# ----------------------------------------------------------------
SensorEntityDescription(
key="current_hour_price_rank_today",
translation_key="current_hour_price_rank_today",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="current_hour_price_rank_today_tomorrow",
translation_key="current_hour_price_rank_today_tomorrow",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_hour_price_rank_today",
translation_key="next_hour_price_rank_today",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_hour_price_rank_today_tomorrow",
translation_key="next_hour_price_rank_today_tomorrow",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None,
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
)
# ----------------------------------------------------------------------------
# 7. BEST/PEAK PRICE TIMING SENSORS (period-based time tracking)
# ----------------------------------------------------------------------------
# These sensors track time relative to best_price/peak_price binary sensor periods.
# They require minute-by-minute updates via async_track_time_interval.
#
# When period is active (binary_sensor ON):
# - end_time: Timestamp when current period ends
# - remaining_minutes: Minutes until period ends
# - progress: Percentage of period completed (0-100%)
#
# When period is inactive (binary_sensor OFF):
# - next_start_time: Timestamp when next period starts
# - next_in_minutes: Minutes until next period starts
#
# All return None/Unknown when no period is active/scheduled.
BEST_PRICE_TIMING_SENSORS = (
SensorEntityDescription(
key="best_price_end_time",
translation_key="best_price_end_time",
icon="mdi:clock-end",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None, # Timestamps: no statistics
),
SensorEntityDescription(
key="best_price_period_duration",
translation_key="best_price_period_duration",
icon="mdi:timer",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Duration not needed in long-term statistics
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="best_price_remaining_minutes",
translation_key="best_price_remaining_minutes",
icon="mdi:timer-sand",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Countdown timers excluded from statistics
suggested_display_precision=2,
),
SensorEntityDescription(
key="best_price_progress",
translation_key="best_price_progress",
icon="mdi:percent", # Dynamic: mdi:percent-0 to mdi:percent-100
native_unit_of_measurement=PERCENTAGE,
state_class=None, # Progress counter: no statistics
suggested_display_precision=0,
),
SensorEntityDescription(
key="best_price_next_start_time",
translation_key="best_price_next_start_time",
icon="mdi:clock-start",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None, # Timestamps: no statistics
),
SensorEntityDescription(
key="best_price_next_in_minutes",
translation_key="best_price_next_in_minutes",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Next-start timers excluded from statistics
suggested_display_precision=2,
),
)
PEAK_PRICE_TIMING_SENSORS = (
SensorEntityDescription(
key="peak_price_end_time",
translation_key="peak_price_end_time",
icon="mdi:clock-end",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None, # Timestamps: no statistics
),
SensorEntityDescription(
key="peak_price_period_duration",
translation_key="peak_price_period_duration",
icon="mdi:timer",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Duration not needed in long-term statistics
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="peak_price_remaining_minutes",
translation_key="peak_price_remaining_minutes",
icon="mdi:timer-sand",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Countdown timers excluded from statistics
suggested_display_precision=2,
),
SensorEntityDescription(
key="peak_price_progress",
translation_key="peak_price_progress",
icon="mdi:percent", # Dynamic: mdi:percent-0 to mdi:percent-100
native_unit_of_measurement=PERCENTAGE,
state_class=None, # Progress counter: no statistics
suggested_display_precision=0,
),
SensorEntityDescription(
key="peak_price_next_start_time",
translation_key="peak_price_next_start_time",
icon="mdi:clock-start",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None, # Timestamps: no statistics
),
SensorEntityDescription(
key="peak_price_next_in_minutes",
translation_key="peak_price_next_in_minutes",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Next-start timers excluded from statistics
suggested_display_precision=2,
),
)
# 8. DAY PATTERN SENSORS (price shape classification per calendar day)
# ----------------------------------------------------------------------------
DAY_PATTERN_SENSORS = (
SensorEntityDescription(
key="day_pattern_yesterday",
translation_key="day_pattern_yesterday",
icon="mdi:chart-bell-curve",
device_class=SensorDeviceClass.ENUM,
options=[
"valley",
"peak",
"double_dip",
"duck_curve",
"flat",
"rising",
"falling",
"mixed",
],
state_class=None,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="day_pattern_today",
translation_key="day_pattern_today",
icon="mdi:chart-bell-curve",
device_class=SensorDeviceClass.ENUM,
options=[
"valley",
"peak",
"double_dip",
"duck_curve",
"flat",
"rising",
"falling",
"mixed",
],
state_class=None,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="day_pattern_tomorrow",
translation_key="day_pattern_tomorrow",
icon="mdi:chart-bell-curve",
device_class=SensorDeviceClass.ENUM,
options=[
"valley",
"peak",
"double_dip",
"duck_curve",
"flat",
"rising",
"falling",
"mixed",
],
state_class=None,
entity_registry_enabled_default=False,
),
)
# 8b. PRICE PHASE SENSORS (current/next intra-day price phase classification)
# ----------------------------------------------------------------------------
PRICE_PHASE_SENSORS = (
SensorEntityDescription(
key="current_price_phase",
translation_key="current_price_phase",
icon="mdi:chart-timeline-variant",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "flat"],
state_class=None,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_price_phase",
translation_key="next_price_phase",
icon="mdi:chart-timeline-variant",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "flat"],
state_class=None,
entity_registry_enabled_default=True,
),
)
# 8c. PRICE PHASE TIMING SENSORS (current phase duration/progress + next-phase-by-type)
# ----------------------------------------------------------------------------
#
# When current phase is active:
# - end_time: Timestamp when current phase ends
# - remaining_minutes: Minutes until current phase ends
# - duration: Total length of current phase (disabled by default)
# - progress: Percentage of current phase completed (disabled by default)
#
# Next occurrence of a specific phase type (after current segment, today or tomorrow):
# - next_*_phase_start_time: Timestamp when next rising/falling/flat phase starts
# - next_*_phase_in_minutes: Minutes until that phase starts
#
# All return None/Unknown when no segment data is available.
PRICE_PHASE_TIMING_SENSORS = (
SensorEntityDescription(
key="current_price_phase_end_time",
translation_key="current_price_phase_end_time",
icon="mdi:clock-end",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None, # Timestamps: no statistics
),
SensorEntityDescription(
key="current_price_phase_remaining_minutes",
translation_key="current_price_phase_remaining_minutes",
icon="mdi:timer-sand",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Countdown timers excluded from statistics
suggested_display_precision=2,
),
SensorEntityDescription(
key="current_price_phase_duration",
translation_key="current_price_phase_duration",
icon="mdi:timer",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None, # Duration not needed in long-term statistics
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="current_price_phase_progress",
translation_key="current_price_phase_progress",
icon="mdi:percent",
native_unit_of_measurement=PERCENTAGE,
state_class=None, # Progress counter: no statistics
suggested_display_precision=0,
entity_registry_enabled_default=False,
),
# Next occurrence of each phase type (across remaining today + tomorrow)
SensorEntityDescription(
key="next_rising_phase_start_time",
translation_key="next_rising_phase_start_time",
icon="mdi:trending-up",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_falling_phase_start_time",
translation_key="next_falling_phase_start_time",
icon="mdi:trending-down",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_flat_phase_start_time",
translation_key="next_flat_phase_start_time",
icon="mdi:trending-neutral",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_rising_phase_in_minutes",
translation_key="next_rising_phase_in_minutes",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None,
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_falling_phase_in_minutes",
translation_key="next_falling_phase_in_minutes",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None,
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_flat_phase_in_minutes",
translation_key="next_flat_phase_in_minutes",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.MINUTES,
suggested_unit_of_measurement=UnitOfTime.HOURS,
state_class=None,
suggested_display_precision=2,
entity_registry_enabled_default=False,
),
)
# 9. DIAGNOSTIC SENSORS (data availability and metadata)
# ----------------------------------------------------------------------------
DIAGNOSTIC_SENSORS = (
SensorEntityDescription(
key="data_lifecycle_status",
translation_key="data_lifecycle_status",
icon="mdi:database-sync",
device_class=SensorDeviceClass.ENUM,
options=["cached", "fresh", "refreshing", "searching_tomorrow", "turnover_pending", "error"],
state_class=None, # Status value: no statistics
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=True, # Critical for debugging
),
# Home metadata from user data
SensorEntityDescription(
key="home_type",
translation_key="home_type",
icon="mdi:home-variant",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=["apartment", "rowhouse", "house", "cottage"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="home_size",
translation_key="home_size",
icon="mdi:ruler-square",
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
state_class=None, # Static user metadata: no statistics useful
entity_registry_enabled_default=False,
suggested_display_precision=0,
),
SensorEntityDescription(
key="main_fuse_size",
translation_key="main_fuse_size",
icon="mdi:fuse",
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
device_class=SensorDeviceClass.CURRENT,
state_class=None, # Static user metadata: no statistics useful
entity_registry_enabled_default=False,
suggested_display_precision=0,
),
SensorEntityDescription(
key="number_of_residents",
translation_key="number_of_residents",
icon="mdi:account-group",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=None, # Static user metadata: no statistics useful
entity_registry_enabled_default=False,
suggested_display_precision=0,
),
SensorEntityDescription(
key="primary_heating_source",
translation_key="primary_heating_source",
icon="mdi:heating-coil",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[
"air2air_heatpump",
"air2water_heatpump",
"boiler",
"central_heating",
"district_heating",
"district_heating",
"district",
"electric_boiler",
"electricity",
"floor",
"gas",
"ground_heatpump",
"ground",
"oil",
"other",
"waste",
],
entity_registry_enabled_default=False,
),
# Metering point data
SensorEntityDescription(
key="grid_company",
translation_key="grid_company",
icon="mdi:transmission-tower",
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="grid_area_code",
translation_key="grid_area_code",
icon="mdi:map-marker",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_area_code",
translation_key="price_area_code",
icon="mdi:currency-eur",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="consumption_ean",
translation_key="consumption_ean",
icon="mdi:barcode",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="production_ean",
translation_key="production_ean",
icon="mdi:solar-power",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="energy_tax_type",
translation_key="energy_tax_type",
icon="mdi:cash",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="vat_type",
translation_key="vat_type",
icon="mdi:percent",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="estimated_annual_consumption",
translation_key="estimated_annual_consumption",
icon="mdi:lightning-bolt",
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=None, # Static Tibber estimate, not an actual accumulating counter
suggested_display_precision=0,
),
# Subscription data
SensorEntityDescription(
key="subscription_status",
translation_key="subscription_status",
icon="mdi:file-document-check",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=["running", "ended", "pending", "unknown"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="chart_data_export",
translation_key="chart_data_export",
icon="mdi:database-export",
device_class=SensorDeviceClass.ENUM,
options=["pending", "ready", "error"],
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, # Opt-in
),
SensorEntityDescription(
key="chart_metadata",
translation_key="chart_metadata",
icon="mdi:chart-box-outline",
device_class=SensorDeviceClass.ENUM,
options=["pending", "ready", "error"],
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=True, # Critical for chart features
),
)
# ----------------------------------------------------------------------------
# COMBINED SENSOR DEFINITIONS
# ----------------------------------------------------------------------------
ENTITY_DESCRIPTIONS = (
*INTERVAL_PRICE_SENSORS,
*INTERVAL_LEVEL_SENSORS,
*INTERVAL_RATING_SENSORS,
*ROLLING_HOUR_PRICE_SENSORS,
*ROLLING_HOUR_LEVEL_SENSORS,
*ROLLING_HOUR_RATING_SENSORS,
*DAILY_STAT_SENSORS,
*DAILY_LEVEL_SENSORS,
*DAILY_RATING_SENSORS,
*WINDOW_24H_SENSORS,
*FUTURE_MEAN_SENSORS,
*FUTURE_TREND_SENSORS,
*PRICE_TRAJECTORY_SENSORS,
*VOLATILITY_SENSORS,
*PERCENTILE_RANK_SENSORS,
*BEST_PRICE_TIMING_SENSORS,
*PEAK_PRICE_TIMING_SENSORS,
*DAY_PATTERN_SENSORS,
*PRICE_PHASE_SENSORS,
*PRICE_PHASE_TIMING_SENSORS,
*DIAGNOSTIC_SENSORS,
)