Spaces:
Sleeping
Sleeping
| import requests | |
| from datetime import datetime | |
| from typing import Dict | |
| from .config import OPENWEATHER_BASE, get_openweather_key, UNITS | |
| from .utils import day_list | |
| def geocode_city(city: str) -> Dict: | |
| url = f"{OPENWEATHER_BASE}/geo/1.0/direct" | |
| r = requests.get( | |
| url, | |
| params={"q": city, "limit": 1, "appid": get_openweather_key()}, | |
| timeout=20, | |
| ) | |
| r.raise_for_status() | |
| arr = r.json() | |
| if not arr: | |
| raise RuntimeError(f"City not found: {city}") | |
| d = arr[0] | |
| return { | |
| "name": d.get("name"), | |
| "lat": d.get("lat"), | |
| "lon": d.get("lon"), | |
| "country": d.get("country"), | |
| } | |
| def forecast_5day(lat: float, lon: float, units=UNITS) -> Dict: | |
| # 5 day / 3-hour forecast | |
| url = f"{OPENWEATHER_BASE}/data/2.5/forecast" | |
| r = requests.get( | |
| url, | |
| params={"lat": lat, "lon": lon, "appid": get_openweather_key(), "units": units}, | |
| timeout=25, | |
| ) | |
| r.raise_for_status() | |
| return r.json() | |
| def summarize_forecast_for_range(city: str, start_date, days: int) -> Dict: | |
| loc = geocode_city(city) | |
| fc = forecast_5day(loc["lat"], loc["lon"]) | |
| daily = {} | |
| for item in fc.get("list", []): | |
| dt_txt = item.get("dt_txt") # 'YYYY-MM-DD HH:MM:SS' | |
| dt = datetime.strptime(dt_txt, "%Y-%m-%d %H:%M:%S") | |
| dkey = dt.date().isoformat() | |
| temp = item.get("main", {}).get("temp") | |
| weather = item.get("weather", [{}])[0].get("description", "") | |
| wind = item.get("wind", {}).get("speed", 0) | |
| if dkey not in daily: | |
| daily[dkey] = {"temps": [], "desc": {}, "wind": []} | |
| daily[dkey]["temps"].append(temp) | |
| daily[dkey]["wind"].append(wind) | |
| daily[dkey]["desc"][weather] = daily[dkey]["desc"].get(weather, 0) + 1 | |
| dates = [d.isoformat() for d in day_list(start_date, days)] | |
| rows = [] | |
| for dkey in dates: | |
| if dkey in daily: | |
| temps = daily[dkey]["temps"] | |
| avg = sum(temps) / len(temps) if temps else None | |
| wavg = ( | |
| sum(daily[dkey]["wind"]) / len(daily[dkey]["wind"]) if daily[dkey]["wind"] else 0 | |
| ) | |
| desc = max(daily[dkey]["desc"], key=daily[dkey]["desc"].get) | |
| rows.append( | |
| { | |
| "date": dkey, | |
| "temp_avg": round(avg, 1) if avg is not None else None, | |
| "wind": round(wavg, 1), | |
| "desc": desc, | |
| } | |
| ) | |
| else: | |
| rows.append( | |
| { | |
| "date": dkey, | |
| "temp_avg": None, | |
| "wind": None, | |
| "desc": "No forecast (beyond 5 days)", | |
| } | |
| ) | |
| summary_text = "\n".join( | |
| [ | |
| f"{r['date']}: {r['desc']}, avg {r['temp_avg']}°C, wind {r['wind']} m/s" | |
| for r in rows | |
| ] | |
| ) | |
| return {"location": loc, "daily": rows, "summary_text": summary_text} | |