Fix partial IOG slots not marking entire 30-min periods as off-peak#3332
Fix partial IOG slots not marking entire 30-min periods as off-peak#3332springfall2008 wants to merge 1 commit intomainfrom
Conversation
…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
There was a problem hiding this comment.
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). |
| # 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 |
There was a problem hiding this comment.
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).
| # 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) |
There was a problem hiding this comment.
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.
| # 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) |
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:
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()inoctopus.pyto:Testing
Added two new tests in
test_rate_add_io_slots.py: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.