-
-
Notifications
You must be signed in to change notification settings - Fork 122
kraken: add GraphQL applicableStandingCharges fallback for TOU tariff 404s #3834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -88,7 +88,8 @@ async def handle_oauth_401(self): | |||||||||||||||
| KRAKEN_VIEWER_QUERY = """{ viewer { accounts { number } } }""" | ||||||||||||||||
|
|
||||||||||||||||
| # GraphQL applicableRates query — fallback when REST product endpoint returns 404 | ||||||||||||||||
| # (product code removed/replaced while customer is still on the tariff). | ||||||||||||||||
| # (product code removed/replaced while customer is still on the tariff, or TOU tariff | ||||||||||||||||
| # with no /standard-unit-rates/ REST endpoint e.g. E-TOU-* tariffs on E.ON Next). | ||||||||||||||||
| # Returns value (pence/kWh inc VAT), validFrom, validTo for the requested window. | ||||||||||||||||
| KRAKEN_APPLICABLE_RATES_QUERY = """{{ | ||||||||||||||||
| applicableRates( | ||||||||||||||||
|
|
@@ -103,6 +104,23 @@ async def handle_oauth_401(self): | |||||||||||||||
| }} | ||||||||||||||||
| }}""" | ||||||||||||||||
|
|
||||||||||||||||
| # GraphQL applicableStandingCharges query — fallback when REST /standing-charges/ returns 404 | ||||||||||||||||
| # (same scenarios as KRAKEN_APPLICABLE_RATES_QUERY above — product removed from REST API, | ||||||||||||||||
| # or TOU tariffs whose /standing-charges/ endpoint is unavailable on the provider API). | ||||||||||||||||
| # Returns value (pence/day inc VAT) for the requested window. | ||||||||||||||||
| KRAKEN_STANDING_CHARGES_QUERY = """{{ | ||||||||||||||||
| applicableStandingCharges( | ||||||||||||||||
| accountNumber: "{account_number}" | ||||||||||||||||
| mpxn: "{mpan}" | ||||||||||||||||
| startAt: "{start_at}" | ||||||||||||||||
| endAt: "{end_at}" | ||||||||||||||||
| ) {{ | ||||||||||||||||
| value | ||||||||||||||||
| validFrom | ||||||||||||||||
| validTo | ||||||||||||||||
| }} | ||||||||||||||||
| }}""" | ||||||||||||||||
|
|
||||||||||||||||
| KRAKEN_BASE_URLS = { | ||||||||||||||||
| "edf": "https://api.edfgb-kraken.energy", | ||||||||||||||||
| "eon": "https://api.eonnext-kraken.energy", | ||||||||||||||||
|
|
@@ -632,36 +650,94 @@ def get_entity_name(self, root, suffix): | |||||||||||||||
| entity_name = root + "." + self.prefix + "_kraken_" + self.account_id.replace("-", "_") + "_" + suffix | ||||||||||||||||
| return entity_name.lower() | ||||||||||||||||
|
|
||||||||||||||||
| async def async_fetch_standing_charges_graphql(self, mpan): | ||||||||||||||||
| """Fetch standing charge via GraphQL applicableStandingCharges — fallback when REST returns non-200. | ||||||||||||||||
|
|
||||||||||||||||
| Used when the product code has been removed from the REST API (e.g. TOU tariffs on E.ON Next | ||||||||||||||||
| whose /standing-charges/ REST endpoint returns 404). Returns the standing charge in pounds/day | ||||||||||||||||
| (pence/day divided by 100), or None on failure. | ||||||||||||||||
|
|
||||||||||||||||
| Args: | ||||||||||||||||
| mpan: The import MPAN (meter point access number) for the account. | ||||||||||||||||
|
|
||||||||||||||||
| Returns the standing charge as pounds/day (float), or None. | ||||||||||||||||
| """ | ||||||||||||||||
| now = datetime.now(timezone.utc) | ||||||||||||||||
| midnight_utc = now.replace(hour=0, minute=0, second=0, microsecond=0) | ||||||||||||||||
| # Use a 3-day window (yesterday → tomorrow+1) to ensure the current standing charge | ||||||||||||||||
| # is captured regardless of when the agreement period started or ends. | ||||||||||||||||
| start_at = (midnight_utc - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ") | ||||||||||||||||
| end_at = (midnight_utc + timedelta(days=2)).strftime("%Y-%m-%dT%H:%M:%SZ") | ||||||||||||||||
| query = KRAKEN_STANDING_CHARGES_QUERY.format( | ||||||||||||||||
| account_number=self.account_id, | ||||||||||||||||
| mpan=mpan, | ||||||||||||||||
| start_at=start_at, | ||||||||||||||||
| end_at=end_at, | ||||||||||||||||
| ) | ||||||||||||||||
| data = await self.async_graphql_query(query, "applicable-standing-charges-graphql") | ||||||||||||||||
| if not data: | ||||||||||||||||
| return None | ||||||||||||||||
|
|
||||||||||||||||
| applicable_charges = data.get("applicableStandingCharges", []) | ||||||||||||||||
| if not applicable_charges: | ||||||||||||||||
| self.log("Warn: Kraken: applicableStandingCharges GraphQL returned no results") | ||||||||||||||||
| return None | ||||||||||||||||
|
|
||||||||||||||||
| # Take the first (most applicable) standing charge entry; value is pence/day inc VAT. | ||||||||||||||||
| # Divide by 100 to match the units expected by the caller (pounds/day). | ||||||||||||||||
| charge = applicable_charges[0] | ||||||||||||||||
| value = charge.get("value") | ||||||||||||||||
| if value is None: | ||||||||||||||||
| return None | ||||||||||||||||
| self.log(f"Kraken: Fetched standing charge via GraphQL applicableStandingCharges for MPAN {mpan}: {value}p/day") | ||||||||||||||||
| return float(value) / 100.0 | ||||||||||||||||
|
|
||||||||||||||||
| async def async_fetch_standing_charges(self, tariff=None): | ||||||||||||||||
| """Fetch standing charges from public REST endpoint. No auth needed.""" | ||||||||||||||||
| """Fetch standing charges from public REST endpoint. No auth needed. | ||||||||||||||||
|
|
||||||||||||||||
| Falls back to GraphQL applicableStandingCharges if the REST endpoint returns a non-200 | ||||||||||||||||
| status (e.g. 404 for TOU tariffs on E.ON Next whose product is not in the REST API) | ||||||||||||||||
| and self.import_mpan is known. | ||||||||||||||||
|
Comment on lines
+698
to
+700
|
||||||||||||||||
| Falls back to GraphQL applicableStandingCharges if the REST endpoint returns a non-200 | |
| status (e.g. 404 for TOU tariffs on E.ON Next whose product is not in the REST API) | |
| and self.import_mpan is known. | |
| Falls back to GraphQL applicableStandingCharges only when the REST endpoint returns | |
| HTTP 404 or 410 (for example, TOU tariffs on E.ON Next whose product is not in the | |
| REST API) and self.import_mpan is known. Other non-200 responses, such as transient | |
| 429/5xx errors, are treated as failures and do not trigger GraphQL fallback. |
Copilot
AI
Apr 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This inline comment about unit conversion appears inverted: fetch.py multiplies metric_standing_charge by 100 to convert from pounds/day to pence/day. Here you’re dividing REST/GraphQL pence/day by 100 to return pounds/day, so the comment should describe that fetch.py expects pounds/day (and converts internally) rather than “expects pounds/day” while multiplying.
| # API returns pence/day; fetch.py multiplies by 100 expecting pounds/day | |
| # API returns pence/day; convert to pounds/day here because fetch.py expects pounds/day | |
| # from this source and performs its own internal conversion to pence/day later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async_fetch_standing_charges_graphql() requests validFrom/validTo but then always returns the first entry in applicableStandingCharges. If multiple standing charge periods are returned within the 3-day window (e.g. a rate change boundary), this can pick an arbitrary/non-current value. Consider selecting the entry that applies to
now(validFrom <= now < validTo/None), or at least sorting by validFrom to make the choice deterministic.