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)