mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
fix(translations): resolve hassfest selector key validation errors
Changed all selector option keys from uppercase to lowercase to comply with Home Assistant's hassfest validation pattern [a-z0-9-_]+. Fixed inconsistency in PEAK_PRICE_MIN_LEVEL_OPTIONS where some values were uppercase while others were lowercase. Changes: - translations/*.json: All selector keys now lowercase (volatility, price_level) - const.py: Added .lower() to all PEAK_PRICE_MIN_LEVEL_OPTIONS values - binary_sensor.py: Added .upper() conversion when looking up price levels in PRICE_LEVEL_MAPPING to handle lowercase config values Impact: Config flow now works correctly with translated selector options. Hassfest validation passes without selector key errors.
This commit is contained in:
parent
df79afc87e
commit
532a91be58
7 changed files with 93 additions and 101 deletions
|
|
@ -30,7 +30,6 @@ from .const import (
|
|||
CONF_BEST_PRICE_MAX_LEVEL,
|
||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
||||
CONF_EXTENDED_DESCRIPTIONS,
|
||||
CONF_MIN_VOLATILITY_FOR_PERIODS,
|
||||
CONF_PEAK_PRICE_MIN_LEVEL,
|
||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
||||
CONF_VOLATILITY_THRESHOLD_HIGH,
|
||||
|
|
@ -39,7 +38,6 @@ from .const import (
|
|||
DEFAULT_BEST_PRICE_MAX_LEVEL,
|
||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
||||
DEFAULT_EXTENDED_DESCRIPTIONS,
|
||||
DEFAULT_MIN_VOLATILITY_FOR_PERIODS,
|
||||
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
||||
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
||||
|
|
@ -47,7 +45,6 @@ from .const import (
|
|||
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
PRICE_LEVEL_MAPPING,
|
||||
VOLATILITY_HIGH,
|
||||
VOLATILITY_LOW,
|
||||
VOLATILITY_MODERATE,
|
||||
VOLATILITY_VERY_HIGH,
|
||||
async_get_entity_description,
|
||||
|
|
@ -420,29 +417,21 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
|||
# Peak price sensor
|
||||
min_volatility = self.coordinator.config_entry.options.get(
|
||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
||||
# Migration: fall back to old global filter, then to new default
|
||||
self.coordinator.config_entry.options.get(
|
||||
CONF_MIN_VOLATILITY_FOR_PERIODS,
|
||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
||||
),
|
||||
)
|
||||
else:
|
||||
# Best price sensor
|
||||
min_volatility = self.coordinator.config_entry.options.get(
|
||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
||||
# Migration: fall back to old global filter, then to new default
|
||||
self.coordinator.config_entry.options.get(
|
||||
CONF_MIN_VOLATILITY_FOR_PERIODS,
|
||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
||||
),
|
||||
)
|
||||
|
||||
# "LOW" means no filtering (show at any volatility ≥0ct)
|
||||
if min_volatility == VOLATILITY_LOW:
|
||||
# "low" means no filtering (show at any volatility ≥0ct)
|
||||
if min_volatility == "low":
|
||||
return True
|
||||
|
||||
# Legacy migration: "ANY" also means no filtering
|
||||
if min_volatility == "ANY":
|
||||
# "any" is legacy alias for "low" (no filtering)
|
||||
if min_volatility == "any":
|
||||
return True
|
||||
|
||||
# Get today's price data to calculate volatility
|
||||
|
|
@ -506,8 +495,8 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
|||
DEFAULT_BEST_PRICE_MAX_LEVEL,
|
||||
)
|
||||
|
||||
# "ANY" means no level filtering
|
||||
if level_config == "ANY":
|
||||
# "any" means no level filtering
|
||||
if level_config == "any":
|
||||
return True
|
||||
|
||||
# Get today's intervals
|
||||
|
|
@ -518,7 +507,8 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
|||
return True # If no data, don't filter
|
||||
|
||||
# Check if ANY interval today meets the level requirement
|
||||
level_order = PRICE_LEVEL_MAPPING.get(level_config, 0)
|
||||
# Note: level_config is lowercase from selector, but PRICE_LEVEL_MAPPING uses uppercase
|
||||
level_order = PRICE_LEVEL_MAPPING.get(level_config.upper(), 0)
|
||||
|
||||
if reverse_sort:
|
||||
# Peak price: level >= min_level (show if ANY interval is expensive enough)
|
||||
|
|
@ -563,9 +553,16 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
|||
A dict with empty periods and a reason attribute explaining why.
|
||||
|
||||
"""
|
||||
# Get appropriate volatility config based on sensor type
|
||||
if reverse_sort:
|
||||
min_volatility = self.coordinator.config_entry.options.get(
|
||||
CONF_MIN_VOLATILITY_FOR_PERIODS,
|
||||
DEFAULT_MIN_VOLATILITY_FOR_PERIODS,
|
||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
||||
)
|
||||
else:
|
||||
min_volatility = self.coordinator.config_entry.options.get(
|
||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
||||
)
|
||||
|
||||
# Get appropriate level config based on sensor type
|
||||
|
|
@ -584,10 +581,10 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
|
|||
|
||||
# Build reason string explaining which filter(s) prevented display
|
||||
reasons = []
|
||||
if min_volatility != "ANY" and not self._check_volatility_filter(reverse_sort=reverse_sort):
|
||||
reasons.append(f"volatility_below_{min_volatility.lower()}")
|
||||
if level_config != "ANY" and not self._check_level_filter(reverse_sort=reverse_sort):
|
||||
reasons.append(f"level_{level_filter_type}_{level_config.lower()}")
|
||||
if min_volatility != "any" and not self._check_volatility_filter(reverse_sort=reverse_sort):
|
||||
reasons.append(f"volatility_below_{min_volatility}")
|
||||
if level_config != "any" and not self._check_level_filter(reverse_sort=reverse_sort):
|
||||
reasons.append(f"level_{level_filter_type}_{level_config}")
|
||||
|
||||
# Join multiple reasons with "and"
|
||||
reason = "_and_".join(reasons) if reasons else "filtered"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ CONF_VOLATILITY_THRESHOLD_HIGH = "volatility_threshold_high"
|
|||
CONF_VOLATILITY_THRESHOLD_VERY_HIGH = "volatility_threshold_very_high"
|
||||
CONF_BEST_PRICE_MIN_VOLATILITY = "best_price_min_volatility"
|
||||
CONF_PEAK_PRICE_MIN_VOLATILITY = "peak_price_min_volatility"
|
||||
CONF_MIN_VOLATILITY_FOR_PERIODS = "min_volatility_for_periods" # Deprecated: Use CONF_BEST_PRICE_MIN_VOLATILITY
|
||||
CONF_BEST_PRICE_MAX_LEVEL = "best_price_max_level"
|
||||
CONF_PEAK_PRICE_MIN_LEVEL = "peak_price_min_level"
|
||||
|
||||
|
|
@ -55,11 +54,10 @@ DEFAULT_PRICE_TREND_THRESHOLD_FALLING = -5 # Default trend threshold for fallin
|
|||
DEFAULT_VOLATILITY_THRESHOLD_MODERATE = 5.0 # Default threshold for MODERATE volatility (ct/øre)
|
||||
DEFAULT_VOLATILITY_THRESHOLD_HIGH = 15.0 # Default threshold for HIGH volatility (ct/øre)
|
||||
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH = 30.0 # Default threshold for VERY_HIGH volatility (ct/øre)
|
||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY = "LOW" # Show best price at any volatility (optimization always useful)
|
||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY = "LOW" # Always show peak price (warning relevant even at low spreads)
|
||||
DEFAULT_MIN_VOLATILITY_FOR_PERIODS = "LOW" # Deprecated: Use DEFAULT_BEST_PRICE_MIN_VOLATILITY
|
||||
DEFAULT_BEST_PRICE_MAX_LEVEL = "ANY" # Default: show best price periods regardless of price level
|
||||
DEFAULT_PEAK_PRICE_MIN_LEVEL = "ANY" # Default: show peak price periods regardless of price level
|
||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY = "low" # Show best price at any volatility (optimization always useful)
|
||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY = "low" # Always show peak price (warning relevant even at low spreads)
|
||||
DEFAULT_BEST_PRICE_MAX_LEVEL = "any" # Default: show best price periods regardless of price level
|
||||
DEFAULT_PEAK_PRICE_MIN_LEVEL = "any" # Default: show peak price periods regardless of price level
|
||||
|
||||
# Home types
|
||||
HOME_TYPE_APARTMENT = "APARTMENT"
|
||||
|
|
@ -227,35 +225,32 @@ VOLATILITY_OPTIONS = [
|
|||
|
||||
# Valid options for minimum volatility filter for periods
|
||||
MIN_VOLATILITY_FOR_PERIODS_OPTIONS = [
|
||||
VOLATILITY_LOW, # Show at any volatility (≥0ct spread) - no filter
|
||||
VOLATILITY_MODERATE, # Only show periods when volatility ≥ MODERATE (≥5ct)
|
||||
VOLATILITY_HIGH, # Only show periods when volatility ≥ HIGH (≥15ct)
|
||||
VOLATILITY_VERY_HIGH, # Only show periods when volatility ≥ VERY_HIGH (≥30ct)
|
||||
VOLATILITY_LOW.lower(), # Show at any volatility (≥0ct spread) - no filter
|
||||
VOLATILITY_MODERATE.lower(), # Only show periods when volatility ≥ MODERATE (≥5ct)
|
||||
VOLATILITY_HIGH.lower(), # Only show periods when volatility ≥ HIGH (≥15ct)
|
||||
VOLATILITY_VERY_HIGH.lower(), # Only show periods when volatility ≥ VERY_HIGH (≥30ct)
|
||||
]
|
||||
|
||||
# Valid options for best price maximum level filter (AND-linked with volatility filter)
|
||||
# Sorted from cheap to expensive: user selects "up to how expensive"
|
||||
BEST_PRICE_MAX_LEVEL_OPTIONS = [
|
||||
"ANY", # No filter, allow all price levels
|
||||
PRICE_LEVEL_VERY_CHEAP, # Only show if level ≤ VERY_CHEAP
|
||||
PRICE_LEVEL_CHEAP, # Only show if level ≤ CHEAP
|
||||
PRICE_LEVEL_NORMAL, # Only show if level ≤ NORMAL
|
||||
PRICE_LEVEL_EXPENSIVE, # Only show if level ≤ EXPENSIVE
|
||||
"any", # No filter, allow all price levels
|
||||
PRICE_LEVEL_VERY_CHEAP.lower(), # Only show if level ≤ VERY_CHEAP
|
||||
PRICE_LEVEL_CHEAP.lower(), # Only show if level ≤ CHEAP
|
||||
PRICE_LEVEL_NORMAL.lower(), # Only show if level ≤ NORMAL
|
||||
PRICE_LEVEL_EXPENSIVE.lower(), # Only show if level ≤ EXPENSIVE
|
||||
]
|
||||
|
||||
# Valid options for peak price minimum level filter (AND-linked with volatility filter)
|
||||
# Sorted from expensive to cheap: user selects "starting from how expensive"
|
||||
PEAK_PRICE_MIN_LEVEL_OPTIONS = [
|
||||
"ANY", # No filter, allow all price levels
|
||||
PRICE_LEVEL_EXPENSIVE, # Only show if level ≥ EXPENSIVE
|
||||
PRICE_LEVEL_NORMAL, # Only show if level ≥ NORMAL
|
||||
PRICE_LEVEL_CHEAP, # Only show if level ≥ CHEAP
|
||||
PRICE_LEVEL_VERY_CHEAP, # Only show if level ≥ VERY_CHEAP
|
||||
"any", # No filter, allow all price levels
|
||||
PRICE_LEVEL_EXPENSIVE.lower(), # Only show if level ≥ EXPENSIVE
|
||||
PRICE_LEVEL_NORMAL.lower(), # Only show if level ≥ NORMAL
|
||||
PRICE_LEVEL_CHEAP.lower(), # Only show if level ≥ CHEAP
|
||||
PRICE_LEVEL_VERY_CHEAP.lower(), # Only show if level ≥ VERY_CHEAP
|
||||
]
|
||||
|
||||
# Deprecated: Use BEST_PRICE_MAX_LEVEL_OPTIONS or PEAK_PRICE_MIN_LEVEL_OPTIONS instead
|
||||
MAX_LEVEL_FOR_PERIODS_OPTIONS = BEST_PRICE_MAX_LEVEL_OPTIONS
|
||||
|
||||
# Mapping for comparing price levels (used for sorting)
|
||||
PRICE_LEVEL_MAPPING = {
|
||||
PRICE_LEVEL_VERY_CHEAP: -2,
|
||||
|
|
|
|||
|
|
@ -479,20 +479,20 @@
|
|||
"selector": {
|
||||
"volatility": {
|
||||
"options": {
|
||||
"LOW": "Niedrig",
|
||||
"MODERATE": "Moderat",
|
||||
"HIGH": "Hoch",
|
||||
"VERY_HIGH": "Sehr hoch"
|
||||
"low": "Niedrig",
|
||||
"moderate": "Moderat",
|
||||
"high": "Hoch",
|
||||
"very_high": "Sehr hoch"
|
||||
}
|
||||
},
|
||||
"price_level": {
|
||||
"options": {
|
||||
"ANY": "Beliebig",
|
||||
"VERY_CHEAP": "Sehr günstig",
|
||||
"CHEAP": "Günstig",
|
||||
"NORMAL": "Normal",
|
||||
"EXPENSIVE": "Teuer",
|
||||
"VERY_EXPENSIVE": "Sehr teuer"
|
||||
"any": "Beliebig",
|
||||
"very_cheap": "Sehr günstig",
|
||||
"cheap": "Günstig",
|
||||
"normal": "Normal",
|
||||
"expensive": "Teuer",
|
||||
"very_expensive": "Sehr teuer"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -475,20 +475,20 @@
|
|||
"selector": {
|
||||
"volatility": {
|
||||
"options": {
|
||||
"LOW": "Low",
|
||||
"MODERATE": "Moderate",
|
||||
"HIGH": "High",
|
||||
"VERY_HIGH": "Very high"
|
||||
"low": "Low",
|
||||
"moderate": "Moderate",
|
||||
"high": "High",
|
||||
"very_high": "Very high"
|
||||
}
|
||||
},
|
||||
"price_level": {
|
||||
"options": {
|
||||
"ANY": "Any",
|
||||
"VERY_CHEAP": "Very cheap",
|
||||
"CHEAP": "Cheap",
|
||||
"NORMAL": "Normal",
|
||||
"EXPENSIVE": "Expensive",
|
||||
"VERY_EXPENSIVE": "Very expensive"
|
||||
"any": "Any",
|
||||
"very_cheap": "Very cheap",
|
||||
"cheap": "Cheap",
|
||||
"normal": "Normal",
|
||||
"expensive": "Expensive",
|
||||
"very_expensive": "Very expensive"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -475,20 +475,20 @@
|
|||
"selector": {
|
||||
"volatility": {
|
||||
"options": {
|
||||
"LOW": "Lav",
|
||||
"MODERATE": "Moderat",
|
||||
"HIGH": "Høy",
|
||||
"VERY_HIGH": "Svært høy"
|
||||
"low": "Lav",
|
||||
"moderate": "Moderat",
|
||||
"high": "Høy",
|
||||
"very_high": "Svært høy"
|
||||
}
|
||||
},
|
||||
"price_level": {
|
||||
"options": {
|
||||
"ANY": "Alle",
|
||||
"VERY_CHEAP": "Svært billig",
|
||||
"CHEAP": "Billig",
|
||||
"NORMAL": "Normal",
|
||||
"EXPENSIVE": "Dyr",
|
||||
"VERY_EXPENSIVE": "Svært dyr"
|
||||
"any": "Alle",
|
||||
"very_cheap": "Svært billig",
|
||||
"cheap": "Billig",
|
||||
"normal": "Normal",
|
||||
"expensive": "Dyr",
|
||||
"very_expensive": "Svært dyr"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -475,20 +475,20 @@
|
|||
"selector": {
|
||||
"volatility": {
|
||||
"options": {
|
||||
"LOW": "Laag",
|
||||
"MODERATE": "Matig",
|
||||
"HIGH": "Hoog",
|
||||
"VERY_HIGH": "Zeer hoog"
|
||||
"low": "Laag",
|
||||
"moderate": "Matig",
|
||||
"high": "Hoog",
|
||||
"very_high": "Zeer hoog"
|
||||
}
|
||||
},
|
||||
"price_level": {
|
||||
"options": {
|
||||
"ANY": "Alle",
|
||||
"VERY_CHEAP": "Zeer goedkoop",
|
||||
"CHEAP": "Goedkoop",
|
||||
"NORMAL": "Normaal",
|
||||
"EXPENSIVE": "Duur",
|
||||
"VERY_EXPENSIVE": "Zeer duur"
|
||||
"any": "Alle",
|
||||
"very_cheap": "Zeer goedkoop",
|
||||
"cheap": "Goedkoop",
|
||||
"normal": "Normaal",
|
||||
"expensive": "Duur",
|
||||
"very_expensive": "Zeer duur"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -475,20 +475,20 @@
|
|||
"selector": {
|
||||
"volatility": {
|
||||
"options": {
|
||||
"LOW": "Låg",
|
||||
"MODERATE": "Måttlig",
|
||||
"HIGH": "Hög",
|
||||
"VERY_HIGH": "Mycket hög"
|
||||
"low": "Låg",
|
||||
"moderate": "Måttlig",
|
||||
"high": "Hög",
|
||||
"very_high": "Mycket hög"
|
||||
}
|
||||
},
|
||||
"price_level": {
|
||||
"options": {
|
||||
"ANY": "Alla",
|
||||
"VERY_CHEAP": "Mycket billigt",
|
||||
"CHEAP": "Billigt",
|
||||
"NORMAL": "Normalt",
|
||||
"EXPENSIVE": "Dyrt",
|
||||
"VERY_EXPENSIVE": "Mycket dyrt"
|
||||
"any": "Alla",
|
||||
"very_cheap": "Mycket billigt",
|
||||
"cheap": "Billigt",
|
||||
"normal": "Normalt",
|
||||
"expensive": "Dyrt",
|
||||
"very_expensive": "Mycket dyrt"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue