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()