Repository navigation
fix(octopus): don't treat dispatches under 5 minutes as low rate unless the car is seen charging - #5657
Conversation
…ss the car is seen charging Octopus has started issuing standalone one-minute Intelligent dispatches. Rounded out to the half hour, each one made a whole 30 minutes cheap for the house battery, and spent part of the daily low-rate cap, although a car rarely starts charging in one. A dispatch shorter than OCTOPUS_SLOT_MIN_MINUTES (5) is now only off-peak in a half hour dynamic load has seen the car charging in. The test sits in the shared dispatch_billed_off_peak(), so load_octopus_slots(), rate_add_io_slots() and octopus_planned_running() agree, and it is judged on the dispatch's own boundaries so the trimmed tail of a long dispatch keeps the low rate. - dynamic_load_car_target() records every confirmed half hour (dynamic_load_car_confirmed_periods), not only the latest, so a confirmed blip stays cheap once a later half hour is confirmed too - held half hours (octopus_held_dispatches()) are exempt: they passed the test when seen running - not applied under window billing (E.ON), which bills the whole planned window regardless - test14_tiny_overlap_issue3328 updated: its 1m16s slot is now cheap only once confirmed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
springfall2008
left a comment
There was a problem hiding this comment.
Automated comment from the triage bot.
Reviewed at b668c04 (PR head, based on main d3c2f72). The mechanism itself is sound: the new gate lives in dispatch_billed_off_peak() so load_octopus_slots(), rate_add_io_slots() and octopus_planned_running() all take it; the confirmations flow from dynamic_load_car_target(), which the 15-second poll calls with record=True; the two cap counters still agree (a confirmed blip charges its own ~1 minute to the budget in both); and the tests discriminate for real (test14_tiny_overlap_issue3328_unconfirmed expects a day-rate half hour that main's code would stamp cheap, so it fails without the change). Two findings inline, one in the body.
apps/predbat/octopus.py:4259
A confirmed short dispatch's half hour gets no GH#5520 held fallback, because octopus_held_update() can only hold a half hour it samples while the dispatch is running at a 5-minute cycle, and a dispatch shorter than 5 minutes that does not straddle a 5-minute boundary is never running during one.
Octopus bills a dispatch's half hour off-peak once the car charges in it (the GH#5316 rule this PR builds on), so a confirmed blip's half hour should stay cheap once it is over. But after the blip ends, the planned entry drops out of the planned feed (the HA integration's planned_dispatches sensor drops ended entries; on Predbat's own component the entry stays only while Octopus's flexPlannedDispatches still lists it), and the completed feed lists the dispatch only later - the 15-60 minute lag GH#5520 measured. In that window the half hour is in neither list, so it prices at the day rate, and today's cost steps up, then falls back when the completed dispatch arrives and the confirmation makes dispatch_too_short() pass. That is exactly the #4946/#5520/#5413 today-cost-steps shape, and the held mechanism (added by PR #5533 for precisely this) cannot fire for it: octopus_held_update() samples octopus_planned_running() only at fetch.py:1577 per 5-minute cycle, and the 15-second poll - the tool that does see the blip run - confirms half hours but never records held ones. Example: a 14:13-14:14 dispatch confirmed by a poll at 14:13:40 is gone from planned by the 14:15 cycle, unlisted in completed until roughly 14:30, and prices at the day rate throughout.
The PR body's claim that "Held half hours are exempt" is true of any held half hour that exists, but no held half hour ever gets created for these dispatches, so the exemption is mostly unexercised for the class of dispatch the PR is about: a held entry is injected for the in-progress half hour only when a long dispatch ended part-way through it (the GH#5316 tail), never for a dispatch that finished before any cycle saw it.
If this is judged acceptable as-is, an octopus_held_update() call from dynamic_load_car_poll() (which already knows the dispatch is running, on the exact clock) would close it; the per-car held dict already keys on absolute half-hour starts, so recording the period the poll saw a confirmed dispatch in needs no new state. I could not fully confirm how long the API keeps an ended blip in flexPlannedDispatches on Predbat's own component path, so on that path the window may be shorter than the integration's 15-60 minutes.
…cle cannot see Review on #5657: - confirm a dispatch that began in the half hour and is over within DYNAMIC_LOAD_CAR_CONFIRM_MINUTES of its start from a reading inside it - the guard otherwise made a 14:00-14:01 blip unconfirmable - the 15 second poll now runs octopus_held_update() on its exact clock, so a confirmed blip's half hour is held until Octopus lists it as completed instead of falling to the day rate in between - persist dynamic_load_car_confirmed_periods with the rest of the dispatch-check state, so a restart does not re-price a confirmed blip's half hour at the day rate - docs: name the HA integration rate-feed exception Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Automated reply from Claude Code (pr-cleanup). Review body (no GH#5520 hold for a confirmed short dispatch): fixed in 99f7dfc, along the lines suggested. CI (pre-commit): the failure is All three new fixes were mutation-checked: removing each one fails its test (t57, t49, t55/t55b). |
… too Undo the over_before_guard exception from 99f7dfc. A dispatch that short is not meant to be counted, and lifting the guard for it only makes a false confirmation from a lagging sensor more likely. t57 now pins that a 14:00-14:01 dispatch is not confirmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Octopus has started issuing standalone Intelligent dispatches as short as one minute (e.g. 14:13–14:14). Predbat rounds every dispatch out to whole half hours, so each blip made a full 30 minutes cheap for the house battery and used up part of the daily low-rate cap, although a car rarely starts charging in a dispatch that short. The planner could then charge the battery at what is really the day rate.
Change
A dispatch shorter than
OCTOPUS_SLOT_MIN_MINUTES(5) is now off-peak only in a half hour where dynamic load (octopus_intelligent_dynamic) has seen the car charging.dispatch_billed_off_peak(), soload_octopus_slots(),rate_add_io_slots()andoctopus_planned_running()agree, and an unconfirmed blip spends none of the cap.load_octopus_slots()now takesdispatch_start/dispatch_endfrom the raw decode, the same wayrate_add_io_slots()already does.dynamic_load_car_confirmedonly holds the latest one, so a confirmed blip would lose its cheap rate once a later half hour was confirmed, and today's cost would step up (the Octopus IOG: completed dispatches with location AWAY should still be priced as off-peak #4946 shape). The new per-cardynamic_load_car_confirmed_periodskeeps every confirmed half hour, pruned after two days.octopus_held_dispatches(), Octopus API: a just-finished half hour of a running Intelligent dispatch is re-priced at day rate until completedDispatches lists it #5520). They passed the test when they were seen running, and the one in progress only runs to now, so it can be any length.intelligent_dispatch_billing: window, E.ON). That rule bills the whole planned window whether or not the car charges, and dynamic load confirms nothing there.In practice, confirmation needs a
car_charging_nowsensor: only the 15-second poll is fine-grained enough to catch a one-minute dispatch. Without one, short dispatches stay at the normal rate. The car's own plan still includes them, priced at the normal rate. Documented under "Very short dispatches" indocs/car-charging.md.Behaviour change:
test14_tiny_overlap_issue3328asserted that a real 1m16s dispatch makes its half hour cheap. That slot came from the same dump as #3328's 45-minute case, which is unaffected. The test now covers both sides: not cheap when unconfirmed, cheap when confirmed.Known limitations
dynamic_load_car_confirmed_periodsis in memory only. After a restart, a confirmed short dispatch from earlier today goes back to the day rate in today's cost.is_intelligent_adjusted) for a short dispatch is not removed. Predbat's own Octopus component path is fully covered.Tests
rate_add_io_slots: unconfirmed/confirmed, exactly 5 minutes, another half hour or another car confirmed, completed blips, cap not spent, window billing.octopus_slots: car-plan pricing; a zero-kWh long-dispatch tail trimmed to 3 minutes keeps the low rate; cap not spent.dynamic_load_car_not_chargingt55/t56: the 15s poll confirms a blip, the confirmation survives a later one, and old half hours are pruned.octopus_dispatch_holdt13: a 2-minute held half hour stays cheap.--quicksuite green (355 tests); pre-commit clean on the changed files.🤖 Generated with Claude Code