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.
116 lines
3.5 KiB
Python
116 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
from dataclasses import dataclass
|
|
from os import environ
|
|
from sys import stderr
|
|
from time import sleep
|
|
|
|
from gibil.classes.oracle.builder import EnergyOracleBuilder
|
|
from gibil.classes.env_loader import EnvLoader
|
|
from gibil.classes.oracle.store import OracleStore
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OracleDaemonConfig:
|
|
poll_seconds: float
|
|
evaluate_forecasts: bool
|
|
evaluation_actual_window_minutes: float
|
|
evaluation_lookback_hours: float
|
|
evaluation_limit: int
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "OracleDaemonConfig":
|
|
return cls(
|
|
poll_seconds=float(environ.get("ASTRAPE_ORACLE_POLL_SECONDS", "300")),
|
|
evaluate_forecasts=environ.get(
|
|
"ASTRAPE_ORACLE_EVALUATE_FORECASTS", "1"
|
|
).lower()
|
|
not in {"0", "false", "no"},
|
|
evaluation_actual_window_minutes=float(
|
|
environ.get("ASTRAPE_ORACLE_EVALUATION_WINDOW_MINUTES", "5")
|
|
),
|
|
evaluation_lookback_hours=float(
|
|
environ.get("ASTRAPE_ORACLE_EVALUATION_LOOKBACK_HOURS", "168")
|
|
),
|
|
evaluation_limit=int(environ.get("ASTRAPE_ORACLE_EVALUATION_LIMIT", "1000")),
|
|
)
|
|
|
|
|
|
class OracleDaemon:
|
|
"""Periodically stores oracle projection curves for evaluation."""
|
|
|
|
def __init__(
|
|
self,
|
|
config: OracleDaemonConfig,
|
|
builder: EnergyOracleBuilder,
|
|
store: OracleStore,
|
|
) -> None:
|
|
self.config = config
|
|
self.builder = builder
|
|
self.store = store
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "OracleDaemon":
|
|
return cls(
|
|
config=OracleDaemonConfig.from_env(),
|
|
builder=EnergyOracleBuilder.from_env(),
|
|
store=OracleStore.from_env(),
|
|
)
|
|
|
|
def run_once(self) -> int:
|
|
solar_run, load_run, net_run = self.builder.build()
|
|
saved_count = self.store.save_runs(solar_run, load_run, net_run)
|
|
if self.config.evaluate_forecasts:
|
|
from datetime import timedelta
|
|
|
|
evaluated_count = self.store.evaluate_due_forecasts(
|
|
actual_window=timedelta(
|
|
minutes=self.config.evaluation_actual_window_minutes
|
|
),
|
|
lookback=timedelta(hours=self.config.evaluation_lookback_hours),
|
|
limit=self.config.evaluation_limit,
|
|
)
|
|
return saved_count + evaluated_count
|
|
return saved_count
|
|
|
|
def run_forever(self) -> None:
|
|
self.store.initialize()
|
|
while True:
|
|
try:
|
|
saved_count = self.run_once()
|
|
print(f"stored_oracle_records={saved_count}", flush=True)
|
|
except Exception as error:
|
|
print(f"oracle_poll_error={error}", file=stderr, flush=True)
|
|
sleep(self.config.poll_seconds)
|
|
|
|
|
|
def main() -> None:
|
|
try:
|
|
EnvLoader().load()
|
|
args = parse_args()
|
|
daemon = OracleDaemon.from_env()
|
|
if args.once:
|
|
print(f"stored_oracle_records={daemon.run_once()}", flush=True)
|
|
return
|
|
daemon.run_forever()
|
|
except Exception as error:
|
|
print(f"oracle_daemon_startup_error={error}", file=stderr)
|
|
raise SystemExit(1) from error
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Store Astrape oracle projection curves."
|
|
)
|
|
parser.add_argument(
|
|
"--once",
|
|
action="store_true",
|
|
help="Save one set of oracle curves and exit.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|