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" @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