From 8f05f8cac71467dcd14d038352d7bd2731048e34 Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Mon, 6 Apr 2026 14:35:33 +0000 Subject: [PATCH] perf(interval_pool): hoist fetch_groups and precompute period criteria - Move UTC import from inline (inside _has_real_gaps_in_range) to module-level in manager.py - Hoist get_fetch_groups() out of while loop in _get_cached_intervals: eliminates ~384 function calls per invocation - Pre-compute criteria_by_day dict in build_periods before the for-loop: eliminates ~381 redundant NamedTuple constructions per call; only ref_price/avg_price vary by day (max 3 entries), flex/min_distance/ reverse_sort are constant throughout Impact: Reduces unnecessary object creation during the hot paths called every 15 minutes and during all relaxation phases. --- .../period_handlers/period_building.py | 21 ++++++++++++------- .../tibber_prices/interval_pool/manager.py | 7 ++----- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/custom_components/tibber_prices/coordinator/period_handlers/period_building.py b/custom_components/tibber_prices/coordinator/period_handlers/period_building.py index 64c476a..0734e76 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/period_building.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/period_building.py @@ -112,6 +112,19 @@ def build_periods( # noqa: PLR0913, PLR0915, PLR0912 - Complex period building intervals_filtered_by_flex = 0 intervals_filtered_by_min_distance = 0 + # Pre-compute criteria per day (flex/min_distance/reverse_sort are constant throughout; + # only ref_price and avg_price vary by day — max 3 entries: yesterday/today/tomorrow) + criteria_by_day: dict[date, TibberPricesIntervalCriteria] = { + day: TibberPricesIntervalCriteria( + ref_price=ref_prices[day], + avg_price=avg_prices[day], + flex=flex, + min_distance_from_avg=min_distance_from_avg, + reverse_sort=reverse_sort, + ) + for day in ref_prices + } + for price_data in all_prices: starts_at = time.get_interval_time(price_data) if starts_at is None: @@ -133,13 +146,7 @@ def build_periods( # noqa: PLR0913, PLR0915, PLR0912 - Complex period building ref_date = date_key # Check flex and minimum distance criteria (using smoothed price and interval's own day reference) - criteria = TibberPricesIntervalCriteria( - ref_price=ref_prices[ref_date], - avg_price=avg_prices[ref_date], - flex=flex, - min_distance_from_avg=min_distance_from_avg, - reverse_sort=reverse_sort, - ) + criteria = criteria_by_day[ref_date] in_flex, meets_min_distance = check_interval_criteria(price_for_criteria, criteria) # Track why intervals are filtered diff --git a/custom_components/tibber_prices/interval_pool/manager.py b/custom_components/tibber_prices/interval_pool/manager.py index 0dc525a..08dfaad 100644 --- a/custom_components/tibber_prices/interval_pool/manager.py +++ b/custom_components/tibber_prices/interval_pool/manager.py @@ -5,7 +5,7 @@ from __future__ import annotations import asyncio import contextlib import logging -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import TYPE_CHECKING, Any from zoneinfo import ZoneInfo @@ -459,8 +459,6 @@ class TibberPricesIntervalPool: True if a real gap exists, False if the range is fully covered. """ - from datetime import UTC # noqa: PLC0415 - UTC constant needed here only - cached_intervals = self._get_cached_intervals(start_iso, end_iso) if not cached_intervals: @@ -582,14 +580,13 @@ class TibberPricesIntervalPool: resolution_change_naive = datetime(2025, 10, 1) # noqa: DTZ001 interval_minutes = INTERVAL_QUARTER_HOURLY if current_naive >= resolution_change_naive else INTERVAL_HOURLY + fetch_groups = self._cache.get_fetch_groups() while current_naive < end_naive: # Check if this timestamp exists in index (O(1) lookup) current_dt_key = current_naive.isoformat()[:19] location = self._index.get(current_dt_key) if location is not None: - # Get interval from fetch group - fetch_groups = self._cache.get_fetch_groups() fetch_group = fetch_groups[location["fetch_group_index"]] interval = fetch_group["intervals"][location["interval_index"]] # CRITICAL: Return shallow copy to prevent external mutations