Skip to content

Axle VPP conflict check is applied when joining a saving session but not when planning an already-joined one #4592

Description

@cparmar

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:

  1. 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
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions