hass.tibber_prices/custom_components/tibber_prices/utils/__init__.py
Julian Pawlowski 625bc222ca refactor(coordinator): centralize time operations through TimeService
Introduce TimeService as single source of truth for all datetime operations,
replacing direct dt_util calls throughout the codebase. This establishes
consistent time context across update cycles and enables future time-travel
testing capability.

Core changes:
- NEW: coordinator/time_service.py with timezone-aware datetime API
- Coordinator now creates TimeService per update cycle, passes to calculators
- Timer callbacks (#2, #3) inject TimeService into entity update flow
- All sensor calculators receive TimeService via coordinator reference
- Attribute builders accept time parameter for timestamp calculations

Key patterns replaced:
- dt_util.now() → time.now() (single reference time per cycle)
- dt_util.parse_datetime() + as_local() → time.get_interval_time()
- Manual interval arithmetic → time.get_interval_offset_time()
- Manual day boundaries → time.get_day_boundaries()
- round_to_nearest_quarter_hour() → time.round_to_nearest_quarter()

Import cleanup:
- Removed dt_util imports from ~30 files (calculators, attributes, utils)
- Restricted dt_util to 3 modules: time_service.py (operations), api/client.py
  (rate limiting), entity_utils/icons.py (cosmetic updates)
- datetime/timedelta only for TYPE_CHECKING (type hints) or duration arithmetic

Interval resolution abstraction:
- Removed hardcoded MINUTES_PER_INTERVAL constant from 10+ files
- New methods: time.minutes_to_intervals(), time.get_interval_duration()
- Supports future 60-minute resolution (legacy data) via TimeService config

Timezone correctness:
- API timestamps (startsAt) already localized by data transformation
- TimeService operations preserve HA user timezone throughout
- DST transitions handled via get_expected_intervals_for_day() (future use)

Timestamp ordering preserved:
- Attribute builders generate default timestamp (rounded quarter)
- Sensors override when needed (next interval, daily midnight, etc.)
- Platform ensures timestamp stays FIRST in attribute dict

Timer integration:
- Timer #2 (quarter-hour): Creates TimeService, calls _handle_time_sensitive_update(time)
- Timer #3 (30-second): Creates TimeService, calls _handle_minute_update(time)
- Consistent time reference for all entities in same update batch

Time-travel readiness:
- TimeService.with_reference_time() enables time injection (not yet used)
- All calculations use time.now() → easy to simulate past/future states
- Foundation for debugging period calculations with historical data

Impact: Eliminates timestamp drift within update cycles (previously 60+ independent
dt_util.now() calls could differ by milliseconds). Establishes architecture for
time-based testing and debugging features.
2025-11-19 18:36:12 +00:00

61 lines
1.9 KiB
Python

"""
Pure data transformation utilities for Tibber Prices integration.
This package contains stateless, pure functions for data processing:
- Time-window calculations (trailing/leading averages, min/max)
- Price enrichment (differences, volatility, rating levels)
- Statistical analysis (aggregation, trends)
These functions operate on raw data structures (dicts, lists) and do NOT depend on:
- Home Assistant entities or state management
- Configuration entries or coordinators
- Translation systems or UI-specific logic
For entity-specific utilities (icons, colors, attributes), see entity_utils/ package.
"""
from __future__ import annotations
from .average import (
calculate_current_leading_avg,
calculate_current_leading_max,
calculate_current_leading_min,
calculate_current_trailing_avg,
calculate_current_trailing_max,
calculate_current_trailing_min,
calculate_next_n_hours_avg,
)
from .price import (
aggregate_period_levels,
aggregate_period_ratings,
aggregate_price_levels,
aggregate_price_rating,
calculate_difference_percentage,
calculate_price_trend,
calculate_rating_level,
calculate_trailing_average_for_interval,
calculate_volatility_level,
enrich_price_info_with_differences,
find_price_data_for_interval,
)
__all__ = [
"aggregate_period_levels",
"aggregate_period_ratings",
"aggregate_price_levels",
"aggregate_price_rating",
"calculate_current_leading_avg",
"calculate_current_leading_max",
"calculate_current_leading_min",
"calculate_current_trailing_avg",
"calculate_current_trailing_max",
"calculate_current_trailing_min",
"calculate_difference_percentage",
"calculate_next_n_hours_avg",
"calculate_price_trend",
"calculate_rating_level",
"calculate_trailing_average_for_interval",
"calculate_volatility_level",
"enrich_price_info_with_differences",
"find_price_data_for_interval",
]