mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
feat(period-calc): adaptive defaults + remove volatility filter
Major improvements to period calculation with smarter defaults and simplified configuration: **Adaptive Defaults:** - ENABLE_MIN_PERIODS: true (was false) - Always try to find periods - MIN_PERIODS target: 2 periods/day (ensures coverage) - BEST_PRICE_MAX_LEVEL: "cheap" (was "any") - Prefer genuinely cheap - PEAK_PRICE_MIN_LEVEL: "expensive" (was "any") - Prefer genuinely expensive - GAP_TOLERANCE: 1 (was 0) - Allow 1-level deviations in sequences - MIN_DISTANCE_FROM_AVG: 5% (was 2%) - Ensure significance - PEAK_PRICE_MIN_PERIOD_LENGTH: 30min (was 60min) - More responsive - PEAK_PRICE_FLEX: -20% (was -15%) - Better peak detection **Volatility Filter Removal:** - Removed CONF_BEST_PRICE_MIN_VOLATILITY from const.py - Removed CONF_PEAK_PRICE_MIN_VOLATILITY from const.py - Removed volatility filter UI controls from config_flow.py - Removed filter_periods_by_volatility() calls from coordinator.py - Updated all 5 translations (de, en, nb, nl, sv) **Period Calculation Logic:** - Level filter now integrated into _build_periods() (applied during interval qualification, not as post-filter) - Gap tolerance implemented via _check_level_with_gap_tolerance() - Short periods (<1.5h) use strict filtering (no gap tolerance) - Relaxation now passes level_filter + gap_count directly to PeriodConfig - show_periods check skipped when relaxation enabled (relaxation tries "any" as fallback) **Documentation:** - Complete rewrite of docs/user/period-calculation.md: * Visual examples with timelines * Step-by-step explanation of 4-step process * Configuration scenarios (5 common use cases) * Troubleshooting section with specific fixes * Advanced topics (per-day independence, early stop, etc.) - Updated README.md: "volatility" → "distance from average" Impact: Periods now reliably appear on most days with meaningful quality filters. Users get warned about expensive periods and notified about cheap opportunities without manual tuning. Relaxation ensures coverage while keeping filters as strict as possible. Breaking change: Volatility filter removed (was never a critical feature, often confused users). Existing configs continue to work (removed keys are simply ignored).
This commit is contained in:
parent
b1c36f5279
commit
53e73a7fda
11 changed files with 1054 additions and 340 deletions
|
|
@ -175,7 +175,7 @@ automation:
|
||||||
entity_id: switch.dishwasher
|
entity_id: switch.dishwasher
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Learn more:** The [period calculation guide](docs/user/period-calculation.md) explains how Best/Peak Price periods are identified and how you can configure filters (flexibility, minimum volatility, price level filters with gap tolerance).
|
> **Learn more:** The [period calculation guide](docs/user/period-calculation.md) explains how Best/Peak Price periods are identified and how you can configure filters (flexibility, minimum distance from average, price level filters with gap tolerance).
|
||||||
|
|
||||||
### Notify on Extremely High Prices
|
### Notify on Extremely High Prices
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ from .const import (
|
||||||
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
||||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
CONF_ENABLE_MIN_PERIODS_BEST,
|
CONF_ENABLE_MIN_PERIODS_BEST,
|
||||||
CONF_ENABLE_MIN_PERIODS_PEAK,
|
CONF_ENABLE_MIN_PERIODS_PEAK,
|
||||||
CONF_EXTENDED_DESCRIPTIONS,
|
CONF_EXTENDED_DESCRIPTIONS,
|
||||||
|
|
@ -56,7 +55,6 @@ from .const import (
|
||||||
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
CONF_PEAK_PRICE_MIN_LEVEL,
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
||||||
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
||||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
CONF_PRICE_RATING_THRESHOLD_LOW,
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
||||||
|
|
@ -71,7 +69,6 @@ from .const import (
|
||||||
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
|
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
|
||||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
||||||
DEFAULT_EXTENDED_DESCRIPTIONS,
|
DEFAULT_EXTENDED_DESCRIPTIONS,
|
||||||
|
|
@ -82,7 +79,6 @@ from .const import (
|
||||||
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
||||||
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
||||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
||||||
DEFAULT_PRICE_TREND_THRESHOLD_FALLING,
|
DEFAULT_PRICE_TREND_THRESHOLD_FALLING,
|
||||||
|
|
@ -94,7 +90,6 @@ from .const import (
|
||||||
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
MIN_VOLATILITY_FOR_PERIODS_OPTIONS,
|
|
||||||
PEAK_PRICE_MIN_LEVEL_OPTIONS,
|
PEAK_PRICE_MIN_LEVEL_OPTIONS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -657,19 +652,6 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
|
||||||
mode=NumberSelectorMode.SLIDER,
|
mode=NumberSelectorMode.SLIDER,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
vol.Optional(
|
|
||||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
default=self.config_entry.options.get(
|
|
||||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
),
|
|
||||||
): SelectSelector(
|
|
||||||
SelectSelectorConfig(
|
|
||||||
options=MIN_VOLATILITY_FOR_PERIODS_OPTIONS,
|
|
||||||
mode=SelectSelectorMode.DROPDOWN,
|
|
||||||
translation_key="volatility",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_BEST_PRICE_MAX_LEVEL,
|
CONF_BEST_PRICE_MAX_LEVEL,
|
||||||
default=self.config_entry.options.get(
|
default=self.config_entry.options.get(
|
||||||
|
|
@ -805,19 +787,6 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
|
||||||
mode=NumberSelectorMode.SLIDER,
|
mode=NumberSelectorMode.SLIDER,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
vol.Optional(
|
|
||||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
default=self.config_entry.options.get(
|
|
||||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
),
|
|
||||||
): SelectSelector(
|
|
||||||
SelectSelectorConfig(
|
|
||||||
options=MIN_VOLATILITY_FOR_PERIODS_OPTIONS,
|
|
||||||
mode=SelectSelectorMode.DROPDOWN,
|
|
||||||
translation_key="volatility",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_PEAK_PRICE_MIN_LEVEL,
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
||||||
default=self.config_entry.options.get(
|
default=self.config_entry.options.get(
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ CONF_PRICE_TREND_THRESHOLD_FALLING = "price_trend_threshold_falling"
|
||||||
CONF_VOLATILITY_THRESHOLD_MODERATE = "volatility_threshold_moderate"
|
CONF_VOLATILITY_THRESHOLD_MODERATE = "volatility_threshold_moderate"
|
||||||
CONF_VOLATILITY_THRESHOLD_HIGH = "volatility_threshold_high"
|
CONF_VOLATILITY_THRESHOLD_HIGH = "volatility_threshold_high"
|
||||||
CONF_VOLATILITY_THRESHOLD_VERY_HIGH = "volatility_threshold_very_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_BEST_PRICE_MAX_LEVEL = "best_price_max_level"
|
CONF_BEST_PRICE_MAX_LEVEL = "best_price_max_level"
|
||||||
CONF_PEAK_PRICE_MIN_LEVEL = "peak_price_min_level"
|
CONF_PEAK_PRICE_MIN_LEVEL = "peak_price_min_level"
|
||||||
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT = "best_price_max_level_gap_count"
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT = "best_price_max_level_gap_count"
|
||||||
|
|
@ -50,11 +48,11 @@ 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 = 15 # 15% flexibility for best price (user-facing, percent)
|
DEFAULT_BEST_PRICE_FLEX = 15 # 15% flexibility for best price (user-facing, percent)
|
||||||
DEFAULT_PEAK_PRICE_FLEX = -15 # 15% flexibility for peak price (user-facing, percent)
|
DEFAULT_PEAK_PRICE_FLEX = -20 # 20% flexibility for peak price (user-facing, percent)
|
||||||
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG = 2 # 2% minimum distance from daily average for best price
|
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG = 5 # 5% minimum distance from daily average (ensures significance)
|
||||||
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG = 2 # 2% minimum distance from daily average for peak price
|
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG = 5 # 5% minimum distance from daily average (ensures significance)
|
||||||
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH = 60 # 60 minutes minimum period length for best price (user-facing, minutes)
|
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH = 60 # 60 minutes minimum period length for best price (user-facing, minutes)
|
||||||
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH = 60 # 60 minutes minimum period length for peak price (user-facing, minutes)
|
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH = 30 # 30 minutes minimum period length for peak price (user-facing, minutes)
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_LOW = -10 # Default rating threshold low percentage
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW = -10 # Default rating threshold low percentage
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_HIGH = 10 # Default rating threshold high percentage
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH = 10 # Default rating threshold high percentage
|
||||||
DEFAULT_PRICE_TREND_THRESHOLD_RISING = 5 # Default trend threshold for rising prices (%)
|
DEFAULT_PRICE_TREND_THRESHOLD_RISING = 5 # Default trend threshold for rising prices (%)
|
||||||
|
|
@ -62,17 +60,15 @@ 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_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_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_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_BEST_PRICE_MAX_LEVEL = "cheap" # Default: prefer genuinely cheap periods, relax to "any" if needed
|
||||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY = "low" # Always show peak price (warning relevant even at low spreads)
|
DEFAULT_PEAK_PRICE_MIN_LEVEL = "expensive" # Default: prefer genuinely expensive periods, relax to "any" if needed
|
||||||
DEFAULT_BEST_PRICE_MAX_LEVEL = "any" # Default: show best price periods regardless of price level
|
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT = 1 # Default: allow 1 level gap (e.g., CHEAP→NORMAL→CHEAP stays together)
|
||||||
DEFAULT_PEAK_PRICE_MIN_LEVEL = "any" # Default: show peak price periods regardless of price level
|
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT = 1 # Default: allow 1 level gap for peak price periods
|
||||||
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT = 0 # Default: no tolerance for level gaps (strict filtering)
|
|
||||||
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT = 0 # Default: no tolerance for level gaps (strict filtering)
|
|
||||||
MIN_INTERVALS_FOR_GAP_TOLERANCE = 6 # Minimum period length (in 15-min intervals = 1.5h) required for gap tolerance
|
MIN_INTERVALS_FOR_GAP_TOLERANCE = 6 # Minimum period length (in 15-min intervals = 1.5h) required for gap tolerance
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_BEST = False # Default: minimum periods feature disabled for best price
|
DEFAULT_ENABLE_MIN_PERIODS_BEST = True # Default: minimum periods feature enabled for best price
|
||||||
DEFAULT_MIN_PERIODS_BEST = 2 # Default: require at least 2 best price periods (when enabled)
|
DEFAULT_MIN_PERIODS_BEST = 2 # Default: require at least 2 best price periods (when enabled)
|
||||||
DEFAULT_RELAXATION_STEP_BEST = 25 # Default: 25% of original threshold per relaxation step for best price
|
DEFAULT_RELAXATION_STEP_BEST = 25 # Default: 25% of original threshold per relaxation step for best price
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_PEAK = False # Default: minimum periods feature disabled for peak price
|
DEFAULT_ENABLE_MIN_PERIODS_PEAK = True # Default: minimum periods feature enabled for peak price
|
||||||
DEFAULT_MIN_PERIODS_PEAK = 2 # Default: require at least 2 peak price periods (when enabled)
|
DEFAULT_MIN_PERIODS_PEAK = 2 # Default: require at least 2 peak price periods (when enabled)
|
||||||
DEFAULT_RELAXATION_STEP_PEAK = 25 # Default: 25% of original threshold per relaxation step for peak price
|
DEFAULT_RELAXATION_STEP_PEAK = 25 # Default: 25% of original threshold per relaxation step for peak price
|
||||||
|
|
||||||
|
|
@ -193,15 +189,7 @@ VOLATILITY_OPTIONS = [
|
||||||
VOLATILITY_VERY_HIGH.lower(),
|
VOLATILITY_VERY_HIGH.lower(),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Valid options for minimum volatility filter for periods
|
# Valid options for best price maximum level filter
|
||||||
MIN_VOLATILITY_FOR_PERIODS_OPTIONS = [
|
|
||||||
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"
|
# Sorted from cheap to expensive: user selects "up to how expensive"
|
||||||
BEST_PRICE_MAX_LEVEL_OPTIONS = [
|
BEST_PRICE_MAX_LEVEL_OPTIONS = [
|
||||||
"any", # No filter, allow all price levels
|
"any", # No filter, allow all price levels
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ from .const import (
|
||||||
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
||||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
CONF_ENABLE_MIN_PERIODS_BEST,
|
CONF_ENABLE_MIN_PERIODS_BEST,
|
||||||
CONF_ENABLE_MIN_PERIODS_PEAK,
|
CONF_ENABLE_MIN_PERIODS_PEAK,
|
||||||
CONF_MIN_PERIODS_BEST,
|
CONF_MIN_PERIODS_BEST,
|
||||||
|
|
@ -40,7 +39,6 @@ from .const import (
|
||||||
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
CONF_PEAK_PRICE_MIN_LEVEL,
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
||||||
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
||||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
CONF_PRICE_RATING_THRESHOLD_LOW,
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
||||||
CONF_RELAXATION_STEP_BEST,
|
CONF_RELAXATION_STEP_BEST,
|
||||||
|
|
@ -53,7 +51,6 @@ from .const import (
|
||||||
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
|
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
|
||||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
||||||
DEFAULT_MIN_PERIODS_BEST,
|
DEFAULT_MIN_PERIODS_BEST,
|
||||||
|
|
@ -63,7 +60,6 @@ from .const import (
|
||||||
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
||||||
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
||||||
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
||||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
||||||
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
||||||
DEFAULT_RELAXATION_STEP_BEST,
|
DEFAULT_RELAXATION_STEP_BEST,
|
||||||
|
|
@ -78,7 +74,6 @@ from .const import (
|
||||||
from .period_utils import (
|
from .period_utils import (
|
||||||
PeriodConfig,
|
PeriodConfig,
|
||||||
calculate_periods_with_relaxation,
|
calculate_periods_with_relaxation,
|
||||||
filter_periods_by_volatility,
|
|
||||||
)
|
)
|
||||||
from .price_utils import (
|
from .price_utils import (
|
||||||
enrich_price_info_with_differences,
|
enrich_price_info_with_differences,
|
||||||
|
|
@ -908,6 +903,10 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
|
||||||
# Periods shorter than MIN_INTERVALS_FOR_GAP_TOLERANCE (1.5h) use strict filtering
|
# Periods shorter than MIN_INTERVALS_FOR_GAP_TOLERANCE (1.5h) use strict filtering
|
||||||
if interval_count < MIN_INTERVALS_FOR_GAP_TOLERANCE:
|
if interval_count < MIN_INTERVALS_FOR_GAP_TOLERANCE:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Using strict filtering for short period (%d intervals)",
|
||||||
|
interval_count,
|
||||||
|
)
|
||||||
return self._check_short_period_strict(today_intervals, level_order, reverse_sort=reverse_sort)
|
return self._check_short_period_strict(today_intervals, level_order, reverse_sort=reverse_sort)
|
||||||
|
|
||||||
# Try normal gap tolerance check first
|
# Try normal gap tolerance check first
|
||||||
|
|
@ -1143,14 +1142,19 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if best price periods should be shown (apply filters)
|
|
||||||
show_best_price = self._should_show_periods(price_info, reverse_sort=False) if all_prices else False
|
|
||||||
|
|
||||||
# Get relaxation configuration for best price
|
# Get relaxation configuration for best price
|
||||||
enable_relaxation_best = self.config_entry.options.get(
|
enable_relaxation_best = self.config_entry.options.get(
|
||||||
CONF_ENABLE_MIN_PERIODS_BEST,
|
CONF_ENABLE_MIN_PERIODS_BEST,
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check if best price periods should be shown
|
||||||
|
# If relaxation is enabled, always calculate (relaxation will try "any" filter)
|
||||||
|
# If relaxation is disabled, apply level filter check
|
||||||
|
if enable_relaxation_best:
|
||||||
|
show_best_price = bool(all_prices)
|
||||||
|
else:
|
||||||
|
show_best_price = self._should_show_periods(price_info, reverse_sort=False) if all_prices else False
|
||||||
min_periods_best = self.config_entry.options.get(
|
min_periods_best = self.config_entry.options.get(
|
||||||
CONF_MIN_PERIODS_BEST,
|
CONF_MIN_PERIODS_BEST,
|
||||||
DEFAULT_MIN_PERIODS_BEST,
|
DEFAULT_MIN_PERIODS_BEST,
|
||||||
|
|
@ -1163,6 +1167,15 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
# Calculate best price periods (or return empty if filtered)
|
# Calculate best price periods (or return empty if filtered)
|
||||||
if show_best_price:
|
if show_best_price:
|
||||||
best_config = self._get_period_config(reverse_sort=False)
|
best_config = self._get_period_config(reverse_sort=False)
|
||||||
|
# Get level filter configuration
|
||||||
|
max_level_best = self.config_entry.options.get(
|
||||||
|
CONF_BEST_PRICE_MAX_LEVEL,
|
||||||
|
DEFAULT_BEST_PRICE_MAX_LEVEL,
|
||||||
|
)
|
||||||
|
gap_count_best = self.config_entry.options.get(
|
||||||
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
|
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
|
)
|
||||||
best_period_config = PeriodConfig(
|
best_period_config = PeriodConfig(
|
||||||
reverse_sort=False,
|
reverse_sort=False,
|
||||||
flex=best_config["flex"],
|
flex=best_config["flex"],
|
||||||
|
|
@ -1173,6 +1186,8 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
threshold_volatility_moderate=threshold_volatility_moderate,
|
threshold_volatility_moderate=threshold_volatility_moderate,
|
||||||
threshold_volatility_high=threshold_volatility_high,
|
threshold_volatility_high=threshold_volatility_high,
|
||||||
threshold_volatility_very_high=threshold_volatility_very_high,
|
threshold_volatility_very_high=threshold_volatility_very_high,
|
||||||
|
level_filter=max_level_best,
|
||||||
|
gap_count=gap_count_best,
|
||||||
)
|
)
|
||||||
best_periods, best_relaxation = calculate_periods_with_relaxation(
|
best_periods, best_relaxation = calculate_periods_with_relaxation(
|
||||||
all_prices,
|
all_prices,
|
||||||
|
|
@ -1186,13 +1201,6 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
level_override=lvl,
|
level_override=lvl,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply period-level volatility filter (after calculation)
|
|
||||||
min_volatility_best = self.config_entry.options.get(
|
|
||||||
CONF_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_BEST_PRICE_MIN_VOLATILITY,
|
|
||||||
)
|
|
||||||
best_periods = filter_periods_by_volatility(best_periods, min_volatility_best)
|
|
||||||
else:
|
else:
|
||||||
best_periods = {
|
best_periods = {
|
||||||
"periods": [],
|
"periods": [],
|
||||||
|
|
@ -1201,14 +1209,19 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
}
|
}
|
||||||
best_relaxation = {"relaxation_active": False, "relaxation_attempted": False}
|
best_relaxation = {"relaxation_active": False, "relaxation_attempted": False}
|
||||||
|
|
||||||
# Check if peak price periods should be shown (apply filters)
|
|
||||||
show_peak_price = self._should_show_periods(price_info, reverse_sort=True) if all_prices else False
|
|
||||||
|
|
||||||
# Get relaxation configuration for peak price
|
# Get relaxation configuration for peak price
|
||||||
enable_relaxation_peak = self.config_entry.options.get(
|
enable_relaxation_peak = self.config_entry.options.get(
|
||||||
CONF_ENABLE_MIN_PERIODS_PEAK,
|
CONF_ENABLE_MIN_PERIODS_PEAK,
|
||||||
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check if peak price periods should be shown
|
||||||
|
# If relaxation is enabled, always calculate (relaxation will try "any" filter)
|
||||||
|
# If relaxation is disabled, apply level filter check
|
||||||
|
if enable_relaxation_peak:
|
||||||
|
show_peak_price = bool(all_prices)
|
||||||
|
else:
|
||||||
|
show_peak_price = self._should_show_periods(price_info, reverse_sort=True) if all_prices else False
|
||||||
min_periods_peak = self.config_entry.options.get(
|
min_periods_peak = self.config_entry.options.get(
|
||||||
CONF_MIN_PERIODS_PEAK,
|
CONF_MIN_PERIODS_PEAK,
|
||||||
DEFAULT_MIN_PERIODS_PEAK,
|
DEFAULT_MIN_PERIODS_PEAK,
|
||||||
|
|
@ -1221,6 +1234,15 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
# Calculate peak price periods (or return empty if filtered)
|
# Calculate peak price periods (or return empty if filtered)
|
||||||
if show_peak_price:
|
if show_peak_price:
|
||||||
peak_config = self._get_period_config(reverse_sort=True)
|
peak_config = self._get_period_config(reverse_sort=True)
|
||||||
|
# Get level filter configuration
|
||||||
|
min_level_peak = self.config_entry.options.get(
|
||||||
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
||||||
|
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
||||||
|
)
|
||||||
|
gap_count_peak = self.config_entry.options.get(
|
||||||
|
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
|
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
||||||
|
)
|
||||||
peak_period_config = PeriodConfig(
|
peak_period_config = PeriodConfig(
|
||||||
reverse_sort=True,
|
reverse_sort=True,
|
||||||
flex=peak_config["flex"],
|
flex=peak_config["flex"],
|
||||||
|
|
@ -1231,6 +1253,8 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
threshold_volatility_moderate=threshold_volatility_moderate,
|
threshold_volatility_moderate=threshold_volatility_moderate,
|
||||||
threshold_volatility_high=threshold_volatility_high,
|
threshold_volatility_high=threshold_volatility_high,
|
||||||
threshold_volatility_very_high=threshold_volatility_very_high,
|
threshold_volatility_very_high=threshold_volatility_very_high,
|
||||||
|
level_filter=min_level_peak,
|
||||||
|
gap_count=gap_count_peak,
|
||||||
)
|
)
|
||||||
peak_periods, peak_relaxation = calculate_periods_with_relaxation(
|
peak_periods, peak_relaxation = calculate_periods_with_relaxation(
|
||||||
all_prices,
|
all_prices,
|
||||||
|
|
@ -1244,13 +1268,6 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
level_override=lvl,
|
level_override=lvl,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply period-level volatility filter (after calculation)
|
|
||||||
min_volatility_peak = self.config_entry.options.get(
|
|
||||||
CONF_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
DEFAULT_PEAK_PRICE_MIN_VOLATILITY,
|
|
||||||
)
|
|
||||||
peak_periods = filter_periods_by_volatility(peak_periods, min_volatility_peak)
|
|
||||||
else:
|
else:
|
||||||
peak_periods = {
|
peak_periods = {
|
||||||
"periods": [],
|
"periods": [],
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from .const import (
|
||||||
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
||||||
DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
||||||
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
||||||
|
PRICE_LEVEL_MAPPING,
|
||||||
)
|
)
|
||||||
from .price_utils import (
|
from .price_utils import (
|
||||||
aggregate_period_levels,
|
aggregate_period_levels,
|
||||||
|
|
@ -49,6 +50,8 @@ class PeriodConfig(NamedTuple):
|
||||||
threshold_volatility_moderate: float = DEFAULT_VOLATILITY_THRESHOLD_MODERATE
|
threshold_volatility_moderate: float = DEFAULT_VOLATILITY_THRESHOLD_MODERATE
|
||||||
threshold_volatility_high: float = DEFAULT_VOLATILITY_THRESHOLD_HIGH
|
threshold_volatility_high: float = DEFAULT_VOLATILITY_THRESHOLD_HIGH
|
||||||
threshold_volatility_very_high: float = DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH
|
threshold_volatility_very_high: float = DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH
|
||||||
|
level_filter: str | None = None # "any", "cheap", "expensive", etc. or None
|
||||||
|
gap_count: int = 0 # Number of allowed consecutive deviating intervals
|
||||||
|
|
||||||
|
|
||||||
class PeriodData(NamedTuple):
|
class PeriodData(NamedTuple):
|
||||||
|
|
@ -87,6 +90,16 @@ class ThresholdConfig(NamedTuple):
|
||||||
reverse_sort: bool
|
reverse_sort: bool
|
||||||
|
|
||||||
|
|
||||||
|
class IntervalCriteria(NamedTuple):
|
||||||
|
"""Criteria for checking if an interval qualifies for a period."""
|
||||||
|
|
||||||
|
ref_price: float
|
||||||
|
avg_price: float
|
||||||
|
flex: float
|
||||||
|
min_distance_from_avg: float
|
||||||
|
reverse_sort: bool
|
||||||
|
|
||||||
|
|
||||||
def calculate_periods(
|
def calculate_periods(
|
||||||
all_prices: list[dict],
|
all_prices: list[dict],
|
||||||
*,
|
*,
|
||||||
|
|
@ -160,7 +173,13 @@ def calculate_periods(
|
||||||
"flex": flex,
|
"flex": flex,
|
||||||
"min_distance_from_avg": min_distance_from_avg,
|
"min_distance_from_avg": min_distance_from_avg,
|
||||||
}
|
}
|
||||||
raw_periods = _build_periods(all_prices_sorted, price_context, reverse_sort=reverse_sort)
|
raw_periods = _build_periods(
|
||||||
|
all_prices_sorted,
|
||||||
|
price_context,
|
||||||
|
reverse_sort=reverse_sort,
|
||||||
|
level_filter=config.level_filter,
|
||||||
|
gap_count=config.gap_count,
|
||||||
|
)
|
||||||
|
|
||||||
# Step 4: Filter by minimum length
|
# Step 4: Filter by minimum length
|
||||||
raw_periods = _filter_periods_by_min_length(raw_periods, min_period_length)
|
raw_periods = _filter_periods_by_min_length(raw_periods, min_period_length)
|
||||||
|
|
@ -238,11 +257,123 @@ def _calculate_reference_prices(intervals_by_day: dict[date, list[dict]], *, rev
|
||||||
return ref_prices
|
return ref_prices
|
||||||
|
|
||||||
|
|
||||||
|
def _check_level_with_gap_tolerance(
|
||||||
|
interval_level: int,
|
||||||
|
level_order: int,
|
||||||
|
consecutive_gaps: int,
|
||||||
|
gap_count: int,
|
||||||
|
*,
|
||||||
|
reverse_sort: bool,
|
||||||
|
) -> tuple[bool, bool, int]:
|
||||||
|
"""
|
||||||
|
Check if interval meets level requirement with gap tolerance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interval_level: Level value of current interval (from PRICE_LEVEL_MAPPING)
|
||||||
|
level_order: Required level value
|
||||||
|
consecutive_gaps: Current count of consecutive gap intervals
|
||||||
|
gap_count: Maximum allowed consecutive gap intervals
|
||||||
|
reverse_sort: True for peak price, False for best price
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (meets_level, is_gap, new_consecutive_gaps):
|
||||||
|
- meets_level: True if interval qualifies (exact match or within gap tolerance)
|
||||||
|
- is_gap: True if this is a gap interval (deviates by exactly 1 step)
|
||||||
|
- new_consecutive_gaps: Updated gap counter
|
||||||
|
|
||||||
|
"""
|
||||||
|
if reverse_sort:
|
||||||
|
# Peak price: interval must be >= level_order (e.g., EXPENSIVE or higher)
|
||||||
|
meets_level_exact = interval_level >= level_order
|
||||||
|
# Gap: exactly 1 step below (e.g., NORMAL when expecting EXPENSIVE)
|
||||||
|
is_gap = interval_level == level_order - 1
|
||||||
|
else:
|
||||||
|
# Best price: interval must be <= level_order (e.g., CHEAP or lower)
|
||||||
|
meets_level_exact = interval_level <= level_order
|
||||||
|
# Gap: exactly 1 step above (e.g., NORMAL when expecting CHEAP)
|
||||||
|
is_gap = interval_level == level_order + 1
|
||||||
|
|
||||||
|
# Apply gap tolerance
|
||||||
|
if meets_level_exact:
|
||||||
|
return True, False, 0 # Meets level, not a gap, reset counter
|
||||||
|
if is_gap and consecutive_gaps < gap_count:
|
||||||
|
return True, True, consecutive_gaps + 1 # Allowed gap, increment counter
|
||||||
|
return False, False, 0 # Doesn't meet level, reset counter
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_level_filter(
|
||||||
|
price_data: dict,
|
||||||
|
level_order: int | None,
|
||||||
|
consecutive_gaps: int,
|
||||||
|
gap_count: int,
|
||||||
|
*,
|
||||||
|
reverse_sort: bool,
|
||||||
|
) -> tuple[bool, int]:
|
||||||
|
"""
|
||||||
|
Apply level filter to a single interval.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
price_data: Price data dict with "level" key
|
||||||
|
level_order: Required level value (from PRICE_LEVEL_MAPPING) or None if disabled
|
||||||
|
consecutive_gaps: Current count of consecutive gap intervals
|
||||||
|
gap_count: Maximum allowed consecutive gap intervals
|
||||||
|
reverse_sort: True for peak price, False for best price
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (meets_level, new_consecutive_gaps)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if level_order is None:
|
||||||
|
return True, consecutive_gaps
|
||||||
|
|
||||||
|
interval_level = PRICE_LEVEL_MAPPING.get(price_data.get("level", "NORMAL"), 0)
|
||||||
|
meets_level, _is_gap, new_consecutive_gaps = _check_level_with_gap_tolerance(
|
||||||
|
interval_level, level_order, consecutive_gaps, gap_count, reverse_sort=reverse_sort
|
||||||
|
)
|
||||||
|
return meets_level, new_consecutive_gaps
|
||||||
|
|
||||||
|
|
||||||
|
def _check_interval_criteria(
|
||||||
|
price: float,
|
||||||
|
criteria: IntervalCriteria,
|
||||||
|
) -> tuple[bool, bool]:
|
||||||
|
"""
|
||||||
|
Check if interval meets flex and minimum distance criteria.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
price: Interval price
|
||||||
|
criteria: Interval criteria (ref_price, avg_price, flex, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (in_flex, meets_min_distance)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Calculate percentage difference from reference
|
||||||
|
percent_diff = ((price - criteria.ref_price) / criteria.ref_price) * 100 if criteria.ref_price != 0 else 0.0
|
||||||
|
|
||||||
|
# Check if interval qualifies for the period
|
||||||
|
in_flex = percent_diff >= criteria.flex * 100 if criteria.reverse_sort else percent_diff <= criteria.flex * 100
|
||||||
|
|
||||||
|
# Minimum distance from average
|
||||||
|
if criteria.reverse_sort:
|
||||||
|
# Peak price: must be at least min_distance_from_avg% above average
|
||||||
|
min_distance_threshold = criteria.avg_price * (1 + criteria.min_distance_from_avg / 100)
|
||||||
|
meets_min_distance = price >= min_distance_threshold
|
||||||
|
else:
|
||||||
|
# Best price: must be at least min_distance_from_avg% below average
|
||||||
|
min_distance_threshold = criteria.avg_price * (1 - criteria.min_distance_from_avg / 100)
|
||||||
|
meets_min_distance = price <= min_distance_threshold
|
||||||
|
|
||||||
|
return in_flex, meets_min_distance
|
||||||
|
|
||||||
|
|
||||||
def _build_periods(
|
def _build_periods(
|
||||||
all_prices: list[dict],
|
all_prices: list[dict],
|
||||||
price_context: dict[str, Any],
|
price_context: dict[str, Any],
|
||||||
*,
|
*,
|
||||||
reverse_sort: bool,
|
reverse_sort: bool,
|
||||||
|
level_filter: str | None = None,
|
||||||
|
gap_count: int = 0,
|
||||||
) -> list[list[dict]]:
|
) -> list[list[dict]]:
|
||||||
"""
|
"""
|
||||||
Build periods, allowing periods to cross midnight (day boundary).
|
Build periods, allowing periods to cross midnight (day boundary).
|
||||||
|
|
@ -251,15 +382,45 @@ def _build_periods(
|
||||||
When a day boundary is crossed, the current period is ended.
|
When a day boundary is crossed, the current period is ended.
|
||||||
Adjacent periods at midnight are merged in a later step.
|
Adjacent periods at midnight are merged in a later step.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
all_prices: All price data points
|
||||||
|
price_context: Dict with ref_prices, avg_prices, flex, min_distance_from_avg
|
||||||
|
reverse_sort: True for peak price (high prices), False for best price (low prices)
|
||||||
|
level_filter: Level filter string ("cheap", "expensive", "any", None)
|
||||||
|
gap_count: Number of allowed consecutive intervals deviating by exactly 1 level step
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ref_prices = price_context["ref_prices"]
|
ref_prices = price_context["ref_prices"]
|
||||||
avg_prices = price_context["avg_prices"]
|
avg_prices = price_context["avg_prices"]
|
||||||
flex = price_context["flex"]
|
flex = price_context["flex"]
|
||||||
min_distance_from_avg = price_context["min_distance_from_avg"]
|
min_distance_from_avg = price_context["min_distance_from_avg"]
|
||||||
|
|
||||||
|
# Calculate level_order if level_filter is active
|
||||||
|
level_order = None
|
||||||
|
level_filter_active = False
|
||||||
|
if level_filter and level_filter.lower() != "any":
|
||||||
|
level_order = PRICE_LEVEL_MAPPING.get(level_filter.upper(), 0)
|
||||||
|
level_filter_active = True
|
||||||
|
filter_direction = "≥" if reverse_sort else "≤"
|
||||||
|
gap_info = f", gap_tolerance={gap_count}" if gap_count > 0 else ""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%sLevel filter active: %s (order %s, require interval level %s filter level%s)",
|
||||||
|
INDENT_L3,
|
||||||
|
level_filter.upper(),
|
||||||
|
level_order,
|
||||||
|
filter_direction,
|
||||||
|
gap_info,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status = "RELAXED to ANY" if (level_filter and level_filter.lower() == "any") else "DISABLED (not configured)"
|
||||||
|
_LOGGER.debug("%sLevel filter: %s (accepting all levels)", INDENT_L3, status)
|
||||||
|
|
||||||
periods: list[list[dict]] = []
|
periods: list[list[dict]] = []
|
||||||
current_period: list[dict] = []
|
current_period: list[dict] = []
|
||||||
last_ref_date: date | None = None
|
last_ref_date: date | None = None
|
||||||
|
consecutive_gaps = 0 # Track consecutive intervals that deviate by 1 level step
|
||||||
|
intervals_checked = 0
|
||||||
|
intervals_filtered_by_level = 0
|
||||||
|
|
||||||
for price_data in all_prices:
|
for price_data in all_prices:
|
||||||
starts_at = dt_util.parse_datetime(price_data["startsAt"])
|
starts_at = dt_util.parse_datetime(price_data["startsAt"])
|
||||||
|
|
@ -267,36 +428,37 @@ def _build_periods(
|
||||||
continue
|
continue
|
||||||
starts_at = dt_util.as_local(starts_at)
|
starts_at = dt_util.as_local(starts_at)
|
||||||
date_key = starts_at.date()
|
date_key = starts_at.date()
|
||||||
ref_price = ref_prices[date_key]
|
|
||||||
avg_price = avg_prices[date_key]
|
|
||||||
price = float(price_data["total"])
|
price = float(price_data["total"])
|
||||||
|
|
||||||
# Calculate percentage difference from reference
|
intervals_checked += 1
|
||||||
percent_diff = ((price - ref_price) / ref_price) * 100 if ref_price != 0 else 0.0
|
|
||||||
percent_diff = round(percent_diff, 2)
|
|
||||||
|
|
||||||
# Check if interval qualifies for the period
|
# Check flex and minimum distance criteria
|
||||||
in_flex = percent_diff >= flex * 100 if reverse_sort else percent_diff <= flex * 100
|
criteria = IntervalCriteria(
|
||||||
|
ref_price=ref_prices[date_key],
|
||||||
|
avg_price=avg_prices[date_key],
|
||||||
|
flex=flex,
|
||||||
|
min_distance_from_avg=min_distance_from_avg,
|
||||||
|
reverse_sort=reverse_sort,
|
||||||
|
)
|
||||||
|
in_flex, meets_min_distance = _check_interval_criteria(price, criteria)
|
||||||
|
|
||||||
# Minimum distance from average
|
# Level filter: Check if interval meets level requirement with gap tolerance
|
||||||
if reverse_sort:
|
meets_level, consecutive_gaps = _apply_level_filter(
|
||||||
# Peak price: must be at least min_distance_from_avg% above average
|
price_data, level_order, consecutive_gaps, gap_count, reverse_sort=reverse_sort
|
||||||
min_distance_threshold = avg_price * (1 + min_distance_from_avg / 100)
|
)
|
||||||
meets_min_distance = price >= min_distance_threshold
|
if not meets_level:
|
||||||
else:
|
intervals_filtered_by_level += 1
|
||||||
# Best price: must be at least min_distance_from_avg% below average
|
|
||||||
min_distance_threshold = avg_price * (1 - min_distance_from_avg / 100)
|
|
||||||
meets_min_distance = price <= min_distance_threshold
|
|
||||||
|
|
||||||
# Split period if day changes
|
# Split period if day changes
|
||||||
if last_ref_date is not None and date_key != last_ref_date and current_period:
|
if last_ref_date is not None and date_key != last_ref_date and current_period:
|
||||||
periods.append(current_period)
|
periods.append(current_period)
|
||||||
current_period = []
|
current_period = []
|
||||||
|
consecutive_gaps = 0 # Reset gap counter on day boundary
|
||||||
|
|
||||||
last_ref_date = date_key
|
last_ref_date = date_key
|
||||||
|
|
||||||
# Add to period if all criteria are met
|
# Add to period if all criteria are met
|
||||||
if in_flex and meets_min_distance:
|
if in_flex and meets_min_distance and meets_level:
|
||||||
current_period.append(
|
current_period.append(
|
||||||
{
|
{
|
||||||
"interval_hour": starts_at.hour,
|
"interval_hour": starts_at.hour,
|
||||||
|
|
@ -310,11 +472,23 @@ def _build_periods(
|
||||||
# Criteria no longer met, end current period
|
# Criteria no longer met, end current period
|
||||||
periods.append(current_period)
|
periods.append(current_period)
|
||||||
current_period = []
|
current_period = []
|
||||||
|
consecutive_gaps = 0 # Reset gap counter
|
||||||
|
|
||||||
# Add final period if exists
|
# Add final period if exists
|
||||||
if current_period:
|
if current_period:
|
||||||
periods.append(current_period)
|
periods.append(current_period)
|
||||||
|
|
||||||
|
# Log summary
|
||||||
|
if level_filter_active and intervals_checked > 0:
|
||||||
|
filtered_pct = (intervals_filtered_by_level / intervals_checked) * 100
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%sLevel filter summary: %d/%d intervals filtered (%.1f%%)",
|
||||||
|
INDENT_L3,
|
||||||
|
intervals_filtered_by_level,
|
||||||
|
intervals_checked,
|
||||||
|
filtered_pct,
|
||||||
|
)
|
||||||
|
|
||||||
return periods
|
return periods
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1196,12 +1370,29 @@ def _relax_single_day( # noqa: PLR0913 - Comprehensive filter relaxation per da
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Calculate periods with this flex + filter combination
|
# Calculate periods with this flex + filter combination
|
||||||
relaxed_config = config._replace(flex=new_flex)
|
# Apply level override if specified
|
||||||
|
level_filter_value = lvl_override if lvl_override else config.level_filter
|
||||||
|
|
||||||
|
# Log filter changes
|
||||||
|
flex_pct = round(abs(new_flex) * 100, 1)
|
||||||
|
if lvl_override:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%sDay %s flex=%.1f%%: OVERRIDING level_filter: %s → %s",
|
||||||
|
INDENT_L2,
|
||||||
|
day_label,
|
||||||
|
flex_pct,
|
||||||
|
config.level_filter or "None",
|
||||||
|
lvl_override.upper(),
|
||||||
|
)
|
||||||
|
|
||||||
|
relaxed_config = config._replace(
|
||||||
|
flex=new_flex,
|
||||||
|
level_filter=level_filter_value,
|
||||||
|
)
|
||||||
relaxed_result = calculate_periods(day_prices, config=relaxed_config)
|
relaxed_result = calculate_periods(day_prices, config=relaxed_config)
|
||||||
new_periods = relaxed_result["periods"]
|
new_periods = relaxed_result["periods"]
|
||||||
|
|
||||||
# Build relaxation level label BEFORE marking periods
|
# Build relaxation level label BEFORE marking periods
|
||||||
flex_pct = round(abs(new_flex) * 100, 1)
|
|
||||||
relaxation_level = f"price_diff_{flex_pct}%{label_suffix}"
|
relaxation_level = f"price_diff_{flex_pct}%{label_suffix}"
|
||||||
phases_used.append(relaxation_level)
|
phases_used.append(relaxation_level)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@
|
||||||
"best_price_min_period_length": "Minimale Periodenlänge",
|
"best_price_min_period_length": "Minimale Periodenlänge",
|
||||||
"best_price_flex": "Flexibilität: Maximal über dem Mindestpreis",
|
"best_price_flex": "Flexibilität: Maximal über dem Mindestpreis",
|
||||||
"best_price_min_distance_from_avg": "Mindestabstand: Erforderlich unter dem Tagesdurchschnitt",
|
"best_price_min_distance_from_avg": "Mindestabstand: Erforderlich unter dem Tagesdurchschnitt",
|
||||||
"best_price_min_volatility": "Mindest-Volatilitätsfilter",
|
|
||||||
"best_price_max_level": "Preisniveau-Filter (Optional)",
|
"best_price_max_level": "Preisniveau-Filter (Optional)",
|
||||||
"best_price_max_level_gap_count": "Lückentoleranz für Niveaufilter",
|
"best_price_max_level_gap_count": "Lückentoleranz für Niveaufilter",
|
||||||
"enable_min_periods_best": "Mindestanzahl Perioden anstreben",
|
"enable_min_periods_best": "Mindestanzahl Perioden anstreben",
|
||||||
|
|
@ -112,7 +111,6 @@
|
||||||
"relaxation_step_best": "Lockerungsschritt"
|
"relaxation_step_best": "Lockerungsschritt"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"best_price_min_volatility": "Zeigt Bestpreis-Perioden nur an, wenn die interne Preisvolatilität der Periode (Preisspanne innerhalb der Periode) mindestens diesem Level entspricht. Standard: 'Niedrig' (zeigt Perioden mit beliebigem Volatilitätslevel) - ermöglicht das Finden günstiger Perioden auch wenn die Preise stabil sind. Wähle 'Moderat'/'Hoch' um nur Perioden mit signifikanten Preisschwankungen anzuzeigen, was auf dynamischere Preismöglichkeiten hinweisen kann.",
|
|
||||||
"best_price_max_level": "Zeigt Bestpreis-Perioden nur an, wenn sie Intervalle mit Preisniveaus ≤ dem gewählten Wert enthalten. Beispiel: Wahl von 'Günstig' bedeutet, dass die Periode mindestens ein 'SEHR_GÜNSTIG' oder 'GÜNSTIG' Intervall haben muss. Dies stellt sicher, dass 'Bestpreis'-Perioden nicht nur relativ günstig für den Tag sind, sondern tatsächlich günstig in absoluten Zahlen. Wähle 'Beliebig' um Bestpreise unabhängig vom absoluten Preisniveau anzuzeigen.",
|
"best_price_max_level": "Zeigt Bestpreis-Perioden nur an, wenn sie Intervalle mit Preisniveaus ≤ dem gewählten Wert enthalten. Beispiel: Wahl von 'Günstig' bedeutet, dass die Periode mindestens ein 'SEHR_GÜNSTIG' oder 'GÜNSTIG' Intervall haben muss. Dies stellt sicher, dass 'Bestpreis'-Perioden nicht nur relativ günstig für den Tag sind, sondern tatsächlich günstig in absoluten Zahlen. Wähle 'Beliebig' um Bestpreise unabhängig vom absoluten Preisniveau anzuzeigen.",
|
||||||
"best_price_max_level_gap_count": "Maximale Anzahl aufeinanderfolgender Intervalle, die exakt um eine Niveaustufe vom geforderten Level abweichen dürfen. Beispiel: Bei Filter 'Günstig' und Lückentoleranz 1 wird die Sequenz 'GÜNSTIG, GÜNSTIG, NORMAL, GÜNSTIG' akzeptiert (NORMAL ist eine Stufe über GÜNSTIG). Dies verhindert, dass Perioden durch gelegentliche Niveau-Abweichungen aufgespalten werden. Standard: 0 (strenge Filterung, keine Toleranz).",
|
"best_price_max_level_gap_count": "Maximale Anzahl aufeinanderfolgender Intervalle, die exakt um eine Niveaustufe vom geforderten Level abweichen dürfen. Beispiel: Bei Filter 'Günstig' und Lückentoleranz 1 wird die Sequenz 'GÜNSTIG, GÜNSTIG, NORMAL, GÜNSTIG' akzeptiert (NORMAL ist eine Stufe über GÜNSTIG). Dies verhindert, dass Perioden durch gelegentliche Niveau-Abweichungen aufgespalten werden. Standard: 0 (strenge Filterung, keine Toleranz).",
|
||||||
"enable_min_periods_best": "Wenn aktiviert, werden Filter schrittweise gelockert, falls nicht genug Perioden gefunden wurden. Dies versucht die gewünschte Mindestanzahl zu erreichen, was dazu führen kann, dass auch weniger optimale Zeiträume als Bestpreis-Perioden markiert werden.",
|
"enable_min_periods_best": "Wenn aktiviert, werden Filter schrittweise gelockert, falls nicht genug Perioden gefunden wurden. Dies versucht die gewünschte Mindestanzahl zu erreichen, was dazu führen kann, dass auch weniger optimale Zeiträume als Bestpreis-Perioden markiert werden.",
|
||||||
|
|
@ -128,7 +126,6 @@
|
||||||
"peak_price_min_period_length": "Minimale Periodenlänge",
|
"peak_price_min_period_length": "Minimale Periodenlänge",
|
||||||
"peak_price_flex": "Flexibilität: Maximal unter dem Höchstpreis (negativer Wert)",
|
"peak_price_flex": "Flexibilität: Maximal unter dem Höchstpreis (negativer Wert)",
|
||||||
"peak_price_min_distance_from_avg": "Mindestabstand: Erforderlich über dem Tagesdurchschnitt",
|
"peak_price_min_distance_from_avg": "Mindestabstand: Erforderlich über dem Tagesdurchschnitt",
|
||||||
"peak_price_min_volatility": "Mindest-Volatilitätsfilter",
|
|
||||||
"peak_price_min_level": "Preisniveau-Filter (Optional)",
|
"peak_price_min_level": "Preisniveau-Filter (Optional)",
|
||||||
"peak_price_max_level_gap_count": "Lückentoleranz für Niveaufilter",
|
"peak_price_max_level_gap_count": "Lückentoleranz für Niveaufilter",
|
||||||
"enable_min_periods_peak": "Mindestanzahl Perioden anstreben",
|
"enable_min_periods_peak": "Mindestanzahl Perioden anstreben",
|
||||||
|
|
@ -136,7 +133,6 @@
|
||||||
"relaxation_step_peak": "Lockerungsschritt"
|
"relaxation_step_peak": "Lockerungsschritt"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"peak_price_min_volatility": "Zeigt Spitzenpreis-Perioden nur an, wenn die interne Preisvolatilität der Periode (Preisspanne innerhalb der Periode) mindestens diesem Level entspricht. Standard: 'Niedrig' (zeigt Perioden mit beliebigem Volatilitätslevel) - ermöglicht das Identifizieren teurer Perioden auch wenn die Preise stabil sind. Wähle 'Moderat'/'Hoch' um nur Perioden mit signifikanten Preisschwankungen anzuzeigen, was auf dringenderen Bedarf hinweisen kann, diese Zeiten zu vermeiden.",
|
|
||||||
"peak_price_min_level": "Zeigt Spitzenpreis-Perioden nur an, wenn sie Intervalle mit Preisniveaus ≥ dem gewählten Wert enthalten. Beispiel: Wahl von 'Teuer' bedeutet, dass die Periode mindestens ein 'TEUER' oder 'SEHR_TEUER' Intervall haben muss. Dies stellt sicher, dass 'Spitzenpreis'-Perioden nicht nur relativ teuer für den Tag sind, sondern tatsächlich teuer in absoluten Zahlen. Wähle 'Beliebig' um Spitzenpreise unabhängig vom absoluten Preisniveau anzuzeigen.",
|
"peak_price_min_level": "Zeigt Spitzenpreis-Perioden nur an, wenn sie Intervalle mit Preisniveaus ≥ dem gewählten Wert enthalten. Beispiel: Wahl von 'Teuer' bedeutet, dass die Periode mindestens ein 'TEUER' oder 'SEHR_TEUER' Intervall haben muss. Dies stellt sicher, dass 'Spitzenpreis'-Perioden nicht nur relativ teuer für den Tag sind, sondern tatsächlich teuer in absoluten Zahlen. Wähle 'Beliebig' um Spitzenpreise unabhängig vom absoluten Preisniveau anzuzeigen.",
|
||||||
"peak_price_max_level_gap_count": "Maximale Anzahl aufeinanderfolgender Intervalle, die exakt um eine Niveaustufe vom geforderten Level abweichen dürfen. Beispiel: Bei Filter 'Teuer' und Lückentoleranz 2 wird die Sequenz 'TEUER, NORMAL, NORMAL, TEUER' akzeptiert (NORMAL ist eine Stufe unter TEUER). Dies verhindert, dass Perioden durch gelegentliche Niveau-Abweichungen aufgespalten werden. Standard: 0 (strenge Filterung, keine Toleranz).",
|
"peak_price_max_level_gap_count": "Maximale Anzahl aufeinanderfolgender Intervalle, die exakt um eine Niveaustufe vom geforderten Level abweichen dürfen. Beispiel: Bei Filter 'Teuer' und Lückentoleranz 2 wird die Sequenz 'TEUER, NORMAL, NORMAL, TEUER' akzeptiert (NORMAL ist eine Stufe unter TEUER). Dies verhindert, dass Perioden durch gelegentliche Niveau-Abweichungen aufgespalten werden. Standard: 0 (strenge Filterung, keine Toleranz).",
|
||||||
"enable_min_periods_peak": "Wenn aktiviert, werden Filter schrittweise gelockert, falls nicht genug Perioden gefunden wurden. Dies versucht die gewünschte Mindestanzahl zu erreichen, um sicherzustellen, dass du auch an Tagen mit ungewöhnlichen Preismustern vor teuren Perioden gewarnt wirst.",
|
"enable_min_periods_peak": "Wenn aktiviert, werden Filter schrittweise gelockert, falls nicht genug Perioden gefunden wurden. Dies versucht die gewünschte Mindestanzahl zu erreichen, um sicherzustellen, dass du auch an Tagen mit ungewöhnlichen Preismustern vor teuren Perioden gewarnt wirst.",
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@
|
||||||
"best_price_min_period_length": "Minimum Period Length",
|
"best_price_min_period_length": "Minimum Period Length",
|
||||||
"best_price_flex": "Flexibility: Maximum above minimum price",
|
"best_price_flex": "Flexibility: Maximum above minimum price",
|
||||||
"best_price_min_distance_from_avg": "Minimum Distance: Required below daily average",
|
"best_price_min_distance_from_avg": "Minimum Distance: Required below daily average",
|
||||||
"best_price_min_volatility": "Minimum Volatility Filter",
|
|
||||||
"best_price_max_level": "Price Level Filter (Optional)",
|
"best_price_max_level": "Price Level Filter (Optional)",
|
||||||
"best_price_max_level_gap_count": "Level Filter Gap Tolerance",
|
"best_price_max_level_gap_count": "Level Filter Gap Tolerance",
|
||||||
"enable_min_periods_best": "Try to Achieve Minimum Period Count",
|
"enable_min_periods_best": "Try to Achieve Minimum Period Count",
|
||||||
|
|
@ -112,7 +111,6 @@
|
||||||
"relaxation_step_best": "Filter Relaxation Step Size"
|
"relaxation_step_best": "Filter Relaxation Step Size"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"best_price_min_volatility": "Only show best price periods when the period's internal price volatility (spread within the period) meets or exceeds this level. Default: 'Low' (show periods with any volatility level) - allows finding cheap periods even if prices are stable. Select 'Moderate'/'High' to only show periods with significant price variations, which may indicate more dynamic pricing opportunities.",
|
|
||||||
"best_price_max_level": "Only show best price periods if they contain intervals with price levels ≤ selected value. For example, selecting 'Cheap' means the period must have at least one 'VERY_CHEAP' or 'CHEAP' interval. This ensures 'best price' periods are not just relatively cheap for the day, but actually cheap in absolute terms. Select 'Any' to show best prices regardless of their absolute price level.",
|
"best_price_max_level": "Only show best price periods if they contain intervals with price levels ≤ selected value. For example, selecting 'Cheap' means the period must have at least one 'VERY_CHEAP' or 'CHEAP' interval. This ensures 'best price' periods are not just relatively cheap for the day, but actually cheap in absolute terms. Select 'Any' to show best prices regardless of their absolute price level.",
|
||||||
"best_price_max_level_gap_count": "Maximum number of consecutive intervals allowed that deviate by exactly one level step from the required level. For example: with 'Cheap' filter and gap count 1, a sequence 'CHEAP, CHEAP, NORMAL, CHEAP' is accepted (NORMAL is one step above CHEAP). This prevents periods from being split by occasional level deviations. Default: 0 (strict filtering, no tolerance).",
|
"best_price_max_level_gap_count": "Maximum number of consecutive intervals allowed that deviate by exactly one level step from the required level. For example: with 'Cheap' filter and gap count 1, a sequence 'CHEAP, CHEAP, NORMAL, CHEAP' is accepted (NORMAL is one step above CHEAP). This prevents periods from being split by occasional level deviations. Default: 0 (strict filtering, no tolerance).",
|
||||||
"enable_min_periods_best": "When enabled, filters will be gradually relaxed if not enough periods are found. This attempts to reach the desired minimum number of periods, which may include less optimal time windows as best-price periods.",
|
"enable_min_periods_best": "When enabled, filters will be gradually relaxed if not enough periods are found. This attempts to reach the desired minimum number of periods, which may include less optimal time windows as best-price periods.",
|
||||||
|
|
@ -128,7 +126,6 @@
|
||||||
"peak_price_min_period_length": "Minimum Period Length",
|
"peak_price_min_period_length": "Minimum Period Length",
|
||||||
"peak_price_flex": "Flexibility: Maximum below maximum price (negative value)",
|
"peak_price_flex": "Flexibility: Maximum below maximum price (negative value)",
|
||||||
"peak_price_min_distance_from_avg": "Minimum Distance: Required above daily average",
|
"peak_price_min_distance_from_avg": "Minimum Distance: Required above daily average",
|
||||||
"peak_price_min_volatility": "Minimum Volatility Filter",
|
|
||||||
"peak_price_min_level": "Price Level Filter (Optional)",
|
"peak_price_min_level": "Price Level Filter (Optional)",
|
||||||
"peak_price_max_level_gap_count": "Level Filter Gap Tolerance",
|
"peak_price_max_level_gap_count": "Level Filter Gap Tolerance",
|
||||||
"enable_min_periods_peak": "Try to Achieve Minimum Period Count",
|
"enable_min_periods_peak": "Try to Achieve Minimum Period Count",
|
||||||
|
|
@ -136,7 +133,6 @@
|
||||||
"relaxation_step_peak": "Filter Relaxation Step Size"
|
"relaxation_step_peak": "Filter Relaxation Step Size"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"peak_price_min_volatility": "Only show peak price periods when the period's internal price volatility (spread within the period) meets or exceeds this level. Default: 'Low' (show periods with any volatility level) - allows identifying expensive periods even if prices are stable. Select 'Moderate'/'High' to only show periods with significant price variations, which may indicate more urgent need to avoid these times.",
|
|
||||||
"peak_price_min_level": "Only show peak price periods if they contain intervals with price levels ≥ selected value. For example, selecting 'Expensive' means the period must have at least one 'EXPENSIVE' or 'VERY_EXPENSIVE' interval. This ensures 'peak price' periods are not just relatively expensive for the day, but actually expensive in absolute terms. Select 'Any' to show peak prices regardless of their absolute price level.",
|
"peak_price_min_level": "Only show peak price periods if they contain intervals with price levels ≥ selected value. For example, selecting 'Expensive' means the period must have at least one 'EXPENSIVE' or 'VERY_EXPENSIVE' interval. This ensures 'peak price' periods are not just relatively expensive for the day, but actually expensive in absolute terms. Select 'Any' to show peak prices regardless of their absolute price level.",
|
||||||
"peak_price_max_level_gap_count": "Maximum number of consecutive intervals allowed that deviate by exactly one level step from the required level. For example: with 'Expensive' filter and gap count 2, a sequence 'EXPENSIVE, NORMAL, NORMAL, EXPENSIVE' is accepted (NORMAL is one step below EXPENSIVE). This prevents periods from being split by occasional level deviations. Default: 0 (strict filtering, no tolerance).",
|
"peak_price_max_level_gap_count": "Maximum number of consecutive intervals allowed that deviate by exactly one level step from the required level. For example: with 'Expensive' filter and gap count 2, a sequence 'EXPENSIVE, NORMAL, NORMAL, EXPENSIVE' is accepted (NORMAL is one step below EXPENSIVE). This prevents periods from being split by occasional level deviations. Default: 0 (strict filtering, no tolerance).",
|
||||||
"enable_min_periods_peak": "When enabled, filters will be gradually relaxed if not enough periods are found. This attempts to reach the desired minimum number of periods to ensure you're warned about expensive periods even on days with unusual price patterns.",
|
"enable_min_periods_peak": "When enabled, filters will be gradually relaxed if not enough periods are found. This attempts to reach the desired minimum number of periods to ensure you're warned about expensive periods even on days with unusual price patterns.",
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@
|
||||||
"best_price_min_period_length": "Minimum periodelengde",
|
"best_price_min_period_length": "Minimum periodelengde",
|
||||||
"best_price_flex": "Fleksibilitet: Maksimum % over minimumspris",
|
"best_price_flex": "Fleksibilitet: Maksimum % over minimumspris",
|
||||||
"best_price_min_distance_from_avg": "Minimumsavstand: Påkrevd % under daglig gjennomsnitt",
|
"best_price_min_distance_from_avg": "Minimumsavstand: Påkrevd % under daglig gjennomsnitt",
|
||||||
"best_price_min_volatility": "Minimum volatilitetsfilter",
|
|
||||||
"best_price_max_level": "Prisnivåfilter (valgfritt)",
|
"best_price_max_level": "Prisnivåfilter (valgfritt)",
|
||||||
"best_price_max_level_gap_count": "Gaptoleranse for nivåfilter",
|
"best_price_max_level_gap_count": "Gaptoleranse for nivåfilter",
|
||||||
"enable_min_periods_best": "Prøv å oppnå minimum antall perioder",
|
"enable_min_periods_best": "Prøv å oppnå minimum antall perioder",
|
||||||
|
|
@ -112,7 +111,6 @@
|
||||||
"relaxation_step_best": "Avslappingstrinn"
|
"relaxation_step_best": "Avslappingstrinn"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"best_price_min_volatility": "Vis kun beste prisperioder når periodens interne prisvolatilitet (prisspennet innenfor perioden) oppfyller eller overskrider dette nivået. Standard: 'Lav' (vis perioder med hvilket som helst volatilitetsnivå) - gjør det mulig å finne billige perioder selv om prisene er stabile. Velg 'Moderat'/'Høy' for kun å vise perioder med betydelige prisvariasjoner, noe som kan indikere mer dynamiske prismuligheter.",
|
|
||||||
"best_price_max_level": "Vis kun beste prisperioder hvis de inneholder intervaller med prisnivåer ≤ valgt verdi. For eksempel: å velge 'Billig' betyr at perioden må ha minst ett 'VELDIG_BILLIG' eller 'BILLIG' intervall. Dette sikrer at 'beste pris'-perioder ikke bare er relativt billige for dagen, men faktisk billige i absolutte tall. Velg 'Alle' for å vise beste priser uavhengig av deres absolutte prisnivå.",
|
"best_price_max_level": "Vis kun beste prisperioder hvis de inneholder intervaller med prisnivåer ≤ valgt verdi. For eksempel: å velge 'Billig' betyr at perioden må ha minst ett 'VELDIG_BILLIG' eller 'BILLIG' intervall. Dette sikrer at 'beste pris'-perioder ikke bare er relativt billige for dagen, men faktisk billige i absolutte tall. Velg 'Alle' for å vise beste priser uavhengig av deres absolutte prisnivå.",
|
||||||
"enable_min_periods_best": "Når aktivert vil filtrene gradvis bli lempeligere hvis det ikke blir funnet nok perioder. Dette forsøker å nå ønsket minimum antall perioder, noe som kan føre til at mindre optimale tidsrom blir markert som beste-pris-perioder.",
|
"enable_min_periods_best": "Når aktivert vil filtrene gradvis bli lempeligere hvis det ikke blir funnet nok perioder. Dette forsøker å nå ønsket minimum antall perioder, noe som kan føre til at mindre optimale tidsrom blir markert som beste-pris-perioder.",
|
||||||
"min_periods_best": "Minimum antall beste-pris-perioder å sikte mot per dag. Filtre vil bli lempet trinn for trinn for å prøve å oppnå dette antallet. Kun aktiv når 'Prøv å oppnå minimum antall perioder' er aktivert. Standard: 1",
|
"min_periods_best": "Minimum antall beste-pris-perioder å sikte mot per dag. Filtre vil bli lempet trinn for trinn for å prøve å oppnå dette antallet. Kun aktiv når 'Prøv å oppnå minimum antall perioder' er aktivert. Standard: 1",
|
||||||
|
|
@ -128,7 +126,6 @@
|
||||||
"peak_price_min_period_length": "Minimum periodelengde",
|
"peak_price_min_period_length": "Minimum periodelengde",
|
||||||
"peak_price_flex": "Fleksibilitet: Maksimum % under maksimumspris (negativ verdi)",
|
"peak_price_flex": "Fleksibilitet: Maksimum % under maksimumspris (negativ verdi)",
|
||||||
"peak_price_min_distance_from_avg": "Minimumsavstand: Påkrevd % over daglig gjennomsnitt",
|
"peak_price_min_distance_from_avg": "Minimumsavstand: Påkrevd % over daglig gjennomsnitt",
|
||||||
"peak_price_min_volatility": "Minimum volatilitetsfilter",
|
|
||||||
"peak_price_min_level": "Prisnivåfilter (valgfritt)",
|
"peak_price_min_level": "Prisnivåfilter (valgfritt)",
|
||||||
"peak_price_max_level_gap_count": "Gaptoleranse for nivåfilter",
|
"peak_price_max_level_gap_count": "Gaptoleranse for nivåfilter",
|
||||||
"enable_min_periods_peak": "Prøv å oppnå minimum antall perioder",
|
"enable_min_periods_peak": "Prøv å oppnå minimum antall perioder",
|
||||||
|
|
@ -136,7 +133,6 @@
|
||||||
"relaxation_step_peak": "Avslappingstrinn"
|
"relaxation_step_peak": "Avslappingstrinn"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"peak_price_min_volatility": "Vis kun topprisperioder når periodens interne prisvolatilitet (prisspennet innenfor perioden) oppfyller eller overskrider dette nivået. Standard: 'Lav' (vis perioder med hvilket som helst volatilitetsnivå) - gjør det mulig å identifisere dyre perioder selv om prisene er stabile. Velg 'Moderat'/'Høy' for kun å vise perioder med betydelige prisvariasjoner, noe som kan indikere mer presserende behov for å unngå disse tidspunktene.",
|
|
||||||
"peak_price_min_level": "Vis kun topprisperioder hvis de inneholder intervaller med prisnivåer ≥ valgt verdi. For eksempel: å velge 'Dyr' betyr at perioden må ha minst ett 'DYR' eller 'VELDIG_DYR' intervall. Dette sikrer at 'topppris'-perioder ikke bare er relativt dyre for dagen, men faktisk dyre i absolutte tall. Velg 'Alle' for å vise topppriser uavhengig av deres absolutte prisnivå.",
|
"peak_price_min_level": "Vis kun topprisperioder hvis de inneholder intervaller med prisnivåer ≥ valgt verdi. For eksempel: å velge 'Dyr' betyr at perioden må ha minst ett 'DYR' eller 'VELDIG_DYR' intervall. Dette sikrer at 'topppris'-perioder ikke bare er relativt dyre for dagen, men faktisk dyre i absolutte tall. Velg 'Alle' for å vise topppriser uavhengig av deres absolutte prisnivå.",
|
||||||
"enable_min_periods_peak": "Når aktivert vil filtrene gradvis bli lempeligere hvis det ikke blir funnet nok perioder. Dette forsøker å nå ønsket minimum antall perioder for å sikre at du blir advart om dyre perioder selv på dager med uvanlige prismønstre.",
|
"enable_min_periods_peak": "Når aktivert vil filtrene gradvis bli lempeligere hvis det ikke blir funnet nok perioder. Dette forsøker å nå ønsket minimum antall perioder for å sikre at du blir advart om dyre perioder selv på dager med uvanlige prismønstre.",
|
||||||
"min_periods_peak": "Minimum antall topp-pris-perioder å sikte mot per dag. Filtre vil bli lempet trinn for trinn for å prøve å oppnå dette antallet. Kun aktiv når 'Prøv å oppnå minimum antall perioder' er aktivert. Standard: 1",
|
"min_periods_peak": "Minimum antall topp-pris-perioder å sikte mot per dag. Filtre vil bli lempet trinn for trinn for å prøve å oppnå dette antallet. Kun aktiv når 'Prøv å oppnå minimum antall perioder' er aktivert. Standard: 1",
|
||||||
|
|
@ -515,4 +511,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Prisinformasjon & Vurderinger"
|
"title": "Tibber Prisinformasjon & Vurderinger"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@
|
||||||
"best_price_min_period_length": "Minimale periode lengte",
|
"best_price_min_period_length": "Minimale periode lengte",
|
||||||
"best_price_flex": "Flexibiliteit: Maximaal % boven minimumprijs",
|
"best_price_flex": "Flexibiliteit: Maximaal % boven minimumprijs",
|
||||||
"best_price_min_distance_from_avg": "Minimale afstand: Vereist % onder dagelijks gemiddelde",
|
"best_price_min_distance_from_avg": "Minimale afstand: Vereist % onder dagelijks gemiddelde",
|
||||||
"best_price_min_volatility": "Minimum volatiliteitsfilter",
|
|
||||||
"best_price_max_level": "Prijsniveaufilter (Optioneel)",
|
"best_price_max_level": "Prijsniveaufilter (Optioneel)",
|
||||||
"best_price_max_level_gap_count": "Gaptolerantie voor niveaufilter",
|
"best_price_max_level_gap_count": "Gaptolerantie voor niveaufilter",
|
||||||
"enable_min_periods_best": "Probeer minimum aantal periodes te bereiken",
|
"enable_min_periods_best": "Probeer minimum aantal periodes te bereiken",
|
||||||
|
|
@ -112,7 +111,6 @@
|
||||||
"relaxation_step_best": "Ontspanningsstap"
|
"relaxation_step_best": "Ontspanningsstap"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"best_price_min_volatility": "Toon alleen beste prijsperiodes wanneer de interne prijsvolatiliteit van de periode (prijsspanne binnen de periode) dit niveau bereikt of overschrijdt. Standaard: 'Laag' (toon periodes met elk volatiliteitsniveau) - maakt het mogelijk om goedkope periodes te vinden zelfs als de prijzen stabiel zijn. Selecteer 'Matig'/'Hoog' om alleen periodes met significante prijsvariaties te tonen, wat kan wijzen op meer dynamische prijsmogelijkheden.",
|
|
||||||
"best_price_max_level": "Toon alleen beste prijsperiodes als ze intervallen bevatten met prijsniveaus ≤ geselecteerde waarde. Bijvoorbeeld: selecteren van 'Goedkoop' betekent dat de periode minstens één 'ZEER_GOEDKOOP' of 'GOEDKOOP' interval moet hebben. Dit zorgt ervoor dat 'beste prijs'-periodes niet alleen relatief goedkoop zijn voor de dag, maar daadwerkelijk goedkoop in absolute termen. Selecteer 'Alle' om beste prijzen te tonen ongeacht hun absolute prijsniveau.",
|
"best_price_max_level": "Toon alleen beste prijsperiodes als ze intervallen bevatten met prijsniveaus ≤ geselecteerde waarde. Bijvoorbeeld: selecteren van 'Goedkoop' betekent dat de periode minstens één 'ZEER_GOEDKOOP' of 'GOEDKOOP' interval moet hebben. Dit zorgt ervoor dat 'beste prijs'-periodes niet alleen relatief goedkoop zijn voor de dag, maar daadwerkelijk goedkoop in absolute termen. Selecteer 'Alle' om beste prijzen te tonen ongeacht hun absolute prijsniveau.",
|
||||||
"enable_min_periods_best": "Wanneer ingeschakeld worden filters geleidelijk versoepeld als er niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken om ervoor te zorgen dat je kansen hebt om van lage prijzen te profiteren, zelfs op dagen met ongebruikelijke prijspatronen.",
|
"enable_min_periods_best": "Wanneer ingeschakeld worden filters geleidelijk versoepeld als er niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken om ervoor te zorgen dat je kansen hebt om van lage prijzen te profiteren, zelfs op dagen met ongebruikelijke prijspatronen.",
|
||||||
"min_periods_best": "Minimum aantal beste prijsperiodes om naar te streven per dag. Filters worden stap voor stap versoepeld om dit aantal te proberen bereiken. Alleen actief wanneer 'Probeer minimum aantal periodes te bereiken' is ingeschakeld. Standaard: 1",
|
"min_periods_best": "Minimum aantal beste prijsperiodes om naar te streven per dag. Filters worden stap voor stap versoepeld om dit aantal te proberen bereiken. Alleen actief wanneer 'Probeer minimum aantal periodes te bereiken' is ingeschakeld. Standaard: 1",
|
||||||
|
|
@ -128,7 +126,6 @@
|
||||||
"peak_price_min_period_length": "Minimale periode lengte",
|
"peak_price_min_period_length": "Minimale periode lengte",
|
||||||
"peak_price_flex": "Flexibiliteit: Maximaal % onder maximumprijs (negatieve waarde)",
|
"peak_price_flex": "Flexibiliteit: Maximaal % onder maximumprijs (negatieve waarde)",
|
||||||
"peak_price_min_distance_from_avg": "Minimale afstand: Vereist % boven dagelijks gemiddelde",
|
"peak_price_min_distance_from_avg": "Minimale afstand: Vereist % boven dagelijks gemiddelde",
|
||||||
"peak_price_min_volatility": "Minimum volatiliteitsfilter",
|
|
||||||
"peak_price_min_level": "Prijsniveaufilter (Optioneel)",
|
"peak_price_min_level": "Prijsniveaufilter (Optioneel)",
|
||||||
"peak_price_max_level_gap_count": "Gaptolerantie voor niveaufilter",
|
"peak_price_max_level_gap_count": "Gaptolerantie voor niveaufilter",
|
||||||
"enable_min_periods_peak": "Probeer minimum aantal periodes te bereiken",
|
"enable_min_periods_peak": "Probeer minimum aantal periodes te bereiken",
|
||||||
|
|
@ -136,7 +133,6 @@
|
||||||
"relaxation_step_peak": "Ontspanningsstap"
|
"relaxation_step_peak": "Ontspanningsstap"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"peak_price_min_volatility": "Toon alleen piekprijsperiodes wanneer de interne prijsvolatiliteit van de periode (prijsspanne binnen de periode) dit niveau bereikt of overschrijdt. Standaard: 'Laag' (toon periodes met elk volatiliteitsniveau) - maakt het mogelijk om dure periodes te identificeren zelfs als de prijzen stabiel zijn. Selecteer 'Matig'/'Hoog' om alleen periodes met significante prijsvariaties te tonen, wat kan wijzen op een urgenter noodzaak om deze tijden te vermijden.",
|
|
||||||
"peak_price_min_level": "Toon alleen piekprijsperiodes als ze intervallen bevatten met prijsniveaus ≥ geselecteerde waarde. Bijvoorbeeld: selecteren van 'Duur' betekent dat de periode minstens één 'DUUR' of 'ZEER_DUUR' interval moet hebben. Dit zorgt ervoor dat 'piekprijs'-periodes niet alleen relatief duur zijn voor de dag, maar daadwerkelijk duur in absolute termen. Selecteer 'Alle' om piekprijzen te tonen ongeacht hun absolute prijsniveau.",
|
"peak_price_min_level": "Toon alleen piekprijsperiodes als ze intervallen bevatten met prijsniveaus ≥ geselecteerde waarde. Bijvoorbeeld: selecteren van 'Duur' betekent dat de periode minstens één 'DUUR' of 'ZEER_DUUR' interval moet hebben. Dit zorgt ervoor dat 'piekprijs'-periodes niet alleen relatief duur zijn voor de dag, maar daadwerkelijk duur in absolute termen. Selecteer 'Alle' om piekprijzen te tonen ongeacht hun absolute prijsniveau.",
|
||||||
"enable_min_periods_peak": "Wanneer ingeschakeld worden filters geleidelijk versoepeld als er niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken om ervoor te zorgen dat je wordt gewaarschuwd voor dure periodes, zelfs op dagen met ongebruikelijke prijspatronen.",
|
"enable_min_periods_peak": "Wanneer ingeschakeld worden filters geleidelijk versoepeld als er niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken om ervoor te zorgen dat je wordt gewaarschuwd voor dure periodes, zelfs op dagen met ongebruikelijke prijspatronen.",
|
||||||
"min_periods_peak": "Minimum aantal piekprijsperiodes om naar te streven per dag. Filters worden stap voor stap versoepeld om dit aantal te proberen bereiken. Alleen actief wanneer 'Probeer minimum aantal periodes te bereiken' is ingeschakeld. Standaard: 1",
|
"min_periods_peak": "Minimum aantal piekprijsperiodes om naar te streven per dag. Filters worden stap voor stap versoepeld om dit aantal te proberen bereiken. Alleen actief wanneer 'Probeer minimum aantal periodes te bereiken' is ingeschakeld. Standaard: 1",
|
||||||
|
|
@ -515,4 +511,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Prijsinformatie & Beoordelingen"
|
"title": "Tibber Prijsinformatie & Beoordelingen"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@
|
||||||
"best_price_min_period_length": "Minsta periodlängd",
|
"best_price_min_period_length": "Minsta periodlängd",
|
||||||
"best_price_flex": "Flexibilitet: Maximalt % över minimumpris",
|
"best_price_flex": "Flexibilitet: Maximalt % över minimumpris",
|
||||||
"best_price_min_distance_from_avg": "Minimiavstånd: Krävd % under dagligt genomsnitt",
|
"best_price_min_distance_from_avg": "Minimiavstånd: Krävd % under dagligt genomsnitt",
|
||||||
"best_price_min_volatility": "Minimum volatilitetsfilter",
|
|
||||||
"best_price_max_level": "Prisnivåfilter (Valfritt)",
|
"best_price_max_level": "Prisnivåfilter (Valfritt)",
|
||||||
"best_price_max_level_gap_count": "Gaptolerens för nivåfilter",
|
"best_price_max_level_gap_count": "Gaptolerens för nivåfilter",
|
||||||
"enable_min_periods_best": "Försök uppnå minsta antal perioder",
|
"enable_min_periods_best": "Försök uppnå minsta antal perioder",
|
||||||
|
|
@ -112,7 +111,6 @@
|
||||||
"relaxation_step_best": "Avslappningssteg"
|
"relaxation_step_best": "Avslappningssteg"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"best_price_min_volatility": "Visa endast bästa prisperioder när periodens interna prisvolatilitet (prisspann inom perioden) uppfyller eller överskrider denna nivå. Standard: 'Låg' (visa perioder med valfri volatilitetsnivå) - möjliggör att hitta billiga perioder även om priserna är stabila. Välj 'Måttlig'/'Hög' för att endast visa perioder med betydande prisvariationer, vilket kan indikera mer dynamiska prismöjligheter.",
|
|
||||||
"best_price_max_level": "Visa endast bästa prisperioder om de innehåller intervall med prisnivåer ≤ valt värde. Till exempel: att välja 'Billigt' betyder att perioden måste ha minst ett 'MYCKET_BILLIGT' eller 'BILLIGT' intervall. Detta säkerställer att 'bästa pris'-perioder inte bara är relativt billiga för dagen, utan faktiskt billiga i absoluta tal. Välj 'Alla' för att visa bästa priser oavsett deras absoluta prisnivå.",
|
"best_price_max_level": "Visa endast bästa prisperioder om de innehåller intervall med prisnivåer ≤ valt värde. Till exempel: att välja 'Billigt' betyder att perioden måste ha minst ett 'MYCKET_BILLIGT' eller 'BILLIGT' intervall. Detta säkerställer att 'bästa pris'-perioder inte bara är relativt billiga för dagen, utan faktiskt billiga i absoluta tal. Välj 'Alla' för att visa bästa priser oavsett deras absoluta prisnivå.",
|
||||||
"enable_min_periods_best": "När aktiverad kommer filtren att gradvis luckras upp om inte tillräckligt många perioder hittas. Detta försöker uppnå det önskade minsta antalet perioder för att säkerställa att du har möjligheter att dra nytta av låga priser även på dagar med ovanliga prismönster.",
|
"enable_min_periods_best": "När aktiverad kommer filtren att gradvis luckras upp om inte tillräckligt många perioder hittas. Detta försöker uppnå det önskade minsta antalet perioder för att säkerställa att du har möjligheter att dra nytta av låga priser även på dagar med ovanliga prismönster.",
|
||||||
"min_periods_best": "Minsta antal bästa prisperioder att sträva efter per dag. Filtren kommer att luckras upp steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Försök uppnå minsta antal perioder' är aktiverad. Standard: 1",
|
"min_periods_best": "Minsta antal bästa prisperioder att sträva efter per dag. Filtren kommer att luckras upp steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Försök uppnå minsta antal perioder' är aktiverad. Standard: 1",
|
||||||
|
|
@ -128,7 +126,6 @@
|
||||||
"peak_price_min_period_length": "Minsta periodlängd",
|
"peak_price_min_period_length": "Minsta periodlängd",
|
||||||
"peak_price_flex": "Flexibilitet: Maximalt % under maximumpris (negativt värde)",
|
"peak_price_flex": "Flexibilitet: Maximalt % under maximumpris (negativt värde)",
|
||||||
"peak_price_min_distance_from_avg": "Minimiavstånd: Krävd % över dagligt genomsnitt",
|
"peak_price_min_distance_from_avg": "Minimiavstånd: Krävd % över dagligt genomsnitt",
|
||||||
"peak_price_min_volatility": "Minimum volatilitetsfilter",
|
|
||||||
"peak_price_min_level": "Prisnivåfilter (Valfritt)",
|
"peak_price_min_level": "Prisnivåfilter (Valfritt)",
|
||||||
"peak_price_max_level_gap_count": "Gaptolerens för nivåfilter",
|
"peak_price_max_level_gap_count": "Gaptolerens för nivåfilter",
|
||||||
"enable_min_periods_peak": "Försök uppnå minsta antal perioder",
|
"enable_min_periods_peak": "Försök uppnå minsta antal perioder",
|
||||||
|
|
@ -136,7 +133,6 @@
|
||||||
"relaxation_step_peak": "Avslappningssteg"
|
"relaxation_step_peak": "Avslappningssteg"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"peak_price_min_volatility": "Visa endast topprisperioder om de har en intern prisvolatilitet (prisspann inom perioden) som uppfyller eller överskrider denna nivå. Standard: 'Låg' (visa oavsett periodens volatilitet) - toppvarningar är relevanta även vid låg intern spridning. Högre volatilitet inom en period kan indikera mer brådskande behov att undvika dessa tider (eftersom priserna varierar kraftigt även inom den korta perioden).",
|
|
||||||
"peak_price_min_level": "Visa endast topprisperioder om de innehåller intervall med prisnivåer ≥ valt värde. Till exempel måste perioden om du väljer 'Dyr' ha minst ett 'DYR' eller 'MYCKET_DYR' intervall. Detta säkerställer att 'toppris'-perioder inte bara är relativt dyra för dagen, utan faktiskt dyra i absoluta termer (inte bara 'lite dyrare än genomsnittet på en billig dag').",
|
"peak_price_min_level": "Visa endast topprisperioder om de innehåller intervall med prisnivåer ≥ valt värde. Till exempel måste perioden om du väljer 'Dyr' ha minst ett 'DYR' eller 'MYCKET_DYR' intervall. Detta säkerställer att 'toppris'-perioder inte bara är relativt dyra för dagen, utan faktiskt dyra i absoluta termer (inte bara 'lite dyrare än genomsnittet på en billig dag').",
|
||||||
"enable_min_periods_peak": "När aktiverad kommer filtren att gradvis luckras upp om inte tillräckligt många perioder hittas. Detta försöker uppnå det önskade minsta antalet perioder för att säkerställa att du blir varnad för dyra perioder även på dagar med ovanliga prismönster.",
|
"enable_min_periods_peak": "När aktiverad kommer filtren att gradvis luckras upp om inte tillräckligt många perioder hittas. Detta försöker uppnå det önskade minsta antalet perioder för att säkerställa att du blir varnad för dyra perioder även på dagar med ovanliga prismönster.",
|
||||||
"min_periods_peak": "Minsta antal topprisperioder att sträva efter per dag. Filtren kommer att luckras upp steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Försök uppnå minsta antal perioder' är aktiverad. Standard: 1",
|
"min_periods_peak": "Minsta antal topprisperioder att sträva efter per dag. Filtren kommer att luckras upp steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Försök uppnå minsta antal perioder' är aktiverad. Standard: 1",
|
||||||
|
|
@ -515,4 +511,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Prisinformation & Betyg"
|
"title": "Tibber Prisinformation & Betyg"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue