Skip to content

fix(octopus): stop IOG dispatch slots becoming a permanent off-peak window - #4854

Merged
springfall2008 merged 3 commits into
mainfrom
fix/octopus-iog-tou-phantom-window
Aug 30, 2026
Merged

fix(octopus): stop IOG dispatch slots becoming a permanent off-peak window#4854
springfall2008 merged 3 commits into
mainfrom
fix/octopus-iog-tou-phantom-window

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Problem

01e39825 began deriving a meter's off-peak windows from the measurements API's TOU_BUCKET_COST labels, taking whichever label was most common for each half-hour across the last 7 days. That premise is sound for Economy 7 — the labels genuinely describe a fixed schedule — but it was applied to every day/night tariff, including Intelligent Octopus Go.

On IOG the off-peak buckets cover the guaranteed overnight window plus whatever ad-hoc dispatch slots Octopus granted that night. Dispatches are decided night by night, so a bonus slot that happened to land on a plurality of the sampled nights was promoted to a permanent recurring window applied to all future days. The vote is a plain plurality (octopus.py _tou_windows_from_measurements) with no consistency threshold and no minimum sample count, so four nights out of seven was enough.

On one user's account (IOG-SMB-VAR) this invented a nightly 22:00–22:30 off-peak period alongside the genuine 23:30–05:30 one:

Info: OctopusAPI: Using off-peak windows [(1260, 1290), (1350, 1710)] (UTC minutes from midnight) from measurement TOU labels

(1260, 1290) is 22:00–22:30 BST and is not part of any guaranteed IOG schedule. Predbat did not merely display it — it planned a battery charge into that window believing the rate was 6.9p while the meter billed the day rate of 30.3p. This is a live cost-risk bug, not a cosmetic one.

Note this is not the phantom-dispatch mechanism: it needs no dispatch data at all. The account in question reported empty flexPlannedDispatches/completedDispatches on every poll.

Fix

  1. Skip TOU inference for Intelligent GO tariffs and use the guaranteed 23:30–05:30 window, which is the only fixed part of an IOG day. octopus_night_times still overrides everything, for a meter whose real bands the API does not report.
  2. Anchor TOU-derived windows to the clock the tariff's off-peak period is actually fixed to instead of hardcoding utc=True. The labels are UTC instants, so a local wall-clock tariff sampled during BST reports its window shifted by an hour; holding that offset fixed ran it an hour early once the clocks went back — the same class of bug 01e39825 set out to fix, reintroduced for local tariffs.

GO/E7/E10 meters keep reading their real windows from the meter, which is what 01e39825 was for.

Tests

Two new cases in test_octopus_tou_windows.py, both watched failing first:

  • Test 12 replays the reported TOU labels on an IOG tariff and asserts no recurring 22:00 window is created (before: 9 occurrences across the schedule).
  • Test 13 covers a local-anchored tariff whose TOU-derived window spans the October DST change (before: 2 of 6 slots an hour early).

./run_all --quick: 298 passed, 0 failed. run_pre_commit clean.

Verified against a live account

Info: OctopusAPI: Tariff <IOG-SMB-VAR> is Intelligent GO, its off-peak periods are
  dispatch-driven so the TOU labels are not read as a schedule
Info: OctopusAPI: Using hard-wired night rate window {'start': (23, 30), 'end': (5, 30),
  'cross_midnight': True, 'utc': False} anchored to local time

6.9p now applies only across the 12 half-hours of 23:30–05:30 local; 22:00–22:30 is back to 30.27p.

Known limitation, not addressed here

A local-anchored window that spans the 02:00 DST changeover comes out an hour short on that one day, because the schedule builder adds a timedelta to an already-localised midnight and keeps that day's opening UTC offset. This is pre-existing and unrelated to this change — the hard-wired GO window produces the identical two entries on that date with no TOU labels involved. Test 13 excludes the changeover day for that reason. Worth a follow-up.

🤖 Generated with Claude Code

springfall2008 and others added 2 commits August 30, 2026 09:15
…indow

01e3982 started deriving a meter's off-peak windows from the measurements
API's TOU bucket labels, taking whichever label was most common for each
half-hour across the last 7 days. That premise holds for Economy 7, where the
labels really do describe a fixed schedule, but not for Intelligent Octopus Go.

On IOG the off-peak buckets cover the guaranteed overnight window plus whatever
ad-hoc dispatch slots Octopus granted that night, and dispatches are decided
night by night. A bonus slot that happened to land on a plurality of the sampled
nights was therefore promoted to a permanent recurring window applied to every
future day. On one user's account (IOG-SMB-VAR) that invented a nightly
22:00-22:30 off-peak period alongside the genuine 23:30-05:30 one, and Predbat
planned a battery charge into it believing the rate was 6.9p while the meter
billed the day rate of 30.3p. The plurality vote has no consistency threshold,
so four nights out of seven was enough.

Skip the inference for Intelligent GO tariffs and use the guaranteed window,
which is the only fixed part of an IOG day. octopus_night_times still overrides
everything for a meter whose real bands the API does not report.

Also anchor TOU-derived windows to whichever clock the tariff's off-peak period
is actually fixed to, rather than assuming UTC. The labels are UTC instants, so
a local wall-clock tariff sampled during BST reports its window shifted by an
hour; holding that offset fixed ran it an hour early once the clocks went back -
the same class of bug 01e3982 set out to fix, reintroduced for local tariffs.

Verified against a reporting user's live account: 6.9p now applies only across
23:30-05:30 local and 22:00-22:30 is back to the 30.3p day rate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The distinguishing symptom is a cheap slot Octopus has never heard of while the
dispatch feeds are empty, which points at the day/night rate path rather than
phantom dispatches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@springfall2008
springfall2008 marked this pull request as ready for review August 30, 2026 08:21
Copilot AI lite review requested due to automatic review settings August 30, 2026 08:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The local-anchor conversion uses the current UTC offset, which can mis-convert TOU-derived windows when the 7-day sample straddles a DST transition, and the new log line is misleading about which window values are actually applied.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes a cost-risk bug in the Octopus integration where Intelligent Octopus Go (IOG) ad-hoc dispatch TOU labels could be inferred as permanent recurring off-peak windows, and improves how TOU-derived windows are anchored (UTC vs local wall-clock) to avoid DST-related drift.

Changes:

  • Skip TOU-label schedule inference for Intelligent GO tariffs and fall back to the guaranteed hard-wired IOG window.
  • Anchor TOU-derived windows to the tariff’s clock basis (UTC-fixed vs local-fixed) and add a helper to convert UTC-minute windows to local minutes.
  • Add regression tests covering the IOG phantom-slot case and a local-anchored TOU window spanning a DST change.
File summaries
File Description
apps/predbat/octopus.py Skips TOU inference for IOG; anchors TOU-derived windows to UTC vs local and converts local-anchored windows.
apps/predbat/tests/test_octopus_tou_windows.py Adds two new regression tests for IOG phantom off-peak slot and DST anchoring behavior.
.claude/skills/issue-triage/references/debug-journal.md Documents the observed IOG TOU-label inference failure mode and DST anchoring nuance for future triage.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/predbat/octopus.py Outdated
Comment thread apps/predbat/octopus.py
@springfall2008 springfall2008 added the BOT_CLEANUP Trigger: bot should address PR review feedback and CI failures, then commit and push label Aug 30, 2026
…bels

For a local-anchored tariff, the log printed tou_windows (raw UTC minutes)
while windows (converted to local wall-clock minutes) was what actually got
applied, making the log line misleading during debugging.
@springfall2008 springfall2008 removed the BOT_CLEANUP Trigger: bot should address PR review feedback and CI failures, then commit and push label Aug 30, 2026
@springfall2008
springfall2008 merged commit cbd9e30 into main Aug 30, 2026
2 checks passed
@springfall2008
springfall2008 deleted the fix/octopus-iog-tou-phantom-window branch August 30, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants