from __future__ import annotations from datetime import datetime, timedelta, timezone from math import pi, sin from gibil.classes.models import WeatherForecastPoint, WeatherResolvedTruth from gibil.classes.weather_display import WeatherDisplayDataset class WeatherSampleData: """Builds temporary display-only weather data for UI tuning.""" def build(self, hours: int = 72) -> WeatherDisplayDataset: now = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) start = now - timedelta(hours=hours) horizons = [2, 4, 8, 12, 24] forecast_points: list[WeatherForecastPoint] = [] resolved_truth: list[WeatherResolvedTruth] = [] for offset in range(hours + 1): target_at = start + timedelta(hours=offset) truth_temperature = self._temperature(target_at, offset) truth_solar = self._solar(target_at, offset) resolved_truth.append( WeatherResolvedTruth( resolved_at=target_at, source="sample", temperature_c=truth_temperature, shortwave_radiation_w_m2=truth_solar, ) ) for horizon in horizons: forecast_points.append( WeatherForecastPoint( issued_at=target_at - timedelta(hours=horizon), target_at=target_at, horizon_hours=horizon, temperature_c=truth_temperature + self._temperature_error(offset, horizon), shortwave_radiation_w_m2=max( 0, truth_solar + self._solar_error(offset, horizon), ), cloud_cover_pct=max( 0, min(100, 45 + 30 * sin((offset + horizon) / 9)), ), source="sample", ) ) return WeatherDisplayDataset( forecast_points=forecast_points, resolved_truth=resolved_truth, ) def _temperature(self, target_at: datetime, offset: int) -> float: daily = sin(((target_at.hour - 7) / 24) * 2 * pi) slow = sin(offset / 18) return round(6.5 + daily * 5.5 + slow * 1.3, 1) def _solar(self, target_at: datetime, offset: int) -> float: daylight = max(0, sin(((target_at.hour - 5) / 15) * pi)) cloud_effect = 0.75 + 0.25 * sin(offset / 7) return round(780 * daylight * cloud_effect, 1) def _temperature_error(self, offset: int, horizon: int) -> float: return round((horizon / 8) * sin((offset + horizon) / 5), 1) def _solar_error(self, offset: int, horizon: int) -> float: return round((horizon * 9) * sin((offset + horizon) / 4), 1)