from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime, timezone from enum import Enum from typing import Any class ObservationQuality(str, Enum): OK = "ok" STALE = "stale" ESTIMATED = "estimated" MISSING = "missing" ERROR = "error" class PowerStage(str, Enum): ALWAYS = "always" SURPLUS = "surplus" CHEAP_GRID = "cheap_grid" STANDARD = "standard" CONSERVE = "conserve" class ForecastKind(str, Enum): SOLAR = "solar" LOAD = "load" @dataclass(frozen=True) class Observation: source: str metric: str value: int | float | str | bool | None unit: str = "none" quality: ObservationQuality = ObservationQuality.OK observed_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) received_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class Snapshot: created_at: datetime solar_power_w: float | None = None home_power_w: float | None = None battery_soc_pct: float | None = None grid_import_w: float | None = None grid_export_w: float | None = None cheap_window_active: bool = False input_quality: dict[str, ObservationQuality] = field(default_factory=dict) @dataclass(frozen=True) class Decision: created_at: datetime stage: PowerStage reasons: list[str] confidence: float @dataclass(frozen=True) class WeatherForecastPoint: issued_at: datetime target_at: datetime horizon_hours: int temperature_c: float | None shortwave_radiation_w_m2: float | None cloud_cover_pct: float | None = None source: str = "open_meteo" @dataclass(frozen=True) class WeatherForecastRun: issued_at: datetime source: str latitude: float longitude: float points: list[WeatherForecastPoint] @dataclass(frozen=True) class WeatherResolvedTruth: resolved_at: datetime temperature_c: float | None shortwave_radiation_w_m2: float | None source: str cloud_cover_pct: float | None = None @dataclass(frozen=True) class SigenPlantSnapshot: observed_at: datetime received_at: datetime source: str = "sigen_modbus" plant_epoch_seconds: int | None = None plant_ems_work_mode: int | None = None plant_running_state: int | None = None grid_sensor_status: int | None = None solar_power_w: float | None = None battery_soc_pct: float | None = None battery_soh_pct: float | None = None battery_power_w: float | None = None grid_power_w: float | None = None grid_import_w: float | None = None grid_export_w: float | None = None load_power_w: float | None = None plant_active_power_w: float | None = None accumulated_pv_energy_kwh: float | None = None daily_consumed_energy_kwh: float | None = None accumulated_consumed_energy_kwh: float | None = None raw_values: dict[str, int | float | str | bool | None] = field(default_factory=dict) @dataclass(frozen=True) class PowerForecastPoint: target_at: datetime horizon_minutes: int expected_power_w: float p10_power_w: float p50_power_w: float p90_power_w: float confidence: float source: str model_version: str metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PowerForecastRun: issued_at: datetime kind: ForecastKind source: str model_version: str points: list[PowerForecastPoint] @dataclass(frozen=True) class NetPowerForecastPoint: target_at: datetime horizon_minutes: int expected_net_power_w: float safe_net_power_w: float p10_net_power_w: float p50_net_power_w: float p90_net_power_w: float solar_p50_power_w: float load_p50_power_w: float solar_p10_power_w: float solar_p90_power_w: float load_p10_power_w: float load_p90_power_w: float @dataclass(frozen=True) class NetPowerForecastRun: issued_at: datetime source: str points: list[NetPowerForecastPoint]