Commit graph

283 commits

Author SHA1 Message Date
dependabot[bot]
a2215517f2
chore(deps): bump actions/checkout from 5.0.1 to 6.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 20:17:45 +00:00
Julian Pawlowski
2e5b48192a chore(release): bump version to 0.12.1 2025-11-21 18:33:18 +00:00
Julian Pawlowski
e35729d9b7 fix(coordinator): tomorrow sensors show unknown after 13:00 data fetch
Synchronized coordinator._cached_price_data after API calls to ensure tomorrow data is available for sensor value calculations and lifecycle state detection.

Impact: Tomorrow sensors now display values correctly after afternoon data fetch. Lifecycle sensor status remains stable without flickering between "searching_tomorrow" and other states.
2025-11-21 18:32:40 +00:00
Julian Pawlowski
f6b553d90e fix(periods): restore relaxation metadata marking with correct sign handling
Restored mark_periods_with_relaxation() function and added call in
relax_all_prices() to properly mark periods found through relaxation.

Problem: Periods found via relaxation were missing metadata attributes:
- relaxation_active
- relaxation_level
- relaxation_threshold_original_%
- relaxation_threshold_applied_%

These attributes are expected by:
- period_overlap.py: For merging periods with correct relaxation info
- binary_sensor/attributes.py: For displaying relaxation info to users

Implementation:
- Added reverse_sort parameter to preserve sign semantics
- For Best Price: Store positive thresholds (e.g., +15%, +18%)
- For Peak Price: Store negative thresholds (e.g., -20%, -23%)
- Mark periods immediately after calculate_periods() and before
  resolve_period_overlaps() so metadata is preserved during merging

Impact: Users can now see which periods were found through relaxation
and at what flex threshold. Peak Price periods show negative thresholds
matching the user's configuration semantics (negative = below maximum).
2025-11-21 17:40:15 +00:00
Julian Pawlowski
14b68a504b refactor(config): optimize volatility thresholds with separate ranges and improved UX
Volatility Threshold Optimization:
- Replaced global MIN/MAX_VOLATILITY_THRESHOLD (0-100%) with six separate
  constants for overlapping ranges per threshold level
- MODERATE: 5.0-25.0% (was: 0-100%)
- HIGH: 20.0-40.0% (was: 0-100%)
- VERY_HIGH: 35.0-80.0% (was: 0-100%)
- Added detailed comments explaining ranges and cascading requirements

Validators:
- Added three specific validation functions (one per threshold level)
- Added cross-validation ensuring MODERATE < HIGH < VERY_HIGH
- Added fallback to existing option values for completeness check
- Updated error keys to specific messages per threshold level

UI Improvements:
- Changed NumberSelector mode: BOX → SLIDER (consistency with other config steps)
- Changed step size: 0.1% → 1.0% (better UX, sufficient precision)
- Updated min/max ranges to match new validation constants

Translations:
- Removed: "invalid_volatility_threshold" (generic)
- Added: "invalid_volatility_threshold_moderate/high/very_high" (specific ranges)
- Added: "invalid_volatility_thresholds" (cross-validation error)
- Updated all 5 languages (de, en, nb, nl, sv)

Files modified:
- config_flow_handlers/options_flow.py: Updated validation logic
- config_flow_handlers/schemas.py: Updated NumberSelector configs
- config_flow_handlers/validators.py: Added specific validators + cross-validation
- const.py: Replaced global constants with six specific constants
- translations/*.json: Updated error messages (5 languages)

Impact: Users get clearer validation errors with specific ranges shown,
better UX with sliders and appropriate step size, and guaranteed
threshold ordering (MODERATE < HIGH < VERY_HIGH).
2025-11-21 17:31:07 +00:00
Julian Pawlowski
0fd98554ae refactor(entity): switch description content based on extended_descriptions
Changed description attribute behavior from "add separate long_description
attribute" to "switch description content" when CONF_EXTENDED_DESCRIPTIONS
is enabled.

OLD: description always shown, long_description added as separate attribute
NEW: description content switches between short and long based on config

Implementation:
- Check extended_descriptions flag BEFORE loading translation
- Load "long_description" key if enabled, fallback to "description" if missing
- Assign loaded content to "description" attribute (same key always)
- usage_tips remains separate attribute (only when extended=true)
- Updated both sync (entities) and async (services) versions

Added PLR0912 noqa: Branch complexity justified by feature requirements
(extended check + fallback logic + position handling).

Impact: Users see more detailed descriptions when extended mode enabled,
without attribute clutter. Fallback ensures robustness if long_description
missing in translations.
2025-11-21 17:30:29 +00:00
Julian Pawlowski
7a1675a55a fix(api): initialize time attribute to prevent AttributeError
Fixed uninitialized self.time attribute causing AttributeError during
config entry creation. Added explicit initialization to None with
Optional type annotation and guard in _get_price_info_for_specific_homes().

Impact: Config flow no longer crashes when creating initial config entry.
Users can complete setup without errors.
2025-11-21 17:29:04 +00:00
Julian Pawlowski
ebd1b8ddbf chore: Enhance validation logic and constants for options configuration flow
- Added new validation functions for various parameters including flexibility percentage, distance percentage, minimum periods, gap count, relaxation attempts, price rating thresholds, volatility threshold, and price trend thresholds.
- Updated constants in `const.py` to define maximum and minimum limits for the new validation criteria.
- Improved error messages in translations for invalid parameters to provide clearer guidance to users.
- Adjusted existing validation functions to ensure they align with the new constants and validation logic.
2025-11-21 13:57:35 +00:00
Julian Pawlowski
db3268e54d chore(release): bump version to 0.12.0 2025-11-21 11:19:14 +00:00
Julian Pawlowski
b461e89f08 fix(setup): improve conditional checks for optional package installations 2025-11-21 11:14:00 +00:00
dependabot[bot]
5a77734ec5
chore(deps): bump actions/checkout from 5.0.1 to 6.0.0 (#32) 2025-11-20 23:13:16 +01:00
Julian Pawlowski
189d3ba84d feat(sensor): add data lifecycle diagnostic sensor with push updates
Add comprehensive data_lifecycle_status sensor showing real-time cache
vs fresh API data status with 6 states and 13+ detailed attributes.

Key features:
- 6 lifecycle states: cached, fresh, refreshing, searching_tomorrow,
  turnover_pending, error
- Push-update system for instant state changes (refreshing→fresh→error)
- Quarter-hour polling for turnover_pending detection at 23:45
- Accurate next_api_poll prediction using Timer #1 offset tracking
- Tomorrow prediction with actual timer schedule (not fixed 13:00)
- 13+ formatted attributes: cache_age, data_completeness, api_calls_today,
  next_api_poll, etc.

Implementation:
- sensor/calculators/lifecycle.py: New calculator with state logic
- sensor/attributes/lifecycle.py: Attribute builders with formatting
- coordinator/core.py: Lifecycle tracking + callback system (+16 lines)
- sensor/core.py: Push callback registration (+3 lines)
- coordinator/constants.py: Added to TIME_SENSITIVE_ENTITY_KEYS
- Translations: All 5 languages (de, en, nb, nl, sv)

Timing optimization:
- Extended turnover warning: 5min → 15min (catches 23:45 quarter boundary)
- No minute-timer needed: quarter-hour updates + push = optimal
- Push-updates: <1sec latency for refreshing/fresh/error states
- Timer offset tracking: Accurate tomorrow predictions

Removed obsolete sensors:
- data_timestamp (replaced by lifecycle attributes)
- price_forecast (never implemented, removed from definitions)

Impact: Users can monitor data freshness, API call patterns, cache age,
and understand integration behavior. Perfect for troubleshooting and
visibility into when data updates occur.
2025-11-20 15:12:41 +00:00
Julian Pawlowski
02935c8d72 fix(data_fetching): enhance user data update logic and return status
fix(core): refresh chart data on coordinator updates
2025-11-20 13:48:26 +00:00
Julian Pawlowski
e950737478 feat(chart_export): migrate sensor config from UI to configuration.yaml
Moved Chart Data Export sensor configuration from config flow textarea
to configuration.yaml for better maintainability and consistency with
Home Assistant standards.

Changes:
- __init__.py: Added async_setup() with CONFIG_SCHEMA for tibber_prices.chart_export
- const.py: Added DATA_CHART_CONFIG constant for hass.data storage
- options_flow.py: Simplified chart_data_export step to info-only page
- schemas.py: get_chart_data_export_schema() returns empty schema (no input fields)
- sensor/chart_data.py: Reads config from hass.data instead of config_entry.options
- All 5 translation files: Updated chart_data_export description with:
  - Clear heading: "📊 Chart Data Export Sensor"
  - Intro line explaining sensor purpose
  - Legacy warning (⚠️) recommending service use
  - Two valid use cases (): attribute-only tools, auto-updating data
  - One discouraged use case (): automations should use service directly
  - 3-step activation instructions
  - YAML configuration example with all parameters
  - Correct default behavior: today+tomorrow, 15-minute intervals, prices only

Impact: Users configure chart export in configuration.yaml instead of UI.
Sensor remains disabled by default (diagnostic sensor). Config flow shows
prominent info page guiding users toward service usage while keeping
sensor available for legacy dashboard tools that only read attributes.
2025-11-20 13:41:26 +00:00
Julian Pawlowski
294da3960c fix(translations): correct typo in price trend title in German localization 2025-11-20 13:00:21 +00:00
Julian Pawlowski
b8a502672b refactor(config_flow): unify translation structure across all languages
Standardized config flow translations (nb, nl, sv) to match German/English
format with minimal field labels and comprehensive data_descriptions.

Changes across Norwegian, Dutch, and Swedish translations:
- Updated step_progress format: **{step_progress}** → _{step_progress}_
- Made all step descriptions bold with **text** formatting
- Simplified field labels (removed verbose explanations)
- Added data_description for price_rating (low/high thresholds)
- Added data_description for price_trend (rising/falling thresholds)
- Added data_description for volatility (moderate/high/very high thresholds)
- Ensured all steps have: emojis, italic step_progress, separator (---)
- Added missing emoji to Swedish price_rating step (📊)

Impact: All 5 languages now have consistent UX with minimal, scannable
field labels and detailed optional descriptions accessible via ⓘ icon.
Users get cleaner config flow with better clarity.
2025-11-20 12:59:12 +00:00
Julian Pawlowski
46fcdb8ba3 docs(period-calculation): update default thresholds for Best Price and Peak Price periods 2025-11-20 11:52:15 +00:00
Julian Pawlowski
c2b9908e69 refactor(naming): complete class naming convention alignment
Renamed 25 public classes + 1 Enum to include TibberPrices prefix
following Home Assistant integration naming standards.

All classes now follow pattern: TibberPrices{SemanticPurpose}
No package hierarchy in names (import path is namespace).

Key changes:
- Coordinator module: DataFetcher, DataTransformer, ListenerManager,
  PeriodCalculator, TimeService (203 usages), CacheData
- Config flow: CannotConnectError, InvalidAuthError
- Entity utils: IconContext
- Sensor calculators: BaseCalculator + 8 subclasses
- Period handlers: 5 NamedTuples (PeriodConfig, PeriodData,
  PeriodStatistics, ThresholdConfig, IntervalCriteria)
- Period handlers: SpikeCandidateContext (dataclass → NamedTuple)
- API: QueryType Enum

Documentation updates:
- AGENTS.md: Added Pyright code generation guidelines
- planning/class-naming-refactoring-plan.md: Complete execution log

Quality metrics:
- 0 Pyright errors (strict type checking)
- 0 Ruff errors (linting + formatting)
- All hassfest checks passed
- 79 files validated

Impact: Aligns with HA Core standards (TibberDataCoordinator pattern).
No user-facing changes - internal refactor only.
2025-11-20 11:22:53 +00:00
Julian Pawlowski
07f5990e06 docs(guidelines): update naming conventions for public and private classes 2025-11-20 10:29:45 +00:00
Julian Pawlowski
5ff61b6d5c feat(setup): add optional pyright installation and create type-check script 2025-11-20 10:10:33 +00:00
Julian Pawlowski
781286216a fix(devcontainer): suppress unused import and variable warnings in Python analysis 2025-11-20 09:44:59 +00:00
Julian Pawlowski
821131dbe9 fix(devcontainer): add custom_components/tibber_prices to Python analysis include 2025-11-20 09:30:32 +00:00
Julian Pawlowski
d6761186f1 chore(release): bump version to 0.11.1 2025-11-19 20:55:44 +00:00
Julian Pawlowski
ced6dcf104 fix(coordinator): sync cached_user_data after API call for new integrations
When adding a new integration (no existing cache), metadata sensors
(grid_company, estimated_annual_consumption, etc.) were marked as
unavailable because coordinator._cached_user_data remained None even
after successful API call.

Root cause: update_user_data_if_needed() stored user data in
_data_fetcher.cached_user_data, but the sync back to coordinator
only happened during _load_cache() (before the API call).

Solution: Added explicit sync of cached_user_data after
handle_main_entry_update() completes, ensuring metadata is available
when sensors first access get_user_homes().

Changes:
- coordinator/core.py: Sync _cached_user_data after main entry update
- __init__.py: Kept preload cache call (helps with HA restarts)

Impact: Metadata sensors now show values immediately on fresh integration
setup, without requiring a second update cycle or manual sensor activation.
2025-11-19 20:55:13 +00:00
Julian Pawlowski
2cbb35afd2 chore(release): bump version to 0.11.0 2025-11-19 20:18:37 +00:00
Julian Pawlowski
457fa7c03f refactor(periods): merge adjacent periods and remove is_extension logic
BREAKING CHANGE: Period overlap resolution now merges adjacent/overlapping periods
instead of marking them as extensions. This simplifies automation logic and provides
clearer period boundaries for users.

Previous Behavior:
- Adjacent periods created by relaxation were marked with is_extension=true
- Multiple short periods instead of one continuous period
- Complex logic needed to determine actual period length in automations

New Behavior:
- Adjacent/overlapping periods are merged into single continuous periods
- Newer period's relaxation attributes override older period's
- Simpler automation: one period = one continuous time window

Changes:
- Period Overlap Resolution (new file: period_overlap.py):
  * Added merge_adjacent_periods() to combine periods and preserve attributes
  * Rewrote resolve_period_overlaps() with simplified merge logic
  * Removed split_period_by_overlaps() (no longer needed)
  * Removed is_extension marking logic
  * Removed unused parameters: min_period_length, baseline_periods

- Relaxation Strategy (relaxation.py):
  * Removed all is_extension filtering from period counting
  * Simplified standalone counting to just len(periods)
  * Changed from period_merging import to period_overlap import
  * Added MAX_FLEX_HARD_LIMIT constant (0.50)
  * Improved debug logging for merged periods

- Code Quality:
  * Fixed all remaining linter errors (N806, PLR2004, PLR0912)
  * Extracted magic values to module-level constants:
    - FLEX_SCALING_THRESHOLD = 0.20
    - SCALE_FACTOR_WARNING_THRESHOLD = 0.8
    - MAX_FLEX_HARD_LIMIT = 0.50
  * Added appropriate noqa comments for unavoidable patterns

- Configuration (from previous work in this session):
  * Removed CONF_RELAXATION_STEP_BEST, CONF_RELAXATION_STEP_PEAK
  * Hard-coded 3% relaxation increment for reliability
  * Optimized defaults: RELAXATION_ATTEMPTS 8→11, ENABLE_MIN_PERIODS False→True,
    MIN_PERIODS undefined→2
  * Removed relaxation_step UI fields from config flow
  * Updated all 5 translation files

- Documentation:
  * Updated period_handlers/__init__.py: period_merging → period_overlap
  * No user-facing docs changes needed (already described continuous periods)

Rationale - Period Merging:
User experience was complicated by fragmented periods:
- Automations had to check multiple adjacent periods
- Binary sensors showed ON/OFF transitions within same cheap time
- No clear way to determine actual continuous period length

With merging:
- One continuous cheap time = one period
- Binary sensor clearly ON during entire period
- Attributes show merge history via merged_from dict
- Relaxation info preserved from newest/highest flex period

Rationale - Hard-Coded Relaxation Increment:
The configurable relaxation_step parameter proved problematic:
- High base flex + high step → rapid explosion (40% base + 10% step → 100% in 6 steps)
- Users don't understand the multiplicative nature
- 3% increment provides optimal balance: 11 attempts to reach 50% hard cap

Impact:
- Existing installations: Periods may appear longer (merged instead of split)
- Automations benefit from simpler logic (no is_extension checks needed)
- Custom relaxation_step values will use new 3% increment
- Users may need to adjust relaxation_attempts if they relied on high step sizes
2025-11-19 20:16:58 +00:00
Julian Pawlowski
625bc222ca refactor(coordinator): centralize time operations through TimeService
Introduce TimeService as single source of truth for all datetime operations,
replacing direct dt_util calls throughout the codebase. This establishes
consistent time context across update cycles and enables future time-travel
testing capability.

Core changes:
- NEW: coordinator/time_service.py with timezone-aware datetime API
- Coordinator now creates TimeService per update cycle, passes to calculators
- Timer callbacks (#2, #3) inject TimeService into entity update flow
- All sensor calculators receive TimeService via coordinator reference
- Attribute builders accept time parameter for timestamp calculations

Key patterns replaced:
- dt_util.now() → time.now() (single reference time per cycle)
- dt_util.parse_datetime() + as_local() → time.get_interval_time()
- Manual interval arithmetic → time.get_interval_offset_time()
- Manual day boundaries → time.get_day_boundaries()
- round_to_nearest_quarter_hour() → time.round_to_nearest_quarter()

Import cleanup:
- Removed dt_util imports from ~30 files (calculators, attributes, utils)
- Restricted dt_util to 3 modules: time_service.py (operations), api/client.py
  (rate limiting), entity_utils/icons.py (cosmetic updates)
- datetime/timedelta only for TYPE_CHECKING (type hints) or duration arithmetic

Interval resolution abstraction:
- Removed hardcoded MINUTES_PER_INTERVAL constant from 10+ files
- New methods: time.minutes_to_intervals(), time.get_interval_duration()
- Supports future 60-minute resolution (legacy data) via TimeService config

Timezone correctness:
- API timestamps (startsAt) already localized by data transformation
- TimeService operations preserve HA user timezone throughout
- DST transitions handled via get_expected_intervals_for_day() (future use)

Timestamp ordering preserved:
- Attribute builders generate default timestamp (rounded quarter)
- Sensors override when needed (next interval, daily midnight, etc.)
- Platform ensures timestamp stays FIRST in attribute dict

Timer integration:
- Timer #2 (quarter-hour): Creates TimeService, calls _handle_time_sensitive_update(time)
- Timer #3 (30-second): Creates TimeService, calls _handle_minute_update(time)
- Consistent time reference for all entities in same update batch

Time-travel readiness:
- TimeService.with_reference_time() enables time injection (not yet used)
- All calculations use time.now() → easy to simulate past/future states
- Foundation for debugging period calculations with historical data

Impact: Eliminates timestamp drift within update cycles (previously 60+ independent
dt_util.now() calls could differ by milliseconds). Establishes architecture for
time-based testing and debugging features.
2025-11-19 18:36:12 +00:00
Julian Pawlowski
b3f91a67ce chore(release): bump version to 0.10.1 2025-11-18 22:19:00 +00:00
Julian Pawlowski
3d1b6a64fc docs(architecture): update architecture documentation with Calculator Pattern details and sensor organization 2025-11-18 21:29:07 +00:00
Julian Pawlowski
a962289682 refactor(sensor): implement Calculator Pattern with specialized modules
Massive refactoring of sensor platform reducing core.py from 2,170 to 909
lines (58% reduction). Extracted business logic into specialized calculators
and attribute builders following separation of concerns principles.

Changes:
- Created sensor/calculators/ package (8 specialized calculators, 1,838 lines):
  * base.py: Abstract BaseCalculator with coordinator access
  * interval.py: Single interval calculations (current/next/previous)
  * rolling_hour.py: 5-interval rolling windows
  * daily_stat.py: Calendar day min/max/avg statistics
  * window_24h.py: Trailing/leading 24h windows
  * volatility.py: Price volatility analysis
  * trend.py: Complex trend analysis with caching (640 lines)
  * timing.py: Best/peak price period timing
  * metadata.py: Home/metering metadata

- Created sensor/attributes/ package (8 specialized modules, 1,209 lines):
  * Modules match calculator types for consistent organization
  * __init__.py: Routing logic + unified builders
  * Handles state presentation separately from business logic

- Created sensor/chart_data.py (144 lines):
  * Extracted chart data export functionality from entity class
  * YAML parsing, service calls, metadata formatting

- Created sensor/value_getters.py (276 lines):
  * Centralized handler mapping for all 80+ sensor types
  * Single source of truth for sensor routing

- Extended sensor/helpers.py (+88 lines):
  * Added aggregate_window_data() unified aggregator
  * Added get_hourly_price_value() for backward compatibility
  * Consolidated sensor-specific helper functions

- Refactored sensor/core.py (909 lines, was 2,170):
  * Instantiates all calculators in __init__
  * Delegates value calculations to appropriate calculator
  * Uses unified handler methods via value_getters mapping
  * Minimal platform-specific logic remains (icon callbacks, entity lifecycle)

- Deleted sensor/attributes.py (1,106 lines):
  * Functionality split into attributes/ package (8 modules)

- Updated AGENTS.md:
  * Documented Calculator Pattern architecture
  * Added guidance for adding new sensors with calculation groups
  * Updated file organization with new package structure

Architecture Benefits:
- Clear separation: Calculators (business logic) vs Attributes (presentation)
- Improved testability: Each calculator independently testable
- Better maintainability: 21 focused modules vs monolithic file
- Easy extensibility: Add sensors by choosing calculation pattern
- Reusable components: Calculators and attribute builders shared across sensors

Impact: Significantly improved code organization and maintainability while
preserving all functionality. All 80+ sensor types continue working with
cleaner, more modular architecture. Developer experience improved with
logical file structure and clear separation of concerns.
2025-11-18 21:25:55 +00:00
Julian Pawlowski
df075ae56a docs(AGENTS): update documentation metadata and guidelines for code examples 2025-11-18 20:08:25 +00:00
Julian Pawlowski
b5a0854cee docs(coordinator): enhance package docstring with detailed overview and components 2025-11-18 20:08:09 +00:00
Julian Pawlowski
5ab7703d90 fix(imports): update imports after utils package reorganization
Updated all imports to reflect new module structure:

1. Utils package imports:
   - average_utils → utils.average
   - price_utils → utils.price
   - Added MINUTES_PER_INTERVAL imports from const.py

2. Entity utils imports:
   - Added entity_utils.helpers imports where needed
   - Fixed find_rolling_hour_center_index import paths
   - Added get_price_value import in binary_sensor

3. Type imports:
   - Added coordinator/period_handlers/types.py MINUTES_PER_INTERVAL
     re-export (with noqa:F401) for period handler modules

4. Platform imports:
   - Updated sensor platform imports (utils.average, utils.price)
   - Updated binary_sensor imports (entity_utils helpers)
   - Updated coordinator imports (utils packages)

All import paths validated:
✓ Integration loads successfully
✓ All service handlers importable
✓ No circular dependencies
✓ Lint checks passing

Impact: Clean import structure, no breaking changes to functionality.
All sensors and services work identically to before.
2025-11-18 20:07:28 +00:00
Julian Pawlowski
4876a2cc29 refactor(entity_utils): extract shared helpers from sensor platform
Created entity_utils/helpers.py with platform-agnostic utility functions:
- get_price_value(): Price unit conversion (major/minor currency)
- translate_level(): Price level translation
- translate_rating_level(): Rating level translation
- find_rolling_hour_center_index(): Rolling hour window calculations

These functions moved from sensor/helpers.py as they are used by both
sensor and binary_sensor platforms. Remaining sensor/helpers.py now
contains only sensor-specific helpers (aggregate_price_data, etc.).

Updated imports:
- sensor/core.py: Import from entity_utils instead of sensor.helpers
- entity_utils/icons.py: Fixed find_rolling_hour_center_index import
- binary_sensor platforms: Can now use shared helpers

Added clear docstrings explaining:
- entity_utils/helpers.py: Platform-agnostic utilities
- sensor/helpers.py: Sensor-specific aggregation functions

Impact: Better code reuse, clearer responsibility boundaries between
platform-specific and shared utilities.
2025-11-18 20:07:17 +00:00
Julian Pawlowski
ac24f6a8cb refactor(services): split monolithic services.py into package
Split services.py (1,097 lines) into modular package (6 files, ~200-600 lines each):

Structure:
- services/__init__.py: Service registration (70 lines)
- services/helpers.py: Entry validation (55 lines)
- services/formatters.py: Data transformation (380 lines)
- services/chartdata.py: Chart data export handler (600 lines)
- services/apexcharts.py: ApexCharts YAML generator (240 lines)
- services/refresh_user_data.py: User data refresh (110 lines)

Benefits:
- Clear separation of concerns (helpers, formatters, handlers)
- Each service isolated and independently testable
- Consistent handler naming (handle_* pattern)
- Better code reuse through formatters module

All services working identically (get_chartdata, get_apexcharts_yaml,
refresh_user_data). Updated __init__.py to import from services package.

Impact: Improved maintainability, reduced max file size from 1,097
to 600 lines. Architecture quality improved from 7.5/10 to ~8.5/10.
2025-11-18 20:07:05 +00:00
Julian Pawlowski
d52eb6b788 refactor(utils): create utils package and consolidate constants
Reorganized utility modules into structured package:
- average_utils.py → utils/average.py
- price_utils.py → utils/price.py
- Created utils/__init__.py with clean exports

Moved MINUTES_PER_INTERVAL to const.py (centralized constant
management), with re-exports in utils modules for backward
compatibility during migration.

Added comprehensive package docstring explaining scope:
- Pure data transformation functions (stateless)
- No HA entity/coordinator dependencies
- Clear separation from entity_utils/ (entity-specific logic)

Impact: Cleaner module structure, easier navigation. Follows
file organization policy from AGENTS.md (keep root clean).
2025-11-18 20:06:46 +00:00
Julian Pawlowski
d828f754be Merge branch 'chore/refactoring' into main
Complete refactoring of module structure and documentation:

- Resolved circular import dependencies
- Split monolithic files into organized packages (api/, coordinator/)
- Added comprehensive architecture and timer documentation
- Implemented smart boundary tolerance for quarter-hour rounding
- Enhanced midnight turnover coordination
- All lint checks passing

This merge brings significant improvements to code maintainability and
documentation quality while maintaining full backward compatibility.
2025-11-18 17:32:54 +00:00
Julian Pawlowski
c316d5deef refactor: resolve circular imports and enhance documentation
This commit completes multiple refactoring efforts and documentation improvements:

Code Structure Changes:
- Move round_to_nearest_quarter_hour() from sensor/helpers.py to average_utils.py
- Resolve circular import between price_utils.py and sensor/helpers.py
- Split api.py into api/ package (client.py, queries.py, exceptions.py, helpers.py)
- Split coordinator.py into coordinator/ package (core.py, cache.py, listeners.py, etc.)
- Move period_utils/ to coordinator/period_handlers/ for better organization
- All lint checks passing (no PLC0415 local import warnings)

Documentation Additions:
- Add docs/development/architecture.md with Mermaid diagrams (end-to-end flow, cache coordination)
- Add docs/development/timer-architecture.md (comprehensive 3-timer system documentation)
- Add docs/development/caching-strategy.md (4-layer cache system with invalidation logic)
- Update docs/development/README.md with cross-references
- Update AGENTS.md with new module structure and patterns

Smart Boundary Tolerance:
- Implement ±2 second tolerance for quarter-hour rounding
- Prevents premature interval switching during HA restarts (14:59:30 stays at 14:45)
- Enables boundary snapping for timer jitter (14:59:58 → 15:00)

Atomic Midnight Coordination:
- Add _check_midnight_turnover_needed() for race-free midnight handling
- Coordinate Timer #1 (HA DataUpdateCoordinator) with Timer #2 (quarter-hour refresh)
- Whoever runs first performs turnover, other skips gracefully

Timer Optimization:
- Change timer scheduling from second=1 to second=0 (absolute-time scheduling)
- Document load distribution rationale (unsynchronized API polling prevents thundering herd)
- Comprehensive explanation of 3 independent timers and their coordination

Impact: Cleaner code structure with resolved circular dependencies, comprehensive
documentation of timer and caching systems, and improved reliability during
boundary conditions and midnight turnovers. All changes are developer-facing
improvements with no user-visible behavior changes.
2025-11-18 17:32:36 +00:00
dependabot[bot]
3f90de0c0a
chore(deps): bump actions/checkout from 5.0.0 to 5.0.1 (#29) 2025-11-18 08:56:11 +01:00
Julian Pawlowski
21b444c16d chore(release): bump version to 0.10.0 2025-11-17 04:11:45 +00:00
Julian Pawlowski
ef983d0a99 feat(sensor): migrate chart_data_export from binary_sensor to sensor platform
Migrated chart_data_export from binary_sensor to sensor to enable
compatibility with dashboard integrations (ApexCharts, etc.) that
require sensor entities for data selection.

Changes:
- Moved chart_data_export from binary_sensor/ to sensor/ platform
- Changed from boolean state (ON/OFF) to ENUM states ("pending", "ready", "error")
- Maintained all functionality: service call, attribute structure, caching
- Updated translations in all 5 languages (de, en, nb, nl, sv)
- Updated user documentation (sensors.md, services.md)
- Removed all chart_data_export code from binary_sensor platform

Technical details:
- State: "pending" (before first call), "ready" (data available), "error" (service failed)
- Attributes: timestamp + error (metadata) → descriptions → service response data
- Cache (_chart_data_response) bridges async service call and sync property access
- Service call: Triggered on async_added_to_hass() and async_update()

Impact: Dashboard integrations can now select chart_data_export sensor
in their entity pickers. No breaking changes for existing users - entity ID
changes from binary_sensor.* to sensor.*, but functionality identical.
2025-11-17 04:11:10 +00:00
Julian Pawlowski
e17f59c283 chore(release): bump version to 0.11.0 2025-11-17 03:14:19 +00:00
Julian Pawlowski
38ce1c4c50 feat(chart_export): add Chart Data Export diagnostic sensor
Added optional diagnostic binary sensor that exposes get_chartdata
service results as entity attributes for legacy dashboard tools.

Key features:
- Entity: binary_sensor.tibber_home_NAME_chart_data_export
- Configurable via Options Flow Step 7 (YAML parameters)
- Calls get_chartdata service with user configuration
- Exposes response as attributes for chart cards
- Disabled by default (opt-in)
- Auto-refreshes on coordinator updates
- Manual refresh via homeassistant.update_entity

Implementation details:
- Added chart_data_export entity description to definitions.py
- Implemented state/attribute logic in binary_sensor/core.py
- Added YAML configuration schema in schemas.py
- Added validation in options_flow.py (Step 7)
- Service call validation with detailed error messages
- Attribute ordering: metadata first, descriptions next, service data last
- Dynamic icon mapping (database-export/database-alert)

Translations:
- Added chart_data_export_config to all 5 languages
- Added Step 7 descriptions with legacy warning
- Added invalid_yaml_syntax/invalid_yaml_structure error messages
- Added custom_translations for sensor descriptions

Documentation:
- Added Chart Data Export section to sensors.md
- Added comprehensive service guide to services.md
- Migration path from sensor to service
- Configuration instructions via Options Flow

Impact: Provides backward compatibility for dashboard tools that can
only read entity attributes (e.g., older ApexCharts versions). New
integrations should use tibber_prices.get_chartdata service directly.
2025-11-17 03:14:02 +00:00
Julian Pawlowski
0bf810f0d5 chore(release): bump version to 0.10.0 2025-11-16 23:52:57 +00:00
Julian Pawlowski
fb70f29ac9 feat(services): rewrite ApexCharts service for modern workflow
Complete overhaul of the ApexCharts integration service layer to support
modern chart card workflows with flexible data formatting and filtering.

Replaced services:
- Removed: get_price, get_apexcharts_data (legacy, entity-based)
- Added: get_chartdata (flexible data service)
- Improved: get_apexcharts_yaml (now uses get_chartdata internally)

New get_chartdata service features:
- Multiple output formats (array_of_objects, array_of_arrays)
- Customizable field names for chart compatibility
- Resolution options (15-min intervals, hourly averages)
- Advanced filtering (level_filter, rating_level_filter)
- NULL insertion modes (none, segments, all) for clean gaps
- Minor currency support (cents/øre) with custom rounding
- Optional fields (level, rating_level, average)
- Multi-day support (yesterday/today/tomorrow)

Enhanced get_apexcharts_yaml service:
- Direct entry_id parameter (no entity_id lookup needed)
- Uses get_chartdata with WebSocket API (data_generator)
- Improved ApexCharts configuration:
  * Gradient fill (70% opacity → 20%)
  * Grid styling with dashed lines
  * Zoom & Pan tools (animations disabled for performance)
  * Optimized legend (top-left, compact markers)
  * Y-axis auto-scaling (min: 0 for visibility, supports negative prices)
  * 2 decimal places (improved precision)
  * Browser locale formatting (automatic comma/point)
  * insert_nulls='segments' for clean gaps between levels
- Multi-language support (translated titles, series names)
- Day selection (yesterday/today/tomorrow with correct span config)

Service translations:
- Added comprehensive field descriptions (all 5 languages: de, en, nb, nl, sv)
- Selector translations for all options (day, resolution, output_format, etc.)
- ApexCharts title translations in custom_translations/

Technical improvements:
- Hourly aggregation uses exact 4-interval windows (:00/:15/:30/:45)
- Level/rating aggregation follows sensor logic (aggregate_level_data, aggregate_rating_data)
- Midnight extension for last interval of filtered data (seamless day transitions)
- Case-insensitive filter matching (normalized to uppercase)
- Ruff complexity fixed (extracted _get_level_translation helper)

Impact: Users can now generate production-ready ApexCharts YAML with a single
service call, or use get_chartdata flexibly with any chart card (ApexCharts,
Plotly, Mini Graph, etc.). Supports complex filtering scenarios (e.g., "show
only LOW rating periods") with clean visual gaps. Full multi-language support.
2025-11-16 23:52:36 +00:00
Julian Pawlowski
7bc83ed3e2 chore(dev): remove IntelliCode extension from devcontainer configuration 2025-11-16 18:15:00 +00:00
Julian Pawlowski
329f96f2f4 chore(dev): enhance sync-hac script to clean up broken symlinks and report changes 2025-11-16 18:13:29 +00:00
Julian Pawlowski
60c3b72932 chore(dev): add HACS installation and sync scripts for testing
Added scripts to install HACS in DevContainer for testing the
integration alongside other HACS components.

Changes:
- scripts/setup: Automatically install HACS and create symlink
- scripts/sync-hacs: Sync HACS-installed integrations via symlinks
- .gitignore: Ignore custom_components/* except tibber_prices

HACS installs to config/custom_components/, symlinks in
custom_components/ make integrations visible to Home Assistant.

Impact: Developers can test with other integrations. No user changes.
2025-11-16 18:05:14 +00:00
Julian Pawlowski
3a9ba55dd3 feat(sensors): improve price trend sensors with temporal context
Enhanced current_price_trend and next_price_trend_change sensors with
consistent temporal information and fixed trend calculation logic.

Changes:
- Fixed trend calculation order: Calculate final trend state (momentum +
  future outlook) BEFORE scanning for next change, ensuring consistency
  between current_price_trend state and next_price_trend_change from_direction
- Added TIME_SENSITIVE_ENTITY_KEYS registration for both trend sensors
  to enable automatic 15-minute boundary updates (Timer #2)
- Removed redundant timestamp field from _trend_change_attributes (was
  duplicate of sensor state)
- Added timestamp attribute (current interval) to both sensors as first
  attribute for temporal reference
- Implemented _find_trend_start_time() to scan backward and determine
  when current trend began
- Added trend_duration_minutes to current_price_trend showing how long
  current trend has been active
- Added from_direction to current_price_trend showing previous trend
  state (enables detection of valleys/plateaus)
- Added minutes_until_change to next_price_trend_change showing time
  until trend changes
- Removed redundant attributes: valid_until, duration_hours,
  duration_minutes from current_price_trend (can be derived from
  next_price_trend_change sensor)
- Removed redundant next_direction from current_price_trend (available
  in next_price_trend_change sensor)

current_price_trend attributes:
- timestamp: Current interval (calculation basis)
- from_direction: Previous trend state (e.g., "stable" → "falling" = starting decline)
- trend_duration_minutes: How long current trend has been active

next_price_trend_change attributes:
- timestamp: Current interval (calculation basis)
- from_direction: Current trend state (should match current_price_trend state)
- direction: Target trend state
- minutes_until_change: Time until change occurs
- current_price_now, price_at_change, avg_after_change, trend_diff_%

Impact: Users can now detect important transitions (valleys: falling→stable,
plateaus: rising→stable) and understand trend context. Both sensors update
automatically every 15 minutes with consistent information.
2025-11-16 17:09:16 +00:00
Julian Pawlowski
76dc488bb5 feat(sensors): add momentum-based trend detection with two new sensors
Added intelligent price trend analysis combining historical momentum
(weighted 1h lookback) with future outlook for more accurate trend
recognition. Introduced two complementary sensors for comprehensive
trend monitoring.

New sensors:
- current_price_trend: Shows active trend direction with duration
- next_price_trend_change: Predicts when trend will reverse

Momentum analysis (historical perspective):
- Weighted 1h lookback (4 × 15-min intervals)
- Linear weight progression [0.5, 0.75, 1.0, 1.25]
- ±3% threshold for momentum classification
- Recognizes ongoing trends earlier than future-only analysis

Two-phase trend calculation:
- Phase 1: Calculate momentum from weighted trailing average
- Phase 2: Validate with volatility-adaptive future comparison
- Combines both for final trend determination (rising/falling/stable)
- Centralized in _calculate_trend_info() with 60s cache

Volatility-adaptive thresholds:
- Existing trend sensors (1h-12h) now use adaptive thresholds
- calculate_price_trend() adjusted by market volatility:
  * LOW volatility (<15% CV): factor 0.6 → more sensitive (e.g., 3%→1.8%)
  * MODERATE volatility (15-30%): factor 1.0 → baseline (3%)
  * HIGH volatility (≥30%): factor 1.4 → less sensitive (e.g., 3%→4.2%)
- Uses same coefficient of variation as volatility sensors
- Ensures mathematical consistency across integration

Default threshold reduction:
- Rising/falling thresholds: 5% → 3% (more responsive)
- Momentum-based detection enables lower thresholds without noise
- Adaptive adjustment compensates during high volatility

Architectural improvements:
- Centralized calculation: Single source of truth for both sensors
- Eliminates Henne-Ei problem (duplicate calculations)
- 60-second cache per coordinator update
- Shared helper methods: _calculate_momentum(), _combine_momentum_with_future()

Translation updates (all 5 languages):
- Documented momentum feature in custom_translations (de/en/nb/nl/sv)
- Explained "recognizes ongoing trends earlier" advantage
- Added sensor names and state options to standard translations
- Updated volatility threshold descriptions (clarify usage by trend sensors)

Files changed:
- custom_components/tibber_prices/sensor/core.py (930 lines added)
  * New: _calculate_momentum(), _combine_momentum_with_future()
  * New: _calculate_trend_info() (centralized with cache)
  * New: _get_current_trend_value(), _get_next_trend_change_value()
  * Modified: _get_price_trend_value() (volatility-adaptive thresholds)
- custom_components/tibber_prices/sensor/definitions.py
  * Added: current_price_trend (ENUM sensor)
  * Added: next_price_trend_change (TIMESTAMP sensor)
- custom_components/tibber_prices/sensor/attributes.py
  * New: _add_cached_trend_attributes() helper
  * Support for current_trend_attributes, trend_change_attributes
- custom_components/tibber_prices/price_utils.py (178 lines added)
  * New: _calculate_lookahead_volatility_factor()
  * Modified: calculate_price_trend() with volatility adjustment
  * Added: VOLATILITY_FACTOR_* constants (0.6/1.0/1.4)
- custom_components/tibber_prices/entity_utils/icons.py
  * Added: Dynamic icon handling for next_price_trend_change
- custom_components/tibber_prices/const.py
  * Changed: DEFAULT_PRICE_TREND_THRESHOLD_RISING/FALLING (5→3%)
- custom_components/tibber_prices/translations/*.json (5 files)
  * Added: Sensor names, state options, descriptions
- custom_components/tibber_prices/custom_translations/*.json (5 files)
  * Added: Long descriptions with momentum feature explanation

Impact: Users get significantly more accurate trend detection that
understands they're ALREADY in a trend, not just predicting future
changes. Momentum-based approach recognizes ongoing movements 15-60
minutes earlier. Adaptive thresholds prevent false signals during
volatile periods. Two complementary sensors enable both status display
(current trend) and event-based automation (when will it change).
Perfect for use cases like "charge EV when next trend change shows
falling prices" or dashboard badges showing "Rising for 2.5h".
2025-11-16 12:49:43 +00:00