Repository navigation
IOG dynamic: keep cheap rate to end of 30-min period after car stops charging mid-period #5325
Description
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_nowwired to a charging-power sensor; car is outside the CT clamp (car_energy_reported_loadOff)octopus_intelligent_chargingOn,octopus_intelligent_dynamicOn,octopus_intelligent_trust_slotsOff
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:
rate_add_io_slotsskips its overlay — https://github.com/springfall2008/batpred/blob/v9.3.3/octopus.py#L3730dynamic_load_car_strip_feed_ratesoverwrites the discount already delivered by the integration's feed (is_intelligent_adjusted), setting those minutes back to peak — https://github.com/springfall2008/batpred/blob/v9.3.3/octopus.py#L3792 — but only minutes ≥ now, outside the fixed 23:30–05:30 window, and not covered by another car's trusted dispatch
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
springfall2008 commentedon Sep 30, 2026
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.logand 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:487dynamic_load_car_target()cancels any car's slots once "not charging" outlasts the grace period, andoctopus_intelligent_trust_slotsonly sets the pre-evidence default (plan.py:502) — your "not specific to confirm-first mode" point checks out.octopus.py:3730inrate_add_io_slots()skips the dispatch overlay for minutes ≥ now, andoctopus.py:3792/octopus.py:3824indynamic_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.