Spaces:
Running
Running
| import requests | |
| from datetime import date, datetime, timedelta | |
| from modules.weather import Weather | |
| def fetch_json(url: str, timeout: int = 10) -> dict: | |
| resp = requests.get(url, timeout=timeout) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| if data.get("error"): | |
| raise RuntimeError(f"Open-Meteo API error: {data.get('reason', data)}") | |
| if "daily" not in data: | |
| raise RuntimeError(f"Unexpected Open-Meteo response: {data}") | |
| return data | |
| def did_or_will_rain( | |
| target_date, | |
| latitude, | |
| longitude, | |
| forecast_threshold=50, | |
| ): | |
| """ | |
| Returns True if: | |
| - it rained on a past date | |
| - rain is forecast on a future date above the threshold | |
| Parameters | |
| ---------- | |
| target_date : str | date | datetime | |
| Date to check ("YYYY-MM-DD" or date object) | |
| latitude : float | |
| longitude : float | |
| forecast_threshold : int | |
| Minimum rain probability (%) for future dates | |
| Returns | |
| ------- | |
| bool | |
| """ | |
| # Normalize date | |
| if isinstance(target_date, str): | |
| target_date = datetime.strptime(target_date, "%Y-%m-%d").date() | |
| elif isinstance(target_date, datetime): | |
| target_date = target_date.date() | |
| today = date.today() | |
| # Historical date | |
| if target_date < today: | |
| url = ( | |
| "https://archive-api.open-meteo.com/v1/archive" | |
| f"?latitude={latitude}" | |
| f"&longitude={longitude}" | |
| f"&start_date={target_date}" | |
| f"&end_date={target_date}" | |
| "&daily=precipitation_sum" | |
| "&timezone=auto" | |
| ) | |
| data = fetch_json(url) | |
| rain_mm = data["daily"]["precipitation_sum"][0] | |
| return rain_mm > 0 | |
| # Today / future date | |
| else: | |
| url = ( | |
| "https://api.open-meteo.com/v1/forecast" | |
| f"?latitude={latitude}" | |
| f"&longitude={longitude}" | |
| "&daily=precipitation_probability_max,precipitation_sum" | |
| "&forecast_days=16" | |
| "&timezone=auto" | |
| ) | |
| data = fetch_json(url) | |
| dates = data["daily"]["time"] | |
| target_date_str = target_date.isoformat() | |
| if target_date_str not in dates: | |
| raise ValueError("Date outside forecast range") | |
| idx = dates.index(target_date_str) | |
| probability = data["daily"]["precipitation_probability_max"][idx] | |
| precipitation = data["daily"]["precipitation_sum"][idx] | |
| return ( | |
| probability >= forecast_threshold | |
| or precipitation > 0 | |
| ) | |
| def weather_comment(code: int) -> str: | |
| WMO_CODES = { | |
| 0: "☀️ Sunny", | |
| 1: "🌤️ Mainly clear", | |
| 2: "⛅ Partly cloudy", | |
| 3: "☁️ Cloudy", | |
| 45: "🌫️ Foggy", | |
| 48: "🌫️ Icy fog", | |
| 51: "🌦️ Light drizzle", | |
| 53: "🌦️ Moderate drizzle", | |
| 55: "🌧️ Dense drizzle", | |
| 56: "🌨️ Light freezing drizzle", | |
| 57: "🌨️ Heavy freezing drizzle", | |
| 61: "🌧️ Slight rain", | |
| 63: "🌧️ Moderate rain", | |
| 65: "🌧️ Heavy rain", | |
| 66: "🌨️ Light freezing rain", | |
| 67: "🌨️ Heavy freezing rain", | |
| 71: "❄️ Slight snowfall", | |
| 73: "❄️ Moderate snowfall", | |
| 75: "❄️ Heavy snowfall", | |
| 77: "🌨️ Snow grains", | |
| 80: "🌦️ Slight rain showers", | |
| 81: "🌧️ Moderate rain showers", | |
| 82: "⛈️ Violent rain showers", | |
| 85: "🌨️ Slight snow showers", | |
| 86: "🌨️ Heavy snow showers", | |
| 95: "⛈️ Thunderstorm", | |
| 96: "⛈️ Thunderstorm with slight hail", | |
| 99: "⛈️ Thunderstorm with heavy hail", | |
| } | |
| return WMO_CODES.get(code, "Unknown weather") | |
| def weather_values(date, latitude, longitude): | |
| # return temperature, wind, precipitation, and a comment (cloudy/sunny/rainy/windy) | |
| url = ( | |
| "https://api.open-meteo.com/v1/forecast" | |
| f"?latitude={latitude}" | |
| f"&longitude={longitude}" | |
| "&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,windspeed_10m_max,weathercode" | |
| "&forecast_days=16" | |
| "&timezone=auto" | |
| ) | |
| data = fetch_json(url) | |
| dates = data["daily"]["time"] | |
| # date_str = date.isoformat() if isinstance(date, date) else date | |
| date_str = date.isoformat() | |
| if date_str not in dates: | |
| raise ValueError("Date outside forecast range") | |
| idx = dates.index(date_str) | |
| temp_max = data["daily"]["temperature_2m_max"][idx] | |
| temp_min = data["daily"]["temperature_2m_min"][idx] | |
| precipitation = data["daily"]["precipitation_sum"][idx] | |
| precipitation_probability = data["daily"]["precipitation_probability_max"][idx] | |
| windspeed = data["daily"]["windspeed_10m_max"][idx] | |
| weathercode = data["daily"]["weathercode"][idx] | |
| comment = weather_comment(weathercode) | |
| return Weather( | |
| temp_max=temp_max, | |
| temp_min=temp_min, | |
| precipitation=precipitation, | |
| precipitation_probability=precipitation_probability, | |
| wind_speed=windspeed, | |
| comment=comment, | |
| ) | |
| def last_rained_date(latitude, longitude, days_back=15): | |
| url = ( | |
| "https://archive-api.open-meteo.com/v1/archive" | |
| f"?latitude={latitude}" | |
| f"&longitude={longitude}" | |
| f"&start_date={(date.today() - timedelta(days=days_back + 1 )).isoformat()}" | |
| f"&end_date={(date.today() - timedelta(days=1)).isoformat()}" # YESTERDAY | |
| "&daily=precipitation_sum" | |
| "&timezone=auto" | |
| ) | |
| data = fetch_json(url) | |
| dates = data["daily"]["time"] | |
| precipitation = data["daily"]["precipitation_sum"] | |
| for d, p in zip(reversed(dates), reversed(precipitation)): | |
| if p > 5: # Consider it rained if precipitation > 5mm | |
| return date.fromisoformat(d) | |
| return None # No rain in the past `days_back` days | |
| # Marseille | |
| LAT = 43.2965 | |
| LON = 5.3698 | |
| # Did it rain yesterday? | |
| print(did_or_will_rain("2026-06-03", LAT, LON)) | |
| # Will it rain tomorrow? | |
| print(did_or_will_rain("2026-06-04", LAT, LON)) | |
| # Require at least 70% confidence | |
| print( | |
| did_or_will_rain( | |
| "2026-06-05", | |
| LAT, | |
| LON, | |
| forecast_threshold=70, | |
| )) |