File size: 3,623 Bytes
51c9c1d
 
 
9731ab0
268cea4
51c9c1d
 
 
 
 
 
 
859856a
268cea4
 
 
859856a
51c9c1d
 
9731ab0
859856a
51c9c1d
268cea4
51c9c1d
 
859856a
51c9c1d
 
 
268cea4
9731ab0
 
 
51c9c1d
9731ab0
859856a
 
 
 
 
51c9c1d
 
 
 
859856a
268cea4
 
 
 
 
 
 
859856a
 
 
268cea4
 
51c9c1d
859856a
51c9c1d
 
 
859856a
51c9c1d
859856a
 
 
 
 
 
268cea4
 
 
 
 
859856a
 
 
 
268cea4
 
859856a
 
 
 
 
 
 
268cea4
859856a
 
268cea4
859856a
 
 
268cea4
 
859856a
268cea4
 
 
859856a
268cea4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import io
import requests
import pandas as pd
import matplotlib
matplotlib.use("Agg")  # headless backend for Hugging Face / servers
import matplotlib.pyplot as plt
import gradio as gr

GEOCODE_URL = "https://geocoding-api.open-meteo.com/v1/search"
FORECAST_URL = "https://api.open-meteo.com/v1/forecast"


def geocode_city(city: str):
    city = (city or "").strip()
    if not city:
        raise ValueError("Please enter a city name.")
    r = requests.get(GEOCODE_URL, params={"name": city, "count": 1, "language": "en"}, timeout=10)
    r.raise_for_status()
    data = r.json()
    if not data.get("results"):
        raise ValueError(f"City '{city}' not found.")
    res = data["results"][0]
    return float(res["latitude"]), float(res["longitude"]), f"{res['name']}, {res.get('country','')}"


def fetch_forecast(lat, lon, days, mode, temp_unit, precip_unit):
    params = {
        "latitude": lat,
        "longitude": lon,
        "forecast_days": int(days),
        "timezone": "auto",
        "temperature_unit": "fahrenheit" if temp_unit == "°F" else "celsius",
        "precipitation_unit": "inch" if precip_unit == "in" else "mm",
    }
    if mode == "Daily":
        params["daily"] = "temperature_2m_max,temperature_2m_min,precipitation_sum"
    else:
        params["hourly"] = "temperature_2m,relativehumidity_2m,precipitation"

    r = requests.get(FORECAST_URL, params=params, timeout=15)
    r.raise_for_status()
    return r.json()


def make_plot(x, ys, labels, title, ylabel):
    # ensure x is datetime-like for nice plotting
    try:
        x = pd.to_datetime(x)
    except Exception:
        # fall back to plain strings
        x = list(map(str, x))

    plt.figure(figsize=(8, 3.5))
    for y, lbl in zip(ys, labels):
        plt.plot(x, y, label=lbl)
    if labels:
        plt.legend()
    plt.title(title)
    plt.xlabel("Time")
    plt.ylabel(ylabel)
    plt.tight_layout()
    buf = io.BytesIO()
    plt.savefig(buf, format="png", dpi=120)
    plt.close()
    buf.seek(0)
    return buf.getvalue()


def run(city, mode, days, temp_unit, precip_unit):
    try:
        # validate small things
        days = int(days)
        if days < 1 or days > 14:
            raise ValueError("Days must be between 1 and 14.")

        lat, lon, place = geocode_city(city)
        data = fetch_forecast(lat, lon, days, mode, temp_unit, precip_unit)

        if mode == "Daily":
            if "daily" not in data:
                raise ValueError("Daily data not available for this location.")
            d = data["daily"]
            df = pd.DataFrame({
                "date": d["time"],
                "temp_max": d["temperature_2m_max"],
                "temp_min": d["temperature_2m_min"],
                "precip": d["precipitation_sum"],
            })
            # return types: (markdown, DataFrame, bytes)
            img = make_plot(df["date"], [df["temp_max"], df["temp_min"]],
                            ["Max", "Min"], f"Daily Temperatures — {place}", f"Temp ({temp_unit})")
            md = f"### Daily forecast for **{place}**\nNext {days} day(s)\n\nUnits: Temperature **{temp_unit}**, Precipitation **{precip_unit}**"
            return md, df, img

        else:  # Hourly
            if "hourly" not in data:
                raise ValueError("Hourly data not available for this location.")
            h = data["hourly"]
            # Ensure we don't slice beyond available length
            max_hours = len(h.get("time", []))
            requested_hours = min(days * 24, max_hours)
            df = pd.DataFrame({
                "time": h["time"][:requested_hours]_]()