"""Value getter mapping for Tibber Prices sensors.""" from __future__ import annotations from typing import TYPE_CHECKING from custom_components.tibber_prices.utils.average import ( calculate_current_leading_avg, calculate_current_leading_max, calculate_current_leading_min, calculate_current_trailing_avg, calculate_current_trailing_max, calculate_current_trailing_min, ) if TYPE_CHECKING: from collections.abc import Callable from custom_components.tibber_prices.sensor.calculators.daily_stat import DailyStatCalculator from custom_components.tibber_prices.sensor.calculators.interval import IntervalCalculator from custom_components.tibber_prices.sensor.calculators.metadata import MetadataCalculator from custom_components.tibber_prices.sensor.calculators.rolling_hour import RollingHourCalculator from custom_components.tibber_prices.sensor.calculators.timing import TimingCalculator from custom_components.tibber_prices.sensor.calculators.trend import TrendCalculator from custom_components.tibber_prices.sensor.calculators.volatility import VolatilityCalculator from custom_components.tibber_prices.sensor.calculators.window_24h import Window24hCalculator def get_value_getter_mapping( # noqa: PLR0913 - needs all calculators as parameters interval_calculator: IntervalCalculator, rolling_hour_calculator: RollingHourCalculator, daily_stat_calculator: DailyStatCalculator, window_24h_calculator: Window24hCalculator, trend_calculator: TrendCalculator, timing_calculator: TimingCalculator, volatility_calculator: VolatilityCalculator, metadata_calculator: MetadataCalculator, get_next_avg_n_hours_value: Callable[[int], float | None], get_price_forecast_value: Callable[[], str | None], get_data_timestamp: Callable[[], str | None], get_chart_data_export_value: Callable[[], str | None], ) -> dict[str, Callable]: """ Build mapping from entity key to value getter callable. This function centralizes the handler mapping logic, making it easier to maintain and understand the relationship between sensor types and their calculation methods. Args: interval_calculator: Calculator for current/next/previous interval values rolling_hour_calculator: Calculator for 5-interval rolling windows daily_stat_calculator: Calculator for daily min/max/avg statistics window_24h_calculator: Calculator for trailing/leading 24h windows trend_calculator: Calculator for price trend analysis timing_calculator: Calculator for best/peak price period timing volatility_calculator: Calculator for price volatility analysis metadata_calculator: Calculator for home/metering metadata get_next_avg_n_hours_value: Method for next N-hour average forecasts get_price_forecast_value: Method for price forecast sensor get_data_timestamp: Method for data timestamp sensor get_chart_data_export_value: Method for chart data export sensor Returns: Dictionary mapping entity keys to their value getter callables. """ return { # ================================================================ # INTERVAL-BASED SENSORS - via IntervalCalculator # ================================================================ # Price level sensors "current_interval_price_level": interval_calculator.get_price_level_value, "next_interval_price_level": lambda: interval_calculator.get_interval_value( interval_offset=1, value_type="level" ), "previous_interval_price_level": lambda: interval_calculator.get_interval_value( interval_offset=-1, value_type="level" ), # Price sensors (in cents) "current_interval_price": lambda: interval_calculator.get_interval_value( interval_offset=0, value_type="price", in_euro=False ), "current_interval_price_major": lambda: interval_calculator.get_interval_value( interval_offset=0, value_type="price", in_euro=True ), "next_interval_price": lambda: interval_calculator.get_interval_value( interval_offset=1, value_type="price", in_euro=False ), "previous_interval_price": lambda: interval_calculator.get_interval_value( interval_offset=-1, value_type="price", in_euro=False ), # Rating sensors "current_interval_price_rating": lambda: interval_calculator.get_rating_value(rating_type="current"), "next_interval_price_rating": lambda: interval_calculator.get_interval_value( interval_offset=1, value_type="rating" ), "previous_interval_price_rating": lambda: interval_calculator.get_interval_value( interval_offset=-1, value_type="rating" ), # ================================================================ # ROLLING HOUR SENSORS (5-interval windows) - via RollingHourCalculator # ================================================================ "current_hour_price_level": lambda: rolling_hour_calculator.get_rolling_hour_value( hour_offset=0, value_type="level" ), "next_hour_price_level": lambda: rolling_hour_calculator.get_rolling_hour_value( hour_offset=1, value_type="level" ), # Rolling hour average (5 intervals: 2 before + current + 2 after) "current_hour_average_price": lambda: rolling_hour_calculator.get_rolling_hour_value( hour_offset=0, value_type="price" ), "next_hour_average_price": lambda: rolling_hour_calculator.get_rolling_hour_value( hour_offset=1, value_type="price" ), "current_hour_price_rating": lambda: rolling_hour_calculator.get_rolling_hour_value( hour_offset=0, value_type="rating" ), "next_hour_price_rating": lambda: rolling_hour_calculator.get_rolling_hour_value( hour_offset=1, value_type="rating" ), # ================================================================ # DAILY STATISTICS SENSORS - via DailyStatCalculator # ================================================================ "lowest_price_today": lambda: daily_stat_calculator.get_daily_stat_value(day="today", stat_func=min), "highest_price_today": lambda: daily_stat_calculator.get_daily_stat_value(day="today", stat_func=max), "average_price_today": lambda: daily_stat_calculator.get_daily_stat_value( day="today", stat_func=lambda prices: sum(prices) / len(prices), ), # Tomorrow statistics sensors "lowest_price_tomorrow": lambda: daily_stat_calculator.get_daily_stat_value(day="tomorrow", stat_func=min), "highest_price_tomorrow": lambda: daily_stat_calculator.get_daily_stat_value(day="tomorrow", stat_func=max), "average_price_tomorrow": lambda: daily_stat_calculator.get_daily_stat_value( day="tomorrow", stat_func=lambda prices: sum(prices) / len(prices), ), # Daily aggregated level sensors "yesterday_price_level": lambda: daily_stat_calculator.get_daily_aggregated_value( day="yesterday", value_type="level" ), "today_price_level": lambda: daily_stat_calculator.get_daily_aggregated_value(day="today", value_type="level"), "tomorrow_price_level": lambda: daily_stat_calculator.get_daily_aggregated_value( day="tomorrow", value_type="level" ), # Daily aggregated rating sensors "yesterday_price_rating": lambda: daily_stat_calculator.get_daily_aggregated_value( day="yesterday", value_type="rating" ), "today_price_rating": lambda: daily_stat_calculator.get_daily_aggregated_value( day="today", value_type="rating" ), "tomorrow_price_rating": lambda: daily_stat_calculator.get_daily_aggregated_value( day="tomorrow", value_type="rating" ), # ================================================================ # 24H WINDOW SENSORS (trailing/leading from current) - via Window24hCalculator # ================================================================ # Trailing and leading average sensors "trailing_price_average": lambda: window_24h_calculator.get_24h_window_value( stat_func=calculate_current_trailing_avg, ), "leading_price_average": lambda: window_24h_calculator.get_24h_window_value( stat_func=calculate_current_leading_avg, ), # Trailing and leading min/max sensors "trailing_price_min": lambda: window_24h_calculator.get_24h_window_value( stat_func=calculate_current_trailing_min, ), "trailing_price_max": lambda: window_24h_calculator.get_24h_window_value( stat_func=calculate_current_trailing_max, ), "leading_price_min": lambda: window_24h_calculator.get_24h_window_value( stat_func=calculate_current_leading_min, ), "leading_price_max": lambda: window_24h_calculator.get_24h_window_value( stat_func=calculate_current_leading_max, ), # ================================================================ # FUTURE FORECAST SENSORS # ================================================================ # Future average sensors (next N hours from next interval) "next_avg_1h": lambda: get_next_avg_n_hours_value(hours=1), "next_avg_2h": lambda: get_next_avg_n_hours_value(hours=2), "next_avg_3h": lambda: get_next_avg_n_hours_value(hours=3), "next_avg_4h": lambda: get_next_avg_n_hours_value(hours=4), "next_avg_5h": lambda: get_next_avg_n_hours_value(hours=5), "next_avg_6h": lambda: get_next_avg_n_hours_value(hours=6), "next_avg_8h": lambda: get_next_avg_n_hours_value(hours=8), "next_avg_12h": lambda: get_next_avg_n_hours_value(hours=12), # Current and next trend change sensors "current_price_trend": trend_calculator.get_current_trend_value, "next_price_trend_change": trend_calculator.get_next_trend_change_value, # Price trend sensors "price_trend_1h": lambda: trend_calculator.get_price_trend_value(hours=1), "price_trend_2h": lambda: trend_calculator.get_price_trend_value(hours=2), "price_trend_3h": lambda: trend_calculator.get_price_trend_value(hours=3), "price_trend_4h": lambda: trend_calculator.get_price_trend_value(hours=4), "price_trend_5h": lambda: trend_calculator.get_price_trend_value(hours=5), "price_trend_6h": lambda: trend_calculator.get_price_trend_value(hours=6), "price_trend_8h": lambda: trend_calculator.get_price_trend_value(hours=8), "price_trend_12h": lambda: trend_calculator.get_price_trend_value(hours=12), # Diagnostic sensors "data_timestamp": get_data_timestamp, # Price forecast sensor "price_forecast": get_price_forecast_value, # Home metadata sensors (via MetadataCalculator) "home_type": lambda: metadata_calculator.get_home_metadata_value("type"), "home_size": lambda: metadata_calculator.get_home_metadata_value("size"), "main_fuse_size": lambda: metadata_calculator.get_home_metadata_value("mainFuseSize"), "number_of_residents": lambda: metadata_calculator.get_home_metadata_value("numberOfResidents"), "primary_heating_source": lambda: metadata_calculator.get_home_metadata_value("primaryHeatingSource"), # Metering point sensors (via MetadataCalculator) "grid_company": lambda: metadata_calculator.get_metering_point_value("gridCompany"), "grid_area_code": lambda: metadata_calculator.get_metering_point_value("gridAreaCode"), "price_area_code": lambda: metadata_calculator.get_metering_point_value("priceAreaCode"), "consumption_ean": lambda: metadata_calculator.get_metering_point_value("consumptionEan"), "production_ean": lambda: metadata_calculator.get_metering_point_value("productionEan"), "energy_tax_type": lambda: metadata_calculator.get_metering_point_value("energyTaxType"), "vat_type": lambda: metadata_calculator.get_metering_point_value("vatType"), "estimated_annual_consumption": lambda: metadata_calculator.get_metering_point_value( "estimatedAnnualConsumption" ), # Subscription sensors (via MetadataCalculator) "subscription_status": lambda: metadata_calculator.get_subscription_value("status"), # Volatility sensors (via VolatilityCalculator) "today_volatility": lambda: volatility_calculator.get_volatility_value(volatility_type="today"), "tomorrow_volatility": lambda: volatility_calculator.get_volatility_value(volatility_type="tomorrow"), "next_24h_volatility": lambda: volatility_calculator.get_volatility_value(volatility_type="next_24h"), "today_tomorrow_volatility": lambda: volatility_calculator.get_volatility_value( volatility_type="today_tomorrow" ), # ================================================================ # BEST/PEAK PRICE TIMING SENSORS - via TimingCalculator # ================================================================ # Best Price timing sensors "best_price_end_time": lambda: timing_calculator.get_period_timing_value( period_type="best_price", value_type="end_time" ), "best_price_period_duration": lambda: timing_calculator.get_period_timing_value( period_type="best_price", value_type="period_duration" ), "best_price_remaining_minutes": lambda: timing_calculator.get_period_timing_value( period_type="best_price", value_type="remaining_minutes" ), "best_price_progress": lambda: timing_calculator.get_period_timing_value( period_type="best_price", value_type="progress" ), "best_price_next_start_time": lambda: timing_calculator.get_period_timing_value( period_type="best_price", value_type="next_start_time" ), "best_price_next_in_minutes": lambda: timing_calculator.get_period_timing_value( period_type="best_price", value_type="next_in_minutes" ), # Peak Price timing sensors "peak_price_end_time": lambda: timing_calculator.get_period_timing_value( period_type="peak_price", value_type="end_time" ), "peak_price_period_duration": lambda: timing_calculator.get_period_timing_value( period_type="peak_price", value_type="period_duration" ), "peak_price_remaining_minutes": lambda: timing_calculator.get_period_timing_value( period_type="peak_price", value_type="remaining_minutes" ), "peak_price_progress": lambda: timing_calculator.get_period_timing_value( period_type="peak_price", value_type="progress" ), "peak_price_next_start_time": lambda: timing_calculator.get_period_timing_value( period_type="peak_price", value_type="next_start_time" ), "peak_price_next_in_minutes": lambda: timing_calculator.get_period_timing_value( period_type="peak_price", value_type="next_in_minutes" ), # Chart data export sensor "chart_data_export": get_chart_data_export_value, }