fix(coordinator): add _is_fetching flag and fix tomorrow data detection

Implement _is_fetching flag to show "refreshing" status during API calls,
and fix needs_tomorrow_data() to recognize single-home cache format.

Changes:
- Set _is_fetching flag before API call, reset after completion (core.py)
- Fix needs_tomorrow_data() to check for "price_info" key instead of "homes"
- Remove redundant "homes" check in should_update_price_data()
- Improve logging: change debug to info for tomorrow data checks

Lifecycle status now correctly transitions after 13:00 when tomorrow data
is missing: cached → searching_tomorrow → refreshing → fresh → cached

Impact: Users will see accurate lifecycle status and tomorrow's electricity
prices will automatically load when available after 13:00, fixing issue
since v0.14.0 where prices weren't fetched without manual HA restart.
This commit is contained in:
Julian Pawlowski 2025-12-02 19:00:20 +00:00
parent d6ae931918
commit 3977d5e329
3 changed files with 34 additions and 21 deletions

View file

@ -587,12 +587,28 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# Track last_price_update timestamp before fetch to detect if data actually changed # Track last_price_update timestamp before fetch to detect if data actually changed
old_price_update = self._last_price_update old_price_update = self._last_price_update
# CRITICAL: Check if we need to fetch data BEFORE starting the fetch
# This allows the lifecycle sensor to show "searching_tomorrow" status
# when we're actively looking for tomorrow's data after 13:00
should_update = self._data_fetcher.should_update_price_data(current_time)
# Set _is_fetching flag if we're about to fetch data
# This makes the lifecycle sensor show "refreshing" status during the API call
if should_update:
self._is_fetching = True
# Immediately notify lifecycle sensor about state change
# This ensures "refreshing" or "searching_tomorrow" appears DURING the fetch
self.async_update_listeners()
result = await self._data_fetcher.handle_main_entry_update( result = await self._data_fetcher.handle_main_entry_update(
current_time, current_time,
self._home_id, self._home_id,
self._transform_data, self._transform_data,
) )
# CRITICAL: Reset fetching flag AFTER data fetch completes
self._is_fetching = False
# CRITICAL: Sync cached data after API call # CRITICAL: Sync cached data after API call
# handle_main_entry_update() updates data_fetcher's cache, we need to sync: # handle_main_entry_update() updates data_fetcher's cache, we need to sync:
# 1. cached_user_data (for new integrations, may be fetched via update_user_data_if_needed()) # 1. cached_user_data (for new integrations, may be fetched via update_user_data_if_needed())

View file

@ -149,14 +149,9 @@ class TibberPricesDataFetcher:
# Check if after 13:00 and tomorrow data is missing or invalid # Check if after 13:00 and tomorrow data is missing or invalid
now_local = self.time.as_local(current_time) now_local = self.time.as_local(current_time)
if ( if now_local.hour >= TOMORROW_DATA_CHECK_HOUR and self._cached_price_data and self.needs_tomorrow_data():
now_local.hour >= TOMORROW_DATA_CHECK_HOUR
and self._cached_price_data
and "homes" in self._cached_price_data
and self.needs_tomorrow_data()
):
self._log( self._log(
"debug", "info",
"API update needed: After %s:00 and tomorrow's data missing/invalid", "API update needed: After %s:00 and tomorrow's data missing/invalid",
TOMORROW_DATA_CHECK_HOUR, TOMORROW_DATA_CHECK_HOUR,
) )
@ -165,6 +160,7 @@ class TibberPricesDataFetcher:
return "tomorrow_check" return "tomorrow_check"
# No update needed - cache is valid and complete # No update needed - cache is valid and complete
self._log("debug", "No API update needed: Cache is valid and complete")
return False return False
def needs_tomorrow_data(self) -> bool: def needs_tomorrow_data(self) -> bool:

View file

@ -109,32 +109,33 @@ def needs_tomorrow_data(
cached_price_data: dict[str, Any] | None, cached_price_data: dict[str, Any] | None,
) -> bool: ) -> bool:
""" """
Check if tomorrow data is missing or invalid in flat interval list. Check if tomorrow data is missing or invalid in cached price data.
Expects single-home cache format: {"price_info": [...], "home_id": "xxx"}
Old multi-home format (v0.14.0) is automatically invalidated by is_cache_valid()
in cache.py, so we only need to handle the current format here.
Uses get_intervals_for_day_offsets() to automatically determine tomorrow Uses get_intervals_for_day_offsets() to automatically determine tomorrow
based on current date. No explicit date parameter needed. based on current date. No explicit date parameter needed.
Args: Args:
cached_price_data: Cached price data with homes structure cached_price_data: Cached price data in single-home structure
Returns: Returns:
True if any home is missing tomorrow's data, False otherwise True if tomorrow's data is missing, False otherwise
""" """
if not cached_price_data or "homes" not in cached_price_data: if not cached_price_data or "price_info" not in cached_price_data:
return False return False
# Check each home's intervals for tomorrow's date # Single-home format: {"price_info": [...], "home_id": "xxx"}
for home_data in cached_price_data["homes"].values():
# Use helper to get tomorrow's intervals (offset +1 from current date) # Use helper to get tomorrow's intervals (offset +1 from current date)
coordinator_data = {"priceInfo": home_data.get("price_info", [])} coordinator_data = {"priceInfo": cached_price_data.get("price_info", [])}
tomorrow_intervals = get_intervals_for_day_offsets(coordinator_data, [1]) tomorrow_intervals = get_intervals_for_day_offsets(coordinator_data, [1])
# If no intervals for tomorrow found, we need tomorrow data # If no intervals for tomorrow found, we need tomorrow data
if not tomorrow_intervals: return len(tomorrow_intervals) == 0
return True
return False
def parse_all_timestamps(price_data: dict[str, Any], *, time: TibberPricesTimeService) -> dict[str, Any]: def parse_all_timestamps(price_data: dict[str, Any], *, time: TibberPricesTimeService) -> dict[str, Any]: