mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
refactor(config): optimize volatility thresholds with separate ranges and improved UX
Volatility Threshold Optimization: - Replaced global MIN/MAX_VOLATILITY_THRESHOLD (0-100%) with six separate constants for overlapping ranges per threshold level - MODERATE: 5.0-25.0% (was: 0-100%) - HIGH: 20.0-40.0% (was: 0-100%) - VERY_HIGH: 35.0-80.0% (was: 0-100%) - Added detailed comments explaining ranges and cascading requirements Validators: - Added three specific validation functions (one per threshold level) - Added cross-validation ensuring MODERATE < HIGH < VERY_HIGH - Added fallback to existing option values for completeness check - Updated error keys to specific messages per threshold level UI Improvements: - Changed NumberSelector mode: BOX → SLIDER (consistency with other config steps) - Changed step size: 0.1% → 1.0% (better UX, sufficient precision) - Updated min/max ranges to match new validation constants Translations: - Removed: "invalid_volatility_threshold" (generic) - Added: "invalid_volatility_threshold_moderate/high/very_high" (specific ranges) - Added: "invalid_volatility_thresholds" (cross-validation error) - Updated all 5 languages (de, en, nb, nl, sv) Files modified: - config_flow_handlers/options_flow.py: Updated validation logic - config_flow_handlers/schemas.py: Updated NumberSelector configs - config_flow_handlers/validators.py: Added specific validators + cross-validation - const.py: Replaced global constants with six specific constants - translations/*.json: Updated error messages (5 languages) Impact: Users get clearer validation errors with specific ranges shown, better UX with sliders and appropriate step size, and guaranteed threshold ordering (MODERATE < HIGH < VERY_HIGH).
This commit is contained in:
parent
0fd98554ae
commit
14b68a504b
9 changed files with 151 additions and 35 deletions
|
|
@ -29,7 +29,10 @@ from custom_components.tibber_prices.config_flow_handlers.validators import (
|
|||
validate_price_trend_falling,
|
||||
validate_price_trend_rising,
|
||||
validate_relaxation_attempts,
|
||||
validate_volatility_threshold,
|
||||
validate_volatility_threshold_high,
|
||||
validate_volatility_threshold_moderate,
|
||||
validate_volatility_threshold_very_high,
|
||||
validate_volatility_thresholds,
|
||||
)
|
||||
from custom_components.tibber_prices.const import (
|
||||
CONF_BEST_PRICE_FLEX,
|
||||
|
|
@ -51,6 +54,9 @@ from custom_components.tibber_prices.const import (
|
|||
CONF_VOLATILITY_THRESHOLD_HIGH,
|
||||
CONF_VOLATILITY_THRESHOLD_MODERATE,
|
||||
CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
||||
DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
||||
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigFlowResult, OptionsFlow
|
||||
|
|
@ -359,22 +365,41 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
|
|||
|
||||
if user_input is not None:
|
||||
# Validate moderate volatility threshold
|
||||
if CONF_VOLATILITY_THRESHOLD_MODERATE in user_input and not validate_volatility_threshold(
|
||||
if CONF_VOLATILITY_THRESHOLD_MODERATE in user_input and not validate_volatility_threshold_moderate(
|
||||
user_input[CONF_VOLATILITY_THRESHOLD_MODERATE]
|
||||
):
|
||||
errors[CONF_VOLATILITY_THRESHOLD_MODERATE] = "invalid_volatility_threshold"
|
||||
errors[CONF_VOLATILITY_THRESHOLD_MODERATE] = "invalid_volatility_threshold_moderate"
|
||||
|
||||
# Validate high volatility threshold
|
||||
if CONF_VOLATILITY_THRESHOLD_HIGH in user_input and not validate_volatility_threshold(
|
||||
if CONF_VOLATILITY_THRESHOLD_HIGH in user_input and not validate_volatility_threshold_high(
|
||||
user_input[CONF_VOLATILITY_THRESHOLD_HIGH]
|
||||
):
|
||||
errors[CONF_VOLATILITY_THRESHOLD_HIGH] = "invalid_volatility_threshold"
|
||||
errors[CONF_VOLATILITY_THRESHOLD_HIGH] = "invalid_volatility_threshold_high"
|
||||
|
||||
# Validate very high volatility threshold
|
||||
if CONF_VOLATILITY_THRESHOLD_VERY_HIGH in user_input and not validate_volatility_threshold(
|
||||
if CONF_VOLATILITY_THRESHOLD_VERY_HIGH in user_input and not validate_volatility_threshold_very_high(
|
||||
user_input[CONF_VOLATILITY_THRESHOLD_VERY_HIGH]
|
||||
):
|
||||
errors[CONF_VOLATILITY_THRESHOLD_VERY_HIGH] = "invalid_volatility_threshold"
|
||||
errors[CONF_VOLATILITY_THRESHOLD_VERY_HIGH] = "invalid_volatility_threshold_very_high"
|
||||
|
||||
# Cross-validation: Ensure MODERATE < HIGH < VERY_HIGH
|
||||
if not errors:
|
||||
existing_options = self.config_entry.options
|
||||
moderate = user_input.get(
|
||||
CONF_VOLATILITY_THRESHOLD_MODERATE,
|
||||
existing_options.get(CONF_VOLATILITY_THRESHOLD_MODERATE, DEFAULT_VOLATILITY_THRESHOLD_MODERATE),
|
||||
)
|
||||
high = user_input.get(
|
||||
CONF_VOLATILITY_THRESHOLD_HIGH,
|
||||
existing_options.get(CONF_VOLATILITY_THRESHOLD_HIGH, DEFAULT_VOLATILITY_THRESHOLD_HIGH),
|
||||
)
|
||||
very_high = user_input.get(
|
||||
CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
existing_options.get(CONF_VOLATILITY_THRESHOLD_VERY_HIGH, DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH),
|
||||
)
|
||||
|
||||
if not validate_volatility_thresholds(moderate, high, very_high):
|
||||
errors["base"] = "invalid_volatility_thresholds"
|
||||
|
||||
if not errors:
|
||||
self._options.update(user_input)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ from custom_components.tibber_prices.const import (
|
|||
MAX_PRICE_TREND_FALLING,
|
||||
MAX_PRICE_TREND_RISING,
|
||||
MAX_RELAXATION_ATTEMPTS,
|
||||
MAX_VOLATILITY_THRESHOLD,
|
||||
MAX_VOLATILITY_THRESHOLD_HIGH,
|
||||
MAX_VOLATILITY_THRESHOLD_MODERATE,
|
||||
MAX_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
MIN_GAP_COUNT,
|
||||
MIN_PERIOD_LENGTH,
|
||||
MIN_PRICE_RATING_THRESHOLD_HIGH,
|
||||
|
|
@ -75,7 +77,9 @@ from custom_components.tibber_prices.const import (
|
|||
MIN_PRICE_TREND_FALLING,
|
||||
MIN_PRICE_TREND_RISING,
|
||||
MIN_RELAXATION_ATTEMPTS,
|
||||
MIN_VOLATILITY_THRESHOLD,
|
||||
MIN_VOLATILITY_THRESHOLD_HIGH,
|
||||
MIN_VOLATILITY_THRESHOLD_MODERATE,
|
||||
MIN_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
PEAK_PRICE_MIN_LEVEL_OPTIONS,
|
||||
)
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
|
@ -215,11 +219,11 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|||
),
|
||||
): NumberSelector(
|
||||
NumberSelectorConfig(
|
||||
min=MIN_VOLATILITY_THRESHOLD,
|
||||
max=MAX_VOLATILITY_THRESHOLD,
|
||||
step=0.1,
|
||||
min=MIN_VOLATILITY_THRESHOLD_MODERATE,
|
||||
max=MAX_VOLATILITY_THRESHOLD_MODERATE,
|
||||
step=1.0,
|
||||
unit_of_measurement="%",
|
||||
mode=NumberSelectorMode.BOX,
|
||||
mode=NumberSelectorMode.SLIDER,
|
||||
),
|
||||
),
|
||||
vol.Optional(
|
||||
|
|
@ -232,11 +236,11 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|||
),
|
||||
): NumberSelector(
|
||||
NumberSelectorConfig(
|
||||
min=MIN_VOLATILITY_THRESHOLD,
|
||||
max=MAX_VOLATILITY_THRESHOLD,
|
||||
step=0.1,
|
||||
min=MIN_VOLATILITY_THRESHOLD_HIGH,
|
||||
max=MAX_VOLATILITY_THRESHOLD_HIGH,
|
||||
step=1.0,
|
||||
unit_of_measurement="%",
|
||||
mode=NumberSelectorMode.BOX,
|
||||
mode=NumberSelectorMode.SLIDER,
|
||||
),
|
||||
),
|
||||
vol.Optional(
|
||||
|
|
@ -249,11 +253,11 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|||
),
|
||||
): NumberSelector(
|
||||
NumberSelectorConfig(
|
||||
min=MIN_VOLATILITY_THRESHOLD,
|
||||
max=MAX_VOLATILITY_THRESHOLD,
|
||||
step=0.1,
|
||||
min=MIN_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
max=MAX_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
step=1.0,
|
||||
unit_of_measurement="%",
|
||||
mode=NumberSelectorMode.BOX,
|
||||
mode=NumberSelectorMode.SLIDER,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ from custom_components.tibber_prices.const import (
|
|||
MAX_PRICE_TREND_FALLING,
|
||||
MAX_PRICE_TREND_RISING,
|
||||
MAX_RELAXATION_ATTEMPTS,
|
||||
MAX_VOLATILITY_THRESHOLD,
|
||||
MAX_VOLATILITY_THRESHOLD_HIGH,
|
||||
MAX_VOLATILITY_THRESHOLD_MODERATE,
|
||||
MAX_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
MIN_GAP_COUNT,
|
||||
MIN_PERIOD_LENGTH,
|
||||
MIN_PRICE_RATING_THRESHOLD_HIGH,
|
||||
|
|
@ -29,7 +31,9 @@ from custom_components.tibber_prices.const import (
|
|||
MIN_PRICE_TREND_FALLING,
|
||||
MIN_PRICE_TREND_RISING,
|
||||
MIN_RELAXATION_ATTEMPTS,
|
||||
MIN_VOLATILITY_THRESHOLD,
|
||||
MIN_VOLATILITY_THRESHOLD_HIGH,
|
||||
MIN_VOLATILITY_THRESHOLD_MODERATE,
|
||||
MIN_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
|
|
@ -219,18 +223,78 @@ def validate_price_rating_thresholds(threshold_low: int, threshold_high: int) ->
|
|||
return threshold_low < threshold_high
|
||||
|
||||
|
||||
def validate_volatility_threshold(threshold: float) -> bool:
|
||||
def validate_volatility_threshold_moderate(threshold: float) -> bool:
|
||||
"""
|
||||
Validate volatility threshold percentage.
|
||||
Validate moderate volatility threshold.
|
||||
|
||||
Args:
|
||||
threshold: Volatility threshold percentage (0.0 to 100.0)
|
||||
threshold: Moderate volatility threshold percentage (5.0 to 25.0)
|
||||
|
||||
Returns:
|
||||
True if threshold is valid (MIN_VOLATILITY_THRESHOLD to MAX_VOLATILITY_THRESHOLD)
|
||||
True if threshold is valid (MIN_VOLATILITY_THRESHOLD_MODERATE to MAX_VOLATILITY_THRESHOLD_MODERATE)
|
||||
|
||||
"""
|
||||
return MIN_VOLATILITY_THRESHOLD <= threshold <= MAX_VOLATILITY_THRESHOLD
|
||||
return MIN_VOLATILITY_THRESHOLD_MODERATE <= threshold <= MAX_VOLATILITY_THRESHOLD_MODERATE
|
||||
|
||||
|
||||
def validate_volatility_threshold_high(threshold: float) -> bool:
|
||||
"""
|
||||
Validate high volatility threshold.
|
||||
|
||||
Args:
|
||||
threshold: High volatility threshold percentage (20.0 to 40.0)
|
||||
|
||||
Returns:
|
||||
True if threshold is valid (MIN_VOLATILITY_THRESHOLD_HIGH to MAX_VOLATILITY_THRESHOLD_HIGH)
|
||||
|
||||
"""
|
||||
return MIN_VOLATILITY_THRESHOLD_HIGH <= threshold <= MAX_VOLATILITY_THRESHOLD_HIGH
|
||||
|
||||
|
||||
def validate_volatility_threshold_very_high(threshold: float) -> bool:
|
||||
"""
|
||||
Validate very high volatility threshold.
|
||||
|
||||
Args:
|
||||
threshold: Very high volatility threshold percentage (35.0 to 80.0)
|
||||
|
||||
Returns:
|
||||
True if threshold is valid (MIN_VOLATILITY_THRESHOLD_VERY_HIGH to MAX_VOLATILITY_THRESHOLD_VERY_HIGH)
|
||||
|
||||
"""
|
||||
return MIN_VOLATILITY_THRESHOLD_VERY_HIGH <= threshold <= MAX_VOLATILITY_THRESHOLD_VERY_HIGH
|
||||
|
||||
|
||||
def validate_volatility_thresholds(
|
||||
threshold_moderate: float,
|
||||
threshold_high: float,
|
||||
threshold_very_high: float,
|
||||
) -> bool:
|
||||
"""
|
||||
Cross-validate all three volatility thresholds together.
|
||||
|
||||
Ensures that MODERATE < HIGH < VERY_HIGH to maintain logical classification
|
||||
boundaries. Each threshold represents an escalating level of price volatility.
|
||||
|
||||
Args:
|
||||
threshold_moderate: Moderate volatility threshold (5.0 to 25.0)
|
||||
threshold_high: High volatility threshold (20.0 to 40.0)
|
||||
threshold_very_high: Very high volatility threshold (35.0 to 80.0)
|
||||
|
||||
Returns:
|
||||
True if all thresholds are valid individually AND maintain proper ordering
|
||||
|
||||
"""
|
||||
# Validate individual ranges first
|
||||
if not validate_volatility_threshold_moderate(threshold_moderate):
|
||||
return False
|
||||
if not validate_volatility_threshold_high(threshold_high):
|
||||
return False
|
||||
if not validate_volatility_threshold_very_high(threshold_very_high):
|
||||
return False
|
||||
|
||||
# Ensure cascading order: MODERATE < HIGH < VERY_HIGH
|
||||
return threshold_moderate < threshold_high < threshold_very_high
|
||||
|
||||
|
||||
def validate_price_trend_rising(threshold: int) -> bool:
|
||||
|
|
|
|||
|
|
@ -112,8 +112,16 @@ MIN_PRICE_RATING_THRESHOLD_HIGH = 5 # Minimum value for high rating threshold (
|
|||
MAX_PRICE_RATING_THRESHOLD_HIGH = 50 # Maximum value for high rating threshold
|
||||
|
||||
# Volatility threshold limits
|
||||
MIN_VOLATILITY_THRESHOLD = 0.0 # Minimum volatility threshold percentage
|
||||
MAX_VOLATILITY_THRESHOLD = 100.0 # Maximum volatility threshold percentage
|
||||
# MODERATE threshold: practical range 5% to 25% (entry point for noticeable fluctuation)
|
||||
# HIGH threshold: practical range 20% to 40% (significant price swings)
|
||||
# VERY_HIGH threshold: practical range 35% to 80% (extreme volatility)
|
||||
# Ensure cascading: MODERATE < HIGH < VERY_HIGH with ~5% minimum gaps
|
||||
MIN_VOLATILITY_THRESHOLD_MODERATE = 5.0 # Minimum for moderate volatility threshold
|
||||
MAX_VOLATILITY_THRESHOLD_MODERATE = 25.0 # Maximum for moderate volatility threshold (must be < HIGH)
|
||||
MIN_VOLATILITY_THRESHOLD_HIGH = 20.0 # Minimum for high volatility threshold (must be > MODERATE)
|
||||
MAX_VOLATILITY_THRESHOLD_HIGH = 40.0 # Maximum for high volatility threshold (must be < VERY_HIGH)
|
||||
MIN_VOLATILITY_THRESHOLD_VERY_HIGH = 35.0 # Minimum for very high volatility threshold (must be > HIGH)
|
||||
MAX_VOLATILITY_THRESHOLD_VERY_HIGH = 80.0 # Maximum for very high volatility threshold
|
||||
|
||||
# Price trend threshold limits
|
||||
MIN_PRICE_TREND_RISING = 1 # Minimum rising trend threshold
|
||||
|
|
|
|||
|
|
@ -207,7 +207,10 @@
|
|||
"invalid_price_rating_low": "Untere Preis-Bewertungsschwelle muss zwischen -50% und -5% liegen",
|
||||
"invalid_price_rating_high": "Obere Preis-Bewertungsschwelle muss zwischen 5% und 50% liegen",
|
||||
"invalid_price_rating_thresholds": "Untere Schwelle muss kleiner als obere Schwelle sein",
|
||||
"invalid_volatility_threshold": "Volatilitätsschwelle muss zwischen 0% und 100% liegen",
|
||||
"invalid_volatility_threshold_moderate": "Moderate Volatilitätsschwelle muss zwischen 5% und 25% liegen",
|
||||
"invalid_volatility_threshold_high": "Hohe Volatilitätsschwelle muss zwischen 20% und 40% liegen",
|
||||
"invalid_volatility_threshold_very_high": "Sehr hohe Volatilitätsschwelle muss zwischen 35% und 80% liegen",
|
||||
"invalid_volatility_thresholds": "Schwellenwerte müssen aufsteigend sein: moderat < hoch < sehr hoch",
|
||||
"invalid_price_trend_rising": "Steigender Trendschwellenwert muss zwischen 1% und 50% liegen",
|
||||
"invalid_price_trend_falling": "Fallender Trendschwellenwert muss zwischen -50% und -1% liegen"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -207,7 +207,10 @@
|
|||
"invalid_price_rating_low": "Low price rating threshold must be between -50% and -5%",
|
||||
"invalid_price_rating_high": "High price rating threshold must be between 5% and 50%",
|
||||
"invalid_price_rating_thresholds": "Low threshold must be less than high threshold",
|
||||
"invalid_volatility_threshold": "Volatility threshold must be between 0% and 100%",
|
||||
"invalid_volatility_threshold_moderate": "Moderate volatility threshold must be between 5% and 25%",
|
||||
"invalid_volatility_threshold_high": "High volatility threshold must be between 20% and 40%",
|
||||
"invalid_volatility_threshold_very_high": "Very high volatility threshold must be between 35% and 80%",
|
||||
"invalid_volatility_thresholds": "Thresholds must be in ascending order: moderate < high < very high",
|
||||
"invalid_price_trend_rising": "Rising trend threshold must be between 1% and 50%",
|
||||
"invalid_price_trend_falling": "Falling trend threshold must be between -50% and -1%"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -207,7 +207,10 @@
|
|||
"invalid_price_rating_low": "Lav prisvurderingsgrense må være mellom -50% og -5%",
|
||||
"invalid_price_rating_high": "Høy prisvurderingsgrense må være mellom 5% og 50%",
|
||||
"invalid_price_rating_thresholds": "Lav grense må være mindre enn høy grense",
|
||||
"invalid_volatility_threshold": "Volatilitetsgrense må være mellom 0% og 100%",
|
||||
"invalid_volatility_threshold_moderate": "Moderat volatilitetsgrense må være mellom 5% og 25%",
|
||||
"invalid_volatility_threshold_high": "Høy volatilitetsgrense må være mellom 20% og 40%",
|
||||
"invalid_volatility_threshold_very_high": "Svært høy volatilitetsgrense må være mellom 35% og 80%",
|
||||
"invalid_volatility_thresholds": "Grensene må være i stigende rekkefølge: moderat < høy < svært høy",
|
||||
"invalid_price_trend_rising": "Stigende trendgrense må være mellom 1% og 50%",
|
||||
"invalid_price_trend_falling": "Fallende trendgrense må være mellom -50% og -1%"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -207,7 +207,10 @@
|
|||
"invalid_price_rating_low": "Lage prijsbeoordelingsdrempel moet tussen -50% en -5% liggen",
|
||||
"invalid_price_rating_high": "Hoge prijsbeoordelingsdrempel moet tussen 5% en 50% liggen",
|
||||
"invalid_price_rating_thresholds": "Lage drempel moet lager zijn dan hoge drempel",
|
||||
"invalid_volatility_threshold": "Volatiliteitsdrempel moet tussen 0% en 100% liggen",
|
||||
"invalid_volatility_threshold_moderate": "Gematigde volatiliteitsdrempel moet tussen 5% en 25% liggen",
|
||||
"invalid_volatility_threshold_high": "Hoge volatiliteitsdrempel moet tussen 20% en 40% liggen",
|
||||
"invalid_volatility_threshold_very_high": "Zeer hoge volatiliteitsdrempel moet tussen 35% en 80% liggen",
|
||||
"invalid_volatility_thresholds": "Drempels moeten in oplopende volgorde zijn: gematigd < hoog < zeer hoog",
|
||||
"invalid_price_trend_rising": "Stijgende trenddrempel moet tussen 1% en 50% liggen",
|
||||
"invalid_price_trend_falling": "Dalende trenddrempel moet tussen -50% en -1% liggen"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -207,7 +207,10 @@
|
|||
"invalid_price_rating_low": "Låg prisklassificeringströskel måste vara mellan -50% och -5%",
|
||||
"invalid_price_rating_high": "Hög prisklassificeringströskel måste vara mellan 5% och 50%",
|
||||
"invalid_price_rating_thresholds": "Låg tröskel måste vara mindre än hög tröskel",
|
||||
"invalid_volatility_threshold": "Volatilitetströskel måste vara mellan 0% och 100%",
|
||||
"invalid_volatility_threshold_moderate": "Måttlig volatilitetströskel måste vara mellan 5% och 25%",
|
||||
"invalid_volatility_threshold_high": "Hög volatilitetströskel måste vara mellan 20% och 40%",
|
||||
"invalid_volatility_threshold_very_high": "Mycket hög volatilitetströskel måste vara mellan 35% och 80%",
|
||||
"invalid_volatility_thresholds": "Trösklar måste vara i stigande ordning: måttlig < hög < mycket hög",
|
||||
"invalid_price_trend_rising": "Stigande trendtröskel måste vara mellan 1% och 50%",
|
||||
"invalid_price_trend_falling": "Fallande trendtröskel måste vara mellan -50% och -1%"
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue