added api
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
WEATHER_CODES: dict[int, str] = {
|
||||
0: "ясно",
|
||||
1: "преимущественно ясно",
|
||||
2: "переменная облачность",
|
||||
3: "пасмурно",
|
||||
45: "туман",
|
||||
48: "изморозь",
|
||||
51: "морось",
|
||||
53: "морось",
|
||||
55: "морось",
|
||||
61: "дождь",
|
||||
63: "дождь",
|
||||
65: "сильный дождь",
|
||||
71: "снег",
|
||||
73: "снег",
|
||||
75: "сильный снег",
|
||||
80: "ливень",
|
||||
81: "ливень",
|
||||
82: "сильный ливень",
|
||||
95: "гроза",
|
||||
96: "гроза с градом",
|
||||
99: "гроза с градом",
|
||||
}
|
||||
|
||||
_cache: dict[str, Any] = {"data": None, "expires_at": 0.0}
|
||||
|
||||
|
||||
class OpenMeteoClient:
|
||||
def __init__(self) -> None:
|
||||
settings = get_settings()
|
||||
self.base_url = settings.openmeteo_base_url.rstrip("/")
|
||||
self.lat = settings.weather_lat
|
||||
self.lon = settings.weather_lon
|
||||
self.location_name = settings.weather_location_name
|
||||
self.cache_ttl = settings.weather_cache_sec
|
||||
|
||||
def _fetch_raw(self) -> dict[str, Any]:
|
||||
now = time.time()
|
||||
if _cache["data"] and now < _cache["expires_at"]:
|
||||
return _cache["data"]
|
||||
|
||||
params = {
|
||||
"latitude": self.lat,
|
||||
"longitude": self.lon,
|
||||
"current": (
|
||||
"temperature_2m,apparent_temperature,relative_humidity_2m,"
|
||||
"precipitation,weather_code,wind_speed_10m"
|
||||
),
|
||||
"hourly": "temperature_2m,precipitation_probability,precipitation,weather_code",
|
||||
"timezone": "auto",
|
||||
"forecast_days": 2,
|
||||
}
|
||||
with httpx.Client(timeout=15.0) as client:
|
||||
response = client.get(f"{self.base_url}/v1/forecast", params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
_cache["data"] = data
|
||||
_cache["expires_at"] = now + self.cache_ttl
|
||||
return data
|
||||
|
||||
def fetch_current_and_hourly(self, hours_ahead: int = 12) -> dict[str, Any]:
|
||||
try:
|
||||
raw = self._fetch_raw()
|
||||
except Exception as exc:
|
||||
return {"ok": False, "error": str(exc), "location": self.location_name}
|
||||
|
||||
current = raw.get("current") or {}
|
||||
hourly = raw.get("hourly") or {}
|
||||
times = hourly.get("time") or []
|
||||
limit = min(hours_ahead, len(times))
|
||||
hourly_slice = []
|
||||
for i in range(limit):
|
||||
hourly_slice.append({
|
||||
"time": times[i],
|
||||
"temperature_c": hourly.get("temperature_2m", [None])[i],
|
||||
"precipitation_mm": hourly.get("precipitation", [None])[i],
|
||||
"precipitation_probability": hourly.get("precipitation_probability", [None])[i],
|
||||
"weather_code": hourly.get("weather_code", [None])[i],
|
||||
})
|
||||
|
||||
code = current.get("weather_code")
|
||||
return {
|
||||
"ok": True,
|
||||
"location": self.location_name,
|
||||
"current": {
|
||||
"time": current.get("time"),
|
||||
"temperature_c": current.get("temperature_2m"),
|
||||
"apparent_temperature_c": current.get("apparent_temperature"),
|
||||
"humidity_pct": current.get("relative_humidity_2m"),
|
||||
"precipitation_mm": current.get("precipitation"),
|
||||
"wind_speed_kmh": current.get("wind_speed_10m"),
|
||||
"weather_code": code,
|
||||
"conditions": WEATHER_CODES.get(code, "неизвестно") if code is not None else "неизвестно",
|
||||
},
|
||||
"hourly": hourly_slice,
|
||||
}
|
||||
|
||||
def rain_summary(self, hours_ahead: int = 12) -> str:
|
||||
data = self.fetch_current_and_hourly(hours_ahead=hours_ahead)
|
||||
if not data.get("ok"):
|
||||
return f"Погода недоступна: {data.get('error', 'ошибка')}"
|
||||
|
||||
rainy_hours = []
|
||||
for hour in data.get("hourly") or []:
|
||||
prob = hour.get("precipitation_probability")
|
||||
precip = hour.get("precipitation_mm") or 0
|
||||
if (prob is not None and prob >= 40) or precip > 0:
|
||||
time_str = (hour.get("time") or "")[11:16]
|
||||
rainy_hours.append(f"{time_str} ({prob}% вероятность, {precip} мм)")
|
||||
|
||||
if rainy_hours:
|
||||
return "Ожидаются осадки: " + ", ".join(rainy_hours[:6])
|
||||
return "Существенных осадков в ближайшие часы не ожидается."
|
||||
|
||||
|
||||
def format_weather_snapshot(data: dict[str, Any] | None = None) -> str:
|
||||
client = OpenMeteoClient()
|
||||
snapshot = data if data is not None else client.fetch_current_and_hourly(hours_ahead=6)
|
||||
|
||||
lines = ["[Погода]"]
|
||||
if not snapshot.get("ok"):
|
||||
lines.append(f"Данные недоступны ({snapshot.get('error', 'ошибка')}).")
|
||||
lines.append("Для точного ответа вызови get_weather.")
|
||||
return "\n".join(lines)
|
||||
|
||||
cur = snapshot.get("current") or {}
|
||||
lines.append(
|
||||
f"{snapshot.get('location')}: {cur.get('temperature_c')}°C "
|
||||
f"(ощущается {cur.get('apparent_temperature_c')}°C), "
|
||||
f"{cur.get('conditions')}, ветер {cur.get('wind_speed_kmh')} км/ч."
|
||||
)
|
||||
lines.append(client.rain_summary(hours_ahead=6))
|
||||
lines.append("Вопросы «что на улице» / «будет ли дождь» — get_weather.")
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user