mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 21:33:39 +00:00
- Remove pool stats/fetch-age from lifecycle sensor to avoid stale data under state-change filtering; add `next_api_poll` for transparency. - Clean lifecycle calculator by dropping unused helpers/constants and delete the obsolete cache age test. - Clarify lifecycle state is diagnostics-only in coordinator comments, keep state-change filtering in timer test, and retain quarter-hour precision notes in constants. - Keep sensor core aligned with lifecycle state filtering. Impact: Lifecycle sensor now exposes only state-relevant fields without recorder noise, next API poll is visible, and dead code/tests tied to removed attributes are gone.
83 lines
3.2 KiB
Python
83 lines
3.2 KiB
Python
"""
|
|
Attribute builders for lifecycle diagnostic sensor.
|
|
|
|
This sensor uses event-based updates with state-change filtering to minimize
|
|
recorder entries. Only attributes that are relevant to the lifecycle STATE
|
|
are included here - attributes that change independently of state belong
|
|
in a separate sensor or diagnostics.
|
|
|
|
Included attributes (update only on state change):
|
|
- tomorrow_available: Whether tomorrow's price data is available
|
|
- next_api_poll: When the next API poll will occur (builds user trust)
|
|
- updates_today: Number of API calls made today
|
|
- last_turnover: When the last midnight turnover occurred
|
|
- last_error: Details of the last error (if any)
|
|
|
|
Pool statistics (sensor_intervals_count, cache_fill_percent, etc.) are
|
|
intentionally NOT included here because they change independently of
|
|
the lifecycle state. With state-change filtering, these would become
|
|
stale. Pool statistics are available via diagnostics or could be
|
|
exposed as a separate sensor if needed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
from custom_components.tibber_prices.coordinator.core import (
|
|
TibberPricesDataUpdateCoordinator,
|
|
)
|
|
from custom_components.tibber_prices.sensor.calculators.lifecycle import (
|
|
TibberPricesLifecycleCalculator,
|
|
)
|
|
|
|
|
|
def build_lifecycle_attributes(
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
lifecycle_calculator: TibberPricesLifecycleCalculator,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Build attributes for data_lifecycle_status sensor.
|
|
|
|
Event-based updates with state-change filtering - attributes only update
|
|
when the lifecycle STATE changes (fresh→cached, cached→turnover_pending, etc.).
|
|
|
|
Only includes attributes that are directly relevant to the lifecycle state.
|
|
Pool statistics are intentionally excluded to avoid stale data.
|
|
|
|
Returns:
|
|
Dict with lifecycle attributes
|
|
|
|
"""
|
|
attributes: dict[str, Any] = {}
|
|
|
|
# === Tomorrow Data Status ===
|
|
# Critical for understanding lifecycle state transitions
|
|
attributes["tomorrow_available"] = lifecycle_calculator.has_tomorrow_data()
|
|
|
|
# === Next API Poll Time ===
|
|
# Builds user trust: shows when the integration will check for tomorrow data
|
|
# - Before 13:00: Shows today 13:00 (when tomorrow-search begins)
|
|
# - After 13:00 without tomorrow data: Shows next Timer #1 execution (active polling)
|
|
# - After 13:00 with tomorrow data: Shows tomorrow 13:00 (predictive)
|
|
next_poll = lifecycle_calculator.get_next_api_poll_time()
|
|
if next_poll:
|
|
attributes["next_api_poll"] = next_poll.isoformat()
|
|
|
|
# === Update Statistics ===
|
|
# Shows API activity - resets at midnight with turnover
|
|
api_calls = lifecycle_calculator.get_api_calls_today()
|
|
attributes["updates_today"] = api_calls
|
|
|
|
# === Midnight Turnover Info ===
|
|
# When was the last successful data rotation
|
|
if coordinator._midnight_handler.last_turnover_time: # noqa: SLF001
|
|
attributes["last_turnover"] = coordinator._midnight_handler.last_turnover_time.isoformat() # noqa: SLF001
|
|
|
|
# === Error Status ===
|
|
# Present only when there's an active error
|
|
if coordinator.last_exception:
|
|
attributes["last_error"] = str(coordinator.last_exception)
|
|
|
|
return attributes
|