143 lines
3.8 KiB
Python
143 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Dict
|
|
|
|
|
|
def _open_meteo_cache_key(
|
|
lat: float,
|
|
lon: float,
|
|
*,
|
|
forecast_days: int = 14,
|
|
use_fahrenheit: bool = False,
|
|
) -> str:
|
|
return (
|
|
f"{round(float(lat), 4)}:{round(float(lon), 4)}:"
|
|
f"{forecast_days}:{'f' if use_fahrenheit else 'c'}"
|
|
)
|
|
|
|
|
|
def _multi_model_cache_key(
|
|
collector: Any,
|
|
city: str,
|
|
lat: float,
|
|
lon: float,
|
|
*,
|
|
use_fahrenheit: bool = False,
|
|
) -> str:
|
|
cache_city = str(city or "").strip().lower()
|
|
return (
|
|
f"{round(float(lat), 4)}:{round(float(lon), 4)}:{cache_city}:"
|
|
f"{'f' if use_fahrenheit else 'c'}:{collector.multi_model_cache_version}"
|
|
)
|
|
|
|
|
|
def _read_open_meteo_bundle_from_cache(
|
|
collector: Any,
|
|
*,
|
|
city: str,
|
|
lat: float,
|
|
lon: float,
|
|
use_fahrenheit: bool,
|
|
include_multi_model: bool,
|
|
) -> Dict[str, Any]:
|
|
collector._maybe_reload_open_meteo_disk_cache()
|
|
results: Dict[str, Any] = {}
|
|
om_key = _open_meteo_cache_key(lat, lon, use_fahrenheit=use_fahrenheit)
|
|
with collector._open_meteo_cache_lock:
|
|
om_cached = collector._open_meteo_cache.get(om_key)
|
|
|
|
if not om_cached or not isinstance(om_cached.get("data"), dict):
|
|
return results
|
|
|
|
results["open-meteo"] = dict(om_cached["data"])
|
|
if include_multi_model:
|
|
mm_key = _multi_model_cache_key(
|
|
collector,
|
|
city,
|
|
lat,
|
|
lon,
|
|
use_fahrenheit=use_fahrenheit,
|
|
)
|
|
with collector._multi_model_cache_lock:
|
|
mm_cached = collector._multi_model_cache.get(mm_key)
|
|
if mm_cached and isinstance(mm_cached.get("data"), dict):
|
|
results["multi_model"] = dict(mm_cached["data"])
|
|
return results
|
|
|
|
|
|
def fetch_open_meteo_forecast_bundle(
|
|
collector: Any,
|
|
*,
|
|
city: str,
|
|
lat: float,
|
|
lon: float,
|
|
use_fahrenheit: bool,
|
|
include_multi_model: bool = True,
|
|
cache_only: bool = False,
|
|
) -> Dict[str, Any]:
|
|
"""Fetch non-high-frequency Open-Meteo forecast payloads for a city.
|
|
|
|
Observation-only refreshes can set *cache_only* so high-frequency callers
|
|
reuse existing model data without making outbound Open-Meteo requests.
|
|
"""
|
|
if lat is None or lon is None:
|
|
return {}
|
|
|
|
if cache_only:
|
|
return _read_open_meteo_bundle_from_cache(
|
|
collector,
|
|
city=city,
|
|
lat=lat,
|
|
lon=lon,
|
|
use_fahrenheit=use_fahrenheit,
|
|
include_multi_model=include_multi_model,
|
|
)
|
|
|
|
results: Dict[str, Any] = {}
|
|
# Populate the richer multi-model cache before the regular forecast
|
|
# endpoint can trip the shared Open-Meteo cooldown.
|
|
if include_multi_model:
|
|
multi_model_data = collector.fetch_multi_model(
|
|
lat,
|
|
lon,
|
|
city=city,
|
|
use_fahrenheit=use_fahrenheit,
|
|
)
|
|
if multi_model_data:
|
|
results["multi_model"] = multi_model_data
|
|
|
|
open_meteo = collector.fetch_from_open_meteo(
|
|
lat,
|
|
lon,
|
|
use_fahrenheit=use_fahrenheit,
|
|
)
|
|
if open_meteo:
|
|
results["open-meteo"] = open_meteo
|
|
|
|
return results
|
|
|
|
|
|
def ensure_multi_model_hourly_payload(
|
|
collector: Any,
|
|
current: Any,
|
|
*,
|
|
city: str,
|
|
lat: float,
|
|
lon: float,
|
|
use_fahrenheit: bool,
|
|
) -> Dict[str, Any]:
|
|
"""Return a multi-model payload with hourly curves when available."""
|
|
current_payload = current if isinstance(current, dict) else {}
|
|
if current_payload.get("hourly_times"):
|
|
return current_payload
|
|
|
|
hourly_payload = collector.fetch_multi_model(
|
|
lat,
|
|
lon,
|
|
city=city,
|
|
use_fahrenheit=use_fahrenheit,
|
|
)
|
|
if hourly_payload and hourly_payload.get("hourly_times"):
|
|
return {**current_payload, **hourly_payload}
|
|
return current_payload
|