Skip to content

Fix partial IOG slots not marking entire 30-min periods as off-peak#3332

Open
springfall2008 wants to merge 1 commit intomainfrom
fixes4
Open

Fix partial IOG slots not marking entire 30-min periods as off-peak#3332
springfall2008 wants to merge 1 commit intomainfrom
fixes4

Conversation

@springfall2008
Copy link
Owner

Description

Fixes #3328

When Intelligent Octopus slots partially overlap 30-minute rate periods, the entire period should be marked as off-peak. Previously, a slot from 19:30-20:15 would only mark 19:30-20:00 as off-peak, not 20:00-20:30.

Problem

The issue occurred when IOG slots didn't align perfectly with 30-minute boundaries. For example:

  • Slot from 19:30-20:15 (45 minutes) only marked 19:30-20:00 as off-peak
  • The 20:00-20:30 period remained at standard rate, even though the IOG slot covered the first 15 minutes

This is incorrect because Octopus Intelligent slots should make the entire 30-minute period off-peak if any part of it is covered.

Solution

Modified rate_add_io_slots() in octopus.py to:

  • Floor the start time (round down) to include the entire starting 30-min slot
  • Ceiling the end time (round up) to include the entire ending 30-min slot
# Before (rounds to nearest):
start_minutes = int(round(start_minutes / plan_interval_minutes, 0) * plan_interval_minutes)
end_minutes = int(round(end_minutes / plan_interval_minutes, 0) * plan_interval_minutes)

# After (floor start, ceiling end):
start_minutes = (start_minutes // plan_interval_minutes) * plan_interval_minutes
end_minutes = ((end_minutes + plan_interval_minutes - 1) // plan_interval_minutes) * plan_interval_minutes

Testing

Added two new tests in test_rate_add_io_slots.py:

  • Test 13: IOG slot 19:30-20:15 now correctly marks both 19:30-20:00 AND 20:00-20:30 as off-peak
  • Test 14: Even a 1-minute IOG slot (13:37-13:38) now marks the entire 13:30-14:00 period as off-peak

All existing tests continue to pass, confirming backward compatibility.

Impact

Users with Intelligent Octopus who receive charge slots that don't align with 30-minute boundaries will now see correct off-peak rates applied to all affected periods.

…3328)

When Intelligent Octopus slots partially overlap 30-minute rate periods,
the entire period should be marked as off-peak. Previously, a slot from
19:30-20:15 would only mark 19:30-20:00 as off-peak, not 20:00-20:30.

Changes:
- Modified rate_add_io_slots() to floor start time and ceiling end time
  instead of rounding to nearest, ensuring partial overlaps are included
- Added tests confirming 45-min slot (19:30-20:15) marks both periods
- Added test confirming 1-min slot marks entire 30-min period

Fixes #3328
Copilot AI review requested due to automatic review settings February 8, 2026 11:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes Octopus Intelligent (IOG) slot handling so that when a slot partially overlaps a 30‑minute tariff period, the whole 30‑minute period is treated as off‑peak (aligning Predbat’s import rates with how Octopus applies Intelligent slots).

Changes:

  • Updated rate_add_io_slots() to floor the slot start time and ceil the slot end time to the boundary interval.
  • Added regression tests covering partial-slot overlap across two adjacent 30‑minute periods and a tiny (≈1 minute) overlap case.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/predbat/octopus.py Adjusts IO slot boundary rounding logic to include partially overlapped periods.
apps/predbat/tests/test_rate_add_io_slots.py Adds new tests to validate off-peak marking for partial overlaps (Issue #3328).

Comment on lines 2122 to +2126
# Round slots to 30 minute boundary
start_minutes = int(round(start_minutes / plan_interval_minutes, 0) * plan_interval_minutes)
end_minutes = int(round(end_minutes / plan_interval_minutes, 0) * plan_interval_minutes)
# Floor the start (round down) and ceiling the end (round up)
# This ensures any partial overlap with a 30-min slot marks the entire slot as off-peak
start_minutes = (start_minutes // plan_interval_minutes) * plan_interval_minutes
end_minutes = ((end_minutes + plan_interval_minutes - 1) // plan_interval_minutes) * plan_interval_minutes
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

rate_add_io_slots() currently floors/ceils start_minutes/end_minutes using self.plan_interval_minutes, but the rest of the function (slot limiting / tracking) is hard-coded to 30-minute boundaries (minute % 30, slot_start = (minute // 30) * 30). If plan_interval_minutes is configured to something other than 30 (e.g., 5/15), this change can cause the loop range to never hit a minute % 30 == 0 boundary, meaning the IO slot may not be applied at all. Consider using a single interval constant for Intelligent/Octopus rate periods (30 minutes) consistently for both the floor/ceil and the later slot accounting (or explicitly enforce/validate that this function only runs with a 30-minute plan interval).

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +261
# Test 14: Another partial slot example - 1 minute into a 30-min slot should make entire slot off-peak
print("\n**** Test 14: Tiny overlap (1 min) should still make entire 30-min slot off-peak ****")
slot_start = midnight_utc + timedelta(hours=13, minutes=37, seconds=11) # 13:37:11
slot_end = slot_start + timedelta(minutes=1, seconds=16) # 13:38:27 (spans two 5-min or 30-min slots)
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The comment on the Test 14 slot says it “spans two 5-min or 30-min slots”, but 13:37:11→13:38:27 stays within the same 5-minute block (13:35–13:40) and the same 30-minute block (13:30–14:00). Consider rewording to avoid confusion about what overlap is being exercised.

Suggested change
# Test 14: Another partial slot example - 1 minute into a 30-min slot should make entire slot off-peak
print("\n**** Test 14: Tiny overlap (1 min) should still make entire 30-min slot off-peak ****")
slot_start = midnight_utc + timedelta(hours=13, minutes=37, seconds=11) # 13:37:11
slot_end = slot_start + timedelta(minutes=1, seconds=16) # 13:38:27 (spans two 5-min or 30-min slots)
# Test 14: Another partial slot example - tiny overlap within a 30-min slot should make entire slot off-peak
print("\n**** Test 14: Tiny overlap (within one 30-min slot) should still make entire 30-min slot off-peak ****")
slot_start = midnight_utc + timedelta(hours=13, minutes=37, seconds=11) # 13:37:11
slot_end = slot_start + timedelta(minutes=1, seconds=16) # 13:38:27 (fully within the same 5-min and 30-min slot)

Copilot uses AI. Check for mistakes.
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.

Partial intelligent slots not counting as off-peak rate

1 participant