c8e3016fd6
- Implement `sigen_daemon.py` to poll Sigenergy plant metrics and store snapshots. - Create `web_daemon.py` for serving a web interface with various endpoints. - Add debug scripts: - `debug_duplicates.py` to find duplicate target times in forecast data. - `debug_energy_forecast.py` to print baseline energy forecast curves. - `debug_oracle_evaluations.py` to run the oracle evaluator. - `debug_sigen.py` to inspect stored Sigenergy plant snapshots. - `debug_weather.py` to trace resolved truth data. - `modbus_test.py` for exploring Sigenergy plants or inverters over Modbus TCP. - Introduce `oracle_evaluator.py` for evaluating stored oracle predictions against actuals. - Add TCN training scripts in `tcn` directory for training usage sequence models.
160 lines
4.0 KiB
Python
160 lines
4.0 KiB
Python
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]
|