Files
astrape-dashboard/astrape_dashboard/classes/sample_data.py
T
rpotter6298 665c941802 Initial commit: astrape-dashboard split out of the combined Astrape tree
The web frontend: the UI itself, the oracle, weather, station and
quality renderers, and the serving daemon.

The weather and station display modules were each split in two. The
dataclasses went to astrape-oracle with the stores that return them;
only the rendering stayed here, so the dependency runs dashboard ->
oracle -> ems -> gibil with no cycle.

Extracted from a single repository where the forecasting engine, the
database plumbing, the hardware client, the ingest daemons and the web
UI all imported each other freely. Verified that every module imports
with gibil resolved from its own installed checkout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 14:19:09 +02:00

75 lines
2.9 KiB
Python

from __future__ import annotations
from datetime import datetime, timedelta, timezone
from math import pi, sin
from gibil.classes.models import WeatherForecastPoint, WeatherResolvedTruth
from astrape_oracle.classes.datasets 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)