Summary
Predbat skips joining an Octopus saving session that overlaps an Axle VPP
session, but it does not apply the same test when turning already-joined
sessions into plan slots. A session that was joined outside Predbat — from the
Octopus app or the email link, or joined before the Axle event was published —
is planned against even though Predbat itself would have refused to join it.
Observed with:
- Predbat v8.48.8
- HomeAssistant-OctopusEnergy v19.0.0
- Axle VPP export sessions alongside Octoplus Power Down sessions
Mechanism
_saving_event_conflicts_axle is defined once and called once, inside the
auto-join loop over available_events:
|
octopoints_kwh = event.get("octopoints_per_kwh", None) |
|
if octopoints_kwh is not None: |
|
saving_rate = octopoints_kwh / octopoints_per_penny # Octopoints per pence |
|
else: |
|
saving_rate = saving_rate # Use default if not specified |
|
# Do not auto-join a saving session that overlaps an Axle VPP session - we cannot honour both for the same period |
|
if self._saving_event_conflicts_axle(start_time, end_time, axle_sessions): |
|
self.log("Octopus: Skipping saving event code {} {}-{} - conflicts with an Axle VPP session".format(code, start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M"))) |
The joined_events loop that builds octopus_saving_slots has no equivalent
guard — its only filter is that the event has a start, an end and a positive
rate:
|
for event in joined_events: |
|
start = event.get("start", None) |
|
end = event.get("end", None) |
|
octopoints_kwh = event.get("octopoints_per_kwh", None) |
|
if octopoints_kwh is not None: |
|
saving_rate = octopoints_kwh / octopoints_per_penny # Octopoints per pence |
|
elif default_rate_pence > 0: |
|
saving_rate = default_rate_pence # Use configured default rate |
|
# Skip events with no rate info unless default is configured |
|
if start and end and (octopoints_kwh is not None or default_rate_pence > 0) and saving_rate > 0: |
|
# Save the saving slot? |
axle_sessions is already in scope at that point (it is a parameter of
fetch_octopus_sessions, passed from fetch.py), so the data needed for the
check is present.
Why it matters
The two programmes cannot both be honoured for the same period — which is
exactly the reasoning behind the existing check on the join path. When the
join-path guard fires, Predbat correctly declines to join. But it will still
plan around the same overlapping session if that session is in joined_events
for any reason Predbat did not control:
- joined in the Octopus app or from the announcement email
- joined by Predbat before the Axle session was published
- joined while
octopus_saving_auto_join was off
The result is a plan that commits the battery to the saving session's
import/export shape during a window already committed to Axle.
Expected behavior
Either:
- Apply
_saving_event_conflicts_axle to joined_events when building
octopus_saving_slots, and log the skip the same way the join path does; or
- If planning around an externally-joined conflicting session is deliberate,
document that the Axle guard is join-only, so operators know they still need
to filter it themselves.
Workaround
A template binary_sensor that re-implements the overlap test and filters
joined_events before Predbat sees them. That is what we run, but it means the
same interval-overlap logic exists in two places, and the template has to be
kept in step with Predbat's own definition of a conflict.
Suggested regression test
A joined session that overlaps an Axle session, with auto-join irrelevant
(already joined). octopus_saving_slots should not contain a slot for that
window. The existing test_saving_session_axle_conflict covers only the join
path, which is why this passes today.
Summary
Predbat skips joining an Octopus saving session that overlaps an Axle VPP
session, but it does not apply the same test when turning already-joined
sessions into plan slots. A session that was joined outside Predbat — from the
Octopus app or the email link, or joined before the Axle event was published —
is planned against even though Predbat itself would have refused to join it.
Observed with:
Mechanism
_saving_event_conflicts_axleis defined once and called once, inside theauto-join loop over
available_events:batpred/apps/predbat/octopus.py
Lines 2977 to 2984 in 004f298
The
joined_eventsloop that buildsoctopus_saving_slotshas no equivalentguard — its only filter is that the event has a start, an end and a positive
rate:
batpred/apps/predbat/octopus.py
Lines 3004 to 3014 in 004f298
axle_sessionsis already in scope at that point (it is a parameter offetch_octopus_sessions, passed fromfetch.py), so the data needed for thecheck is present.
Why it matters
The two programmes cannot both be honoured for the same period — which is
exactly the reasoning behind the existing check on the join path. When the
join-path guard fires, Predbat correctly declines to join. But it will still
plan around the same overlapping session if that session is in
joined_eventsfor any reason Predbat did not control:
octopus_saving_auto_joinwas offThe result is a plan that commits the battery to the saving session's
import/export shape during a window already committed to Axle.
Expected behavior
Either:
_saving_event_conflicts_axletojoined_eventswhen buildingoctopus_saving_slots, and log the skip the same way the join path does; ordocument that the Axle guard is join-only, so operators know they still need
to filter it themselves.
Workaround
A template
binary_sensorthat re-implements the overlap test and filtersjoined_eventsbefore Predbat sees them. That is what we run, but it means thesame interval-overlap logic exists in two places, and the template has to be
kept in step with Predbat's own definition of a conflict.
Suggested regression test
A joined session that overlaps an Axle session, with auto-join irrelevant
(already joined).
octopus_saving_slotsshould not contain a slot for thatwindow. The existing
test_saving_session_axle_conflictcovers only the joinpath, which is why this passes today.