Add dynamic additional load forecasts#3885
Conversation
|
I found two edge cases while reviewing this and pushed fixes in
I added regression coverage for both cases and ran:
|
There was a problem hiding this comment.
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_forecastcoverage/./run_pre_commit
There was a problem hiding this comment.
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_forecastYAML config plus runtime one-shot updates viaselect.predbat_load_forecast_delta_apiandpredbat.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. |
| 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) |
| if not name and entity_id: | ||
| marker = "_load_forecast_delta_" | ||
| if marker in entity_id: | ||
| name = entity_id.split(marker, 1)[1] |
|
I simplified the dynamic update side of this PR. Changes:
I kept the remaining behavior intact: static YAML forecasts, dynamic one-shot forecasts, flexible scheduling lifecycle, restart metadata, Validation:
|
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.yamlconfiguration and dynamic one-shot requests from Home Assistant automations.What This Adds
Static YAML Load Forecasts
Adds
house_load_additional_forecastentries inapps.yaml, keyed byname.Supported fields include:
nameenabledmode:fixedorflexiblestart_timeend_timedurationenergy: total kWh across the whole loadslot_energy: advanced kWh per Predbat plan slotweighting: optional profile shaping across the durationStatic 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_apifor runtime one-shot forecasts from Home Assistant automations.Example:
These dynamic forecasts are one-shot requests:
binary_sensor.predbat_load_forecast_delta_dishwasherbutton.predbat_load_forecast_delta_dishwasher_deleteA service path is also supported via
predbat.update_load_forecast_deltafor integrations that prefer a service-style update.Published HA Attributes
Each named load forecast publishes useful attributes including:
enabledmodeenergyslot_energydurationload_modeplan_interval_minutesslotstotal_energyrequested_startrequested_endsuggested_startsuggested_endselection_reasonselection_lockedcandidate_countsourceauto_expireexpires_attarget_timesThese attributes make the selected plan inspectable in Home Assistant and usable from automations.
Flexible Load Scheduling
Adds
mode=flexiblefor appliance loads that can run anytime within a deadline.Flexible semantics:
start_timemeans earliest allowed startend_timemeans the load must be done by this timeend_timemeans the remaining forecast horizonstart_timediffers by source:Predbat chooses the flexible start time using the full Predbat optimiser metric, not just raw import rate or raw prediction cost. This means selection considers the current optimised plan, battery value, PV10 weighting, import/export prices, losses, cycle/keep penalties, self-sufficiency settings, and other predicted load.
One-Shot Lifecycle
Dynamic flexible forecasts follow the practical appliance lifecycle:
Before
suggested_start, the selected window is suggestion-only. Predbat publishessuggested_startandsuggested_end, but does not publish committedtarget_times,slots, ortotal_energyfor 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_starthas 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 pointselection_lockedbecomes true, future replans no longer move it, and the sensor publishes only the remaining futuretarget_timesuntil auto-expiry atsuggested_end.Completed/expired dynamic additional loads are archived only at the end of the scheduled slot, not when merely suggested or started. This avoids subtracting loads from historical learning before Predbat knows the scheduled slot has completed.
Additional Load History
Adds Predbat-owned persistence for completed additional load slots:
sensor.predbat_load_forecast_delta_historyrecordsArchived completed loads are subtracted from future historical load filtering so one-shot appliance forecasts do not permanently inflate learned household demand.
Optimisation Behaviour
This PR also fixes flexible-load planning details discovered during testing:
Requested Start Drift Fix
This PR fixes an issue found during testing where dynamic API loads with omitted
start_timehadrequested_startdrift forward on each refresh.The fix stamps
_requested_start_minuteswhen a one-shot API/service request is first parsed or created. This keepsrequested_startstable 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, sosuggested_startcannot be earlier than the published requested window.Textual Plan
The textual plan now includes confirmed additional loads once they have target slots, for example:
Pending flexible loads are not included until Predbat has selected an actual window.
Documentation
Docs were added/updated in:
docs/apps-yaml.mddocs/manual-api.mdThe docs cover:
house_load_additional_forecastconfigurationenergyvsslot_energystart_timebehaviour vs rolling YAML behavioursuggested_startautomation, and manual-start cleanup automationValidation
Latest local validation run successfully:
The targeted test coverage includes:
start_time/durationstart_time/end_timeenergydistributionslot_energysuggested_startsuggested_startsuggested_startsuggested_endstart_timefreezestart_timerolling behaviourrequested_start