Commit graph

434 commits

Author SHA1 Message Date
Julian Pawlowski
b9647659bd chore(devcontainer): update Node.js version to 24 in devcontainer configuration 2025-11-26 14:36:30 +00:00
Julian Pawlowski
7c0000039e refactor(config_flow): disable subentry flow temporarily due to incomplete time-travel feature 2025-11-26 14:36:08 +00:00
Julian Pawlowski
50021ce3ba chore(devcontainer): add setup-git.sh script for host Git configuration 2025-11-26 14:36:00 +00:00
Julian Pawlowski
a90fef6f2d refactor(scripts): reorganize and standardize development scripts
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.
2025-11-26 13:11:52 +00:00
Julian Pawlowski
1a396a4faf docs(agents): update class naming documentation
Updated AGENTS.md:
- Fixed TibberPricesFlowHandler → TibberPricesConfigFlowHandler reference

Impact: Documentation now matches current code structure.
2025-11-25 20:44:40 +00:00
Julian Pawlowski
cca104dfc4 chore(dev): update dev environment configuration
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.
2025-11-25 20:44:40 +00:00
Julian Pawlowski
3c69807c05 refactor(logging): use details logger for verbose period calculation logs
Moved verbose debug logging to separate _LOGGER_DETAILS logger:
- core.py: Outlier flex capping messages
- outlier_filtering.py: Spike detection, context validation, smoothing details
- period_building.py: Level filter details, gap tolerance info
- relaxation.py: Per-phase iteration details, filter combination attempts

Pattern: Main _LOGGER for high-level progress, _LOGGER_DETAILS for step-by-step

Benefits:
- Users can disable verbose logs via logger configuration
- Main DEBUG log stays readable (high-level flow)
- Details available when needed for troubleshooting

Added:
- period_overlap.py: Docstring for extend_period_if_adjacent()

Impact: Cleaner log output by default. Enable details logger
(homeassistant.components.tibber_prices.coordinator.period_handlers.details)
for deep debugging.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
9ae618fff9 refactor(config_flow): rename TibberPricesFlowHandler to TibberPricesConfigFlowHandler
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.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
6338f51527 refactor(services): rename service modules to match service names
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.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
7c117a2267 docs(schemas): update JSON schemas for translation structure
Updated translation JSON schemas to reflect current implementation:
- translation_schema.json: Documents HA's official translation structure
  (config, options, selector paths, entity states)
- custom_translation_schema.json: Documents custom extension structure
  (entity descriptions not supported by HA schema)

Schema updates:
- Added time_units section (day, days, hour, hours, minute, minutes, ago, now)
- Documented selector.{translation_key}.options.{value} pattern
- Added account_choice selector structure

Impact: Provides validation and documentation for translation files.
Helps maintain consistency across all 5 language files (de, en, nb, nl, sv).
2025-11-25 20:44:39 +00:00
Julian Pawlowski
b6f5f1678f feat(services): add fetch_price_info_range service and update schema
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.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
44f6ae2c5e feat(interval-pool): add intelligent interval caching and memory optimization
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.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
74789877ff test: fix async mocking and add noqa comments for private access
Fixed test issues:
- test_resource_cleanup.py: Use AsyncMock for async_unload_entry
  (was MagicMock, caused TypeError with async context)
- Added # noqa: SLF001 comments to all private member access in tests
  (18 instances - legitimate test access patterns)

Test files updated:
- test_resource_cleanup.py (AsyncMock fix)
- test_interval_pool_memory_leak.py (8 noqa comments)
- test_interval_pool_optimization.py (4 noqa comments)

Impact: All tests pass linting, async tests execute correctly.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
e04e38d09f refactor(logging): remove verbose debug logging from price enrichment
Removed excessive debug logging in enrich_price_info_with_differences():
- Deleted per-interval "Processing" messages (cluttered logs)
- Kept boundary INFO messages (enrichment start/skip counts)
- Removed unused variable expected_intervals_24h
- Removed unused parameter day_label from _process_price_interval()

Impact: Cleaner logs, no functional changes. Reduces log volume during
price data processing.
2025-11-25 20:44:39 +00:00
Julian Pawlowski
2449c28a88 feat(i18n): localize time offset descriptions and config flow strings
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.
2025-11-25 20:44:39 +00:00
dependabot[bot]
bab72ac341
chore(deps): bump actions/setup-python from 6.0.0 to 6.1.0 (#41)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](e797f83bcb...83679a892e)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-25 21:30:12 +01:00
dependabot[bot]
78ef7d1098
chore(deps): update pre-commit requirement (#38) 2025-11-24 22:26:21 +01:00
dependabot[bot]
9c86de20fc
chore(deps): bump home-assistant/actions (#39) 2025-11-24 22:25:28 +01:00
Julian Pawlowski
7e47ef5995 docs: fix attribute names in AGENTS.md examples
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.
2025-11-24 16:26:23 +00:00
Julian Pawlowski
6b78cd757f refactor: simplify needs_tomorrow_data() - remove tomorrow_date parameter
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.
2025-11-24 16:26:08 +00:00
Julian Pawlowski
2de793cfda refactor: migrate from multi-home to single-home-per-coordinator architecture
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.
2025-11-24 16:24:37 +00:00
Julian Pawlowski
981fb08a69 refactor(price_info): price data handling to use unified interval retrieval
- 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.
2025-11-24 10:49:34 +00:00
Julian Pawlowski
294d84128b refactor(services): rename and reorganize custom services for clarity and functionality 2025-11-23 13:17:21 +00:00
Julian Pawlowski
9ee7f81164 fix(coordinator): invalidate transformation cache when source data changes
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.
2025-11-23 13:10:19 +00:00
Julian Pawlowski
cfae3c9387 chore(release): bump version to 0.14.0 2025-11-23 11:20:16 +00:00
Julian Pawlowski
ea21b229ee refactor(calculators): consolidate duplicate data access patterns
Refactored Trend, Timing, and Lifecycle calculators to use BaseCalculator
helper methods instead of duplicating data access logic.

Changes:
- TrendCalculator: Simplified 12 lines of repeated price_info access to
  3-4 clean property calls (intervals_today/tomorrow, get_all_intervals)
- TimingCalculator: Replaced direct coordinator.data checks with has_data()
- LifecycleCalculator: Replaced 5 lines of nested gets with 2 helper calls

Benefits:
- Eliminated 10+ duplicate data access patterns
- Consistent None-handling across all calculators
- Single source of truth for coordinator data access
- Easier to maintain (changes propagate automatically)

BaseCalculator helpers used:
- has_data(): Replaces 'if not self.coordinator.data:' checks
- intervals_today/tomorrow: Direct property access to day intervals
- get_intervals(day): Safe day-specific interval retrieval
- get_all_intervals(): Combined yesterday+today+tomorrow
- coordinator_data: Property for coordinator.data access

Validation:
- Type checker: 0 errors, 0 warnings
- Tests: 347 passed, 2 skipped (no behavior change)
- Net: 19 deletions, 14 insertions (5 lines removed, patterns simplified)

Impact: Cleaner code, reduced duplication, consistent error handling.
Future calculator additions will automatically benefit from centralized
data access patterns.
2025-11-22 14:54:06 +00:00
Julian Pawlowski
ed08bc29da docs(architecture): document import architecture and dependency management
Added comprehensive 'Import Architecture and Dependency Management' section
to AGENTS.md documenting the calculator package's import patterns and
dependency flow.

Key documentation:
- Dependency flow: calculators → attributes/helpers (one-way, no circular)
- Hybrid Pattern: Trend/Volatility calculators build own attributes
  (intentional design from Nov 2025 refactoring)
- TYPE_CHECKING best practices: All 8 calculators use optimal pattern
- Import anti-patterns to avoid

Analysis findings:
- No circular dependencies detected (verified Jan 2025)
- All TYPE_CHECKING usage already optimal (no changes needed)
- Clean separation: attributes/helpers never import from calculators
- Backwards dependency (calculator → attributes) limited to 2 calculators

Validation:
- Type checker: 0 errors, 0 warnings
- Linter: All checks passed
- Tests: 347 passed, 2 skipped

Impact: Documents architectural decisions for future maintenance.
Provides clear guidelines for adding new calculators or modifying
import patterns without introducing circular dependencies.
2025-11-22 14:48:50 +00:00
Julian Pawlowski
36fef2da89 docs(agents): remove status tracking, focus on patterns only
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".
2025-11-22 14:40:16 +00:00
Julian Pawlowski
3b11c6721e feat(types): add TypedDict documentation and BaseCalculator helpers
Phase 1.1 - TypedDict Documentation System:
- Created sensor/types.py with 14 TypedDict classes documenting sensor attributes
- Created binary_sensor/types.py with 3 TypedDict classes for binary sensors
- Added Literal types (PriceLevel, PriceRating, VolatilityLevel, DataCompleteness)
- Updated imports in sensor/attributes/__init__.py and binary_sensor/attributes.py
- Changed function signatures to use dict[str, Any] for runtime flexibility
- TypedDicts serve as IDE documentation, not runtime validation

Phase 1.2 - BaseCalculator Improvements:
- Added 8 smart data access methods to BaseCalculator:
  * get_intervals(day) - day-specific intervals with None-safety
  * intervals_today/tomorrow/yesterday - convenience properties
  * get_all_intervals() - combined yesterday+today+tomorrow
  * find_interval_at_offset(offset) - interval lookup with bounds checking
  * safe_get_from_interval(interval, key, default) - safe dict access
  * has_data() / has_price_info() - existence checks
  * get_day_intervals(day) - alias for consistency
- Refactored 5 calculator files to use new helper methods:
  * daily_stat.py: -11 lines (coordinator_data checks, get_intervals usage)
  * interval.py: -18 lines (eliminated find_price_data_for_interval duplication)
  * rolling_hour.py: -3 lines (simplified interval collection)
  * volatility.py: -4 lines (eliminated price_info local variable)
  * window_24h.py: -2 lines (replaced coordinator_data check)
  * Total: -38 lines of duplicate code eliminated
- Added noqa comment for lazy import (circular import avoidance)

Type Duplication Resolution:
- Identified duplication: Literal types in types.py vs string constants in const.py
- Attempted solution: Derive constants from Literal types using typing.get_args()
- Result: Circular import failure (const.py → sensor/types.py → sensor/__init__.py → const.py)
- Final solution: Keep string constants as single source of truth
- Added SYNC comments in all 3 files (const.py, sensor/types.py, binary_sensor/types.py)
- Accept manual synchronization to avoid circular dependencies
- Platform separation maintained (no cross-imports between sensor/ and binary_sensor/)

Impact: Developers get IDE autocomplete and type hints for attribute dictionaries.
Calculator code is more readable with fewer None-checks and clearer data access patterns.
Type/constant duplication documented with sync requirements.
2025-11-22 14:32:24 +00:00
Julian Pawlowski
32857c0cc0 test: remove obsolete lifecycle callback tests
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.
2025-11-22 13:04:47 +00:00
Julian Pawlowski
2d0febdab3 fix(binary_sensor): remove 6-hour lookahead limit for period icons
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.
2025-11-22 13:04:17 +00:00
Julian Pawlowski
f373c01fbb test: remove obsolete lifecycle callback tests
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.
2025-11-22 13:01:30 +00:00
Julian Pawlowski
48d6e2580a refactor(coordinator): remove redundant lifecycle callback system
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.
2025-11-22 13:01:17 +00:00
Julian Pawlowski
f2627a5292 fix(period_handlers): normalize flex and min_distance to absolute values
Fixed critical sign convention bug where negative user-facing values
(e.g., peak_price_flex=-20%) weren't normalized for internal calculations,
causing incorrect period filtering.

Changes:
- periods.py: Added abs() normalization for flex and min_distance_from_avg
- core.py: Added comment documenting flex normalization by get_period_config()
- level_filtering.py: Simplified check_interval_criteria() to work with normalized
  positive values only, removed complex negative price handling
- relaxation.py: Removed sign handling since values are pre-normalized

Internal convention:
- User-facing: Best price uses positive (+15%), Peak price uses negative (-20%)
- Internal: Always positive (0.15 or 0.20) with reverse_sort flag for direction

Added comprehensive regression tests:
- test_best_price_e2e.py: Validates Best price periods generate correctly
- test_peak_price_e2e.py: Validates Peak price periods generate correctly
- test_level_filtering.py: Unit tests for flex/distance filter logic

Impact: Peak price periods now generate correctly. Bug caused 100% FLEX
filtering (192/192 intervals blocked) → 0 periods found. Fix ensures
reasonable filtering (~40-50%) with periods successfully generated.
2025-11-22 13:01:01 +00:00
Julian Pawlowski
476b0f6ef8 chore(release): bump version to 0.13.0 2025-11-22 04:47:44 +00:00
Julian Pawlowski
f128d00c99 test(period): document period calculation testing strategy
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.
2025-11-22 04:47:09 +00:00
Julian Pawlowski
a85c37e5ca test(time): add boundary tolerance and DST handling tests
Added 40+ tests for TibberPricesTimeService:

Quarter-hour rounding with ±2s tolerance:
- 17 tests covering boundary cases (exact, within tolerance, outside)
- Midnight wrap-around scenarios
- Microsecond precision edge cases

DST handling (23h/25h days):
- Standard day: 96 intervals (24h × 4)
- Spring DST: 92 intervals (23h × 4)
- Fall DST: 100 intervals (25h × 4)
- Note: Full DST tests require Europe/Berlin timezone setup

Day boundaries and interval offsets:
- Yesterday/today/tomorrow boundary calculation
- Interval offset (current/next/previous) with day wrapping
- Time comparison helpers (is_current_interval)

Impact: Validates critical time handling logic. Ensures quarter-hour
rounding works correctly for sensor updates despite HA scheduling jitter.
2025-11-22 04:46:53 +00:00
Julian Pawlowski
91ef2806e5 test(timers): comprehensive timer architecture validation
Added 60+ tests for three-timer architecture:

Timer #1 (API polling): next_api_poll_time calculation
- 8 tests covering timer offset calculation before/after 13:00
- Tests tomorrow data presence logic
- Verifies minute/second offset preservation

Timer #2 (quarter-hour refresh): :00, :15, :30, :45 boundaries
- 10 tests covering registration, cancellation, callback execution
- Verifies exact boundary timing (second=0)
- Tests independence from Timer #3

Timer #3 (minute refresh): :00, :30 every minute
- 6 tests covering 30-second boundary registration
- Verifies timing sensors assignment
- Tests countdown/progress update frequency

Sensor assignment:
- 20+ tests mapping 80+ sensors to correct timers
- Verifies TIME_SENSITIVE and MINUTE_UPDATE constants
- Catches missing/incorrect timer assignments

Impact: Comprehensive validation of timer architecture prevents
regression in entity update scheduling. Documents which sensors
use which timers.
2025-11-22 04:46:30 +00:00
Julian Pawlowski
d1376c8921 test(cleanup): add comprehensive resource cleanup tests
Added 40+ tests verifying memory leak prevention patterns:

- Listener cleanup: Time-sensitive, minute-update, lifecycle callbacks
- Timer cancellation: Quarter-hour, minute timers
- Config entry cleanup: Options update listener via async_on_unload
- Cache invalidation: Config, period, trend caches
- Storage cleanup: Cache files deleted on entry removal

Tests verify cleanup patterns exist in code (not full integration tests
due to complex mocking requirements).

Impact: Documents and tests cleanup contracts for future maintainability.
Prevents memory leaks when entities removed or config changed.
2025-11-22 04:46:11 +00:00
Julian Pawlowski
c7f6843c5b fix(sensors): ensure connection/tomorrow_data/lifecycle consistency
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.
2025-11-22 04:45:57 +00:00
Julian Pawlowski
85fe9666a7 feat(coordinator): add atomic midnight turnover coordination
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.
2025-11-22 04:45:41 +00:00
Julian Pawlowski
9c3c094305 fix(calculations): handle negative electricity prices correctly
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.
2025-11-22 04:45:23 +00:00
Julian Pawlowski
9a6eb44382 refactor(config): use negative values for Best Price min_distance
Best Price min_distance now uses negative values (-50 to 0) to match
semantic meaning "below average". Peak Price continues using positive
values (0 to 50) for "above average".

Uniform formula: avg * (1 + distance/100) works for both period types.
Sign indicates direction: negative = toward MIN (cheap), positive = toward MAX (expensive).

Changes:
- const.py: DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG = -5 (was 5)
- schemas.py: Best Price range -50 to 0, Peak Price range 0 to 50
- validators.py: Separate validate_best_price_distance_percentage()
- level_filtering.py: Simplified to uniform formula (removed conditionals)
- translations: Separate error messages for Best/Peak distance validation
- tests: 37 comprehensive validator tests (100% coverage)

Impact: Configuration UI now visually represents direction relative to average.
Users see intuitive negative values for "below average" pricing.
2025-11-22 04:44:57 +00:00
Julian Pawlowski
215ac02302 feat(sensors): add lifecycle callback for chart_data_export sensor
chart_data_export now registers lifecycle callback for immediate
updates when coordinator data changes ("fresh" lifecycle state).
Previously only updated via polling intervals.

Changes:
- Register callback in sensor constructor (when entity_key matches)
- Callback triggers async_write_ha_state() on "fresh" lifecycle
- 5 new tests covering callback registration and triggering

Impact: Chart data export updates immediately on API data arrival,
enabling real-time dashboard updates without polling delay.
2025-11-22 04:44:38 +00:00
Julian Pawlowski
49866f26fa fix(coordinator): use coordinator update timestamp for cache validity
Cache validity now checks _last_coordinator_update (within 30min)
instead of _api_calls_today counter. Fixes false "stale" status
when coordinator runs every 15min but cache validation was only
checking API call counter.

Bug #1: Cache validity shows "stale" at 05:57 AM
Bug #2: Cache age calculation incorrect after midnight turnover
Bug #3: get_cache_validity inconsistent with cache_age sensor

Changes:
- Coordinator: Use _last_coordinator_update for cache validation
- Lifecycle: Extract cache validation to dedicated helper function
- Tests: 7 new tests covering midnight scenarios and edge cases

Impact: Cache validity sensor now accurately reflects coordinator
activity, not just explicit API calls. Correctly handles midnight
turnover without false "stale" status.
2025-11-22 04:44:22 +00:00
Julian Pawlowski
c6f41b1aa5 fix(manifest): remove integration_type field 2025-11-22 03:51:58 +00:00
Julian Pawlowski
c0069e32b8 fix(listeners): ensure both normal and time-sensitive listeners are notified after midnight turnover 2025-11-21 23:57:04 +00:00
Julian Pawlowski
f60b5990ae test: add pytest framework and midnight-crossing tests
Set up pytest with Home Assistant support and created 6 tests for
midnight-crossing period logic (5 unit tests + 1 integration test).

Added pytest configuration, test dependencies, test runner script
(./scripts/test), and comprehensive tests for group_periods_by_day()
and midnight turnover consistency.

All tests pass in 0.12s.

Impact: Provides regression testing for midnight-crossing period bugs.
Tests validate periods remain visible across midnight turnover.
2025-11-21 23:47:01 +00:00
Julian Pawlowski
47b0a298d4 feat(periods): add midnight-crossing periods and day volatility attributes
Periods can now naturally cross midnight boundaries, and new diagnostic
attributes help users understand price classification changes at midnight.

**New Features:**

1. Midnight-Crossing Period Support (relaxation.py):
   - group_periods_by_day() assigns periods to ALL spanned days
   - Periods crossing midnight appear in both yesterday and today
   - Enables period formation across calendar day boundaries
   - Ensures min_periods checking works correctly at midnight

2. Extended Price Data Window (relaxation.py):
   - Period calculation now uses full 3-day data (yesterday+today+tomorrow)
   - Enables natural period formation without artificial midnight cutoff
   - Removed date filter that excluded yesterday's prices

3. Day Volatility Diagnostic Attributes (period_statistics.py, core.py):
   - day_volatility_%: Daily price spread as percentage (span/avg × 100)
   - day_price_min/max/span: Daily price range in minor currency (ct/øre)
   - Helps detect when midnight classification changes are economically significant
   - Uses period start day's reference prices for consistency

**Documentation:**

4. Design Principles (period-calculation-theory.md):
   - Clarified per-day evaluation principle (always was the design)
   - Added comprehensive section on midnight boundary handling
   - Documented volatility threshold separation (sensor vs period filters)
   - Explained market context for midnight price jumps (EPEX SPOT timing)

5. User Guides (period-calculation.md, automation-examples.md):
   - Added \"Midnight Price Classification Changes\" troubleshooting section
   - Provided automation examples using volatility attributes
   - Explained why Best→Peak classification can change at midnight
   - Documented level filter volatility threshold behavior

**Architecture:**

- Per-day evaluation: Each interval evaluated against its OWN day's min/max/avg
  (not period start day) ensures mathematical correctness across midnight
- Period boundaries: Periods can naturally cross midnight but may split when
  consecutive days differ significantly (intentional, mathematically correct)
- Volatility thresholds: Sensor thresholds (user-configurable) remain separate
  from period filter thresholds (fixed internal) to prevent unexpected behavior

Impact: Periods crossing midnight are now consistently visible before and
after midnight turnover. Users can understand and handle edge cases where
price classification changes at midnight on low-volatility days.
2025-11-21 23:18:46 +00:00
dependabot[bot]
dd12f97207
chore(deps): bump astral-sh/setup-uv from 7.1.3 to 7.1.4 (#34)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.3 to 7.1.4.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](5a7eac68fb...1e862dfacb)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 7.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-21 21:22:38 +01:00