feat(weather): live current conditions endpoint + polling display in briefing view
This commit is contained in:
@@ -155,6 +155,44 @@ async def get_weather():
|
||||
return jsonify({"locations": cards, "temp_unit": temp_unit})
|
||||
|
||||
|
||||
@briefing_bp.route("/weather/current", methods=["GET"])
|
||||
@_REQUIRE
|
||||
async def get_current_weather():
|
||||
"""Return current temperature, conditions, and precipitation for the user's primary location.
|
||||
|
||||
Lightweight — fetches live from Open-Meteo, no caching. Intended for periodic frontend polling.
|
||||
"""
|
||||
raw = await get_setting(g.user.id, "briefing_config", "{}")
|
||||
try:
|
||||
config = json.loads(raw) if isinstance(raw, str) else (raw or {})
|
||||
temp_unit = config.get("temp_unit", "C")
|
||||
if temp_unit not in ("C", "F"):
|
||||
temp_unit = "C"
|
||||
locations = config.get("locations", {})
|
||||
except Exception:
|
||||
return jsonify({"error": "No briefing config"}), 404
|
||||
|
||||
# Use home location, fall back to work
|
||||
loc = locations.get("home") or locations.get("work")
|
||||
if not loc or not loc.get("lat") or not loc.get("lon"):
|
||||
return jsonify({"error": "No location configured"}), 404
|
||||
|
||||
from fabledassistant.services.weather import fetch_current_conditions
|
||||
current = await fetch_current_conditions(loc["lat"], loc["lon"])
|
||||
if current is None:
|
||||
return jsonify({"error": "Failed to fetch current conditions"}), 502
|
||||
|
||||
# Convert temperature if needed
|
||||
temp = current["temperature"]
|
||||
if temp is not None and temp_unit == "F":
|
||||
temp = temp * 9 / 5 + 32
|
||||
current["temperature"] = round(temp) if temp is not None else None
|
||||
current["temp_unit"] = temp_unit
|
||||
current["location"] = loc.get("label") or "Home"
|
||||
|
||||
return jsonify(current)
|
||||
|
||||
|
||||
@briefing_bp.route("/weather/geocode", methods=["POST"])
|
||||
@_REQUIRE
|
||||
async def geocode_location():
|
||||
|
||||
@@ -184,6 +184,54 @@ async def geocode(query: str) -> tuple[float, float, str]:
|
||||
return float(r["lat"]), float(r["lon"]), r.get("display_name", query)
|
||||
|
||||
|
||||
async def fetch_current_conditions(lat: float, lon: float) -> dict | None:
|
||||
"""Fetch current temperature + conditions + next 3 hours precipitation.
|
||||
|
||||
Lightweight call — no daily forecast, no caching needed.
|
||||
Returns dict with: temperature, windspeed, weathercode, description,
|
||||
and precip_next_3h (list of hourly precip probabilities).
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
resp = await client.get(OPEN_METEO_URL, params={
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"current_weather": "true",
|
||||
"hourly": "precipitation_probability",
|
||||
"forecast_days": 1,
|
||||
"timezone": "auto",
|
||||
})
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
current = data.get("current_weather", {})
|
||||
hourly = data.get("hourly", {})
|
||||
hourly_times = hourly.get("time", [])
|
||||
hourly_precip = hourly.get("precipitation_probability", [])
|
||||
|
||||
# Find the current hour index and get next 3 hours of precip
|
||||
current_time = current.get("time", "")
|
||||
precip_next_3h = []
|
||||
try:
|
||||
idx = next(i for i, t in enumerate(hourly_times) if t >= current_time)
|
||||
precip_next_3h = hourly_precip[idx:idx + 3]
|
||||
except (StopIteration, IndexError):
|
||||
pass
|
||||
|
||||
code = current.get("weathercode", 0)
|
||||
return {
|
||||
"temperature": current.get("temperature"),
|
||||
"windspeed": current.get("windspeed"),
|
||||
"weathercode": code,
|
||||
"description": describe_weathercode(code),
|
||||
"time": current_time,
|
||||
"precip_next_3h": precip_next_3h,
|
||||
}
|
||||
except Exception:
|
||||
logger.debug("Failed to fetch current conditions for %s, %s", lat, lon, exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
async def _fetch_open_meteo(lat: float, lon: float) -> dict:
|
||||
"""Fetch 7-day forecast from Open-Meteo with current conditions and yesterday's data."""
|
||||
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||
|
||||
Reference in New Issue
Block a user