mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-04-07 08:03:40 +00:00
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.
This commit is contained in:
parent
636bd7a797
commit
8f05f8cac7
2 changed files with 16 additions and 12 deletions
|
|
@ -112,6 +112,19 @@ def build_periods( # noqa: PLR0913, PLR0915, PLR0912 - Complex period building
|
||||||
intervals_filtered_by_flex = 0
|
intervals_filtered_by_flex = 0
|
||||||
intervals_filtered_by_min_distance = 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:
|
for price_data in all_prices:
|
||||||
starts_at = time.get_interval_time(price_data)
|
starts_at = time.get_interval_time(price_data)
|
||||||
if starts_at is None:
|
if starts_at is None:
|
||||||
|
|
@ -133,13 +146,7 @@ def build_periods( # noqa: PLR0913, PLR0915, PLR0912 - Complex period building
|
||||||
ref_date = date_key
|
ref_date = date_key
|
||||||
|
|
||||||
# Check flex and minimum distance criteria (using smoothed price and interval's own day reference)
|
# Check flex and minimum distance criteria (using smoothed price and interval's own day reference)
|
||||||
criteria = TibberPricesIntervalCriteria(
|
criteria = criteria_by_day[ref_date]
|
||||||
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,
|
|
||||||
)
|
|
||||||
in_flex, meets_min_distance = check_interval_criteria(price_for_criteria, criteria)
|
in_flex, meets_min_distance = check_interval_criteria(price_for_criteria, criteria)
|
||||||
|
|
||||||
# Track why intervals are filtered
|
# Track why intervals are filtered
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
|
@ -459,8 +459,6 @@ class TibberPricesIntervalPool:
|
||||||
True if a real gap exists, False if the range is fully covered.
|
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)
|
cached_intervals = self._get_cached_intervals(start_iso, end_iso)
|
||||||
|
|
||||||
if not cached_intervals:
|
if not cached_intervals:
|
||||||
|
|
@ -582,14 +580,13 @@ class TibberPricesIntervalPool:
|
||||||
resolution_change_naive = datetime(2025, 10, 1) # noqa: DTZ001
|
resolution_change_naive = datetime(2025, 10, 1) # noqa: DTZ001
|
||||||
interval_minutes = INTERVAL_QUARTER_HOURLY if current_naive >= resolution_change_naive else INTERVAL_HOURLY
|
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:
|
while current_naive < end_naive:
|
||||||
# Check if this timestamp exists in index (O(1) lookup)
|
# Check if this timestamp exists in index (O(1) lookup)
|
||||||
current_dt_key = current_naive.isoformat()[:19]
|
current_dt_key = current_naive.isoformat()[:19]
|
||||||
location = self._index.get(current_dt_key)
|
location = self._index.get(current_dt_key)
|
||||||
|
|
||||||
if location is not None:
|
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"]]
|
fetch_group = fetch_groups[location["fetch_group_index"]]
|
||||||
interval = fetch_group["intervals"][location["interval_index"]]
|
interval = fetch_group["intervals"][location["interval_index"]]
|
||||||
# CRITICAL: Return shallow copy to prevent external mutations
|
# CRITICAL: Return shallow copy to prevent external mutations
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue