mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-05-28 18:43:40 +00:00
_resolve_time_with_day_offset() was calling dt_util.now() internally instead of using the injected now parameter. This caused incorrect date calculations in tests and any caller that passes a specific reference time. Also add missing price_rank_* sensor keys to TIME_SENSITIVE_ENTITY_KEYS in coordinator/constants.py so quarter-hour refresh is registered for all 11 price rank sensors (current/next/previous interval and hour variants). Rename dt as dt_utils → dt as dt_util (ICN001) across 11 files to follow the project-wide import alias convention. Apply ruff auto-fixes for import ordering and collapsing single-item imports throughout the codebase. Released-Bug: no
79 lines
3.2 KiB
Python
79 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
|