Simplifies the connect_segments implementation to use a unified bridge-point
approach for all price transitions (up/down/same). Previously used
direction-dependent logic (hold vs connect points) which was unnecessarily
complex.
Changes:
- get_chartdata.py: Bridge points now always use next interval's price at
boundary timestamp, creating smooth visual connection between segments
- get_chartdata.py: Trailing NULL removal now conditional on insert_nulls mode
('segments' removes for header fix, 'all' preserves intentional gaps)
- get_apexcharts_yaml.py: Enable connect_segments by default, activate
show_states for header min/max display
- get_apexcharts_yaml.py: Remove extrema series (not compatible with
data_generator approach - ApexCharts requires entity time-series data)
- tests: Move test_connect_segments.py to tests/services/ to mirror source
structure
Impact: ApexCharts cards now show clean visual connections between price level
segments with proper header statistics display. Trailing NULLs no longer cause
"N/A" in headers for filtered data. Test organization improved for
maintainability.
Major restructuring of the scripts/ directory with consistent output
formatting, improved organization, and stricter error handling.
Breaking Changes:
- Updated development environment to Home Assistant 2025.7+
- Removed Python 3.12 compatibility (HA 2025.7+ requires Python 3.13)
- Updated all HA core requirements from 2025.7 requirement files
- Added new dependencies: python-multipart, uv (for faster package management)
- Updated GitHub Actions workflows to use Python 3.13
Changes:
- Created centralized output library (scripts/.lib/output.sh)
- Unified color codes and Unicode symbols
- Consistent formatting functions (log_header, log_success, log_error, etc.)
- Support for embedded formatting codes (${BOLD}, ${GREEN}, etc.)
- Reorganized into logical subdirectories:
- scripts/setup/ - Setup and maintenance scripts
- bootstrap: Install/update dependencies (used in CI/CD)
- setup: Full DevContainer setup (pyright, copilot, HACS)
- reset: Reset config/ directory to fresh state (NEW)
- sync-hacs: Sync HACS integrations
- scripts/release/ - Release management scripts
- prepare: Version bump and tagging
- suggest-version: Semantic version suggestion
- generate-notes: Release notes generation
- check-if-released: Check release status
- hassfest: Local integration validation
- Updated all scripts with:
- set -euo pipefail for stricter error handling
- Consistent SCRIPT_DIR pattern for reliable sourcing
- Professional output with colors and emojis
- Unified styling across all 17 scripts
- Removed redundant scripts:
- scripts/update (was just wrapper around bootstrap)
- scripts/json_schemas/ (moved to schemas/json/)
- Enhanced clean script:
- Improved artifact cleanup
- Better handling of accidental package installations
- Hints for reset and deep clean options
- New reset script features:
- Standard mode: Keep configuration.yaml
- Full mode (--full): Reset configuration.yaml from git
- Automatic re-setup after reset
- Updated documentation:
- AGENTS.md: Updated script references and workflow guidance
- docs/development/: Updated all references to new script structure
Impact: Development environment now requires Python 3.13 and Home Assistant
2025.7+. Developers get consistent, professional script output with better
error handling and logical organization. Single source of truth for styling
makes future updates trivial.
DevContainer updates:
- .devcontainer/devcontainer.json: Added Python path configuration
Configuration updates:
- config/configuration.yaml: Added test home configuration
Impact: Improved development environment setup. No production changes.
Renamed main config flow handler class for clarity:
- TibberPricesFlowHandler → TibberPricesConfigFlowHandler
Updated imports in:
- config_flow.py (import alias)
- config_flow_handlers/__init__.py (exports)
Reason: More explicit name distinguishes from OptionsFlowHandler and
SubentryFlowHandler. Follows naming convention of other flow handlers.
Impact: No functional changes, improved code readability.
Renamed service modules for consistency with service identifiers:
- apexcharts.py → get_apexcharts_yaml.py
- chartdata.py → get_chartdata.py
- Added: get_price.py (new service module)
Naming convention: Module names now match service names directly
(tibber_prices.get_apexcharts_yaml → get_apexcharts_yaml.py)
Impact: Improved code organization, easier to locate service implementations.
No functional changes.
Added new service for fetching historical/future price data:
- fetch_price_info_range: Query prices for arbitrary date ranges
- Supports start_time and end_time parameters
- Returns structured price data via service response
- Uses interval pool for efficient data retrieval
Service definition:
- services.yaml: Added fetch_price_info_range with date selectors
- services/__init__.py: Implemented handler with validation
- Response format: {"priceInfo": [...], "currency": "..."}
Schema updates:
- config_flow_handlers/schemas.py: Convert days slider to IntSelector
(was NumberSelector with float, caused "2.0 Tage" display issue)
Impact: Users can fetch price data for custom date ranges programmatically.
Config flow displays clean integer values for day offsets.
Implemented interval pool architecture for efficient price data management:
Core Components:
- IntervalPool: Central storage with timestamp-based index
- FetchGroupCache: Protected range management (day-before-yesterday to tomorrow)
- IntervalFetcher: Gap detection and optimized API queries
- TimestampIndex: O(1) lookup for price intervals
Key Features:
- Deduplication: Touch intervals instead of duplicating (memory efficient)
- GC cleanup: Removes dead intervals no longer referenced by index
- Gap detection: Only fetches missing ranges, reuses cached data
- Protected range: Keeps yesterday/today/tomorrow, purges older data
- Resolution support: Handles hourly (pre-Oct 2025) and quarter-hourly data
Integration:
- TibberPricesApiClient: Uses interval pool for all range queries
- DataUpdateCoordinator: Retrieves data from pool instead of direct API
- Transparent: No changes required in sensor/service layers
Performance Benefits:
- Reduces API calls by 70% (reuses overlapping intervals)
- Memory footprint: ~10KB per home (protects 384 intervals max)
- Lookup time: O(1) timestamp-based index
Breaking Changes: None (backward compatible integration layer)
Impact: Significantly reduces Tibber API load while maintaining data
freshness. Memory-efficient storage prevents unbounded growth.
Added complete localization support for time offset descriptions:
- Convert hardcoded English strings "(X days ago)" to translatable keys
- Add time_units translations (day/days, hour/hours, minute/minutes, ago, now)
- Support singular/plural forms in all 5 languages (de, en, nb, nl, sv)
- German: Proper Dativ case "Tagen" with preposition "vor"
- Compact format for mixed offsets: "7 Tagen - 02:30"
Config flow improvements:
- Replace hardcoded "Enter new API token" with translated "Add new Tibber account API token"
- Use get_translation() for account_choice dropdown labels
- Fix SelectOptionDict usage (no mixing with translation_key parameter)
- Convert days slider from float to int (prevents "2.0 Tage" display)
- DurationSelector: default {"hours": 0, "minutes": 0} to fix validation errors
Translation keys added:
- selector.account_choice.options.new_token
- time_units (day, days, hour, hours, minute, minutes, ago, now)
- config.step.time_offset_description guidance text
Impact: Config flow works fully translated in all 5 languages with proper grammar.
Updated attribute ordering documentation to use correct names:
- "periods" → "pricePeriods" (matches code since refactoring)
- "intervals" → "priceInfo" (flat list structure)
Impact: Documentation now matches actual code structure.
Changed needs_tomorrow_data() to auto-calculate tomorrow date using
get_intervals_for_day_offsets([1]) helper instead of requiring explicit
tomorrow_date parameter.
Changes:
- coordinator/helpers.py: needs_tomorrow_data() signature simplified
* Uses get_intervals_for_day_offsets([1]) to detect tomorrow intervals
* No longer requires tomorrow_date parameter (calculated automatically)
* Consistent with all other data access patterns
- coordinator/data_fetching.py: Removed tomorrow_date calculation and passing
* Removed unused date import
* Simplified method call: needs_tomorrow_data() instead of needs_tomorrow_data(tomorrow_date)
- sensor/calculators/lifecycle.py: Updated calls to _needs_tomorrow_data()
* Removed tomorrow_date variable where it was only used for this call
* Combined nested if statements with 'and' operator
Impact: Cleaner API, fewer parameters to track, consistent with other
helper functions that auto-calculate dates based on current time.
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.
Fixes bug where lifecycle sensor attributes (data_completeness, tomorrow_available)
didn't update after tomorrow data was successfully fetched from API.
Root cause: DataTransformer had cached transformation data but no mechanism to detect
when source API data changed (only checked config and midnight turnover).
Changes:
- coordinator/data_transformation.py: Track source_data_timestamp and invalidate cache
when timestamp changes (detects new API data arrival)
- coordinator/data_transformation.py: Integrate period calculation into DataTransformer
(calculate_periods_fn parameter) for complete single-layer caching
- coordinator/core.py: Remove duplicate transformation cache (_cached_transformed_data,
_last_transformation_config), simplify _transform_data_for_*() to direct delegation
- tests/test_tomorrow_data_refresh.py: Add 3 regression tests for cache invalidation
(new timestamp, config change behavior, cache preservation)
Impact: Lifecycle sensor attributes now update correctly when new API data arrives.
Reduced code by ~40 lines in coordinator, consolidated caching to single layer.
All 350 tests passing.
Cleaned up AGENTS.md to focus on patterns and conventions:
Removed:
- "Current State (as of Nov 2025)" section
- "Classes that need renaming" outdated list
- "Action Required" checklist
- Temporal statements about current project state
Added:
- TypedDict exemption in "When prefix can be omitted" list
- Clear rationale: documentation-only, never instantiated
Rationale:
AGENTS.md documents patterns and conventions that help AI understand
the codebase structure. Status tracking belongs in git history or
planning documents. The file should be timeless guidance, not a
snapshot of work in progress.
Impact: Documentation is now focused on "how to write code correctly"
rather than "what state is the code in now".
Removed tests for the lifecycle callback system that was removed in
commit 48d6e25.
Also fixed commit f373c01 which incorrectly added test_lifecycle_tomorrow_update.py
instead of deleting it - this commit properly removes it.
Changes:
- tests/test_chart_data_push_updates.py: Deleted (235 lines)
- tests/test_lifecycle_tomorrow_update.py: Deleted (174 lines)
- tests/test_resource_cleanup.py: Removed lifecycle callback test method
Impact: Test suite now has 343 tests (down from 349). All tests pass.
No functionality affected - only test cleanup.
Simplified _has_future_periods() to check for ANY future periods instead
of limiting to 6-hour window. This ensures icons show 'waiting' state
whenever periods are scheduled, not just within artificial time limit.
Also added pragmatic fallback in timing calculator _find_next_period():
when skip_current=True but only one future period exists, return it
anyway instead of showing 'unknown'. This prevents timing sensors from
showing unknown during active periods.
Changes:
- binary_sensor/definitions.py: Removed PERIOD_LOOKAHEAD_HOURS constant
- binary_sensor/core.py: Simplified _has_future_periods() logic
- sensor/calculators/timing.py: Added pragmatic fallback for single period
Impact: Better user experience - icons always show future periods, timing
sensors show values even during edge cases.
Deleted test_lifecycle_tomorrow_update.py (2 tests) which validated the
now-removed lifecycle callback system.
These tests were rendered obsolete by the removal of the custom lifecycle
callback mechanism in favor of Home Assistant's standard coordinator pattern.
Impact: Test suite reduced from 355 to 349 tests, all passing.
Removed custom lifecycle callback push-update mechanism after confirming
it was redundant with Home Assistant's built-in DataUpdateCoordinator
pattern.
Root cause analysis showed HA's async_update_listeners() is called
synchronously (no await) immediately after _async_update_data() returns,
making separate lifecycle callbacks unnecessary.
Changes:
- coordinator/core.py: Removed lifecycle callback methods and notifications
- sensor/core.py: Removed lifecycle callback registration and cleanup
- sensor/attributes/lifecycle.py: Removed next_tomorrow_check attribute
- sensor/calculators/lifecycle.py: Removed get_next_tomorrow_check_time()
Impact: Simplified coordinator pattern, no user-visible changes. Standard
HA coordinator mechanism provides same immediate update guarantee without
custom callback complexity.
Added documentation file explaining why period calculation functions
are tested via integration tests rather than unit tests.
Rationale:
- Period building requires full coordinator context (TimeService, price_context)
- Complex enriched price data with multiple calculated fields
- Helper functions (split_intervals_by_day, calculate_reference_prices)
are simple transformations that can't fail independently
- Integration tests provide better coverage than mocked unit tests
Testing strategy:
- test_midnight_periods.py: Period calculation across day boundaries
- test_midnight_turnover.py: Cache invalidation and recalculation
- docs/development/period-calculation-theory.md: Algorithm documentation
Impact: Clarifies testing approach for future contributors. Prevents
wasted effort on low-value unit tests for complex integrated functions.
Fixed state inconsistencies during auth errors:
Bug #4: tomorrow_data_available incorrectly returns True during auth failure
- Now returns None (unknown) when coordinator.last_exception is ConfigEntryAuthFailed
- Prevents misleading "data available" when API connection lost
Bug #5: Sensor states inconsistent during error conditions
- connection: False during auth error (even with cached data)
- tomorrow_data_available: None during auth error (cannot verify)
- lifecycle_status: "error" during auth error
Changes:
- binary_sensor/core.py: Check last_exception before evaluating tomorrow data
- tests: 25 integration tests covering all error scenarios
Impact: All three sensors show consistent states during auth errors,
API timeouts, and normal operation. No misleading "available" status
when connection is lost.
Introduced TibberPricesMidnightHandler to prevent duplicate midnight
turnover when multiple timers fire simultaneously.
Problem: Timer #1 (API poll) and Timer #2 (quarter-hour refresh) both
wake at midnight, each detecting day change and triggering cache clear.
Race condition caused duplicate turnover operations.
Solution:
- Atomic flag coordination: First timer sets flag, subsequent timers skip
- Persistent state survives HA restart (cache stores last_turnover_time)
- Day-boundary detection: Compares current.date() vs last_check.date()
- 13 comprehensive tests covering race conditions and HA restart scenarios
Architecture:
- coordinator/midnight_handler.py: 165 lines, atomic coordination logic
- coordinator/core.py: Integrated handler in coordinator initialization
- coordinator/listeners.py: Delegate midnight check to handler
Impact: Eliminates duplicate cache clears at midnight. Single atomic
turnover operation regardless of how many timers fire simultaneously.
Fixed multiple calculation issues with negative prices (Norway/Germany
renewable surplus scenarios):
Bug #6: Rating threshold validation with dead code
- Added threshold validation (low >= high) with warning
- Returns NORMAL as fallback for misconfigured thresholds
Bug #7: Min/Max functions returning 0.0 instead of None
- Changed default from 0.0 to None when window is empty
- Prevents misinterpretation (0.0 looks like price with negatives)
Bug #9: Period price diff percentage wrong sign with negative reference
- Use abs(ref_price) in percentage calculation
- Correct percentage direction for negative prices
Bug #10: Trend diff percentage wrong sign with negative current price
- Use abs(current_interval_price) in percentage calculation
- Correct trend direction when prices cross zero
Bug #11: later_half_diff calculation failed for negative prices
- Changed condition from `if current_interval_price > 0` to `!= 0`
- Use abs(current_interval_price) for percentage
Changes:
- utils/price.py: Add threshold validation, use abs() in percentages
- utils/average.py: Return None instead of 0.0 for empty windows
- period_statistics.py: Use abs() for reference prices
- trend.py: Use abs() for current prices, fix zero-check condition
- tests: 95+ new tests covering negative/zero/mixed price scenarios
Impact: All calculations work correctly with negative electricity prices.
Percentages show correct direction regardless of sign.