From f5dcf04aabb2e921c9e59c8845c423d7e51d0367 Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Thu, 9 Apr 2026 18:27:21 +0000 Subject: [PATCH] feat(api): add energy and tax fields to Tibber GraphQL queries Request `energy` and `tax` fields alongside `total` in both quarter-hourly price queries. These represent the raw spot price and the tax/fee component that together make up the total consumer price. Updated hourly aggregation in formatters.py to carry energy/tax values through to aggregated output. Impact: Enables downstream consumers (sensors, services) to expose price composition data. Useful for solar feed-in compensation and net metering (saldering) calculations where the raw energy price is needed separately from taxes. --- custom_components/tibber_prices/api/client.py | 8 +++---- .../tibber_prices/services/formatters.py | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/custom_components/tibber_prices/api/client.py b/custom_components/tibber_prices/api/client.py index bbbf56d..22c8a2e 100644 --- a/custom_components/tibber_prices/api/client.py +++ b/custom_components/tibber_prices/api/client.py @@ -230,12 +230,12 @@ class TibberPricesApiClient: priceInfoRange(resolution:QUARTER_HOURLY, first:192, after: "{cursor}") {{ pageInfo{{ count }} edges{{node{{ - startsAt total level + startsAt total energy tax level }}}} }} priceInfo(resolution:QUARTER_HOURLY) {{ - today{{startsAt total level}} - tomorrow{{startsAt total level}} + today{{startsAt total energy tax level}} + tomorrow{{startsAt total energy tax level}} }} }} }} @@ -500,7 +500,7 @@ class TibberPricesApiClient: edges{{ cursor node{{ - startsAt total level + startsAt total energy tax level }} }} }} diff --git a/custom_components/tibber_prices/services/formatters.py b/custom_components/tibber_prices/services/formatters.py index 3db0d04..f4b36d6 100644 --- a/custom_components/tibber_prices/services/formatters.py +++ b/custom_components/tibber_prices/services/formatters.py @@ -103,6 +103,8 @@ def aggregate_to_hourly( # noqa: PLR0912 # Collect 5-interval rolling window: -2, -1, 0, +1, +2 window_prices: list[float] = [] + window_energy_prices: list[float] = [] + window_tax_prices: list[float] = [] window_intervals: list[dict] = [] for offset in range(-2, 3): # -2, -1, 0, +1, +2 @@ -113,6 +115,12 @@ def aggregate_to_hourly( # noqa: PLR0912 if price is not None: window_prices.append(price) window_intervals.append(target_interval) + energy = target_interval.get("energy") + tax = target_interval.get("tax") + if energy is not None: + window_energy_prices.append(energy) + if tax is not None: + window_tax_prices.append(tax) # Calculate aggregated price based on user preference if window_prices: @@ -127,6 +135,20 @@ def aggregate_to_hourly( # noqa: PLR0912 "total": aggregated_price, } + # Add aggregated energy and tax prices + if window_energy_prices: + aggregated_energy = ( + calculate_median(window_energy_prices) if use_median else calculate_mean(window_energy_prices) + ) + if aggregated_energy is not None: + data_point["energy"] = aggregated_energy + if window_tax_prices: + aggregated_tax = ( + calculate_median(window_tax_prices) if use_median else calculate_mean(window_tax_prices) + ) + if aggregated_tax is not None: + data_point["tax"] = aggregated_tax + # Add aggregated level if window_intervals: aggregated_level = aggregate_level_data(window_intervals)