Architecture
This document provides a visual overview of the integration's architecture, focusing on end-to-end data flow and caching layers.
For detailed implementation patterns, see AGENTS.md.
End-to-End Data Flow
Flow Description
-
Setup (
__init__.py)- Integration loads, creates coordinator instance
- Registers entity platforms (sensor, binary_sensor)
- Sets up custom services
-
Data Fetch (every 15 minutes)
- Coordinator triggers update via
api.py - API client checks persistent cache first (
coordinator/cache.py) - If cache valid → return cached data
- If cache stale → query Tibber GraphQL API
- Store fresh data in persistent cache (survives HA restart)
- Coordinator triggers update via
-
Price Enrichment
- Coordinator passes raw prices to
DataTransformer - Transformer checks transformation cache (memory)
- If cache valid → return enriched data
- If cache invalid → enrich via
price_utils.py+average_utils.py- Calculate 24h trailing/leading averages
- Calculate price differences (% from average)
- Assign rating levels (LOW/NORMAL/HIGH)
- Store enriched data in transformation cache
- Coordinator passes raw prices to
-
Period Calculation
- Coordinator passes enriched data to
PeriodCalculator - Calculator computes hash from prices + config
- If hash matches cache → return cached periods
- If hash differs → recalculate best/peak price periods
- Store periods with new hash
- Coordinator passes enriched data to
-
Entity Updates
- Coordinator provides complete data (prices + periods)
- Sensors read values via unified handlers
- Binary sensors evaluate period states
- Entities update on quarter-hour boundaries (00/15/30/45)
-
Service Calls
- Custom services access coordinator data directly
- Return formatted responses (JSON, ApexCharts format)
Caching Architecture
Overview
The integration uses 5 independent caching layers for optimal performance:
| Layer | Location | Lifetime | Invalidation | Memory |
|---|---|---|---|---|
| API Cache | coordinator/cache.py | 24h (user) Until midnight (prices) | Automatic | 50KB |
| Translation Cache | const.py | Until HA restart | Never | 5KB |
| Config Cache | coordinator/* | Until config change | Explicit | 1KB |
| Period Cache | coordinator/periods.py | Until data/config change | Hash-based | 10KB |
| Transformation Cache | coordinator/data_transformation.py | Until midnight/config | Automatic | 60KB |
Total cache overhead: ~126KB per coordinator instance (main entry + subentries)
Cache Coordination
Key insight: No cascading invalidations - each cache is independent and rebuilds on-demand.
For detailed cache behavior, see Caching Strategy.
Component Responsibilities
Core Components
| Component | File | Responsibility |
|---|---|---|
| API Client | api.py | GraphQL queries to Tibber, retry logic, error handling |
| Coordinator | coordinator.py | Update orchestration, cache management, absolute-time scheduling with boundary tolerance |
| Data Transformer | coordinator/data_transformation.py | Price enrichment (averages, ratings, differences) |
| Period Calculator | coordinator/periods.py | Best/peak price period calculation with relaxation |
| Sensors | sensor/ | 80+ entities for prices, levels, ratings, statistics |
| Binary Sensors | binary_sensor/ | Period indicators (best/peak price active) |
| Services | services/ | Custom service endpoints (get_chartdata, get_apexcharts_yaml, refresh_user_data) |
Sensor Architecture (Calculator Pattern)
The sensor platform uses Calculator Pattern for clean separation of concerns (refactored Nov 2025):
| Component | Files | Lines | Responsibility |
|---|---|---|---|
| Entity Class | sensor/core.py | 909 | Entity lifecycle, coordinator, delegates to calculators |
| Calculators | sensor/calculators/ | 1,838 | Business logic (8 specialized calculators) |
| Attributes | sensor/attributes/ | 1,209 | State presentation (8 specialized modules) |
| Routing | sensor/value_getters.py | 276 | Centralized sensor → calculator mapping |
| Chart Export | sensor/chart_data.py | 144 | Service call handling, YAML parsing |
| Helpers | sensor/helpers.py | 188 | Aggregation functions, utilities |
Calculator Package (sensor/calculators/):
base.py- Abstract BaseCalculator with coordinator accessinterval.py- Single interval calculations (current/next/previous)rolling_hour.py- 5-interval rolling windowsdaily_stat.py- Calendar day min/max/avg statisticswindow_24h.py- Trailing/leading 24h windowsvolatility.py- Price volatility analysistrend.py- Complex trend analysis with cachingtiming.py- Best/peak price period timingmetadata.py- Home/metering metadata
Benefits:
- 58% reduction in core.py (2,170 → 909 lines)
- Clear separation: Calculators (logic) vs Attributes (presentation)
- Independent testability for each calculator
- Easy to add sensors: Choose calculation pattern, add to routing
Helper Utilities
| Utility | File | Purpose |
|---|---|---|
| Price Utils | utils/price.py | Rating calculation, enrichment, level aggregation |
| Average Utils | utils/average.py | Trailing/leading 24h average calculations |
| Entity Utils | entity_utils/ | Shared icon/color/attribute logic |
| Translations | const.py | Translation loading and caching |
Key Patterns
1. Dual Translation System
- Standard translations (
/translations/*.json): HA-compliant schema for entity names - Custom translations (
/custom_translations/*.json): Extended descriptions, usage tips - Both loaded at integration setup, cached in memory
- Access via
get_translation()helper function
2. Price Data Enrichment
All quarter-hourly price intervals get augmented via utils/price.py:
# Original from Tibber API
{
"startsAt": "2025-11-03T14:00:00+01:00",
"total": 0.2534,
"level": "NORMAL"
}
# After enrichment (utils/price.py)
{
"startsAt": "2025-11-03T14:00:00+01:00",
"total": 0.2534,
"level": "NORMAL",
"trailing_avg_24h": 0.2312, # ← Added: 24h trailing average
"difference": 9.6, # ← Added: % diff from trailing avg
"rating_level": "NORMAL" # ← Added: LOW/NORMAL/HIGH based on thresholds
}
3. Quarter-Hour Precision
- API polling: Every 15 minutes (coordinator fetch cycle)
- Entity updates: On 00/15/30/45-minute boundaries via
coordinator/listeners.py - Timer scheduling: Uses
async_track_utc_time_change(minute=[0, 15, 30, 45], second=0)- HA may trigger ±few milliseconds before/after exact boundary
- Smart boundary tolerance (±2 seconds) handles scheduling jitter in
sensor/helpers.py - If HA schedules at 14:59:58 → rounds to 15:00:00 (shows new interval data)
- If HA restarts at 14:59:30 → stays at 14:45:00 (shows current interval data)
- Absolute time tracking: Timer plans for all future boundaries (not relative delays)
- Prevents double-updates (if triggered at 14:59:58, next trigger is 15:15:00, not 15:00:00)
- Result: Current price sensors update without waiting for next API poll
4. Calculator Pattern (Sensor Platform)
Sensors organized by calculation method (refactored Nov 2025):
Unified Handler Methods (sensor/core.py):
_get_interval_value(offset, type)- current/next/previous intervals_get_rolling_hour_value(offset, type)- 5-interval rolling windows_get_daily_stat_value(day, stat_func)- calendar day min/max/avg_get_24h_window_value(stat_func)- trailing/leading statistics
Routing (sensor/value_getters.py):
- Single source of truth mapping 80+ entity keys to calculator methods
- Organized by calculation type (Interval, Rolling Hour, Daily Stats, etc.)
Calculators (sensor/calculators/):
- Each calculator inherits from
BaseCalculatorwith coordinator access - Focused responsibility:
IntervalCalculator,TrendCalculator, etc. - Complex logic isolated (e.g.,
TrendCalculatorhas internal caching)
Attributes (sensor/attributes/):
- Separate from business logic, handles state presentation
- Builds extra_state_attributes dicts for entity classes
- Unified builders:
build_sensor_attributes(),build_extra_state_attributes()
Benefits:
- Minimal code duplication across 80+ sensors
- Clear separation of concerns (calculation vs presentation)
- Easy to extend: Add sensor → choose pattern → add to routing
- Independent testability for each component
Performance Characteristics
API Call Reduction
- Without caching: 96 API calls/day (every 15 min)
- With caching: ~1-2 API calls/day (only when cache expires)
- Reduction: ~98%
CPU Optimization
| Optimization | Location | Savings |
|---|---|---|
| Config caching | coordinator/* | ~50% on config checks |
| Period caching | coordinator/periods.py | ~70% on period recalculation |
| Lazy logging | Throughout | ~15% on log-heavy operations |
| Import optimization | Module structure | ~20% faster loading |
Memory Usage
- Per coordinator instance: ~126KB cache overhead
- Typical setup: 1 main + 2 subentries = ~378KB total
- Redundancy eliminated: 14% reduction (10KB saved per coordinator)
Related Documentation
- Timer Architecture - Timer system, scheduling, coordination (3 independent timers)
- Caching Strategy - Detailed cache behavior, invalidation, debugging
- Setup Guide - Development environment setup
- Testing Guide - How to test changes
- Release Management - Release workflow and versioning
- AGENTS.md - Complete reference for AI development