Add resolution parameter to get_chartdata and get_apexcharts_yaml services,
allowing users to choose between original 15-minute intervals or aggregated
hourly values for chart visualization.
Implementation uses rolling 5-interval window aggregation (-2, -1, 0, +1, +2
around :00 of each hour = 60 minutes total), matching the sensor rolling
hour methodology. Respects user's CONF_AVERAGE_SENSOR_DISPLAY setting for
mean vs median calculation.
Changes:
- formatters.py: Add aggregate_to_hourly() function preserving original
field names (startsAt, total, level, rating_level) for unified processing
- get_chartdata.py: Pre-aggregate data before processing when resolution is
'hourly', enabling same code path for filters/insert_nulls/connect_segments
- get_apexcharts_yaml.py: Add resolution parameter, pass to all 4 get_chartdata
service calls in generated JavaScript
- services.yaml: Add resolution field with interval/hourly selector
- icons.json: Add section icons for get_apexcharts_yaml fields
- translations: Add highlight_peak_price and resolution field translations
for all 5 languages (en, de, sv, nb, nl)
Impact: Users can now generate cleaner charts with 24 hourly data points
instead of 96 quarter-hourly intervals. The unified processing approach
ensures all chart features (filters, null insertion, segment connection)
work identically for both resolutions.
Period data in array_of_arrays format now generates proper segment structure
for stepline charts. Each period produces 2-3 data points depending on
insert_nulls parameter:
1. Start time with price (begin period)
2. End time with price (hold price level)
3. End time with NULL (terminate segment, only if insert_nulls='segments'/'all')
This enables ApexCharts to correctly display periods as continuous blocks with
clean gaps between them. Previously only start point was generated, causing
periods to render as single points instead of continuous segments.
Changes:
- formatters.py: Updated get_period_data() to generate 2-3 points per period
- formatters.py: Added insert_nulls parameter to control NULL termination
- get_chartdata.py: Pass insert_nulls parameter to get_period_data()
- get_apexcharts_yaml.py: Set insert_nulls='segments' for period overlay
- get_apexcharts_yaml.py: Preserve NULL values in data_generator mapping
- get_apexcharts_yaml.py: Store original price for potential tooltip access
- tests: Added comprehensive period data format tests
Impact: Best price and peak price period overlays now display correctly as
continuous blocks with proper segment separation in ApexCharts cards.
Changed from centralized main+subentry coordinator pattern to independent
coordinators per home. Each config entry now manages its own home data
with its own API client and access token.
Architecture changes:
- API Client: async_get_price_info() changed from home_ids: set[str] to home_id: str
* Removed GraphQL alias pattern (home0, home1, ...)
* Single-home query structure without aliasing
* Simplified response parsing (viewer.home instead of viewer.home0)
- Coordinator: Removed main/subentry distinction
* Deleted is_main_entry() and _has_existing_main_coordinator()
* Each coordinator fetches its own data independently
* Removed _find_main_coordinator() and _get_configured_home_ids()
* Simplified _async_update_data() - no subentry logic
* Added _home_id instance variable from config_entry.data
- __init__.py: New _get_access_token() helper
* Handles token retrieval for both parent and subentries
* Subentries find parent entry to get shared access token
* Creates single API client instance per coordinator
- Data structures: Flat single-home format
* Old: {"homes": {home_id: {"price_info": [...]}}}
* New: {"home_id": str, "price_info": [...], "currency": str}
* Attribute name: "periods" → "pricePeriods" (consistent with priceInfo)
- helpers.py: Removed get_configured_home_ids() (no longer needed)
* parse_all_timestamps() updated for single-home structure
Impact: Each home operates independently with its own lifecycle tracking,
caching, and period calculations. Simpler architecture, easier debugging,
better isolation between homes.
- Introduced `get_intervals_for_day_offsets` helper to streamline access to price intervals for yesterday, today, and tomorrow.
- Updated various components to replace direct access to `priceInfo` with the new helper, ensuring a flat structure for price intervals.
- Adjusted calculations and data processing methods to accommodate the new data structure.
- Enhanced documentation to reflect changes in caching strategy and data structure.
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.