Skip to content

IOG dynamic: keep cheap rate to end of 30-min period after car stops charging mid-period #5325

Closed as not planned

Description

@wwicks

Writen by GLM 5.3 Flash

Setup

  • Predbat v9.3.3 (add-on), Octopus Intelligent via the Octopus Energy integration, tariff IOG-SMB (octopus_slot_max: 12)
  • car_charging_now wired to a charging-power sensor; car is outside the CT clamp (car_energy_reported_load Off)
  • octopus_intelligent_charging On, octopus_intelligent_dynamic On, octopus_intelligent_trust_slots Off

Current behaviour

When the car stops charging inside a dispatch (typically on reaching target SoC), the dynamic check cancels the car's slots once "not charging" has lasted DYNAMIC_LOAD_CAR_SENSOR_MINUTES (2 min, after the 3-min start band DYNAMIC_LOAD_CAR_START_MINUTES). Two things then remove the cheap rate for minutes ≥ now:

Elapsed minutes keep their cheap rate.

This isn't specific to confirm-first mode: the cancellation in dynamic_load_car_target() (https://github.com/springfall2008/batpred/blob/v9.3.3/plan.py#L487) fires for any car inside a dispatch that stops charging, regardless of octopus_intelligent_trust_slots — that switch only controls the pre-confirmation default. I run confirm-first, but the fix would apply to the dynamic check generally.

Why this looks wrong

Predbat's own rate model already treats any partial overlap with a 30-minute period as the whole period off-peak: rate_add_io_slots floors the start and ceilings the end ("to ensure any partial overlap with a 30-min slot marks the entire slot as off-peak", https://github.com/springfall2008/batpred/blob/v9.3.3/octopus.py#L3712), and dynamic_load_car_strip_feed_rates likewise rounds out to whole periods (https://github.com/springfall2008/batpred/blob/v9.3.3/octopus.py#L3812). My working assumption is that Octopus prices the same way per half-hour settlement period — I can attach a bill/consumption export if needed. On that assumption, when the car stops part-way through a period, the rest of that period is cheap in reality but modelled at peak, and Predbat won't plan the house battery into minutes that are actually cheap. The cancellation is finer-grained than the rate model's own period granularity.

Example

Dispatch 20:00–21:00, car reaches target and stops at 20:12:

Time Today Proposed
20:12 Car stops Car stops
20:14 Slots cancelled; cheap rate stripped from 20:14 Car load/hold stop; house cheap rate kept
20:30 Peak Period ends; cheap rate removed from here
20:30–21:00 Peak (no charging) Peak (no charging evidence, unchanged)

Request

Once charging has been seen within the current 30-min period, keep the house cheap rate until that period ends, i.e. snap the strip/cancel effective time to the next period boundary after charging evidence.

  • Periods with no charging evidence keep today's behaviour, so the GH#5080 protection against unused dispatches is preserved
  • The car's modelled charging and "Hold for car" should still stop when the car stops; only the house rate persists
  • The fixed 23:30–05:30 window is unaffected
  • Happy for this to sit behind a switch if a default change is too opinionated

Scope

At most one 30-minute period per stop event: only the tail of a period where charging stopped mid-period.

Activity

  1. springfall2008 commented on Sep 30, 2026

    @springfall2008
    Owner

    Automated first-pass triage (a maintainer will review before any action is taken).

    Classification: duplicate of #5316 — closing in favour of that ticket, which carries the same report with a predbat.log and debug yaml attached, and already has a fix drafted (PR #5319, "keep a started IOG dispatch's half hour cheap after the car stops").

    I verified your description against current main (your v9.3.3 links match it line for line):

    • plan.py:487 dynamic_load_car_target() cancels any car's slots once "not charging" outlasts the grace period, and octopus_intelligent_trust_slots only sets the pre-evidence default (plan.py:502) — your "not specific to confirm-first mode" point checks out.
    • octopus.py:3730 in rate_add_io_slots() skips the dispatch overlay for minutes ≥ now, and octopus.py:3792 / octopus.py:3824 in dynamic_load_car_strip_feed_rates() overwrite the feed's discount from now onwards, outside the fixed 23:30–05:30 window and not covered by another car's trusted dispatch — exactly the two removal sites you identified.

    PR #5319 does precisely what your Request section asks: it records the end of the half hour the car was last seen charging in and starts both removals from the later of now and that half hour's end, while still cancelling the car's own slots and releasing "Hold for car", still dropping later half hours of a longer dispatch, and still keeping the #5080 protection for periods with no charging evidence. One difference from your proposal: #5319 changes the default outright rather than hiding it behind a switch ("Happy for this to sit behind a switch...") — that's a maintainer decision to weigh in on over in #5316.

    One thing both tickets share: the billing premise (that the remainder of a started half hour is billed off-peak) is asserted by a commenter in #5316 but is flagged as unverified in PR #5319's known-limits list. If you do have a bill/consumption export showing a partially-used half hour billed off-peak, attaching it to #5316 would settle that.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    BOT_TRIAGEDHas been through the triage botOctopusduplicateThis issue or pull request already exists

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions