| import datetime, logging, os, requests, warnings |
| from typing import Optional, List, Dict |
| from dotenv import load_dotenv |
| from fastapi import FastAPI, Depends, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from pydantic import BaseModel, Field |
| from geopy.geocoders import Photon |
|
|
| warnings.filterwarnings("ignore") |
| load_dotenv() |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") |
| logger = logging.getLogger("WeatherAPI") |
|
|
| app = FastAPI(title="Farmer Weather API") |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) |
|
|
| PHOTON = Photon(user_agent="FarmerWeatherApp", timeout=10) |
| NV_KEY = os.getenv("NVIDIA_API_KEY") |
| OWM_KEY = os.getenv("OPENWEATHERMAP_API_KEY") |
|
|
| ICONS = {0: "βοΈ", 1: "β
", 2: "β
", 3: "β
", 45: "π«οΈ", 51: "π¦οΈ", 61: "π§οΈ", 71: "βοΈ", 80: "π¦οΈ", 95: "βοΈ"} |
| DESC = {0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast", 45: "Fog", 51: "Drizzle", 61: "Rain", 71: "Snow", 80: "Showers", 95: "Thunderstorm"} |
|
|
| class WeatherResponse(BaseModel): |
| location: str |
| current: dict |
| highlights: dict |
| forecast: List[dict] |
| alerts: List[dict] |
| critical_days: List[str] |
| warning_days: List[str] |
| ai_recommendations: Optional[str] |
|
|
| def get_addr(lat, lon): |
| try: return PHOTON.reverse((lat, lon), exactly_one=True).address |
| except: return f"Lat: {lat}, Lon: {lon}" |
|
|
| def build_alerts(forecast): |
| alerts, crit, warn = [], [], [] |
| for d in forecast: |
| conds, sev = [], "INFO" |
| tmx, tmn = d['temp_max'], d['temp_min'] |
| if tmx > 40: (conds.append("π‘οΈ Extreme heat"), sev := "CRITICAL") |
| elif tmx > 35: (conds.append("π‘οΈ High temp"), sev := "WARNING") |
| if tmn < 5: (conds.append("βοΈ Frost risk"), sev := "CRITICAL") |
| if "storm" in d['description'].lower(): (conds.append("βοΈ Severe storm"), sev := "CRITICAL") |
| if conds: |
| (crit.append(d['day_name']) if sev=="CRITICAL" else warn.append(d['day_name'])) |
| alerts.append({"day": d['day_name'], "severity": sev, "conditions": conds}) |
| return alerts, crit, warn |
|
|
| @app.get("/weather", response_model=WeatherResponse) |
| def get_weather(lat: float = 18.52, lon: float = 73.85): |
| addr = get_addr(lat, lon) |
| params = {"latitude": lat, "longitude": lon, "current_weather": True, "forecast_days": 10, "timezone": "auto", |
| "hourly": "temperature_2m,relative_humidity_2m,precipitation,windspeed_10m", |
| "daily": "weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset"} |
| |
| data = requests.get("https://api.open-meteo.com/v1/forecast", params=params).json() |
| cw, daily, hourly = data['current_weather'], data['daily'], data['hourly'] |
| |
| cur = {"temp": cw['temperature'], "icon": ICONS.get(cw['weathercode'], "β"), "desc": DESC.get(cw['weathercode'], "Unknown"), "wind": cw['windspeed']} |
| |
| if OWM_KEY: |
| try: |
| ow = requests.get(f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={OWM_KEY}&units=metric").json() |
| cur.update({"temp": ow['main']['temp'], "desc": ow['weather'][0]['description'].capitalize()}) |
| except: pass |
|
|
| forecast = [] |
| for i, t in enumerate(daily['time']): |
| dt = datetime.datetime.fromisoformat(t) |
| forecast.append({"day_name": dt.strftime("%A"), "date": dt.strftime("%b %d"), "icon": ICONS.get(daily['weathercode'][i], "β"), |
| "description": DESC.get(daily['weathercode'][i], "Unknown"), "temp_max": daily['temperature_2m_max'][i], "temp_min": daily['temperature_2m_min'][i]}) |
|
|
| alerts, crit, warn = build_alerts(forecast) |
| ai_rec = None |
| if alerts and NV_KEY: |
| try: |
| from openai import OpenAI |
| nv = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=NV_KEY) |
| res = nv.chat.completions.create(model="meta/llama-3.1-70b-instruct", |
| messages=[{"role": "user", "content": f"Brief farming advice for {addr.split(',')[-1]} with {crit+warn} weather."}], max_tokens=100) |
| ai_rec = res.choices[0].message.content |
| except: ai_rec = f"Weather alert in {addr}. Monitor crops." |
|
|
| return {"location": addr, "current": cur, "highlights": {}, "forecast": forecast, "alerts": alerts, "critical_days": crit, "warning_days": warn, "ai_recommendations": ai_rec} |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run(app, host="0.0.0.0", port=8001) |