Skip to content

Commit fbf9c89

Browse files
feat(octopus): flag Octopus rate URLs with no current/future rates
Fixes #2726. download_octopus_rates_func() already handles connection errors, bad HTTP status, JSON decode failures and a missing "results" key - covering "is this URL valid and does it return JSON" for both rates_import/export_ octopus_url and the compare section (both already route through the same shared download_octopus_rates()). What was missing: a retired Octopus product's URL can still return a genuine 200/valid-JSON response with historical-only results - exactly the "Agile import rates all 0.0" (#2721) and "Compare chart never loads" (#2690) symptoms gcoan pointed at. Added a check that the downloaded data actually reaches self.minutes_now onward; if not, it's treated the same as any other download failure (Warn log + record_status(had_errors=True), falls back to stale cache if available, else raises) rather than silently caching/using data with nothing current in it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent bb39a50 commit fbf9c89

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

apps/predbat/octopus.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2310,6 +2310,19 @@ def download_octopus_rates(self, url):
23102310
else:
23112311
raise ValueError
23122312

2313+
# Downloaded data with a valid response but nothing covering the current time onward -
2314+
# e.g. a retired Octopus product whose URL still returns historical results (#2726).
2315+
# Retrying wouldn't help (the URL isn't broken, the product behind it is stale), so this
2316+
# is checked once per fresh download rather than inside the retry loop above.
2317+
if max(pdata.keys(), default=-1) < self.minutes_now:
2318+
self.log("Warn: Octopus: Downloaded data from URL {} has no current or future rates - the product may have been retired, check the URL in apps.yaml".format(url))
2319+
self.record_status("Warn: Octopus: URL has no current rates, check apps.yaml", debug=url, had_errors=True)
2320+
if url in self.octopus_url_cache:
2321+
pdata = self.octopus_url_cache[url]["data"]
2322+
return pdata
2323+
else:
2324+
raise ValueError
2325+
23132326
# Cache New Octopus data
23142327
self.octopus_url_cache[url] = {}
23152328
self.octopus_url_cache[url]["stamp"] = now

apps/predbat/tests/test_octopus_download_rates.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def test_octopus_download_rates(my_predbat):
2828
10. download_octopus_rates_func - HTTP error status
2929
11. download_octopus_rates_func - JSON decode error
3030
12. download_octopus_rates_func - missing 'results' key
31+
13. No current/future rates (#2726) - falls back to stale cache when available
32+
14. No current/future rates (#2726) - raises ValueError when no cache available
3133
"""
3234
print("\n=== Test Octopus download_octopus_rates ===")
3335
failed = False
@@ -38,6 +40,11 @@ def test_octopus_download_rates(my_predbat):
3840
my_predbat.midnight_utc = datetime.strptime("2024-06-12T00:00:00+00:00", "%Y-%m-%dT%H:%M:%S%z")
3941
my_predbat.debug_enable = False
4042
my_predbat.failures_total = 0
43+
# All the mock rate data below starts at minute 0 and covers forward from there - pin "now"
44+
# to 0 so the #2726 "has current rates" check (added below, tested explicitly in Test 13/14)
45+
# passes for scenarios that aren't about that check, regardless of real wall-clock time or
46+
# whatever a previous test left minutes_now as.
47+
my_predbat.minutes_now = 0
4148

4249
# Mock the download_octopus_rates_func to return rate data
4350
mock_rate_data = {
@@ -195,6 +202,50 @@ def test_octopus_download_rates(my_predbat):
195202
else:
196203
print("✓ Test 6 passed - Retry mechanism works correctly")
197204

205+
# Test 13: No current/future rates (#2726) - falls back to stale cache when available.
206+
# A retired Octopus product's URL can still return a valid 200/JSON response with genuine
207+
# historical results, so this can't be caught by any of the existing failure paths above -
208+
# it needs its own check that the data actually reaches "now" onward.
209+
print("\nTest 13: No current/future rates (#2726) - falls back to stale cache")
210+
test_url = "https://api.octopus.energy/test-stale-product"
211+
stale_cached_data = {0: 8.0, 60: 9.0}
212+
my_predbat.octopus_url_cache = {
213+
test_url: {
214+
"stamp": datetime.now() - timedelta(minutes=50), # expired, forces a fresh download
215+
"midnight_utc": my_predbat.midnight_utc,
216+
"data": stale_cached_data,
217+
}
218+
}
219+
my_predbat.minutes_now = 120
220+
# Every key is before "now" (120) - looks like a retired product still serving historical data.
221+
historical_only_data = {0: 5.0, 60: 6.0}
222+
223+
with patch.object(my_predbat, 'download_octopus_rates_func', return_value=historical_only_data):
224+
result = my_predbat.download_octopus_rates(test_url)
225+
226+
if result != stale_cached_data:
227+
print(f"✗ Test 13 failed - Expected stale cached data {stale_cached_data}, got {result}")
228+
failed = True
229+
else:
230+
print("✓ Test 13 passed - No current rates falls back to stale cache")
231+
232+
# Test 14: No current/future rates (#2726) - raises ValueError when no cache available
233+
print("\nTest 14: No current/future rates (#2726) - raises ValueError when no cache")
234+
test_url = "https://api.octopus.energy/test-stale-product-no-cache"
235+
my_predbat.octopus_url_cache = {}
236+
my_predbat.minutes_now = 120
237+
historical_only_data = {0: 5.0, 60: 6.0}
238+
239+
with patch.object(my_predbat, 'download_octopus_rates_func', return_value=historical_only_data):
240+
try:
241+
result = my_predbat.download_octopus_rates(test_url)
242+
print("✗ Test 14 failed - Expected ValueError to be raised")
243+
failed = True
244+
except ValueError:
245+
print("✓ Test 14 passed - ValueError raised when no current rates and no cache")
246+
247+
my_predbat.minutes_now = 0 # restore for the remaining tests below
248+
198249
# Test 7: download_octopus_rates_func - successful single page
199250
print("\nTest 7: download_octopus_rates_func - successful single page")
200251
my_predbat.debug_enable = False

0 commit comments

Comments
 (0)