feat(periods): add gap tolerance for price level filters with intelligent period splitting

Implemented configurable gap tolerance (0-8 intervals) for best price and peak price
level filters to prevent periods from being split by occasional level deviations.

Key features:
- Gap tolerance only applies to periods ≥ MIN_INTERVALS_FOR_GAP_TOLERANCE (1.5h)
- Short periods (< 1.5h) use strict filtering (zero tolerance)
- Dynamic minimum distance between gaps: max(2, (interval_count // max_gap_count) // 2)
- 25% maximum cap on total gaps to prevent excessive outliers in long periods
- Intelligent period splitting at gap clusters (2+ consecutive non-qualifying intervals)
- Each sub-period independently validated with same gap tolerance rules

Technical implementation:
- Added CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT and CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT constants
- Added MIN_INTERVALS_FOR_GAP_TOLERANCE = 6 (1.5h minimum for gap tolerance)
- Implemented _split_at_gap_clusters() for period recovery
- Implemented _check_short_period_strict() for strict short-period filtering
- Implemented _check_level_filter_with_gaps() with fallback splitting logic
- Extracted _check_sequence_with_gap_tolerance() for reusable core validation
- Enhanced _check_level_filter() to use gap-tolerant validation

Configuration UI:
- Added NumberSelector (0-8, slider mode) for gap count in config flow
- Added translations for all 5 languages (de, en, nb, nl, sv)
- Default: 0 (strict filtering, backwards compatible)

Impact: Users can now configure how many occasional level deviations are acceptable
within qualifying price periods. This reduces period fragmentation while maintaining
meaningful price-based filtering. Long periods are protected by the 25% cap, and
gap clusters trigger intelligent splitting to recover usable sub-periods.
This commit is contained in:
Julian Pawlowski 2025-11-10 04:38:44 +00:00
parent 40a335dabe
commit 817658f230
8 changed files with 346 additions and 18 deletions

View file

@ -42,6 +42,7 @@ from .const import (
BEST_PRICE_MAX_LEVEL_OPTIONS, BEST_PRICE_MAX_LEVEL_OPTIONS,
CONF_BEST_PRICE_FLEX, CONF_BEST_PRICE_FLEX,
CONF_BEST_PRICE_MAX_LEVEL, CONF_BEST_PRICE_MAX_LEVEL,
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_BEST_PRICE_MIN_VOLATILITY,
@ -51,6 +52,7 @@ from .const import (
CONF_MIN_PERIODS_BEST, CONF_MIN_PERIODS_BEST,
CONF_MIN_PERIODS_PEAK, CONF_MIN_PERIODS_PEAK,
CONF_PEAK_PRICE_FLEX, CONF_PEAK_PRICE_FLEX,
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
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,
@ -66,6 +68,7 @@ from .const import (
CONF_VOLATILITY_THRESHOLD_VERY_HIGH, CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
DEFAULT_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX,
DEFAULT_BEST_PRICE_MAX_LEVEL, DEFAULT_BEST_PRICE_MAX_LEVEL,
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_BEST_PRICE_MIN_VOLATILITY,
@ -75,6 +78,7 @@ from .const import (
DEFAULT_MIN_PERIODS_BEST, DEFAULT_MIN_PERIODS_BEST,
DEFAULT_MIN_PERIODS_PEAK, DEFAULT_MIN_PERIODS_PEAK,
DEFAULT_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX,
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
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,
@ -679,6 +683,22 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
translation_key="price_level", translation_key="price_level",
), ),
), ),
vol.Optional(
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
default=int(
self.config_entry.options.get(
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
)
),
): NumberSelector(
NumberSelectorConfig(
min=0,
max=8,
step=1,
mode=NumberSelectorMode.SLIDER,
),
),
vol.Optional( vol.Optional(
CONF_ENABLE_MIN_PERIODS_BEST, CONF_ENABLE_MIN_PERIODS_BEST,
default=self.config_entry.options.get( default=self.config_entry.options.get(
@ -811,6 +831,22 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
translation_key="price_level", translation_key="price_level",
), ),
), ),
vol.Optional(
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
default=int(
self.config_entry.options.get(
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
)
),
): NumberSelector(
NumberSelectorConfig(
min=0,
max=8,
step=1,
mode=NumberSelectorMode.SLIDER,
),
),
vol.Optional( vol.Optional(
CONF_ENABLE_MIN_PERIODS_PEAK, CONF_ENABLE_MIN_PERIODS_PEAK,
default=self.config_entry.options.get( default=self.config_entry.options.get(

View file

@ -35,6 +35,8 @@ CONF_BEST_PRICE_MIN_VOLATILITY = "best_price_min_volatility"
CONF_PEAK_PRICE_MIN_VOLATILITY = "peak_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_PEAK_PRICE_MAX_LEVEL_GAP_COUNT = "peak_price_max_level_gap_count"
CONF_ENABLE_MIN_PERIODS_BEST = "enable_min_periods_best" CONF_ENABLE_MIN_PERIODS_BEST = "enable_min_periods_best"
CONF_MIN_PERIODS_BEST = "min_periods_best" CONF_MIN_PERIODS_BEST = "min_periods_best"
CONF_RELAXATION_STEP_BEST = "relaxation_step_best" CONF_RELAXATION_STEP_BEST = "relaxation_step_best"
@ -64,6 +66,9 @@ DEFAULT_BEST_PRICE_MIN_VOLATILITY = "low" # Show best price at any volatility (
DEFAULT_PEAK_PRICE_MIN_VOLATILITY = "low" # Always show peak price (warning relevant even at low spreads) DEFAULT_PEAK_PRICE_MIN_VOLATILITY = "low" # Always show peak price (warning relevant even at low spreads)
DEFAULT_BEST_PRICE_MAX_LEVEL = "any" # Default: show best price periods regardless of price level DEFAULT_BEST_PRICE_MAX_LEVEL = "any" # Default: show best price periods regardless of price level
DEFAULT_PEAK_PRICE_MIN_LEVEL = "any" # Default: show peak price periods regardless of price level DEFAULT_PEAK_PRICE_MIN_LEVEL = "any" # Default: show peak price periods regardless of price level
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
DEFAULT_ENABLE_MIN_PERIODS_BEST = False # Default: minimum periods feature disabled for best price DEFAULT_ENABLE_MIN_PERIODS_BEST = False # Default: minimum periods feature disabled 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

View file

@ -27,6 +27,7 @@ from .api import (
from .const import ( from .const import (
CONF_BEST_PRICE_FLEX, CONF_BEST_PRICE_FLEX,
CONF_BEST_PRICE_MAX_LEVEL, CONF_BEST_PRICE_MAX_LEVEL,
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_BEST_PRICE_MIN_VOLATILITY,
@ -35,6 +36,7 @@ from .const import (
CONF_MIN_PERIODS_BEST, CONF_MIN_PERIODS_BEST,
CONF_MIN_PERIODS_PEAK, CONF_MIN_PERIODS_PEAK,
CONF_PEAK_PRICE_FLEX, CONF_PEAK_PRICE_FLEX,
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
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,
@ -48,6 +50,7 @@ from .const import (
CONF_VOLATILITY_THRESHOLD_VERY_HIGH, CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
DEFAULT_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX,
DEFAULT_BEST_PRICE_MAX_LEVEL, DEFAULT_BEST_PRICE_MAX_LEVEL,
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_BEST_PRICE_MIN_VOLATILITY,
@ -56,6 +59,7 @@ from .const import (
DEFAULT_MIN_PERIODS_BEST, DEFAULT_MIN_PERIODS_BEST,
DEFAULT_MIN_PERIODS_PEAK, DEFAULT_MIN_PERIODS_PEAK,
DEFAULT_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX,
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
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,
@ -68,6 +72,7 @@ from .const import (
DEFAULT_VOLATILITY_THRESHOLD_MODERATE, DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH, DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
DOMAIN, DOMAIN,
MIN_INTERVALS_FOR_GAP_TOLERANCE,
PRICE_LEVEL_MAPPING, PRICE_LEVEL_MAPPING,
) )
from .period_utils import ( from .period_utils import (
@ -778,6 +783,242 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
override=level_override, override=level_override,
) )
def _split_at_gap_clusters(
self,
today_intervals: list[dict[str, Any]],
level_order: int,
min_period_length: int,
*,
reverse_sort: bool,
) -> list[list[dict[str, Any]]]:
"""
Split intervals into sub-sequences at gap clusters.
A gap cluster is 2+ consecutive intervals that don't meet the level requirement.
This allows recovering usable periods from sequences that would otherwise be rejected.
Args:
today_intervals: List of price intervals for today
level_order: Required level order from PRICE_LEVEL_MAPPING
min_period_length: Minimum number of intervals required for a valid sub-sequence
reverse_sort: True for peak price, False for best price
Returns:
List of sub-sequences, each at least min_period_length long.
"""
sub_sequences = []
current_sequence = []
consecutive_non_qualifying = 0
for interval in today_intervals:
interval_level = PRICE_LEVEL_MAPPING.get(interval.get("level", "NORMAL"), 0)
meets_requirement = interval_level >= level_order if reverse_sort else interval_level <= level_order
if meets_requirement:
# Qualifying interval - add to current sequence
current_sequence.append(interval)
consecutive_non_qualifying = 0
elif consecutive_non_qualifying == 0:
# First non-qualifying interval (single gap) - add to current sequence
current_sequence.append(interval)
consecutive_non_qualifying = 1
else:
# Second+ consecutive non-qualifying interval = gap cluster starts
# Save current sequence if long enough (excluding the first gap we just added)
if len(current_sequence) - 1 >= min_period_length:
sub_sequences.append(current_sequence[:-1]) # Exclude the first gap
current_sequence = []
consecutive_non_qualifying = 0
# Don't forget last sequence
if len(current_sequence) >= min_period_length:
sub_sequences.append(current_sequence)
return sub_sequences
def _check_short_period_strict(
self,
today_intervals: list[dict[str, Any]],
level_order: int,
*,
reverse_sort: bool,
) -> bool:
"""
Strict filtering for short periods (< 1.5h) without gap tolerance.
All intervals must meet the requirement perfectly, or at least one does
and all others are exact matches.
Args:
today_intervals: List of price intervals for today
level_order: Required level order from PRICE_LEVEL_MAPPING
reverse_sort: True for peak price, False for best price
Returns:
True if all intervals meet requirement (with at least one qualifying), False otherwise.
"""
has_qualifying = False
for interval in today_intervals:
interval_level = PRICE_LEVEL_MAPPING.get(interval.get("level", "NORMAL"), 0)
meets_requirement = interval_level >= level_order if reverse_sort else interval_level <= level_order
if meets_requirement:
has_qualifying = True
elif interval_level != level_order:
# Any deviation in short periods disqualifies the entire sequence
return False
return has_qualifying
def _check_level_filter_with_gaps(
self,
today_intervals: list[dict[str, Any]],
level_order: int,
max_gap_count: int,
*,
reverse_sort: bool,
) -> bool:
"""
Check if intervals meet level requirements with gap tolerance and minimum distance.
A "gap" is an interval that deviates by exactly 1 level step.
For best price: CHEAP allows NORMAL as gap (but not EXPENSIVE).
For peak price: EXPENSIVE allows NORMAL as gap (but not CHEAP).
Gap tolerance is only applied to periods with at least MIN_INTERVALS_FOR_GAP_TOLERANCE
intervals (1.5h). Shorter periods use strict filtering (zero tolerance).
Between gaps, there must be a minimum number of "good" intervals to prevent
periods that are mostly interrupted by gaps.
Args:
today_intervals: List of price intervals for today
level_order: Required level order from PRICE_LEVEL_MAPPING
max_gap_count: Maximum total gaps allowed
reverse_sort: True for peak price, False for best price
Returns:
True if any qualifying sequence exists, False otherwise.
"""
if not today_intervals:
return False
interval_count = len(today_intervals)
# Periods shorter than MIN_INTERVALS_FOR_GAP_TOLERANCE (1.5h) use strict filtering
if interval_count < MIN_INTERVALS_FOR_GAP_TOLERANCE:
return self._check_short_period_strict(today_intervals, level_order, reverse_sort=reverse_sort)
# Try normal gap tolerance check first
if self._check_sequence_with_gap_tolerance(
today_intervals, level_order, max_gap_count, reverse_sort=reverse_sort
):
return True
# Normal check failed - try splitting at gap clusters as fallback
# Get minimum period length from config (convert minutes to intervals)
if reverse_sort:
min_period_minutes = self.config_entry.options.get(
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
)
else:
min_period_minutes = self.config_entry.options.get(
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
)
min_period_intervals = min_period_minutes // 15
sub_sequences = self._split_at_gap_clusters(
today_intervals,
level_order,
min_period_intervals,
reverse_sort=reverse_sort,
)
# Check if ANY sub-sequence passes gap tolerance
for sub_seq in sub_sequences:
if self._check_sequence_with_gap_tolerance(sub_seq, level_order, max_gap_count, reverse_sort=reverse_sort):
return True
return False
def _check_sequence_with_gap_tolerance(
self,
intervals: list[dict[str, Any]],
level_order: int,
max_gap_count: int,
*,
reverse_sort: bool,
) -> bool:
"""
Check if a single interval sequence passes gap tolerance requirements.
This is the core gap tolerance logic extracted for reuse with sub-sequences.
Args:
intervals: List of price intervals to check
level_order: Required level order from PRICE_LEVEL_MAPPING
max_gap_count: Maximum total gaps allowed
reverse_sort: True for peak price, False for best price
Returns:
True if sequence meets all gap tolerance requirements, False otherwise.
"""
if not intervals:
return False
interval_count = len(intervals)
# Calculate minimum distance between gaps dynamically.
# Shorter periods require relatively larger distances.
# Longer periods allow gaps closer together.
# Distance is never less than 2 intervals between gaps.
min_distance_between_gaps = max(2, (interval_count // max_gap_count) // 2)
# Limit total gaps to max 25% of period length to prevent too many outliers.
# This ensures periods remain predominantly "good" even when long.
effective_max_gaps = min(max_gap_count, interval_count // 4)
gap_count = 0
consecutive_good_count = 0
has_qualifying_interval = False
for interval in intervals:
interval_level = PRICE_LEVEL_MAPPING.get(interval.get("level", "NORMAL"), 0)
# Check if interval meets the strict requirement
meets_requirement = interval_level >= level_order if reverse_sort else interval_level <= level_order
if meets_requirement:
has_qualifying_interval = True
consecutive_good_count += 1
continue
# Check if this is a tolerable gap (exactly 1 step deviation)
is_tolerable_gap = interval_level == level_order - 1 if reverse_sort else interval_level == level_order + 1
if is_tolerable_gap:
# If we already had gaps, check minimum distance
if gap_count > 0 and consecutive_good_count < min_distance_between_gaps:
# Not enough "good" intervals between gaps
return False
gap_count += 1
if gap_count > effective_max_gaps:
return False
# Reset counter for next gap
consecutive_good_count = 0
else:
# Too far from required level (more than 1 step deviation)
return False
return has_qualifying_interval
def _check_level_filter( def _check_level_filter(
self, self,
price_info: dict[str, Any], price_info: dict[str, Any],
@ -786,7 +1027,10 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
override: str | None = None, override: str | None = None,
) -> bool: ) -> bool:
""" """
Check if today has any intervals that meet the level requirement. Check if today has any intervals that meet the level requirement with gap tolerance.
Gap tolerance allows a configurable number of intervals within a qualifying sequence
to deviate by one level step (e.g., CHEAP allows NORMAL, but not EXPENSIVE).
Args: Args:
price_info: Price information dict with today data price_info: Price information dict with today data
@ -795,7 +1039,8 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
override: Optional override value (e.g., "any" to disable filter) override: Optional override value (e.g., "any" to disable filter)
Returns: Returns:
True if ANY interval meets the level requirement, False otherwise. True if ANY sequence of intervals meets the level requirement
(considering gap tolerance), False otherwise.
""" """
# Use override if provided # Use override if provided
@ -825,10 +1070,23 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
if not today_intervals: if not today_intervals:
return True # If no data, don't filter return True # If no data, don't filter
# Check if ANY interval today meets the level requirement # Get gap tolerance configuration
if reverse_sort:
max_gap_count = self.config_entry.options.get(
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
)
else:
max_gap_count = self.config_entry.options.get(
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
)
# Note: level_config is lowercase from selector, but PRICE_LEVEL_MAPPING uses uppercase # Note: level_config is lowercase from selector, but PRICE_LEVEL_MAPPING uses uppercase
level_order = PRICE_LEVEL_MAPPING.get(level_config.upper(), 0) level_order = PRICE_LEVEL_MAPPING.get(level_config.upper(), 0)
# If gap tolerance is 0, use simple ANY check (backwards compatible)
if max_gap_count == 0:
if reverse_sort: if reverse_sort:
# Peak price: level >= min_level (show if ANY interval is expensive enough) # Peak price: level >= min_level (show if ANY interval is expensive enough)
return any( return any(
@ -837,7 +1095,16 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
) )
# Best price: level <= max_level (show if ANY interval is cheap enough) # Best price: level <= max_level (show if ANY interval is cheap enough)
return any( return any(
PRICE_LEVEL_MAPPING.get(interval.get("level", "NORMAL"), 0) <= level_order for interval in today_intervals PRICE_LEVEL_MAPPING.get(interval.get("level", "NORMAL"), 0) <= level_order
for interval in today_intervals
)
# Use gap-tolerant check
return self._check_level_filter_with_gaps(
today_intervals,
level_order,
max_gap_count,
reverse_sort=reverse_sort,
) )
def _calculate_periods_for_price_info(self, price_info: dict[str, Any]) -> dict[str, Any]: def _calculate_periods_for_price_info(self, price_info: dict[str, Any]) -> dict[str, Any]:

View file

@ -106,6 +106,7 @@
"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_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",
"enable_min_periods_best": "Mindestanzahl Perioden anstreben", "enable_min_periods_best": "Mindestanzahl Perioden anstreben",
"min_periods_best": "Mindestanzahl Perioden", "min_periods_best": "Mindestanzahl Perioden",
"relaxation_step_best": "Lockerungsschritt" "relaxation_step_best": "Lockerungsschritt"
@ -113,6 +114,7 @@
"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_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).",
"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.",
"min_periods_best": "Mindestanzahl an Bestpreis-Perioden, die pro Tag angestrebt werden. Filter werden schrittweise gelockert, um diese Anzahl zu erreichen. Nur aktiv, wenn 'Mindestanzahl Perioden anstreben' aktiviert ist. Standard: 1", "min_periods_best": "Mindestanzahl an Bestpreis-Perioden, die pro Tag angestrebt werden. Filter werden schrittweise gelockert, um diese Anzahl zu erreichen. Nur aktiv, wenn 'Mindestanzahl Perioden anstreben' aktiviert ist. Standard: 1",
"relaxation_step_best": "Prozentsatz des ursprünglichen Flexibilitätsschwellwerts, der pro Lockerungsschritt addiert wird. Beispiel: Bei 15% Flexibilität und 25% Schrittgröße werden 15%, 18,75%, 22,5% usw. versucht. Höhere Werte bedeuten schnellere Lockerung, aber geringere Präzision." "relaxation_step_best": "Prozentsatz des ursprünglichen Flexibilitätsschwellwerts, der pro Lockerungsschritt addiert wird. Beispiel: Bei 15% Flexibilität und 25% Schrittgröße werden 15%, 18,75%, 22,5% usw. versucht. Höhere Werte bedeuten schnellere Lockerung, aber geringere Präzision."
@ -128,6 +130,7 @@
"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_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",
"enable_min_periods_peak": "Mindestanzahl Perioden anstreben", "enable_min_periods_peak": "Mindestanzahl Perioden anstreben",
"min_periods_peak": "Mindestanzahl Perioden", "min_periods_peak": "Mindestanzahl Perioden",
"relaxation_step_peak": "Lockerungsschritt" "relaxation_step_peak": "Lockerungsschritt"
@ -135,6 +138,7 @@
"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_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).",
"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.",
"min_periods_peak": "Mindestanzahl an Spitzenpreis-Perioden, die pro Tag angestrebt werden. Filter werden schrittweise gelockert, um diese Anzahl zu erreichen. Nur aktiv, wenn 'Mindestanzahl Perioden anstreben' aktiviert ist. Standard: 1", "min_periods_peak": "Mindestanzahl an Spitzenpreis-Perioden, die pro Tag angestrebt werden. Filter werden schrittweise gelockert, um diese Anzahl zu erreichen. Nur aktiv, wenn 'Mindestanzahl Perioden anstreben' aktiviert ist. Standard: 1",
"relaxation_step_peak": "Prozentsatz des ursprünglichen Flexibilitätsschwellwerts, der pro Lockerungsschritt addiert wird. Beispiel: Bei -15% Flexibilität und 25% Schrittgröße werden -15%, -18,75%, -22,5% usw. versucht. Höhere Werte bedeuten schnellere Lockerung, aber geringere Präzision." "relaxation_step_peak": "Prozentsatz des ursprünglichen Flexibilitätsschwellwerts, der pro Lockerungsschritt addiert wird. Beispiel: Bei -15% Flexibilität und 25% Schrittgröße werden -15%, -18,75%, -22,5% usw. versucht. Höhere Werte bedeuten schnellere Lockerung, aber geringere Präzision."

View file

@ -106,6 +106,7 @@
"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_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",
"enable_min_periods_best": "Try to Achieve Minimum Period Count", "enable_min_periods_best": "Try to Achieve Minimum Period Count",
"min_periods_best": "Minimum Periods Required", "min_periods_best": "Minimum Periods Required",
"relaxation_step_best": "Filter Relaxation Step Size" "relaxation_step_best": "Filter Relaxation Step Size"
@ -113,6 +114,7 @@
"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_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).",
"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.",
"min_periods_best": "Minimum number of best price periods to aim for per day. Filters will be relaxed step-by-step to try achieving this count. Only active when 'Try to Achieve Minimum Period Count' is enabled. Default: 1", "min_periods_best": "Minimum number of best price periods to aim for per day. Filters will be relaxed step-by-step to try achieving this count. Only active when 'Try to Achieve Minimum Period Count' is enabled. Default: 1",
"relaxation_step_best": "Percentage of the original flexibility threshold to add per relaxation step. For example: with 15% flexibility and 25% step size, filters will try 15%, 18.75%, 22.5%, etc. Higher values mean faster relaxation but less precision." "relaxation_step_best": "Percentage of the original flexibility threshold to add per relaxation step. For example: with 15% flexibility and 25% step size, filters will try 15%, 18.75%, 22.5%, etc. Higher values mean faster relaxation but less precision."
@ -128,6 +130,7 @@
"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_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",
"enable_min_periods_peak": "Try to Achieve Minimum Period Count", "enable_min_periods_peak": "Try to Achieve Minimum Period Count",
"min_periods_peak": "Minimum Periods Required", "min_periods_peak": "Minimum Periods Required",
"relaxation_step_peak": "Filter Relaxation Step Size" "relaxation_step_peak": "Filter Relaxation Step Size"
@ -135,6 +138,7 @@
"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_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).",
"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.",
"min_periods_peak": "Minimum number of peak price periods to aim for per day. Filters will be relaxed step-by-step to try achieving this count. Only active when 'Try to Achieve Minimum Period Count' is enabled. Default: 1", "min_periods_peak": "Minimum number of peak price periods to aim for per day. Filters will be relaxed step-by-step to try achieving this count. Only active when 'Try to Achieve Minimum Period Count' is enabled. Default: 1",
"relaxation_step_peak": "Percentage of the original flexibility threshold to add per relaxation step. For example: with -15% flexibility and 25% step size, filters will try -15%, -18.75%, -22.5%, etc. Higher values mean faster relaxation but less precision." "relaxation_step_peak": "Percentage of the original flexibility threshold to add per relaxation step. For example: with -15% flexibility and 25% step size, filters will try -15%, -18.75%, -22.5%, etc. Higher values mean faster relaxation but less precision."

View file

@ -106,6 +106,7 @@
"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_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",
"enable_min_periods_best": "Prøv å oppnå minimum antall perioder", "enable_min_periods_best": "Prøv å oppnå minimum antall perioder",
"min_periods_best": "Minimum antall perioder", "min_periods_best": "Minimum antall perioder",
"relaxation_step_best": "Avslappingstrinn" "relaxation_step_best": "Avslappingstrinn"
@ -115,7 +116,8 @@
"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",
"relaxation_step_best": "Prosentandel av den opprinnelige fleksibilitetsterskealen som legges til per avslappingstrinn. For eksempel: med 15% fleksibilitet og 25% trinnstørrelse vil filtrene prøve 15%, 18,75%, 22,5%, osv. Høyere verdier betyr raskere avslapping men mindre presisjon." "relaxation_step_best": "Prosentandel av den opprinnelige fleksibilitetsterskealen som legges til per avslappingstrinn. For eksempel: med 15% fleksibilitet og 25% trinnstørrelse vil filtrene prøve 15%, 18,75%, 22,5%, osv. Høyere verdier betyr raskere avslapping men mindre presisjon.",
"best_price_max_level_gap_count": "Maksimalt antall påfølgende intervaller som kan avvike med nøyaktig ett nivåtrinn fra det nødvendige nivået. For eksempel: med 'Billig' filter og gapantall 1, aksepteres sekvensen 'BILLIG, BILLIG, NORMAL, BILLIG' (NORMAL er ett trinn over BILLIG). Dette forhindrer at perioder blir delt opp av tilfeldige nivåavvik. Standard: 0 (streng filtrering, ingen toleranse)."
}, },
"submit": "Neste til steg 5" "submit": "Neste til steg 5"
}, },
@ -128,6 +130,7 @@
"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_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",
"enable_min_periods_peak": "Prøv å oppnå minimum antall perioder", "enable_min_periods_peak": "Prøv å oppnå minimum antall perioder",
"min_periods_peak": "Minimum antall perioder", "min_periods_peak": "Minimum antall perioder",
"relaxation_step_peak": "Avslappingstrinn" "relaxation_step_peak": "Avslappingstrinn"
@ -137,7 +140,8 @@
"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",
"relaxation_step_peak": "Prosentandel av den opprinnelige fleksibilitetsterskealen som legges til per avslappingstrinn. For eksempel: med -15% fleksibilitet og 25% trinnstørrelse vil filtrene prøve -15%, -18,75%, -22,5%, osv. Høyere verdier betyr raskere avslapping men mindre presisjon." "relaxation_step_peak": "Prosentandel av den opprinnelige fleksibilitetsterskealen som legges til per avslappingstrinn. For eksempel: med -15% fleksibilitet og 25% trinnstørrelse vil filtrene prøve -15%, -18,75%, -22,5%, osv. Høyere verdier betyr raskere avslapping men mindre presisjon.",
"peak_price_max_level_gap_count": "Maksimalt antall påfølgende intervaller som kan avvike med nøyaktig ett nivåtrinn fra det nødvendige nivået. For eksempel: med 'Dyr' filter og gapantall 2, aksepteres sekvensen 'DYR, NORMAL, NORMAL, DYR' (NORMAL er ett trinn under DYR). Dette forhindrer at perioder blir delt opp av tilfeldige nivåavvik. Standard: 0 (streng filtrering, ingen toleranse)."
}, },
"submit": "Neste til steg 6" "submit": "Neste til steg 6"
}, },

View file

@ -106,6 +106,7 @@
"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_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",
"enable_min_periods_best": "Probeer minimum aantal periodes te bereiken", "enable_min_periods_best": "Probeer minimum aantal periodes te bereiken",
"min_periods_best": "Minimum aantal periodes", "min_periods_best": "Minimum aantal periodes",
"relaxation_step_best": "Ontspanningsstap" "relaxation_step_best": "Ontspanningsstap"
@ -115,7 +116,8 @@
"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",
"relaxation_step_best": "Percentage van de oorspronkelijke flexibiliteitsdrempel om toe te voegen per ontspanningsstap. Bijvoorbeeld: met 15% flexibiliteit en 25% stapgrootte zullen de filters 15%, 18,75%, 22,5%, enz. proberen. Hogere waarden betekenen snellere ontspanning maar minder precisie." "relaxation_step_best": "Percentage van de oorspronkelijke flexibiliteitsdrempel om toe te voegen per ontspanningsstap. Bijvoorbeeld: met 15% flexibiliteit en 25% stapgrootte zullen de filters 15%, 18,75%, 22,5%, enz. proberen. Hogere waarden betekenen snellere ontspanning maar minder precisie.",
"best_price_max_level_gap_count": "Maximum aantal opeenvolgende intervallen dat precies één niveaustap mag afwijken van het vereiste niveau. Bijvoorbeeld: met 'Goedkoop' filter en gaptelling 1 wordt de reeks 'GOEDKOOP, GOEDKOOP, NORMAAL, GOEDKOOP' geaccepteerd (NORMAAL is één stap boven GOEDKOOP). Dit voorkomt dat periodes worden opgesplitst door incidentele niveauafwijkingen. Standaard: 0 (strikte filtering, geen tolerantie)."
}, },
"submit": "Volgende naar stap 5" "submit": "Volgende naar stap 5"
}, },
@ -128,6 +130,7 @@
"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_volatility": "Minimum volatiliteitsfilter",
"peak_price_min_level": "Prijsniveaufilter (Optioneel)", "peak_price_min_level": "Prijsniveaufilter (Optioneel)",
"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",
"min_periods_peak": "Minimum aantal periodes", "min_periods_peak": "Minimum aantal periodes",
"relaxation_step_peak": "Ontspanningsstap" "relaxation_step_peak": "Ontspanningsstap"
@ -137,7 +140,8 @@
"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",
"relaxation_step_peak": "Percentage van de oorspronkelijke flexibiliteitsdrempel om toe te voegen per ontspanningsstap. Bijvoorbeeld: met -15% flexibiliteit en 25% stapgrootte zullen de filters -15%, -18,75%, -22,5%, enz. proberen. Hogere waarden betekenen snellere ontspanning maar minder precisie." "relaxation_step_peak": "Percentage van de oorspronkelijke flexibiliteitsdrempel om toe te voegen per ontspanningsstap. Bijvoorbeeld: met -15% flexibiliteit en 25% stapgrootte zullen de filters -15%, -18,75%, -22,5%, enz. proberen. Hogere waarden betekenen snellere ontspanning maar minder precisie.",
"peak_price_max_level_gap_count": "Maximum aantal opeenvolgende intervallen dat precies één niveaustap mag afwijken van het vereiste niveau. Bijvoorbeeld: met 'Duur' filter en gaptelling 2 wordt de reeks 'DUUR, NORMAAL, NORMAAL, DUUR' geaccepteerd (NORMAAL is één stap onder DUUR). Dit voorkomt dat periodes worden opgesplitst door incidentele niveauafwijkingen. Standaard: 0 (strikte filtering, geen tolerantie)."
}, },
"submit": "Volgende naar stap 6" "submit": "Volgende naar stap 6"
}, },

View file

@ -106,6 +106,7 @@
"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_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",
"enable_min_periods_best": "Försök uppnå minsta antal perioder", "enable_min_periods_best": "Försök uppnå minsta antal perioder",
"min_periods_best": "Minsta antal perioder", "min_periods_best": "Minsta antal perioder",
"relaxation_step_best": "Avslappningssteg" "relaxation_step_best": "Avslappningssteg"
@ -115,7 +116,8 @@
"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",
"relaxation_step_best": "Procentandel av den ursprungliga flexibilitetströskeln att lägga till per avslappningssteg. Till exempel: med 15% flexibilitet och 25% stegstorlek kommer filtren att prova 15%, 18,75%, 22,5%, osv. Högre värden innebär snabbare avslappning men mindre precision." "relaxation_step_best": "Procentandel av den ursprungliga flexibilitetströskeln att lägga till per avslappningssteg. Till exempel: med 15% flexibilitet och 25% stegstorlek kommer filtren att prova 15%, 18,75%, 22,5%, osv. Högre värden innebär snabbare avslappning men mindre precision.",
"best_price_max_level_gap_count": "Maximalt antal på varandra följande intervaller som får avvika med exakt ett nivåsteg från det erforderliga nivået. Till exempel: med 'Billigt' filter och gapantal 1 accepteras sekvensen 'BILLIGT, BILLIGT, NORMALT, BILLIGT' (NORMALT är ett steg över BILLIGT). Detta förhindrar att perioder delas upp av tillfälliga nivåavvikelser. Standard: 0 (strikt filtrering, ingen tolerans)."
}, },
"submit": "Nästa till steg 5" "submit": "Nästa till steg 5"
}, },
@ -128,6 +130,7 @@
"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_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",
"enable_min_periods_peak": "Försök uppnå minsta antal perioder", "enable_min_periods_peak": "Försök uppnå minsta antal perioder",
"min_periods_peak": "Minsta antal perioder", "min_periods_peak": "Minsta antal perioder",
"relaxation_step_peak": "Avslappningssteg" "relaxation_step_peak": "Avslappningssteg"
@ -137,7 +140,8 @@
"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",
"relaxation_step_peak": "Procentandel av den ursprungliga flexibilitetströskeln att lägga till per avslappningssteg. Till exempel: med -15% flexibilitet och 25% stegstorlek kommer filtren att prova -15%, -18,75%, -22,5%, osv. Högre värden innebär snabbare avslappning men mindre precision." "relaxation_step_peak": "Procentandel av den ursprungliga flexibilitetströskeln att lägga till per avslappningssteg. Till exempel: med -15% flexibilitet och 25% stegstorlek kommer filtren att prova -15%, -18,75%, -22,5%, osv. Högre värden innebär snabbare avslappning men mindre precision.",
"peak_price_max_level_gap_count": "Maximalt antal på varandra följande intervaller som får avvika med exakt ett nivåsteg från det erforderliga nivået. Till exempel: med 'Dyrt' filter och gapantal 2 accepteras sekvensen 'DYRT, NORMALT, NORMALT, DYRT' (NORMALT är ett steg under DYRT). Detta förhindrar att perioder delas upp av tillfälliga nivåavvikelser. Standard: 0 (strikt filtrering, ingen tolerans)."
}, },
"submit": "Nästa till steg 6" "submit": "Nästa till steg 6"
}, },