mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
refactoring for QUARTER_HOURLY prices
This commit is contained in:
parent
70b5a0acd1
commit
8c61292acf
8 changed files with 161 additions and 100 deletions
|
|
@ -182,6 +182,7 @@ def _is_data_empty(data: dict, query_type: str) -> bool:
|
||||||
|
|
||||||
elif query_type == "price_info":
|
elif query_type == "price_info":
|
||||||
# Check for homes existence and non-emptiness before accessing
|
# Check for homes existence and non-emptiness before accessing
|
||||||
|
subscription = None
|
||||||
if (
|
if (
|
||||||
"viewer" not in data
|
"viewer" not in data
|
||||||
or "homes" not in data["viewer"]
|
or "homes" not in data["viewer"]
|
||||||
|
|
@ -189,29 +190,34 @@ def _is_data_empty(data: dict, query_type: str) -> bool:
|
||||||
or len(data["viewer"]["homes"]) == 0
|
or len(data["viewer"]["homes"]) == 0
|
||||||
or "currentSubscription" not in data["viewer"]["homes"][0]
|
or "currentSubscription" not in data["viewer"]["homes"][0]
|
||||||
or data["viewer"]["homes"][0]["currentSubscription"] is None
|
or data["viewer"]["homes"][0]["currentSubscription"] is None
|
||||||
or "priceInfo" not in data["viewer"]["homes"][0]["currentSubscription"]
|
|
||||||
):
|
):
|
||||||
_LOGGER.debug("Missing homes/currentSubscription/priceInfo in price_info check")
|
_LOGGER.debug("Missing homes/currentSubscription in price_info check")
|
||||||
is_empty = True
|
is_empty = True
|
||||||
else:
|
else:
|
||||||
price_info = data["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]
|
subscription = data["viewer"]["homes"][0]["currentSubscription"]
|
||||||
|
|
||||||
# Check historical data (either range or yesterday)
|
# Check priceInfoRange (192 quarter-hourly intervals)
|
||||||
has_historical = (
|
has_historical = (
|
||||||
"range" in price_info
|
"priceInfoRange" in subscription
|
||||||
and price_info["range"] is not None
|
and subscription["priceInfoRange"] is not None
|
||||||
and "edges" in price_info["range"]
|
and "edges" in subscription["priceInfoRange"]
|
||||||
and price_info["range"]["edges"]
|
and subscription["priceInfoRange"]["edges"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check today's data
|
# Check priceInfo for today's data
|
||||||
has_today = "today" in price_info and price_info["today"] is not None and len(price_info["today"]) > 0
|
has_price_info = "priceInfo" in subscription and subscription["priceInfo"] is not None
|
||||||
|
has_today = (
|
||||||
|
has_price_info
|
||||||
|
and "today" in subscription["priceInfo"]
|
||||||
|
and subscription["priceInfo"]["today"] is not None
|
||||||
|
and len(subscription["priceInfo"]["today"]) > 0
|
||||||
|
)
|
||||||
|
|
||||||
# Data is empty if we don't have historical data or today's data
|
# Data is empty if we don't have historical data or today's data
|
||||||
is_empty = not has_historical or not has_today
|
is_empty = not has_historical or not has_today
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Price info check - historical data historical: %s, today: %s, is_empty: %s",
|
"Price info check - priceInfoRange: %s, today: %s, is_empty: %s",
|
||||||
bool(has_historical),
|
bool(has_historical),
|
||||||
bool(has_today),
|
bool(has_today),
|
||||||
is_empty,
|
is_empty,
|
||||||
|
|
@ -233,34 +239,22 @@ def _is_data_empty(data: dict, query_type: str) -> bool:
|
||||||
else:
|
else:
|
||||||
rating = data["viewer"]["homes"][0]["currentSubscription"]["priceRating"]
|
rating = data["viewer"]["homes"][0]["currentSubscription"]["priceRating"]
|
||||||
|
|
||||||
# Check threshold percentages
|
# Check rating entries
|
||||||
has_thresholds = (
|
has_entries = (
|
||||||
"thresholdPercentages" in rating
|
query_type in rating
|
||||||
and rating["thresholdPercentages"] is not None
|
and rating[query_type] is not None
|
||||||
and "low" in rating["thresholdPercentages"]
|
and "entries" in rating[query_type]
|
||||||
and "high" in rating["thresholdPercentages"]
|
and rating[query_type]["entries"] is not None
|
||||||
|
and len(rating[query_type]["entries"]) > 0
|
||||||
)
|
)
|
||||||
if not has_thresholds:
|
|
||||||
_LOGGER.debug("Missing or invalid threshold percentages for %s rating", query_type)
|
|
||||||
is_empty = True
|
|
||||||
else:
|
|
||||||
# Check rating entries
|
|
||||||
has_entries = (
|
|
||||||
query_type in rating
|
|
||||||
and rating[query_type] is not None
|
|
||||||
and "entries" in rating[query_type]
|
|
||||||
and rating[query_type]["entries"] is not None
|
|
||||||
and len(rating[query_type]["entries"]) > 0
|
|
||||||
)
|
|
||||||
|
|
||||||
is_empty = not has_entries
|
is_empty = not has_entries
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s rating check - has_thresholds: %s, entries count: %d, is_empty: %s",
|
"%s rating check - entries count: %d, is_empty: %s",
|
||||||
query_type,
|
query_type,
|
||||||
has_thresholds,
|
len(rating[query_type]["entries"]) if has_entries else 0,
|
||||||
len(rating[query_type]["entries"]) if has_entries else 0,
|
is_empty,
|
||||||
is_empty,
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Unknown query type %s, treating as non-empty", query_type)
|
_LOGGER.debug("Unknown query type %s, treating as non-empty", query_type)
|
||||||
is_empty = False
|
is_empty = False
|
||||||
|
|
@ -280,19 +274,25 @@ def _prepare_headers(access_token: str) -> dict[str, str]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _flatten_price_info(subscription: dict) -> dict:
|
def _flatten_price_info(subscription: dict, currency: str | None = None) -> dict:
|
||||||
"""Transform and flatten priceInfo from full API data structure."""
|
"""
|
||||||
|
Transform and flatten priceInfo from full API data structure.
|
||||||
|
|
||||||
|
Now handles priceInfoRange (192 quarter-hourly intervals) separately from
|
||||||
|
priceInfo (today and tomorrow data). Currency is stored as a separate attribute.
|
||||||
|
"""
|
||||||
price_info = subscription.get("priceInfo", {})
|
price_info = subscription.get("priceInfo", {})
|
||||||
|
price_info_range = subscription.get("priceInfoRange", {})
|
||||||
|
|
||||||
# Get today and yesterday dates using Home Assistant's dt_util
|
# Get today and yesterday dates using Home Assistant's dt_util
|
||||||
today_local = dt_util.now().date()
|
today_local = dt_util.now().date()
|
||||||
yesterday_local = today_local - timedelta(days=1)
|
yesterday_local = today_local - timedelta(days=1)
|
||||||
_LOGGER.debug("Processing data for yesterday's date: %s", yesterday_local)
|
_LOGGER.debug("Processing data for yesterday's date: %s", yesterday_local)
|
||||||
|
|
||||||
# Transform edges data (extract yesterday's prices)
|
# Transform priceInfoRange edges data (extract yesterday's quarter-hourly prices)
|
||||||
if "range" in price_info and "edges" in price_info["range"]:
|
yesterday_prices = []
|
||||||
edges = price_info["range"]["edges"]
|
if "edges" in price_info_range:
|
||||||
yesterday_prices = []
|
edges = price_info_range["edges"]
|
||||||
|
|
||||||
for edge in edges:
|
for edge in edges:
|
||||||
if "node" not in edge:
|
if "node" not in edge:
|
||||||
|
|
@ -315,14 +315,12 @@ def _flatten_price_info(subscription: dict) -> dict:
|
||||||
yesterday_prices.append(price_data)
|
yesterday_prices.append(price_data)
|
||||||
|
|
||||||
_LOGGER.debug("Found %d price entries for yesterday", len(yesterday_prices))
|
_LOGGER.debug("Found %d price entries for yesterday", len(yesterday_prices))
|
||||||
# Replace the entire range object with yesterday prices
|
|
||||||
price_info["yesterday"] = yesterday_prices
|
|
||||||
del price_info["range"]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"yesterday": price_info.get("yesterday", []),
|
"yesterday": yesterday_prices,
|
||||||
"today": price_info.get("today", []),
|
"today": price_info.get("today", []),
|
||||||
"tomorrow": price_info.get("tomorrow", []),
|
"tomorrow": price_info.get("tomorrow", []),
|
||||||
|
"currency": currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -344,7 +342,6 @@ def _flatten_price_rating(subscription: dict) -> dict:
|
||||||
"hourly": hourly_entries,
|
"hourly": hourly_entries,
|
||||||
"daily": daily_entries,
|
"daily": daily_entries,
|
||||||
"monthly": monthly_entries,
|
"monthly": monthly_entries,
|
||||||
"thresholdPercentages": price_rating.get("thresholdPercentages"),
|
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,13 +401,23 @@ class TibberPricesApiClient:
|
||||||
data = await self._api_wrapper(
|
data = await self._api_wrapper(
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceInfo{
|
{viewer{homes{
|
||||||
range(resolution:HOURLY,last:48){edges{node{
|
id
|
||||||
startsAt total energy tax level
|
consumption(resolution:DAILY,last:1){
|
||||||
}}}
|
pageInfo{currency}
|
||||||
today{startsAt total energy tax level}
|
}
|
||||||
tomorrow{startsAt total energy tax level}
|
currentSubscription{
|
||||||
}}}}}"""
|
priceInfoRange(resolution:QUARTER_HOURLY,last:192){
|
||||||
|
edges{node{
|
||||||
|
startsAt total energy tax level
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
priceInfo(resolution:QUARTER_HOURLY){
|
||||||
|
today{startsAt total energy tax level}
|
||||||
|
tomorrow{startsAt total energy tax level}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}}"""
|
||||||
},
|
},
|
||||||
query_type=QueryType.PRICE_INFO,
|
query_type=QueryType.PRICE_INFO,
|
||||||
)
|
)
|
||||||
|
|
@ -421,7 +428,17 @@ class TibberPricesApiClient:
|
||||||
home_id = home.get("id")
|
home_id = home.get("id")
|
||||||
if home_id:
|
if home_id:
|
||||||
if "currentSubscription" in home:
|
if "currentSubscription" in home:
|
||||||
homes_data[home_id] = _flatten_price_info(home["currentSubscription"])
|
# Extract currency from consumption data if available
|
||||||
|
currency = None
|
||||||
|
if home.get("consumption"):
|
||||||
|
page_info = home["consumption"].get("pageInfo")
|
||||||
|
if page_info:
|
||||||
|
currency = page_info.get("currency")
|
||||||
|
|
||||||
|
homes_data[home_id] = _flatten_price_info(
|
||||||
|
home["currentSubscription"],
|
||||||
|
currency,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
homes_data[home_id] = {}
|
homes_data[home_id] = {}
|
||||||
|
|
||||||
|
|
@ -434,7 +451,6 @@ class TibberPricesApiClient:
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceRating{
|
{viewer{homes{id,currentSubscription{priceRating{
|
||||||
thresholdPercentages{low high}
|
|
||||||
daily{
|
daily{
|
||||||
currency
|
currency
|
||||||
entries{time total energy tax difference level}
|
entries{time total energy tax difference level}
|
||||||
|
|
@ -463,7 +479,6 @@ class TibberPricesApiClient:
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceRating{
|
{viewer{homes{id,currentSubscription{priceRating{
|
||||||
thresholdPercentages{low high}
|
|
||||||
hourly{
|
hourly{
|
||||||
currency
|
currency
|
||||||
entries{time total energy tax difference level}
|
entries{time total energy tax difference level}
|
||||||
|
|
@ -492,7 +507,6 @@ class TibberPricesApiClient:
|
||||||
data={
|
data={
|
||||||
"query": """
|
"query": """
|
||||||
{viewer{homes{id,currentSubscription{priceRating{
|
{viewer{homes{id,currentSubscription{priceRating{
|
||||||
thresholdPercentages{low high}
|
|
||||||
monthly{
|
monthly{
|
||||||
currency
|
currency
|
||||||
entries{time total energy tax difference level}
|
entries{time total energy tax difference level}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,21 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_flex_option(self, option_key: str, default: float) -> float:
|
def _get_flex_option(self, option_key: str, default: float) -> float:
|
||||||
"""Get a float option from config entry options or fallback to default. Accepts 0-100."""
|
"""
|
||||||
|
Get a float option from config entry.
|
||||||
|
|
||||||
|
Converts percentage values to decimal fractions.
|
||||||
|
- CONF_BEST_PRICE_FLEX: positive 0-100 → 0.0-1.0
|
||||||
|
- CONF_PEAK_PRICE_FLEX: negative -100 to 0 → -1.0 to 0.0
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option_key: The config key (CONF_BEST_PRICE_FLEX or CONF_PEAK_PRICE_FLEX)
|
||||||
|
default: Default value to use if not found
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Value converted to decimal fraction (e.g., 5 → 0.05, -5 → -0.05)
|
||||||
|
|
||||||
|
"""
|
||||||
options = self.coordinator.config_entry.options
|
options = self.coordinator.config_entry.options
|
||||||
data = self.coordinator.config_entry.data
|
data = self.coordinator.config_entry.data
|
||||||
value = options.get(option_key, data.get(option_key, default))
|
value = options.get(option_key, data.get(option_key, default))
|
||||||
|
|
@ -407,7 +421,8 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
||||||
price = float(price_data["total"])
|
price = float(price_data["total"])
|
||||||
percent_diff = ((price - ref_price) / ref_price) * 100 if ref_price != 0 else 0.0
|
percent_diff = ((price - ref_price) / ref_price) * 100 if ref_price != 0 else 0.0
|
||||||
percent_diff = round(percent_diff, 2)
|
percent_diff = round(percent_diff, 2)
|
||||||
# For best price: percent_diff <= flex*100; for peak: percent_diff >= -flex*100
|
# For best price (flex >= 0): percent_diff <= flex*100 (prices up to flex% above reference)
|
||||||
|
# For peak price (flex <= 0): percent_diff >= -flex*100 (prices up to |flex|% above reference)
|
||||||
in_flex = percent_diff <= flex * 100 if not reverse_sort else percent_diff >= -flex * 100
|
in_flex = percent_diff <= flex * 100 if not reverse_sort else percent_diff >= -flex * 100
|
||||||
# Split period if day or interval length changes
|
# Split period if day or interval length changes
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,13 @@ from .const import (
|
||||||
CONF_BEST_PRICE_FLEX,
|
CONF_BEST_PRICE_FLEX,
|
||||||
CONF_EXTENDED_DESCRIPTIONS,
|
CONF_EXTENDED_DESCRIPTIONS,
|
||||||
CONF_PEAK_PRICE_FLEX,
|
CONF_PEAK_PRICE_FLEX,
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
DEFAULT_BEST_PRICE_FLEX,
|
DEFAULT_BEST_PRICE_FLEX,
|
||||||
DEFAULT_EXTENDED_DESCRIPTIONS,
|
DEFAULT_EXTENDED_DESCRIPTIONS,
|
||||||
DEFAULT_PEAK_PRICE_FLEX,
|
DEFAULT_PEAK_PRICE_FLEX,
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
)
|
)
|
||||||
|
|
@ -380,38 +384,6 @@ class TibberPricesSubentryFlowHandler(ConfigSubentryFlow):
|
||||||
CONF_EXTENDED_DESCRIPTIONS,
|
CONF_EXTENDED_DESCRIPTIONS,
|
||||||
default=subentry.data.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS),
|
default=subentry.data.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS),
|
||||||
): BooleanSelector(),
|
): BooleanSelector(),
|
||||||
vol.Optional(
|
|
||||||
CONF_BEST_PRICE_FLEX,
|
|
||||||
default=int(
|
|
||||||
subentry.data.get(
|
|
||||||
CONF_BEST_PRICE_FLEX,
|
|
||||||
DEFAULT_BEST_PRICE_FLEX,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
): NumberSelector(
|
|
||||||
NumberSelectorConfig(
|
|
||||||
min=0,
|
|
||||||
max=100,
|
|
||||||
step=1,
|
|
||||||
mode=NumberSelectorMode.SLIDER,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vol.Optional(
|
|
||||||
CONF_PEAK_PRICE_FLEX,
|
|
||||||
default=int(
|
|
||||||
subentry.data.get(
|
|
||||||
CONF_PEAK_PRICE_FLEX,
|
|
||||||
DEFAULT_PEAK_PRICE_FLEX,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
): NumberSelector(
|
|
||||||
NumberSelectorConfig(
|
|
||||||
min=0,
|
|
||||||
max=100,
|
|
||||||
step=1,
|
|
||||||
mode=NumberSelectorMode.SLIDER,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
|
|
@ -473,6 +445,38 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
|
||||||
DEFAULT_PEAK_PRICE_FLEX,
|
DEFAULT_PEAK_PRICE_FLEX,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
): NumberSelector(
|
||||||
|
NumberSelectorConfig(
|
||||||
|
min=-100,
|
||||||
|
max=0,
|
||||||
|
step=1,
|
||||||
|
mode=NumberSelectorMode.SLIDER,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
|
default=int(
|
||||||
|
self.config_entry.options.get(
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
): NumberSelector(
|
||||||
|
NumberSelectorConfig(
|
||||||
|
min=-100,
|
||||||
|
max=0,
|
||||||
|
step=1,
|
||||||
|
mode=NumberSelectorMode.SLIDER,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
default=int(
|
||||||
|
self.config_entry.options.get(
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
)
|
||||||
|
),
|
||||||
): NumberSelector(
|
): NumberSelector(
|
||||||
NumberSelectorConfig(
|
NumberSelectorConfig(
|
||||||
min=0,
|
min=0,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ DOMAIN = "tibber_prices"
|
||||||
CONF_EXTENDED_DESCRIPTIONS = "extended_descriptions"
|
CONF_EXTENDED_DESCRIPTIONS = "extended_descriptions"
|
||||||
CONF_BEST_PRICE_FLEX = "best_price_flex"
|
CONF_BEST_PRICE_FLEX = "best_price_flex"
|
||||||
CONF_PEAK_PRICE_FLEX = "peak_price_flex"
|
CONF_PEAK_PRICE_FLEX = "peak_price_flex"
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_LOW = "price_rating_threshold_low"
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_HIGH = "price_rating_threshold_high"
|
||||||
|
|
||||||
ATTRIBUTION = "Data provided by Tibber"
|
ATTRIBUTION = "Data provided by Tibber"
|
||||||
|
|
||||||
|
|
@ -24,7 +26,9 @@ ATTRIBUTION = "Data provided by Tibber"
|
||||||
DEFAULT_NAME = "Tibber Price Information & Ratings"
|
DEFAULT_NAME = "Tibber Price Information & Ratings"
|
||||||
DEFAULT_EXTENDED_DESCRIPTIONS = False
|
DEFAULT_EXTENDED_DESCRIPTIONS = False
|
||||||
DEFAULT_BEST_PRICE_FLEX = 5 # 5% flexibility for best price (user-facing, percent)
|
DEFAULT_BEST_PRICE_FLEX = 5 # 5% flexibility for best price (user-facing, percent)
|
||||||
DEFAULT_PEAK_PRICE_FLEX = 5 # 5% flexibility for peak price (user-facing, percent)
|
DEFAULT_PEAK_PRICE_FLEX = -5 # 5% flexibility for peak price (user-facing, percent)
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW = -10 # Default rating threshold low percentage
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH = 10 # Default rating threshold high percentage
|
||||||
|
|
||||||
# Home types
|
# Home types
|
||||||
HOME_TYPE_APARTMENT = "APARTMENT"
|
HOME_TYPE_APARTMENT = "APARTMENT"
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,13 @@ from .api import (
|
||||||
TibberPricesApiClientCommunicationError,
|
TibberPricesApiClientCommunicationError,
|
||||||
TibberPricesApiClientError,
|
TibberPricesApiClientError,
|
||||||
)
|
)
|
||||||
from .const import DOMAIN
|
from .const import (
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -268,6 +274,14 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
return {}
|
return {}
|
||||||
return self._transform_data_for_main_entry(self._cached_price_data)
|
return self._transform_data_for_main_entry(self._cached_price_data)
|
||||||
|
|
||||||
|
def _get_threshold_percentages(self) -> dict[str, int]:
|
||||||
|
"""Get threshold percentages from config options."""
|
||||||
|
options = self.config_entry.options or {}
|
||||||
|
return {
|
||||||
|
"low": options.get(CONF_PRICE_RATING_THRESHOLD_LOW, DEFAULT_PRICE_RATING_THRESHOLD_LOW),
|
||||||
|
"high": options.get(CONF_PRICE_RATING_THRESHOLD_HIGH, DEFAULT_PRICE_RATING_THRESHOLD_HIGH),
|
||||||
|
}
|
||||||
|
|
||||||
def _transform_data_for_main_entry(self, raw_data: dict[str, Any]) -> dict[str, Any]:
|
def _transform_data_for_main_entry(self, raw_data: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Transform raw data for main entry (aggregated view of all homes)."""
|
"""Transform raw data for main entry (aggregated view of all homes)."""
|
||||||
# For main entry, we can show data from the first home as default
|
# For main entry, we can show data from the first home as default
|
||||||
|
|
@ -290,6 +304,7 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
"hourly": {"entries": first_home_data.get("hourly_rating", [])},
|
"hourly": {"entries": first_home_data.get("hourly_rating", [])},
|
||||||
"daily": {"entries": first_home_data.get("daily_rating", [])},
|
"daily": {"entries": first_home_data.get("daily_rating", [])},
|
||||||
"monthly": {"entries": first_home_data.get("monthly_rating", [])},
|
"monthly": {"entries": first_home_data.get("monthly_rating", [])},
|
||||||
|
"thresholdPercentages": self._get_threshold_percentages(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -322,6 +337,7 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
"hourly": {"entries": home_data.get("hourly_rating", [])},
|
"hourly": {"entries": home_data.get("hourly_rating", [])},
|
||||||
"daily": {"entries": home_data.get("daily_rating", [])},
|
"daily": {"entries": home_data.get("daily_rating", [])},
|
||||||
"monthly": {"entries": home_data.get("monthly_rating", [])},
|
"monthly": {"entries": home_data.get("monthly_rating", [])},
|
||||||
|
"thresholdPercentages": self._get_threshold_percentages(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,7 @@ def _extract_price_data(data: dict) -> tuple[dict, dict, list, Any, Any]:
|
||||||
price_rating_data = data.get("priceRating") or {}
|
price_rating_data = data.get("priceRating") or {}
|
||||||
hourly_ratings = price_rating_data.get("hourly") or []
|
hourly_ratings = price_rating_data.get("hourly") or []
|
||||||
rating_threshold_percentages = price_rating_data.get("thresholdPercentages")
|
rating_threshold_percentages = price_rating_data.get("thresholdPercentages")
|
||||||
currency = price_rating_data.get("currency")
|
currency = price_info_data.get("currency")
|
||||||
return price_info_data, price_rating_data, hourly_ratings, rating_threshold_percentages, currency
|
return price_info_data, price_rating_data, hourly_ratings, rating_threshold_percentages, currency
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,9 @@
|
||||||
"access_token": "API-Zugriffstoken",
|
"access_token": "API-Zugriffstoken",
|
||||||
"extended_descriptions": "Erweiterte Beschreibungen in Entitätsattributen anzeigen",
|
"extended_descriptions": "Erweiterte Beschreibungen in Entitätsattributen anzeigen",
|
||||||
"best_price_flex": "Flexibilität für Bestpreis (%)",
|
"best_price_flex": "Flexibilität für Bestpreis (%)",
|
||||||
"peak_price_flex": "Flexibilität für Spitzenpreis (%)"
|
"peak_price_flex": "Flexibilität für Spitzenpreis (%)",
|
||||||
|
"price_rating_threshold_low": "Preisbewertungs-Schwellenwert Niedrig (% vs. Durchschnitt)",
|
||||||
|
"price_rating_threshold_high": "Preisbewertungs-Schwellenwert Hoch (% vs. Durchschnitt)"
|
||||||
},
|
},
|
||||||
"title": "Optionen für Tibber Preisinformationen & Bewertungen",
|
"title": "Optionen für Tibber Preisinformationen & Bewertungen",
|
||||||
"submit": "Optionen speichern"
|
"submit": "Optionen speichern"
|
||||||
|
|
@ -93,7 +95,9 @@
|
||||||
"entry_not_found": "Tibber Konfigurationseintrag nicht gefunden."
|
"entry_not_found": "Tibber Konfigurationseintrag nicht gefunden."
|
||||||
},
|
},
|
||||||
"best_price_flex": "Bestpreis Flexibilität (%)",
|
"best_price_flex": "Bestpreis Flexibilität (%)",
|
||||||
"peak_price_flex": "Spitzenpreis Flexibilität (%)"
|
"peak_price_flex": "Spitzenpreis Flexibilität (%)",
|
||||||
|
"price_rating_threshold_low": "Niedriger Preis Schwellenwert (% zum gleitenden Durchschnitt)",
|
||||||
|
"price_rating_threshold_high": "Hoher Preis Schwellenwert (% zum gleitenden Durchschnitt)"
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,9 @@
|
||||||
"access_token": "API access token",
|
"access_token": "API access token",
|
||||||
"extended_descriptions": "Show extended descriptions in entity attributes",
|
"extended_descriptions": "Show extended descriptions in entity attributes",
|
||||||
"best_price_flex": "Best Price Flexibility (%)",
|
"best_price_flex": "Best Price Flexibility (%)",
|
||||||
"peak_price_flex": "Peak Price Flexibility (%)"
|
"peak_price_flex": "Peak Price Flexibility (%)",
|
||||||
|
"price_rating_threshold_low": "Price Rating Threshold Low (% vs trailing average)",
|
||||||
|
"price_rating_threshold_high": "Price Rating Threshold High (% vs trailing average)"
|
||||||
},
|
},
|
||||||
"title": "Options for Tibber Price Information & Ratings",
|
"title": "Options for Tibber Price Information & Ratings",
|
||||||
"submit": "Save Options"
|
"submit": "Save Options"
|
||||||
|
|
@ -93,7 +95,9 @@
|
||||||
"entry_not_found": "Tibber configuration entry not found."
|
"entry_not_found": "Tibber configuration entry not found."
|
||||||
},
|
},
|
||||||
"best_price_flex": "Best Price Flexibility (%)",
|
"best_price_flex": "Best Price Flexibility (%)",
|
||||||
"peak_price_flex": "Peak Price Flexibility (%)"
|
"peak_price_flex": "Peak Price Flexibility (%)",
|
||||||
|
"price_rating_threshold_low": "Price Low Threshold (% to trailing average)",
|
||||||
|
"price_rating_threshold_high": "Price High Threshold (% to trailing average)"
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue