Skip to content

Add dynamic additional load forecasts#3885

Open
Scholdan wants to merge 25 commits intospringfall2008:mainfrom
Scholdan:main
Open

Add dynamic additional load forecasts#3885
Scholdan wants to merge 25 commits intospringfall2008:mainfrom
Scholdan:main

Conversation

@Scholdan
Copy link
Copy Markdown
Contributor

@Scholdan Scholdan commented May 7, 2026

Summary

This PR adds named additional house load forecasts so Predbat can include known future appliance/load events in the forward plan. The main use case is loads that are not well represented by historical usage, for example a dishwasher, cooking, hot water, or heating demand.

Fixes #2917.

It supports both static apps.yaml configuration and dynamic one-shot requests from Home Assistant automations.

What This Adds

Static YAML Load Forecasts

Adds house_load_additional_forecast entries in apps.yaml, keyed by name.

Supported fields include:

  • name
  • enabled
  • mode: fixed or flexible
  • start_time
  • end_time
  • duration
  • energy: total kWh across the whole load
  • slot_energy: advanced kWh per Predbat plan slot
  • weighting: optional profile shaping across the duration

Static YAML entries are intended for recurring or known configuration-driven load injection. They do not get delete buttons and are edited or removed through apps.yaml.

Dynamic Home Assistant API Forecasts

Adds/uses select.predbat_load_forecast_delta_api for runtime one-shot forecasts from Home Assistant automations.

Example:

action: select.select_option
target:
  entity_id: select.predbat_load_forecast_delta_api
data:
  option: "dishwasher?enabled=true&mode=flexible&end_time=07:00&duration=5&energy=0.7"

These dynamic forecasts are one-shot requests:

  • Published as per-load binary sensors such as binary_sensor.predbat_load_forecast_delta_dishwasher
  • Given delete buttons such as button.predbat_load_forecast_delta_dishwasher_delete
  • Automatically expired after their scheduled finish time
  • Can be cancelled early with the delete button
  • Can be recreated by sending the select command again

A service path is also supported via predbat.update_load_forecast_delta for integrations that prefer a service-style update.

Published HA Attributes

Each named load forecast publishes useful attributes including:

  • enabled
  • mode
  • energy
  • slot_energy
  • duration
  • load_mode
  • plan_interval_minutes
  • slots
  • total_energy
  • requested_start
  • requested_end
  • suggested_start
  • suggested_end
  • selection_reason
  • selection_locked
  • candidate_count
  • source
  • auto_expire
  • expires_at
  • target_times

These attributes make the selected plan inspectable in Home Assistant and usable from automations.

Flexible Load Scheduling

Adds mode=flexible for appliance loads that can run anytime within a deadline.

Flexible semantics:

  • start_time means earliest allowed start
  • end_time means the load must be done by this time
  • Omitted end_time means the remaining forecast horizon
  • Omitted start_time differs by source:
    • YAML/static entries roll with the current plan slot on each refresh
    • API/service one-shot requests freeze the current plan slot when the request is received

Predbat chooses the flexible start time using the full prediction metric, rather than just the raw import rate. This means selection considers the current plan, battery state, PV forecast, import/export prices, losses, and other predicted load.

One-Shot Lifecycle

Dynamic flexible forecasts now follow the practical appliance lifecycle:

pending request
→ suggested_start/suggested_end published as a movable suggestion
→ reselected on later replans until suggested_start is reached
→ locked once now reaches suggested_start
→ only remaining future target_times are published while running
→ expired/deleted once now reaches suggested_end

Before suggested_start, the selected window is suggestion-only. Predbat publishes suggested_start and suggested_end, but does not publish committed target_times, slots, or total_energy for the load sensor yet. This keeps the request movable and allows later planning runs to choose a better window if the wider prediction metric changes.

Once suggested_start has passed, the load is treated as committed/running because a real appliance such as a dishwasher cannot normally be moved after it has started. At that point selection_locked becomes true, future replans no longer move it, and the sensor publishes only the remaining future target_times until auto-expiry at suggested_end.

Requested Start Drift Fix

This PR fixes an issue found during testing where dynamic API loads with omitted start_time had requested_start drift forward on each refresh.

The fix stamps _requested_start_minutes when a one-shot API/service request is first parsed or created. This keeps requested_start stable across replans while still allowing a fresh start time when the same command is sent again.

A follow-up edge case is also handled: if stale selected flexible metadata exists before the frozen requested start, the selected start is clamped to requested_start, so suggested_start cannot be earlier than the published requested window.

Textual Plan

The textual plan now includes confirmed additional loads once they have target slots, for example:

- Additional load dishwasher from 10:00 to 12:00 using 1.20 kWh is planned.

Pending flexible loads are not included until Predbat has selected an actual window.

Documentation

Docs were added/updated in:

  • docs/apps-yaml.md
  • docs/manual-api.md

The docs cover:

  • Static house_load_additional_forecast configuration
  • energy vs slot_energy
  • Fixed vs flexible load behaviour
  • API one-shot behaviour
  • Delete buttons and expiry
  • Frozen API start_time behaviour vs rolling YAML behaviour
  • A generic dishwasher scheduling example with helper, request automation, start-at-suggested_start automation, and manual-start cleanup automation

Validation

Ran successfully:

./run_all --test additional_load_forecast
./run_pre_commit

The targeted test coverage includes:

  • Disabled and enabled load forecasts
  • Fixed loads using start_time/duration
  • Fixed loads using start_time/end_time
  • Total energy distribution
  • Advanced slot_energy
  • Weighting behaviour
  • Select API updates
  • Service updates
  • Delete button cleanup
  • Auto-expiry
  • Stale entity cleanup
  • Flexible pending state before plan selection
  • Flexible prediction-metric selection
  • Flexible API suggestion-only state before suggested_start
  • Flexible API reselection before suggested_start
  • Flexible API lock after suggested_start
  • Expiry after locked flexible load reaches suggested_end
  • API omitted start_time freeze
  • YAML omitted start_time rolling behaviour
  • Stale selected metadata not starting before frozen requested_start
  • Textual plan output for confirmed additional loads

@Scholdan
Copy link
Copy Markdown
Contributor Author

Scholdan commented May 7, 2026

I found two edge cases while reviewing this and pushed fixes in c851bf93.

  1. Partial-duration loads were not fully represented in the prediction data when the duration did not line up exactly with the plan interval. The attributes could show the configured total kWh, but the planner could see less energy. The fix keeps the published slot energy as-is and scales the per-minute adjustment for partial slots.

  2. Forecast names that get sanitized for Home Assistant entity IDs, for example Dishwasher Eco becoming dishwasher_eco, could fail to map back to the original forecast name for service updates or delete buttons. The fix resolves entity suffixes against the active forecasts, overrides, and API commands before updating or deleting.

I added regression coverage for both cases and ran:

  • coverage/./run_all --test additional_load_forecast
  • coverage/./run_pre_commit

Copy link
Copy Markdown
Contributor Author

@Scholdan Scholdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-checked the PR after c851bf93.

The two issues I found earlier are now fixed and covered by tests:

  • Partial-duration loads now keep the configured total energy in the planner.
  • Sanitized Home Assistant entity names now map back to the original forecast names for service updates and delete buttons.

I did not find any new blocking issues in the updated diff.

Checks looked good:

  • coverage/./run_all --test additional_load_forecast
  • coverage/./run_pre_commit

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for named “additional house load forecasts” so Predbat can account for known future appliance/load events in its forward plan, including a new flexible scheduling mode that selects the best run window using the full prediction metric. It also documents the new configuration/API and adds targeted unit tests.

Changes:

  • Add house_load_additional_forecast YAML config plus runtime one-shot updates via select.predbat_load_forecast_delta_api and predbat.update_load_forecast_delta.
  • Integrate additional load deltas into plan load-step data, with flexible loads selected using prediction-metric scoring and published to HA as binary sensors (+ delete buttons for dynamic entries).
  • Add documentation and a dedicated test suite covering fixed/flexible semantics, weighting, expiry, delete, and restart persistence.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/manual-api.md Documents how to create/update one-shot additional load forecasts via HA select, including flexible scheduling examples.
docs/apps-yaml.md Documents static house_load_additional_forecast configuration, weighting, fixed vs flexible semantics, and API updates.
apps/predbat/userinterface.py Wires HA select/service/button events to update/delete additional load forecasts and trigger refresh.
apps/predbat/unit_test.py Registers the new additional load forecast test suite.
apps/predbat/tests/test_additional_load_forecast.py Adds comprehensive tests for additional load forecasts (fixed/flexible/API/service/delete/expiry/restart).
apps/predbat/predbat.py Adds delete_state_wrapper() and initializes new additional-load-related runtime state on reset.
apps/predbat/plan.py Adds prediction-metric candidate scoring/selection for flexible additional loads and injects adjustments into plan load step data.
apps/predbat/output.py Adds textual plan summary lines for planned/suggested/running additional loads.
apps/predbat/ha.py Adds HA state deletion support and extends api_call() to support HTTP DELETE.
apps/predbat/fetch.py Implements parsing, merging, scheduling, publishing, expiry, and entity lifecycle for additional load forecasts.
apps/predbat/config.py Adds the new API select (load_forecast_delta_api) and config schema for house_load_additional_forecast.
.cspell/custom-dictionary-workspace.txt Adds delayedstart for spellchecking in docs/examples.

Comment thread apps/predbat/ha.py
Comment on lines +893 to +900
def delete_state(self, entity_id):
"""
Delete a state from Home Assistant.
"""
self.db_mirror_list.pop(entity_id, None)
self.state_data.pop(entity_id.lower(), None)
if self.ha_key:
self.api_call("/api/states/{}".format(entity_id), delete=True)
Comment thread apps/predbat/userinterface.py Outdated
if not name and entity_id:
marker = "_load_forecast_delta_"
if marker in entity_id:
name = entity_id.split(marker, 1)[1]
@Scholdan
Copy link
Copy Markdown
Contributor Author

I simplified the dynamic update side of this PR.

Changes:

  • Kept the dynamic API select-backed only via select.predbat_load_forecast_delta_api, which matches Predbat's existing manual API pattern and preserves restart persistence.
  • Removed the separate predbat.update_load_forecast_delta service path because it duplicated the API and did not use the same persistence mechanism.
  • Centralized additional-load forecast record construction, timestamp formatting, and API command parsing in fetch.py to reduce duplicated logic without changing behavior.

I kept the remaining behavior intact: static YAML forecasts, dynamic one-shot forecasts, flexible scheduling lifecycle, restart metadata, slot_energy, weighting, delete buttons, and auto-expiry.

Validation:

  • coverage/./run_all --test additional_load_forecast
  • coverage/./run_pre_commit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ability to add additional non-forecasted load into the predbat forward plan

2 participants