import os import requests import pandas as pd import gradio as gr from datetime import datetime, timedelta, timezone from zoneinfo import ZoneInfo # --- CONFIG --- TOMORROW_API_KEY = os.getenv("TOMORROW_API_KEY", "teKj9Rkys1UzWxKBEs36pAR8paCXnPW6") METEOMATICS_USERNAME = os.getenv("METEO_USERNAME", "ptbukitteknologidigital_tudjuka_ricky") METEOMATICS_PASSWORD = os.getenv("METEO_PASSWORD", "u5n25MwaviEG3O3v8q94") DEFAULT_LAT = 0.46876 DEFAULT_LON = 116.16879 LOCAL_TZ = ZoneInfo("Asia/Kuala_Lumpur") # ✅ changed from Asia/Jakarta to Asia/Kuala_Lumpur MM_VAR_RAIN = "precip_1h:mm" MM_VAR_PROB = "prob_precip_1h:p" DEFAULT_MODEL = "ecmwf-ifs" # --- Tomorrow.io fetch --- def fetch_tomorrow(lat, lon, hours=24): if not TOMORROW_API_KEY: raise RuntimeError("Missing TOMORROW_API_KEY") url = "https://api.tomorrow.io/v4/weather/forecast" params = { "location": f"{lat},{lon}", "units": "metric", "fields": "rainIntensity,precipitationProbability", "apikey": TOMORROW_API_KEY, } r = requests.get(url, params=params, timeout=25) if r.status_code != 200: raise RuntimeError(f"Tomorrow.io API error: {r.status_code} {r.text}") data = r.json().get("timelines", {}).get("hourly", []) if not data: raise RuntimeError("Tomorrow.io: no hourly data returned") rows = [] for p in data[:hours]: ts = pd.to_datetime(p["time"], utc=True).tz_convert(LOCAL_TZ) vals = p.get("values", {}) rain = float(vals.get("rainIntensity", 0.0) or 0.0) prob = float(vals.get("precipitationProbability", 0.0) or 0.0) rows.append({"time": ts, "tio_rain": rain, "tio_prob": prob}) return pd.DataFrame(rows) # --- Meteomatics fetch --- def fetch_meteomatics(lat, lon, hours=24): if not METEOMATICS_USERNAME or not METEOMATICS_PASSWORD: raise RuntimeError("Missing METEOMATICS credentials") start = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) end = start + timedelta(hours=hours - 1) timepath = f"{start.isoformat().replace('+00:00','Z')}--{end.isoformat().replace('+00:00','Z')}:PT1H" url = f"https://api.meteomatics.com/{timepath}/{MM_VAR_RAIN},{MM_VAR_PROB}/{lat},{lon}/json" params = {"model": DEFAULT_MODEL} r = requests.get(url, auth=(METEOMATICS_USERNAME, METEOMATICS_PASSWORD), params=params, timeout=25) if r.status_code != 200: raise RuntimeError(f"Meteomatics API error: {r.status_code} {r.text}") js = r.json().get("data", []) if not js: raise RuntimeError("Meteomatics: no data returned") time_map = {} for entry in js: var = entry.get("parameter") for d in entry["coordinates"][0]["dates"]: ts = pd.to_datetime(d["date"], utc=True).tz_convert(LOCAL_TZ) val = float(d.get("value", 0.0) or 0.0) rec = time_map.setdefault(ts, {}) if var == MM_VAR_RAIN: rec["mm_rain"] = val elif var == MM_VAR_PROB: rec["mm_prob"] = val df = pd.DataFrame([ {"time": t, "mm_rain": rec.get("mm_rain", 0.0), "mm_prob": rec.get("mm_prob", 0.0)} for t, rec in sorted(time_map.items()) ]) return df # --- Combine & table --- def build_table(lat, lon): df_tio = fetch_tomorrow(lat, lon, 24) df_mm = fetch_meteomatics(lat, lon, 24) df = pd.merge(df_tio, df_mm, on="time", how="outer").sort_values("time") table = pd.DataFrame({ "Time (Asia/Kuala_Lumpur)": df["time"].dt.strftime("%Y-%m-%d %H:%M"), "Tomorrow.io Rain (mm/hr)": df["tio_rain"].round(3), "Tomorrow.io Prob (%)": df["tio_prob"].round(1), "Meteomatics Rain (mm/hr)": df["mm_rain"].round(3), "Meteomatics Prob (%)": df["mm_prob"].round(1), }) return table def run_table(lat, lon): try: table = build_table(lat, lon) info = f"Location: {lat:.5f}, {lon:.5f} | Model: {DEFAULT_MODEL} | Timezone: Asia/Kuala_Lumpur | Units: mm/hr | Horizon: 24 hours" return table, info except Exception as e: return None, f"

Error: {e}

" # --- Gradio UI --- with gr.Blocks(title="Next 24 Hours — Tomorrow.io + Meteomatics Table (mm/hr)") as demo: gr.Markdown("## 🌧️ Next 24 Hours — **Tomorrow.io** & **Meteomatics** (All in mm/hr)\n" "Side-by-side hourly rain and probability forecasts for the next 24 hours.") with gr.Row(): lat = gr.Number(label="Latitude", value=DEFAULT_LAT) lon = gr.Number(label="Longitude", value=DEFAULT_LON) btn = gr.Button("Fetch 24h Table") table_out = gr.Dataframe(interactive=False, wrap=True) info_out = gr.HTML() btn.click(run_table, inputs=[lat, lon], outputs=[table_out, info_out]) # if __name__ == "__main__": # os.environ["GRADIO_ANALYTICS_ENABLED"]="false" # os.environ["HF_HUB_DISABLE_TELEMETRY"]="1" # demo.launch(server_name="127.0.0.1", server_port=7861, show_error=True, inbrowser=False, debug=True) if __name__ == "__main__": demo.launch()