Implemented comprehensive performance optimizations to eliminate asyncio
warnings and reduce unnecessary API calls:
Performance Improvements:
- Two-tier caching: raw API data + transformed data
- Transformation caching prevents re-processing on every coordinator update
- Only retransform when config changes, new data arrives, or midnight turnover
- Reduced coordinator update time from ~120ms to <10ms (cache hits)
API Call Optimization:
- Removed periodic 6-hour "safety" API calls (trust cache validation)
- Random delay (0-30s) for tomorrow data checks (prevents thundering herd)
- API calls only when truly needed (no cache, invalid cache, tomorrow missing)
- Expected reduction: ~50% fewer API calls (from ~4-5/day to ~2/day)
Enhanced Logging:
- Clear [Timer #1/2/3] prefixes for multi-timer system
- Distinguish "Fetching from API" vs "Using cache"
- Separate "Transforming data" vs "Using cached transformed data"
- Hierarchical logging shows which timer triggered which action
Documentation:
- Comprehensive TIMER SYSTEM block explaining three independent timers
- Enhanced docstrings for timer handlers (synchronous callbacks vs async def)
- Clarified why @callback handlers don't use async/await (no I/O operations)
- Updated UPDATE_INTERVAL documentation (removed periodic check reference)
Technical Details:
- _get_current_transformation_config(): Captures all config affecting transformation
- _should_retransform_data(): Intelligent cache invalidation logic
- _should_update_price_data(): Returns bool|"tomorrow_check" to signal delay needed
- Timer handlers use @callback decorator (synchronous, no I/O, fast execution)
Impact: Eliminates asyncio warnings (tasks >0.1s), reduces API load by 50%,
maintains data accuracy through robust cache validation. No user-visible changes.
Added comprehensive user documentation for visual dashboard customization:
- docs/user/icon-colors.md: New guide for using icon_color attribute
* Explains CSS variable approach for theme compatibility
* Shows when to use icon_color vs state interpretation
* Examples for Custom Button Card, Entities Card, Mushroom, Glance
* Custom color override options (theme-based and direct)
* All state values use lowercase (HA convention)
- docs/user/dynamic-icons.md: New guide for automatic icon changes
* Explains state-based icon behavior without cataloging specifics
* Dashboard examples with standard and custom cards
* Icon override instructions for fixed icons
* Binary sensor icon behavior details
* Integration with dynamic colors
- Updated cross-references in README.md, sensors.md, automation-examples.md
to link both new guides
Impact: Users can now create visually rich dashboards with color-coded and
icon-changing sensors without writing complex conditional logic. Documentation
focuses on principles and practical examples rather than exhaustive listings,
making it easy to understand and maintain.
Home Assistant's hassfest validation requires config flows to be defined
in a file named config_flow.py (not a package directory).
Changes:
- Renamed custom_components/tibber_prices/config_flow/ → config_flow_handlers/
- Created config_flow.py as bridge file re-exporting from config_flow_handlers/
- Updated all import paths across 5 files (user_flow, options_flow, subentry_flow, etc.)
- Added ./scripts/hassfest for local validation (JSON/Python syntax, required files)
- Added ./scripts/clean with three modes (--minimal, normal, --deep)
- Refactored develop/lint/lint-check to use centralized cleanup (DRY principle)
- Updated documentation in AGENTS.md and docs/development/
Technical details:
- Bridge file uses __all__ exports to maintain clean public API
- hassfest script uses ast.parse() for syntax validation (no disk artifacts)
- clean --minimal removes .egg-info only (silent, for automated scripts)
- Dual pip/uv pip compatibility for package uninstallation
Impact: Integration now passes hassfest validation. Local validation available
via ./scripts/hassfest before pushing to GitHub. Cleanup logic centralized and
DRY across all development scripts.
Added 10 new timing sensors (5 for best_price, 5 for peak_price) to track
period timing and progress:
Timestamp sensors (quarter-hour updates):
- best_price_end_time / peak_price_end_time
Shows when current/next period ends (always useful reference time)
- best_price_next_start_time / peak_price_next_start_time
Shows when next period starts (even during active periods)
Countdown sensors (minute updates):
- best_price_remaining_minutes / peak_price_remaining_minutes
Minutes left in current period (0 when inactive)
- best_price_next_in_minutes / peak_price_next_in_minutes
Minutes until next period starts
- best_price_progress / peak_price_progress
Progress percentage through current period (0-100%)
Smart fallback behavior:
- Sensors always show useful values (no 'Unknown' during normal operation)
- Timestamp sensors show current OR next period end/start times
- Countdown sensors return 0 when no period is active
- Grace period: Progress stays at 100% for 60 seconds after period ends
Dynamic visual feedback:
- Progress icons differentiate 3 states at 0%:
* No data: mdi:help-circle-outline (gray)
* Waiting for next period: mdi:timer-pause-outline
* Period just started: mdi:circle-outline
- Progress 1-99%: mdi:circle-slice-1 to mdi:circle-slice-8 (pie chart)
- Timer icons based on urgency (alert/timer/timer-sand/timer-outline)
- Dynamic colors: green (best_price), orange/red (peak_price), gray (disabled)
- icon_color attribute for UI styling
Implementation details:
- Dual update mechanism: quarter-hour (timestamps) + minute (countdowns)
- Period state callbacks: Check if period is currently active
- IconContext dataclass: Reduced function parameters from 6 to 3
- Unit constants: UnitOfTime.MINUTES, PERCENTAGE from homeassistant.const
- Complete translations for 5 languages (de, en, nb, nl, sv)
Impact: Users can now build sophisticated automations based on period timing
('start dishwasher if remaining_minutes > 60'), display countdowns in
dashboards, and get clear visual feedback about period states. All sensors
provide meaningful values at all times, making automation logic simpler.
Added timestamp attributes to all sensors and enhanced the dynamic icon
system for comprehensive price sensor coverage with rolling hour support.
TIMESTAMP ATTRIBUTES:
Core Changes:
- sensor/attributes.py:
* Enhanced add_average_price_attributes() to track extreme intervals
for min/max sensors and add appropriate timestamps
* Added _update_extreme_interval() helper to reduce complexity
* Extended add_volatility_type_attributes() with timestamp logic for
all 4 volatility types (today/tomorrow/today_tomorrow/next_24h)
* Fixed current_interval_price timestamp assignment (use interval_data)
Timestamp Logic:
- Interval-based sensors: Use startsAt of specific 15-minute interval
- Min/Max sensors: Use startsAt of interval with extreme price
- Average sensors: Use startsAt of first interval in window
- Volatility sensors: Use midnight (00:00) for calendar day sensors,
current time for rolling 24h window
- Daily sensors: Already used fallback to midnight (verified)
ICON SYSTEM ENHANCEMENTS:
Major Extensions:
- entity_utils/icons.py:
* Created get_rolling_hour_price_level_for_icon() implementing
5-interval window aggregation matching sensor calculation logic
* Extended get_price_sensor_icon() coverage from 1 to 4 sensors:
- current_interval_price (existing)
- next_interval_price (NEW - dynamic instead of static)
- current_hour_average_price (NEW - uses rolling hour aggregation)
- next_hour_average_price (NEW - uses rolling hour aggregation)
* Added imports for aggregate_level_data and find_rolling_hour_center_index
Documentation:
- sensor/definitions.py:
* Updated 30+ sensor descriptions with detailed icon behavior comments
* Changed next_interval_price from static to dynamic icon
* Documented dynamic vs static icons for all sensor types
* Added clear icon mapping source documentation
SENSOR KEY RENAMING:
Renamed for clarity (current_hour_average → current_hour_average_price):
- sensor/core.py: Updated value getters and cached data lookup
- sensor/definitions.py: Updated entity descriptions
- sensor/attributes.py: Updated key references in attribute builders
- coordinator.py: Updated TIME_SENSITIVE_ENTITY_KEYS set
- const.py: Updated comment documentation
Translation Updates:
- custom_translations/*.json (5 files): Updated sensor keys
- translations/*.json (5 files): Updated sensor keys
Impact:
- All sensors now have timestamp attribute showing applicable time/interval
- Icon system provides richer visual feedback for more sensor types
- Consistent sensor naming improves code readability
- Users get temporal context for all sensor values
- Dynamic icons adapt to price conditions across more sensors
Added 6 new sensors for yesterday/today/tomorrow aggregated price
levels and ratings, following the same calculation logic as existing
current/next interval sensors.
New sensors:
- yesterday_price_level, today_price_level, tomorrow_price_level
- yesterday_price_rating, today_price_rating, tomorrow_price_rating
Implementation details:
- Added DAILY_LEVEL_SENSORS and DAILY_RATING_SENSORS in sensor/definitions.py
- Implemented _get_daily_aggregated_value() in sensor/core.py using
existing aggregate_level_data() and aggregate_rating_data() helpers
- Extended icon support in entity_utils/icons.py for dynamic icons
- Added icon_color attributes in sensor/attributes.py with helper
functions _get_day_key_from_sensor_key() and _add_fallback_timestamp()
- Complete translations in all 5 languages (de, en, nb, nl, sv):
* Standard translations: sensor names
* Custom translations: description, long_description, usage_tips
Impact: Users can now see aggregated daily price levels and ratings
for yesterday, today, and tomorrow at a glance, making it easier to
compare overall price situations across days and plan energy consumption
accordingly. Sensors use same aggregation logic as hourly sensors for
consistency.
Split binary_sensor.py (645 lines) into binary_sensor/ package with
4 modules following the established sensor/ pattern for consistency
and maintainability.
Package structure:
- binary_sensor/__init__.py (32 lines): Platform setup
- binary_sensor/definitions.py (46 lines): ENTITY_DESCRIPTIONS, constants
- binary_sensor/attributes.py (443 lines): Attribute builder functions
- binary_sensor/core.py (282 lines): TibberPricesBinarySensor class
Changes:
- Created binary_sensor/ package with __init__.py importing from .core
- Extracted ENTITY_DESCRIPTIONS and constants to definitions.py
- Moved 13 attribute builders to attributes.py (get_price_intervals_attributes,
build_async/sync_extra_state_attributes, add_* helpers)
- Moved TibberPricesBinarySensor class to core.py with state logic and
icon handling
- Used keyword-only parameters to satisfy Ruff PLR0913 (too many args)
- Applied absolute imports (custom_components.tibber_prices.*) in modules
All 4 binary sensors tested and working:
- peak_price_period
- best_price_period
- connection
- tomorrow_data_available
Documentation updated:
- AGENTS.md: Architecture Overview, Component Structure, Common Tasks
- binary-sensor-refactoring-plan.md: Marked ✅ COMPLETED with summary
Impact: Symmetric platform structure (sensor/ ↔ binary_sensor/). Easier
to add new binary sensors following documented pattern. No user-visible
changes.
* Initial plan
* Fix AttributeError for homes without active subscription
Co-authored-by: jpawlowski <75446+jpawlowski@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jpawlowski <75446+jpawlowski@users.noreply.github.com>
- Introduced a comprehensive refactoring guide detailing when and how to plan major refactorings, including a structured planning process and real-world examples.
- Created a README for the planning directory, outlining its purpose, document lifecycle, and best practices for creating planning documents.
- Removed the `calculate_current_rolling_5interval_avg` and `calculate_next_hour_rolling_5interval_avg` functions from `average_utils.py` to streamline the codebase.
- Introduced unified methods for retrieving interval values and rolling hour calculations in `sensor.py`, enhancing code reusability and readability.
- Organized sensor definitions into categories based on calculation methods for better maintainability.
- Updated handler methods to utilize the new unified methods, ensuring consistent data retrieval across different sensor types.
- Improved documentation and comments throughout the code to clarify the purpose and functionality of various methods.
Renamed internal sensor keys to be more explicit about their temporal scope:
- current_price → current_interval_price
- price_level → current_interval_price_level
- price_rating → current_interval_price_rating
This naming makes it clearer that these sensors represent the current
15-minute interval, distinguishing them from hourly averages and other
time-based calculations.
Updated across all components:
- Sensor entity descriptions and handlers (sensor.py)
- Time-sensitive entity keys list (coordinator.py)
- Config flow step IDs (config_flow.py)
- Translation keys in all 5 languages (de, en, nb, nl, sv)
- Custom translations (entity descriptions, usage tips)
- Price level/rating lookups (const.py, sensor.py)
- Documentation examples (AGENTS.md, README.md)
Impact: Sensor entity IDs remain unchanged due to translation_key system.
Existing automations continue to work. Only internal code references and
translation structures updated for consistency.
Replaced absolute volatility thresholds (ct/øre) with relative coefficient
of variation (CV = std_dev / mean * 100%) for scale-independent volatility
measurement that works across all price levels.
Changes to volatility calculation:
- price_utils.py: Rewrote calculate_volatility_level() to accept price list
instead of spread value, using statistics.mean() and statistics.stdev()
- sensor.py: Updated volatility sensors to pass price lists (not spread)
- services.py: Modified _get_price_stats() to calculate CV from prices
- period_statistics.py: Extract prices for CV calculation in period summaries
- const.py: Updated default thresholds to 15%/30%/50% (was 5/15/30 ct)
with comprehensive documentation explaining CV-based approach
Dead code removal:
- period_utils/core.py: Removed filter_periods_by_volatility() function
(86 lines of code that was never actually called)
- period_utils/__init__.py: Removed dead function export
- period_utils/relaxation.py: Simplified callback signature from
Callable[[str|None, str|None], bool] to Callable[[str|None], bool]
- coordinator.py: Updated lambda callbacks to match new signature
- const.py: Replaced RELAXATION_VOLATILITY_ANY with RELAXATION_LEVEL_ANY
Bug fix:
- relaxation.py: Added int() conversion for max_relaxation_attempts
(line 435: attempts = max(1, int(max_relaxation_attempts)))
Fixes TypeError when config value arrives as float
Configuration UI:
- config_flow.py: Changed volatility threshold unit display from "ct" to "%"
Translations (all 5 languages):
- Updated volatility descriptions to explain coefficient of variation
- Changed threshold labels from "spread ≥ value" to "CV ≥ percentage"
- Languages: de, en, nb, nl, sv
Documentation:
- period-calculation.md: Removed volatility filter section (dead feature)
Impact: Breaking change for users with custom volatility thresholds.
Old absolute values (e.g., 5 ct) will be interpreted as percentages (5%).
However, new defaults (15%/30%/50%) are more conservative and work
universally across all currencies and price levels. No data migration
needed - existing configs continue to work with new interpretation.
- Skip asymmetry/zigzag rejection near the data tail and refactor spike
validation so legitimate end-of-day spikes stop breaking periods.
- Expose relaxation attempt sliders for both Best/Peak flows, wire the values
through the coordinator, and extend the relaxation engine to honor the new
max-attempt cap with richer logging & metadata.
- Raise the default attempt count to eight flex levels so the 25% increment
pattern can stretch much further before stopping, keeping translations and
docs (including the matrix explanation) in sync across all locales.
Impact: Tail spikes no longer get thrown out incorrectly, users can tune how
aggressively the period search relaxes, and the defaults now find more viable
periods on volatile days.
* feat(period-calc): adaptive defaults + remove volatility filter
Major improvements to period calculation with smarter defaults and
simplified configuration:
**Adaptive Defaults:**
- ENABLE_MIN_PERIODS: true (was false) - Always try to find periods
- MIN_PERIODS target: 2 periods/day (ensures coverage)
- BEST_PRICE_MAX_LEVEL: "cheap" (was "any") - Prefer genuinely cheap
- PEAK_PRICE_MIN_LEVEL: "expensive" (was "any") - Prefer genuinely expensive
- GAP_TOLERANCE: 1 (was 0) - Allow 1-level deviations in sequences
- MIN_DISTANCE_FROM_AVG: 5% (was 2%) - Ensure significance
- PEAK_PRICE_MIN_PERIOD_LENGTH: 30min (was 60min) - More responsive
- PEAK_PRICE_FLEX: -20% (was -15%) - Better peak detection
**Volatility Filter Removal:**
- Removed CONF_BEST_PRICE_MIN_VOLATILITY from const.py
- Removed CONF_PEAK_PRICE_MIN_VOLATILITY from const.py
- Removed volatility filter UI controls from config_flow.py
- Removed filter_periods_by_volatility() calls from coordinator.py
- Updated all 5 translations (de, en, nb, nl, sv)
**Period Calculation Logic:**
- Level filter now integrated into _build_periods() (applied during
interval qualification, not as post-filter)
- Gap tolerance implemented via _check_level_with_gap_tolerance()
- Short periods (<1.5h) use strict filtering (no gap tolerance)
- Relaxation now passes level_filter + gap_count directly to
PeriodConfig
- show_periods check skipped when relaxation enabled (relaxation
tries "any" as fallback)
**Documentation:**
- Complete rewrite of docs/user/period-calculation.md:
* Visual examples with timelines
* Step-by-step explanation of 4-step process
* Configuration scenarios (5 common use cases)
* Troubleshooting section with specific fixes
* Advanced topics (per-day independence, early stop, etc.)
- Updated README.md: "volatility" → "distance from average"
Impact: Periods now reliably appear on most days with meaningful
quality filters. Users get warned about expensive periods and notified
about cheap opportunities without manual tuning. Relaxation ensures
coverage while keeping filters as strict as possible.
Breaking change: Volatility filter removed (was never a critical
feature, often confused users). Existing configs continue to work
(removed keys are simply ignored).
* feat(periods): modularize period_utils and add statistical outlier filtering
Refactored monolithic period_utils.py (1800 lines) into focused modules
for better maintainability and added advanced outlier filtering with
smart impact tracking.
Modular structure:
- types.py: Type definitions and constants (89 lines)
- level_filtering.py: Level filtering with gap tolerance (121 lines)
- period_building.py: Period construction from intervals (238 lines)
- period_statistics.py: Statistics and summaries (318 lines)
- period_merging.py: Overlap resolution (382 lines)
- relaxation.py: Per-day relaxation strategy (547 lines)
- core.py: Main API orchestration (251 lines)
- outlier_filtering.py: Statistical spike detection (294 lines)
- __init__.py: Public API exports (62 lines)
New statistical outlier filtering:
- Linear regression for trend-based spike detection
- 2 standard deviation confidence intervals (95%)
- Symmetry checking to preserve legitimate price shifts
- Enhanced zigzag detection with relative volatility (catches clusters)
- Replaces simple average smoothing with trend-based predictions
Smart impact tracking:
- Tests if original price would have passed criteria
- Only counts smoothed intervals that actually changed period formation
- Tracks level gap tolerance usage separately
- Both attributes only appear when > 0 (clean UI)
New period attributes:
- period_interval_smoothed_count: Intervals kept via outlier smoothing
- period_interval_level_gap_count: Intervals kept via gap tolerance
Impact: Statistical outlier filtering prevents isolated price spikes from
breaking continuous periods while preserving data integrity. All statistics
use original prices. Smart tracking shows only meaningful interventions,
making it clear when tolerance mechanisms actually influenced results.
Backwards compatible: All public APIs re-exported from period_utils package.
* Update docs/user/period-calculation.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update custom_components/tibber_prices/const.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update custom_components/tibber_prices/coordinator.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update custom_components/tibber_prices/const.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* docs(periods): fix corrupted period-calculation.md and add outlier filtering documentation
Completely rewrote period-calculation.md after severe corruption (massive text
duplication throughout the file made it 2489 lines).
Changes:
- Fixed formatting: Removed all duplicate text and headers
- Reduced file size: 2594 lines down to 516 lines (clean, readable structure)
- Added section 5: "Statistical Outlier Filtering (NEW)" explaining:
- Linear regression-based spike detection (95% confidence intervals)
- Symmetry checking to preserve legitimate price shifts
- Enhanced zigzag detection with relative volatility
- Data integrity guarantees (original prices always used)
- New period attributes: period_interval_smoothed_count
- Added troubleshooting: "Price spikes breaking periods" section
- Added technical details: Algorithm constants and implementation notes
Impact: Users can now understand how outlier filtering prevents isolated
price spikes from breaking continuous periods. Documentation is readable
again with no duplicate content.
* fix(const): improve clarity in comments regarding period lengths for price alerts
* docs(periods): improve formatting and clarity in period-calculation.md
* Initial plan
* refactor: convert flexibility_pct to ratio once at function entry
Co-authored-by: jpawlowski <75446+jpawlowski@users.noreply.github.com>
* Update custom_components/tibber_prices/const.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update custom_components/tibber_prices/period_utils/period_building.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update custom_components/tibber_prices/period_utils/relaxation.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Julian Pawlowski <jpawlowski@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Disabled analytics integration in development configuration to prevent:
- Skewing production statistics at analytics.home-assistant.io
- Sending error reports from development instance (expected to have errors)
- Transmitting usage data from non-production environment
This ensures dev container only affects local development, not HA statistics.
Impact: Development environment no longer sends telemetry data to Home
Assistant. No user-visible changes.
Restructured relaxation mechanism to process each day independently instead
of globally, enabling different days to relax at different levels.
Key changes:
- Added hierarchical logging with INDENT_L0-L5 constants
- Replaced global relaxation loop with per-day relaxation (_relax_single_day)
- Implemented 4×4 matrix strategy (4 flex levels × 4 filter combinations)
- Enhanced _resolve_period_overlaps with replacement and extension logic
- Added helper functions: _group_periods_by_day, _group_prices_by_day,
_check_min_periods_per_day
Relaxation strategy:
- Each flex level tries 4 filter combinations before increasing flex
- Early exit after EACH successful combination (minimal relaxation)
- Extensions preserve baseline metadata, replacements use relaxed metadata
- Only standalone periods count toward min_periods requirement
Impact: Users get more accurate period detection per day. Days with clear
cheap/expensive patterns use strict filters while difficult days relax as
needed. Reduces over-relaxation - finds 'good enough' solutions faster.
Extended AGENTS.md with comprehensive patterns learned from period
calculation development and documentation rewrite.
Logging Guidelines:
- Added hierarchical indentation pattern (INDENT_L0-L5)
- Defined log level strategy (INFO=compact/scannable,
DEBUG=detailed/hierarchical, WARNING=top-level)
- Added configuration context headers for complex calculations
- Documented per-day processing patterns
- Added note about logs as documentation foundation
User Documentation Quality:
- Added principles: clarity over completeness, visual examples,
use-case driven structure, practical troubleshooting, progressive
disclosure
- Added validation rules for code-documentation sync
Documentation Writing Strategy (new section):
- Live understanding vs. cold code analysis
- User feedback loop importance
- Log-driven documentation approach
- Concrete examples over abstract descriptions
- Context accumulation in long sessions
- Document the "why", not just the "what"
Impact: Future AI sessions can produce better logs (traceable logic
with visual hierarchy) and better documentation (user-focused with
concrete examples from live development).
Completely rewrote period-calculation.md based on user feedback and
live development understanding.
Changes:
- Replaced outdated 3-phase relaxation description with correct 4×4
matrix approach (4 flex levels × 4 filter combinations)
- Added per-day independence explanation (each day relaxes independently)
- Documented replacement logic (larger periods replace smaller ones)
- Added extension logic (baseline periods get expanded, not replaced)
- Updated metadata format examples (price_diff_27.3%+level_any)
- Restructured for clarity: Quick Start → How It Works → Config →
Relaxation → Scenarios → Troubleshooting
- Added 4 real-world scenarios with automation examples (dishwasher,
heat pump, EV charging, peak avoidance)
- Added visual timeline examples
- Reduced technical complexity, focused on user understanding
- Added practical troubleshooting with specific solutions
Impact: Users can now understand how period calculation actually works,
with correct information matching the implemented 4×4 relaxation
strategy. Documentation evolved from cold code reading to live
development insights with user feedback.
Implemented configurable gap tolerance (0-8 intervals) for best price and peak price
level filters to prevent periods from being split by occasional level deviations.
Key features:
- Gap tolerance only applies to periods ≥ MIN_INTERVALS_FOR_GAP_TOLERANCE (1.5h)
- Short periods (< 1.5h) use strict filtering (zero tolerance)
- Dynamic minimum distance between gaps: max(2, (interval_count // max_gap_count) // 2)
- 25% maximum cap on total gaps to prevent excessive outliers in long periods
- Intelligent period splitting at gap clusters (2+ consecutive non-qualifying intervals)
- Each sub-period independently validated with same gap tolerance rules
Technical implementation:
- Added CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT and CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT constants
- Added MIN_INTERVALS_FOR_GAP_TOLERANCE = 6 (1.5h minimum for gap tolerance)
- Implemented _split_at_gap_clusters() for period recovery
- Implemented _check_short_period_strict() for strict short-period filtering
- Implemented _check_level_filter_with_gaps() with fallback splitting logic
- Extracted _check_sequence_with_gap_tolerance() for reusable core validation
- Enhanced _check_level_filter() to use gap-tolerant validation
Configuration UI:
- Added NumberSelector (0-8, slider mode) for gap count in config flow
- Added translations for all 5 languages (de, en, nb, nl, sv)
- Default: 0 (strict filtering, backwards compatible)
Impact: Users can now configure how many occasional level deviations are acceptable
within qualifying price periods. This reduces period fragmentation while maintaining
meaningful price-based filtering. Long periods are protected by the 25% cap, and
gap clusters trigger intelligent splitting to recover usable sub-periods.
Implemented multi-phase filter relaxation system to ensure minimum number
of best-price and peak-price periods are found, even on days with unusual
price patterns.
New configuration options per period type (best/peak):
- enable_min_periods_{best|peak}: Toggle feature on/off
- min_periods_{best|peak}: Target number of periods (default: 2)
- relaxation_step_{best|peak}: Step size for threshold increase (default: 25%)
Relaxation phases (applied sequentially until target reached):
1. Flex threshold increase (up to 4 steps, e.g., 15% → 18.75% → 22.5% → ...)
2. Volatility filter bypass + continued flex increase
3. All filters off + continued flex increase
Changes to period calculation:
- New calculate_periods_with_relaxation() wrapper function
- filter_periods_by_volatility() now applies post-calculation filtering
- _resolve_period_overlaps() merges baseline + relaxed periods intelligently
- Relaxed periods marked with relaxation_level, relaxation_threshold_* attributes
- Overlap detection prevents double-counting same intervals
Binary sensor attribute ordering improvements:
- Added helper methods for consistent attribute priority
- Relaxation info grouped in priority 6 (after detail attributes)
- Only shown when period was actually relaxed (relaxation_active=true)
Translation updates:
- Added UI labels + descriptions for 6 new config options (all 5 languages)
- Explained relaxation concept with examples in data_description fields
- Clarified volatility filter now applies per-period, not per-day
Impact: Users can configure integration to guarantee minimum number of
periods per day. System automatically relaxes filters when needed while
preserving baseline periods found with strict filters. Particularly useful
for automation reliability on days with flat pricing or unusual patterns.
Fixes edge case where no periods were found despite prices varying enough
for meaningful optimization decisions.
Moved filter logic and all period attribute calculations from binary_sensor.py
to coordinator.py and period_utils.py, following Home Assistant best practices
for data flow architecture.
ARCHITECTURE CHANGES:
Binary Sensor Simplification (~225 lines removed):
- Removed _build_periods_summary, _add_price_diff_for_period (calculation logic)
- Removed _get_period_intervals_from_price_info (107 lines, interval reconstruction)
- Removed _should_show_periods, _check_volatility_filter, _check_level_filter
- Removed _build_empty_periods_result (filtering result builder)
- Removed _get_price_hours_attributes (24 lines, dead code)
- Removed datetime import (unused after cleanup)
- New: _build_final_attributes_simple (~20 lines, timestamp-only)
- Result: Pure display-only logic, reads pre-calculated data from coordinator
Coordinator Enhancement (+160 lines):
- Added _should_show_periods(): UND-Verknüpfung of volatility and level filters
- Added _check_volatility_filter(): Checks min_volatility threshold
- Added _check_level_filter(): Checks min/max level bounds
- Enhanced _calculate_periods_for_price_info(): Applies filters before period calculation
- Returns empty periods when filters don't match (instead of calculating unnecessarily)
- Passes volatility thresholds (moderate/high/very_high) to PeriodConfig
Period Utils Refactoring (+110 lines):
- Extended PeriodConfig with threshold_volatility_moderate/high/very_high
- Added PeriodData NamedTuple: Groups timing data (start, end, length, position)
- Added PeriodStatistics NamedTuple: Groups calculated stats (prices, volatility, ratings)
- Added ThresholdConfig NamedTuple: Groups all thresholds + reverse_sort flag
- New _calculate_period_price_statistics(): Extracts price_avg/min/max/spread calculation
- New _build_period_summary_dict(): Builds final dict with correct attribute ordering
- Enhanced _extract_period_summaries(): Now calculates ALL attributes (no longer lightweight):
* price_avg, price_min, price_max, price_spread (in minor units: ct/øre)
* volatility (low/moderate/high/very_high based on absolute thresholds)
* rating_difference_% (average of interval differences)
* period_price_diff_from_daily_min/max (period avg vs daily reference)
* aggregated level and rating_level
* period_interval_count (renamed from interval_count for clarity)
- Removed interval_starts array (redundant - start/end/count sufficient)
- Function signature refactored from 9→4 parameters using NamedTuples
Code Organization (HA Best Practice):
- Moved calculate_volatility_level() from const.py to price_utils.py
- Rule: const.py should contain only constants, no functions
- Removed duplicate VOLATILITY_THRESHOLD_* constants from const.py
- Updated imports in sensor.py, services.py, period_utils.py
DATA FLOW:
Before:
API → Coordinator (basic enrichment) → Binary Sensor (calculate everything on each access)
After:
API → Coordinator (enrichment + filtering + period calculation with ALL attributes) →
Cached Data → Binary Sensor (display + timestamp only)
ATTRIBUTE STRUCTURE:
Period summaries now contain (following copilot-instructions.md ordering):
1. Time: start, end, duration_minutes
2. Decision: level, rating_level, rating_difference_%
3. Prices: price_avg, price_min, price_max, price_spread, volatility
4. Differences: period_price_diff_from_daily_min/max (conditional)
5. Details: period_interval_count, period_position
6. Meta: periods_total, periods_remaining
BREAKING CHANGES: None
- Period data structure enhanced but backwards compatible
- Binary sensor API unchanged (state + attributes)
Impact: Binary sensors now display pre-calculated data from coordinator instead
of calculating on every access. Reduces complexity, improves performance, and
centralizes business logic following Home Assistant coordinator pattern. All
period filtering (volatility + level) now happens in coordinator before caching.