From 134f0abb5f1a6b8e62eb8477ef8921c11163017a Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Fri, 17 Jul 2026 18:49:26 +0200 Subject: [PATCH] fix(tibber): fetch newest price slots via priceInfoRange last:192 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit priceInfoRange is a Relay-style cursor connection over the subscription's entire price history. With no cursor, first:96 returned the OLDEST 96 slots, anchored at the subscription start date — a fixed window that never advanced. For a contract added mid-period this meant refresh_prices kept re-upserting the same day-one slots forever, so GET /api/energy/prices found nothing in the today+tomorrow window and the UI showed "No Tibber price points available". Use last:192 to return the newest 192 quarter-hourly slots (2 days), which ends at the latest published slot and advances daily, fully covering the prices endpoint's today+tomorrow window. --- app/integrations/tibber/client.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/app/integrations/tibber/client.py b/app/integrations/tibber/client.py index ce8df20..072caf5 100644 --- a/app/integrations/tibber/client.py +++ b/app/integrations/tibber/client.py @@ -31,18 +31,24 @@ _TIBBER_API_URL = "https://api.tibber.com/v1-beta/gql" _DEFAULT_TIMEOUT = 15.0 # GraphQL query to fetch a range of 15-minute price nodes. -# ``priceInfoRange(resolution: QUARTER_HOURLY, first: 96)`` fetches up to -# 96 quarter-hourly slots which covers today + tomorrow (2 × 24 × 4 = 192 max, -# but the Tibber API typically starts from the current slot and returns at -# most the remaining hours of today plus tomorrow, so 96 is a good cap for -# "today + tomorrow"). +# ``priceInfoRange`` is a Relay-style cursor connection over the subscription's +# entire available price history. With no before/after cursor: +# * ``first: N`` returns the OLDEST N nodes (anchored at the subscription +# start date) — a fixed window that never advances, so it is WRONG for +# "current + upcoming" prices. +# * ``last: N`` returns the NEWEST N nodes (ending at the latest published +# slot: tomorrow 23:45 once day-ahead prices are out, else today 23:45). +# We want the newest slots, so we use ``last``. 192 = 2 days × 24 h × 4 slots, +# which fully covers the "today + tomorrow" window that GET /api/energy/prices +# queries (the earliest of the 192 newest slots reaches back past today 00:00 +# UTC in either publish state). _PRICE_RANGE_QUERY = """ { viewer { homes { id currentSubscription { - priceInfoRange(resolution: QUARTER_HOURLY, first: 96) { + priceInfoRange(resolution: QUARTER_HOURLY, last: 192) { nodes { startsAt total @@ -232,9 +238,11 @@ def fetch_price_range( ) -> list[PricePoint]: """Fetch a range of 15-minute price nodes from the Tibber API. - Sends the ``priceInfoRange(resolution: QUARTER_HOURLY, first: 96)`` query - and parses every returned node into a ``PricePoint``. The number of nodes - is not assumed — all returned nodes are parsed regardless of count. + Sends the ``priceInfoRange(resolution: QUARTER_HOURLY, last: 192)`` query + and parses every returned node into a ``PricePoint``. ``last`` (not + ``first``) is used so the newest slots are returned; ``first`` would anchor + at the subscription start date and never advance. The number of nodes is + not assumed — all returned nodes are parsed regardless of count. Parameters ----------