Commit graph

430 commits

Author SHA1 Message Date
Julian Pawlowski
de6da790ab chore(release): bump version to 0.32.0 2026-07-04 18:25:27 +00:00
Julian Pawlowski
13b9870f45 fix(get_chartdata): use proper statistics.median for chart metadata
_calculate_metadata()'s inline calc_stats() computed the median as a
naive sorted(data)[len(data) // 2], which returns the upper-middle
value for even-length datasets instead of averaging the two middle
values. A full day is always an even interval count (96 quarter-hours
or 24 hours), so this silently overstated "median" and
"median_position" in nearly every price_stats block of the
get_chartdata response. The rest of the codebase already has a
correct calculate_median() in utils/average.py; this was an isolated
inline duplicate with the bug.

Impact: get_chartdata's price_stats.combined/dayN.median and
median_position now report the statistically correct median for
chart annotations and metadata-driven dashboards.
2026-07-04 18:23:25 +00:00
Julian Pawlowski
1b207f0db1 fix(services): correct min_distance_from_avg direction for negative prices
check_min_distance_from_avg() computed the distance threshold as
range_avg * (1 ± ratio). Tibber prices can go negative during grid
oversupply, and multiplying a negative range_avg directly flips the
intended direction: e.g. avg * 1.05 makes a negative average MORE
negative (i.e. cheaper), which is the wrong direction for a
"most expensive" threshold check, and analogously wrong for
"cheapest" checks.

Compute the threshold as range_avg ± abs(range_avg) * ratio instead,
matching the sign-safe normalization pattern already used for
min_distance_from_avg in the period system
(coordinator/period_handlers/level_filtering.py). Behavior for
positive averages (the common case) is unchanged.

Used by find_cheapest_block, find_cheapest_hours, and plan_charging.

Impact: min_distance_from_avg now correctly filters cheapest/most
expensive windows during negative-price periods instead of silently
accepting windows in the wrong direction relative to the search range
average.
2026-07-04 18:23:06 +00:00
Julian Pawlowski
95004efa2d fix(services): prevent power_profile truncation during relaxation
find_cheapest_block, find_cheapest_hours, and find_cheapest_schedule
accept an optional power_profile (fixed per-interval watt array
matching the requested duration/task length). When allow_relaxation
was enabled and no full-duration window could be found, the
duration-reduction relaxation phase silently reduced the interval
count without shrinking power_profile accordingly. Downstream code
(find_cheapest_contiguous_window, calculate_window_statistics,
_find_cheapest_window_in_pool) then truncated the profile from the
front to match, dropping trailing appliance-cycle phases and using
the wrong per-interval weights for both window selection and the
reported cost estimate.

Disable duration-reduction relaxation whenever a power_profile is
supplied (find_cheapest_schedule guards per-task via
any_task_has_power_profile); distance and level-filter relaxation
phases remain available since they don't affect interval count.

Impact: Services with a power_profile no longer silently pick a
shorter window with mismatched power weighting during relaxation;
they now correctly report no window found if the full duration isn't
available, preserving the accuracy of appliance-cycle cost estimates.
2026-07-04 18:22:44 +00:00
Julian Pawlowski
36e9cdf4b9 fix(find_cheapest_schedule): retry valid window start after time gaps
_find_cheapest_window_in_pool() scans for contiguous available blocks
when scheduling multiple tasks. When a block-in-progress hit a
temporal gap (e.g. from price-level filtering removing intervals from
the middle of the search range, or missing API data), the scanner
jumped to i = j + 1 instead of i = j, silently skipping the interval
right after the gap as a valid — sometimes cheaper — window start.

Distinguish 'unavailable slot' (correctly skipped via j + 1) from
'temporal gap' (must retry at j, since that slot was never actually
tested as a window start).

Impact: find_cheapest_schedule can now find the true cheapest window
for a task when the search range contains time gaps, instead of
occasionally picking a more expensive window right after such a gap.
2026-07-04 18:18:42 +00:00
Julian Pawlowski
b8e40bfa3b fix(services): prevent plan_charging overcharge from segment-constraint bridging
apply_segment_constraints could add far more grid energy than requested when
max_cycles_per_day or min_charge_duration_minutes forced bridging across
expensive gaps between cheap intervals. With max_cycles_per_day=1, isolated
cheap intervals were merged into one continuous segment by filling every gap
in between, without ever trimming the surplus back down, resulting in
achieved_soc_percent far above 100%.

Add a post-bridging trim step that removes segment-edge intervals (highest
price first) until total grid energy matches the requested target again,
while still respecting max_cycles_per_day/min_charge_duration_minutes and
never dropping below the target itself (fixed-power mode's expected
last-interval rounding overshoot is preserved).

A related edge case is also fixed: trimming could previously remove an
interval required to satisfy a must_reach_by deadline, silently flipping
deadline_met to False even though the overall energy target was still
reached. Deadline-critical intervals are now passed through as
protected_starts and are never removed during trimming.

Fixes #167

Impact: plan_charging no longer overcharges the battery/EV past the
requested target SoC when max_cycles_per_day or min_charge_duration_minutes
is set, and must_reach_by deadlines are honored even when those constraints
require bridging across expensive price gaps.
2026-07-04 17:43:04 +00:00
Julian Pawlowski
e39d0be162 fix(translations): correct message formatting in multiple language files
Some checks are pending
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
Updated error messages in German, English, Norwegian, Dutch, and Swedish translations to remove unnecessary quotes around placeholders. This improves clarity and consistency in error reporting.

Impact: Users will see clearer error messages without quotes around variable placeholders.
2026-06-01 11:31:18 +00:00
Julian Pawlowski
3a44bc8267 Merge branch 'main' of https://github.com/jpawlowski/hass.tibber_prices 2026-06-01 11:16:23 +00:00
Julian Pawlowski
5ea6e75295 refactor(services): change step value to 'any' for plan_charging fields
Updated the step value for number fields in the plan_charging service to allow any value instead of a fixed increment. This change enhances flexibility in user input.

Impact: Users can now specify any value within the defined range for charging plans.

---
refactor(translations): update error message formatting for entity attributes

Modified the error messages in multiple translation files to remove curly braces around entity attributes. This improves consistency in message formatting across languages.

Impact: Users will see cleaner error messages when attributes are not found.
2026-06-01 11:16:21 +00:00
WouterGithb
db5d172fb8
fix(services): preserve service call data through coordinator data fetch (#151)
* fix(services): preserve service call data through coordinator data fetch

In `_handle_find_block` and `_handle_find_hours`, the local `data`
variable holding the resolved service call data was rebound to the
coordinator data dict returned by `get_entry_and_data()`. As a result,
the subsequent calls to `validate_search_params(data)`,
`apply_must_finish_by(data, ...)` and `resolve_search_range(...)` read
search-range parameters from coordinator data instead of from the
service call, silently ignoring:

- must_finish_by
- search_scope
- search_start, search_end
- search_start_time, search_end_time
- search_start_day_offset, search_end_day_offset
- search_start_offset_minutes, search_end_offset_minutes
- include_current_interval

The functions fell back to the default range ("now → end of tomorrow")
for every call that depended on these parameters.

Rename the third return value of `get_entry_and_data()` to
`coordinator_data` so the service call `data` survives, restoring
deadline and search-scope semantics. `find_cheapest_schedule.py`
already uses `data_dict` for the same purpose and was not affected.

Verified locally against v0.31.0: a call with
`must_finish_by: 2026-06-01T20:00:00+02:00` now correctly produces
`search_end: 2026-06-01T20:00:00+02:00` (was end-of-tomorrow before).

* refactor(services): update data handling in find_cheapest_schedule service

Refactor the data retrieval process to use coordinator data instead of entry data for improved clarity and consistency.

Impact: Enhances maintainability of the service code without altering user-facing functionality.

---------

Co-authored-by: “WouterK” <kwaken.geringd0w@icloud.com”git config --global user.name “WouterK”git config --global user.email kwaken.geringd0w@icloud.com”>
Co-authored-by: Julian Pawlowski <jpawlowski@users.noreply.github.com>
2026-06-01 12:45:10 +02:00
Julian Pawlowski
6937068a7a chore(release): bump version to 0.32.0b1
Some checks failed
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Has been cancelled
Lint / Ruff (push) Has been cancelled
Auto-Tag on Version Bump / Check and create version tag (push) Has been cancelled
Validate / Hassfest validation (push) Has been cancelled
Validate / HACS validation (push) Has been cancelled
2026-05-30 14:48:01 +00:00
Julian Pawlowski
ada4e33448 feat(coordinator): surface prolonged API outages and cache-fallback state
Track API failures over time in the repair manager and raise an api_outage
repair issue once fresh data has been missing for longer than two hours,
independent of the update interval. A genuine successful fetch clears it;
serving cached data as a fallback keeps the outage active. Expose the degraded
cache-fallback state via the connection binary sensor (using_cached_data,
last_successful_update) so users can tell when sensors run on locally cached
prices. Add the api_outage repair strings to all supported languages.

Impact: Users get a clear repair notice during prolonged Tibber outages and
can see when the integration is running on cached data.
2026-05-30 14:46:32 +00:00
Julian Pawlowski
feaf748cb6 feat(services): isolate price-fetch failures and add success flag to responses
Add a track_degraded parameter to IntervalPool.get_intervals so service calls
(track_degraded=False) never flip the coordinator's sensor-health flags or use
the covers-current-interval cache fallback; authentication errors still
propagate. All price-fetching services now go through
async_fetch_service_intervals and return a structured response with a
top-level success flag: success=false with reason="price_data_unavailable" on
a real API outage, and success=true (possibly empty) otherwise.

Impact: Action calls (get_price, find_cheapest_*, plan_charging) always return
the expected shape, never impair sensors during a Tibber outage, and let
automations distinguish an outage from "no data yet".
2026-05-30 14:46:23 +00:00
Julian Pawlowski
8da3083fb2 fix(api): accept priceInfoRange-only responses and retry transient empty data
Some markets/accounts (e.g. NL hourly) return an empty
priceInfo(QUARTER_HOURLY).today while priceInfoRange still delivers the full
set of intervals. _check_price_info_empty required today's data and wrongly
classified such complete responses as empty, which blocked setup. It now
treats a response as valid when today OR yesterday data is present, since
priceInfoRange is the authoritative source the interval pool uses.

Additionally, "Empty data received" is now retried a few times with
exponential backoff, smoothing brief Tibber outages that transiently return
empty data within a single update cycle. Long outages are still absorbed by
the IntervalPool cache fallback, so the in-request retry count stays small.

Reported in #141.

Impact: Setup and updates no longer fail with "Empty data received" for NL
hourly accounts (and similar), and short Tibber blips are smoothed over.
2026-05-30 14:46:13 +00:00
Julian Pawlowski
b28d30f3fb chore(release): bump version to 0.31.0 2026-05-30 13:05:45 +00:00
Julian Pawlowski
a27d04d1c8 Merge branch 'main' of https://github.com/jpawlowski/hass.tibber_prices 2026-05-30 13:05:09 +00:00
Julian Pawlowski
d45fe0b14c docs(sensor-reference): update translations for price phase sensors
Updated the translations for various price phase sensors in the sensor reference documentation to ensure consistency and accuracy across multiple languages.

Impact: Users will see improved clarity in the documentation for price phase sensors in their preferred language.
2026-05-30 13:04:55 +00:00
github-actions[bot]
f7bb6dbfe8 chore(release): sync manifest.json with tag v0.31.0b2 2026-05-30 12:33:40 +00:00
Julian Pawlowski
0df089cc11 chore(release): bump version to 0.31.0b4
Some checks failed
Auto-Tag on Version Bump / Check and create version tag (push) Has been cancelled
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Has been cancelled
Lint / Ruff (push) Has been cancelled
Validate / Hassfest validation (push) Has been cancelled
Validate / HACS validation (push) Has been cancelled
2026-05-03 22:19:02 +00:00
Julian Pawlowski
1f74451adf chore(blueprints): disable automatic blueprint installation
Blueprints are kept in the repository for development but are not yet
ready for distribution. Commented out the install and remove calls in
async_setup and async_remove_entry so they are not copied to the user's
HA config directory on integration setup.

Release-Notes: skip
User-Impact: none
2026-05-03 22:17:02 +00:00
Julian Pawlowski
c2ff9cd2f2 fix(blueprints): fix home_connect_alt service call and correct sensor description
Two fixes in all 4 home_connect_alt blueprints:

1. home_connect_alt.start_program uses its own `device_id` field (not HA's
   standard target mechanism) and `program_key` (not `program`). The
   previous `target: entity_id:` was silently ignored, causing the service
   call to fail due to missing required `device_id`. Fixed by:
   - Removing `target: entity_id:` block
   - Adding `data.device_id: "{{ device_id(program_entity) }}"`
   - Renaming `program:` to `program_key:`
   - Adding `| int` filter to numeric option values

2. Same remote_start_sensor description fix as the non-alt variants
   (RemoteControlActive → RemoteControlStartAllowed).

Also reset blueprint version from v2.0.0 to v1.0.0.

Release-Notes: skip
Released-Bug: no
2026-05-03 22:16:56 +00:00
Julian Pawlowski
95d0278241 fix(blueprints): correct remote_start_sensor description in home_connect blueprints
The input field described the wrong binary sensor entity. The automation
correctly triggers on `RemoteControlStartAllowed`, but the label and
description still referenced `RemoteControlActive`.

Updated all 4 home_connect (non-alt) blueprints:
- dishwasher_home_connect.yaml
- washing_machine_home_connect.yaml
- dryer_home_connect.yaml
- laundry_day_pipeline_home_connect.yaml

Also reset blueprint version from v2.0.0 to v1.0.0 (version was bumped
prematurely, blueprints not yet released).

Release-Notes: skip
Released-Bug: no
2026-05-03 22:16:46 +00:00
Julian Pawlowski
b93eedf00e feat(services): add power-profile-weighted window selection
Add `include_current_interval` parameter to `find_cheapest_block` and
`find_cheapest_schedule` services, controlling whether the currently
active price interval can be the start of the selected window.

Add power-profile weighting to `find_cheapest_contiguous_window`: accepts
an optional `power_profile` list that weights each interval's price by
relative power draw (e.g. heat-up phase heavier than steady state). Without
a profile the behaviour is unchanged (uniform weighting).

Extend search-range tests and add price-window unit tests covering weighted
and unweighted scenarios, edge cases, and sequential scheduling interactions.
Update scheduling-actions documentation with parameter and profile examples.

Impact: Users can now model appliances with non-uniform power draw (e.g. heat
pumps, washing machines) to find truly cheapest windows based on actual energy
cost rather than average price.
2026-05-03 22:16:08 +00:00
Julian Pawlowski
ba08bd34c6 chore(periods): house keeping 2026-05-03 19:43:31 +00:00
Julian Pawlowski
9cb5b35184 fix(periods): separate smoothed levels from period detection
Keep raw Tibber API levels for best/peak period filtering while leaving smoothed levels in priceInfo for display-oriented sensors. Also make relaxation retry each flex step with the configured level filter before falling back to level_filter="any", and add regression tests for both paths.

Impact: Best-price periods no longer extend into expensive intervals because of level smoothing, and adjacent best/peak windows stay separated as expected.
2026-05-03 19:40:34 +00:00
Julian Pawlowski
dc4933ec5c feat(services): add price_source parameter to get_chartdata and get_apexcharts_yaml
Add a `price_source` field (total | energy | tax, default: total) to both
services, allowing users to choose which price component is used as the
primary chart series.

- get_chartdata: all 9 interval.get("total") calls now use price_source
- get_apexcharts_yaml: price_source forwarded through all 4 JS
  data_generator calls; yaxis template variables resolve to
  yaxis_min_energy / yaxis_min_tax when price_source != "total"
- Metadata-only path: always computes yaxis_suggested_energy and
  yaxis_suggested_tax alongside the main yaxis bounds so the
  chart_metadata sensor can expose the correct axis scale for any source
- chart_metadata sensor: exposes yaxis_min_energy, yaxis_max_energy,
  yaxis_min_tax, yaxis_max_tax as new attributes
- services.yaml + all 5 language files (en, de, nb, nl, sv): price_source
  field and selector options added

Impact: Users can now chart the raw energy (spot) price or the tax component separately, with correct Y-axis scaling in ApexCharts.

Co-authored-by: Copilot <copilot@github.com>
2026-05-03 18:48:36 +00:00
Julian Pawlowski
bbcfdd4443 fix(periods): stabilize best and peak period outputs
Recompute merged relaxed periods from raw intervals, harden numeric period option normalization, update day-volatility handling for zero or negative averages, and expose day context on period binary sensors.

Add focused regressions for overlap merges, cache invalidation, day statistics, and visible binary sensor attributes.

Impact: Best and peak period entities stay consistent on negative-price days, refresh correctly when same-day prices change, and expose the documented day context attributes.
2026-04-25 22:46:38 +00:00
Julian Pawlowski
10c83d6720 fix(periods): keep negative best-price windows strictly local
Always treat prices at or below zero as valid best-price intervals, rescue short
negative cores with directly adjacent cheap shoulders before min-length filtering,
and block geometric or shape-based widening for periods that already contain a
negative-price core.

Impact: Negative best-price periods no longer expand into positive edge intervals on days with extreme negative prices.
2026-04-25 20:00:04 +00:00
Julian Pawlowski
96f36a3339 feat(services): add plan_charging service for battery/EV scheduling
Accepts battery parameters (capacity, current/target SoC, max power) and
returns a cost-minimized charging schedule with per-interval power, SoC
progression, and total cost — no manual duration calculation needed.

Supports fixed, continuous (min_charge_power_w), and stepped
(charge_power_steps_w) charging modes, deadline-aware two-pass planning
(must_reach_soc + must_reach_by / must_reach_by_event), and round-trip
economics (expected_discharge_price, reserve_for_discharge,
max_cost_per_kwh) for arbitrage use cases. Includes min_charge_duration
and max_cycles_per_day constraints.

Groups deadline fields (must_reach_soc_*, must_reach_by,
must_reach_by_event) into a dedicated section so a deadline use case can
be configured in one place. Battery section lists capacity before the
percent SoC fields that depend on it. Response exposes stable reason
codes (already_at_target, energy_unreachable, energy_unreachable_by_
deadline, no_intervals_after_economic_filter, …) documented in the
service description and user docs.
2026-04-20 21:43:41 +00:00
Julian Pawlowski
e75e0ed1dc feat(blueprints): add appliance scheduling blueprints with auto-install
Bundle automation and script blueprints into the integration and
install them automatically at HA startup via _install_blueprints().
Remove them cleanly when the last config entry is removed.

Automation blueprints (standalone):
- dishwasher, washing_machine, dryer — smart plug and Home Connect
  variants (HC: door/remote-start sensors; HC Alt: program selector)

Automation blueprints (pipeline):
- laundry_day_pipeline — chains washer → dryer for multiple loads,
  HC and HC Alt variants

Other automation blueprints:
- ev_charging, heat_pump_price_level, heat_pump_smart_boost,
  home_battery, water_heater, laundry_day_pipeline (smart plug)

Script blueprint:
- notify_residents — presence-aware dispatcher for up to 10 residents
  with auto-discovered mobile_app notify services, iOS/Android push
  settings, and per-resident notify overrides

Notification UX across all blueprints:
- Apple Watch-optimised titles (~25 chars) and messages (most
  important info first, middle-dot separators, emoji anchors)
- Customisable notification titles via blueprint inputs (standalone)
- Comma-separated notify services for simple multi-target delivery
- Advanced script path for presence filtering and platform push data

Impact: Users get ready-to-use blueprints installed automatically
with the integration for scheduling appliances during cheap Tibber
price windows. No manual import required.
2026-04-20 18:45:05 +00:00
Julian Pawlowski
2d2873f75f feat(entity): expose integration version as sw_version in device info
Read the version field from manifest.json at import time via a new
INTEGRATION_VERSION constant in const.py. Pass it as sw_version to
DeviceInfo in TibberPricesEntity so the current integration version
is visible in the HA device registry.

Impact: Device page in HA now shows the installed integration version
under firmware/software version.
2026-04-20 18:44:34 +00:00
Julian Pawlowski
e01cc5d447 feat(services): allow entity IDs as service parameter values
Add entity_resolver module that lets all service parameters accept
HA entity references in place of literal values. The entity's current
state (or a specific attribute via the @attr syntax) is resolved at
call time and coerced to the expected Python type.

Syntax:
  "sensor.washing_duration"           → uses entity state
  "sensor.washing_duration@run_minutes" → uses entity attribute

Apply or_entity_ref() and resolve_entity_references() to all five
service handlers (get_price, find_cheapest_block, find_cheapest_hours,
find_cheapest_schedule, get_chartdata) for every parameter where a
dynamic value from another entity is useful (duration, start/end times,
offsets, etc.).

Add five new translation keys for entity-resolution error messages
(invalid_entity_reference, entity_not_found, entity_attribute_not_found,
entity_state_unavailable, entity_value_conversion_failed) across all
five language files.

Fix pytest warning filter to suppress AsyncMock cleanup noise, and
update test_resource_cleanup to mock hass.config_entries.async_entries
so the blueprint-removal path in async_remove_entry does not raise.

Impact: Automations and scripts can pass sensor entity IDs as service
parameters (e.g. duration from a sensor) instead of having to use
template-based workarounds.
2026-04-20 18:44:24 +00:00
Julian Pawlowski
31fca73ccd feat(services): add sequential parameter to find_cheapest_schedule
When sequential: true, tasks are placed in declaration order instead of
being sorted by duration. Each task's search window starts after the
previous task ends (plus gap_minutes). If a task cannot be placed, all
subsequent tasks in the chain are also marked unscheduled.

Adds 12 tests covering ordering, chaining, gap enforcement, and
chain-breaking behavior.

Impact: Users can now schedule dependent appliances (e.g., washing
machine → dryer) in a single find_cheapest_schedule call with guaranteed
order, instead of chaining two find_cheapest_block calls.
2026-04-19 14:17:32 +00:00
Julian Pawlowski
3057642cba refactor(icons): streamline service icon definitions and enhance chartdata sections
Some checks failed
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Waiting to run
Lint / Ruff (push) Has been cancelled
Validate / Hassfest validation (push) Has been cancelled
Validate / HACS validation (push) Has been cancelled
Removed unnecessary sections from the get_apexcharts_yaml service and added new fields for search tuning and cost estimation in chartdata services. This improves the clarity and usability of the service definitions.

Impact: Users will benefit from a more concise and organized service structure, enhancing the overall experience.
2026-04-19 12:35:13 +00:00
Julian Pawlowski
60b2de0379 refactor(periods): replace cross-day extension with bidirectional bridging
The old extension algorithm extended a single late-evening period forward
past midnight by appending qualifying intervals one-directionally.  This
caused false extensions (e.g. peak 19:45–21:30 extended to 01:00 by
pulling in 14 declining-price intervals).

Replace with bidirectional bridging: two independently qualifying periods
on both sides of midnight are merged only when separated by a small gap
(≤4 intervals = 1 hour) and the combined period passes the CV quality
gate (≤25%).  A period ending well before midnight is no longer touched.

User-Impact: none
2026-04-19 11:47:45 +00:00
Julian Pawlowski
303a7c7835 feat(pricing): add relaxation logic for progressive filter loosening
Some checks are pending
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
Implement a new service that progressively relaxes user-defined filters to ensure a result is always returned when price data is available. This includes three phases: halving the minimum distance from average, expanding level filters, and reducing duration.

Impact: Users will receive results even when strict filters would otherwise yield no matches, improving the reliability of scheduling actions.

feat(pricing): enhance scheduling actions with new parameters

Introduce new parameters `smooth_outliers`, `min_distance_from_avg`, and `allow_relaxation` to scheduling actions, allowing for better control over price selection and ensuring results are meaningfully different from average prices.

Impact: Users can now fine-tune their scheduling actions to avoid marginal savings and ensure more uniform pricing within selected windows.

docs(scheduling): update documentation for new features

Revise the scheduling actions documentation to include new parameters and their effects, such as outlier smoothing and minimum distance from average, along with examples for better user understanding.

Impact: Users will have clearer guidance on how to utilize new features effectively in their automations.

test(scheduling): add tests for new relaxation logic

Implement unit tests to verify the behavior of the new relaxation logic in scheduling actions, ensuring that filters are correctly relaxed and results are returned as expected.

Impact: Increased test coverage and reliability of the scheduling features.
2026-04-18 21:27:05 +00:00
Julian Pawlowski
7783a0b629 refactor(periods): enhance period gap handling and cross-day validation
Improve the logic for trimming trailing gap-tolerance intervals in periods to prevent misleading period end shifts. Additionally, refine cross-day boundary validation for both best and peak periods to eliminate false extremes caused by inter-day reference shifts.

Impact: More accurate period calculations and reduced artifacts in reported price periods.
2026-04-18 09:53:31 +00:00
Julian Pawlowski
2b63440933 refactor(periods): enhance peak period filtering and validation logic
Some checks failed
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
Auto-Tag on Version Bump / Check and create version tag (push) Has been cancelled
Improve the filtering of peak periods to eliminate cross-day artifacts and ensure that only genuine high-price windows are retained. This includes adjustments to the criteria for peak classification and the introduction of validation against previous day's prices for overnight intervals.

Impact: Users will experience more accurate peak pricing data, reducing misleading peak classifications on flat days.
2026-04-17 22:24:18 +00:00
Julian Pawlowski
75da094c81 refactor(day_patterns): rename double valley/peak to double dip/duck curve
Some checks are pending
Auto-Tag on Version Bump / Check and create version tag (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
Updated pattern names for clarity and consistency in the codebase. The changes include renaming constants and updating related logic to reflect the new terminology.

Impact: Improved readability and understanding of day pattern classifications for developers.
2026-04-17 14:37:17 +00:00
Julian Pawlowski
ba3e127ac7 refactor(day_pattern): enhance pattern classification with price boundaries
Refactor the pattern classification logic to include start and end prices for better accuracy in identifying day patterns. This change improves the classification of price patterns, particularly for cases involving valleys and peaks.

Impact: Users will experience more accurate price pattern classifications, leading to better decision-making based on price trends.
2026-04-17 14:02:02 +00:00
Julian Pawlowski
432eb6502c chore(release): bump version to 0.31.0b3 2026-04-17 12:23:04 +00:00
Julian Pawlowski
db02f262b6 perf(interval_pool): skip redundant API calls when prior fetch covers range
Some checks are pending
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
The PRICE_INFO endpoint returns all available intervals (~384) regardless
of the requested range. When fetching multiple missing ranges, subsequent
calls are redundant if the first response already covers them.

After each fetch, track returned timestamps and skip ranges that are
already covered by previously fetched data.

Impact: Reduces redundant Tibber API calls, especially after restarts or
cache invalidation when multiple gaps exist in the interval pool.
2026-04-17 12:00:57 +00:00
Julian Pawlowski
361498b7f5 perf(interval_pool): run GC after touch-only interval updates
Touch operations create dead intervals in old fetch groups, but GC only
ran when new intervals were added. Dead intervals accumulated until
the next fetch with genuinely new data.

Now run GC after touch-only paths and schedule a save if data changed.

Impact: Reduces memory usage by cleaning up stale fetch groups promptly
instead of letting dead intervals accumulate between API fetches.
2026-04-17 12:00:48 +00:00
Julian Pawlowski
ebcb9cfe77 fix(interval_pool): rebuild index after dead interval cleanup without empty groups
Cherry-pick of v0.30.1 hotfix (ec3bc9f). When _cleanup_dead_intervals
compacted group lists but no groups became fully empty, the index retained
stale interval_index values pointing past compacted list ends, causing
IndexError in _get_cached_intervals.

Now rebuilds the index whenever dead intervals are removed, even if no
groups are deleted.

Includes regression test for Issue #118 and updated touch operation tests
to reflect that GC now runs immediately after touch-only paths.

Closes #118

Impact: Eliminates IndexError crash for users with brand-new Tibber accounts
that have limited price history, where partial group compaction was most likely.
2026-04-17 12:00:37 +00:00
Julian Pawlowski
c3173a16d6 refactor(attributes): streamline phase type retrieval and attribute building
Consolidate logic for determining current price phase and associated attributes by introducing shared helper functions. This enhances code maintainability and reduces duplication across components.

Impact: Improved clarity and efficiency in price phase handling for users.
2026-04-17 08:52:17 +00:00
Julian Pawlowski
2adb64e5a0 refactor(translations): update terminology for previous interval price ranks
Revised the descriptions and names for the previous interval price rank entities across multiple language translation files to enhance clarity and consistency.

Impact: Users will see improved terminology in the interface, making it clearer that the ranks refer to the previous interval's prices.
2026-04-15 10:43:29 +00:00
Julian Pawlowski
7629c0f628 refactor(repairs): simplify currency mode change notification to one-shot
Remove the DATA_STATISTICS_REVIEW_REQUIRED flag and all associated
persistence logic. The flag approach was over-engineered: we cannot
detect whether the Recorder statistics have been fixed, and requiring
the user to re-save display settings as acknowledgement is bad UX.

New design: show the repair notice once when the mode changes.
The user dismisses it when done reviewing. The HA Recorder will
independently show its own unit-change dialog — that is sufficient.

Changes:
- Remove DATA_STATISTICS_REVIEW_REQUIRED constant from const.py
- Remove _check_statistics_review_repair() from __init__.py
- Remove ir import from __init__.py (no longer needed there)
- Remove flag set/clear logic from options_flow.py
- Change is_persistent=False (no restart persistence needed)
- Update all 5 translations: restore simple "Dismiss this notice" ending
2026-04-15 10:00:59 +00:00
Julian Pawlowski
09edcdb9a3 fix(repairs): reliably show statistics-review issue on every HA restart
We have no way to programmatically detect whether the Recorder statistics
have been fixed. Dismissing the Repairs notification does not mean the
problem is resolved, only that the user has seen it.

Revert to delete + create on every async_setup_entry when the flag is set.
This guarantees the issue is visible after every restart until the user
explicitly acknowledges completion by re-saving the display settings in
the options flow.

Remove the dismissed_version auto-clear logic that was treating dismissal
as acknowledgement (it was not).

Update all 5 translation files: replace "Dismiss this notice" with
instructions to re-save display settings as the only way to permanently
close the notification.

Released-Bug: no
2026-04-15 09:56:24 +00:00
Julian Pawlowski
e6ec54d8c5 fix(repairs): force statistics-review issue visible on mode change
async_create_issue preserves dismissed_version when called with the same
issue_id. This means that if a user had previously dismissed the repair
issue, changing the currency mode again would set the flag but the issue
would stay hidden (because it still has a dismissed_version).

Fix: use delete + create in the options flow when mode_changed=True.
This resets dismissed_version so the new instance is always visible.

The __init__.py path (HA restart with flag set) continues to use plain
async_create_issue so that a restart alone does not un-dismiss an issue
the user already acknowledged.

Released-Bug: no
2026-04-15 09:47:30 +00:00
Julian Pawlowski
5b5d5e73b0 fix(repairs): respect dismissal of statistics-review repair issue
The previous implementation used delete + create on every async_setup_entry,
which reset dismissed_version and forced the issue to reappear after every
HA restart regardless of whether the user had dismissed it.

Fix: use async_create_issue (internally get-or-create, preserves
dismissed_version when params are unchanged) instead of delete + create.
The options flow still uses delete + create when the mode changes again,
ensuring the issue is forced into view for any new change.

Also auto-clear the DATA_STATISTICS_REVIEW_REQUIRED flag from
config_entry.data when the issue is detected as dismissed on setup,
so the flag does not linger indefinitely after the user has acknowledged
the issue.

Released-Bug: no
2026-04-15 09:37:54 +00:00