from flask import Flask, render_template, request, Response import requests import datetime from twilio.rest import Client # For Twilio integration from geopy.geocoders import Photon from geopy.exc import GeocoderTimedOut, GeocoderServiceError from transformers import pipeline import warnings # Suppress warnings warnings.filterwarnings('ignore') app = Flask(__name__) # Initialize Photon geocoder (no API key required) photon_geolocator = Photon(user_agent="MyWeatherApp", timeout=10) # Initialize LLM for personalized recommendations print("Loading LLM model for personalized recommendations...") try: llm_generator = pipeline( "text-generation", model="distilgpt2", # Lightweight model max_length=200, device=-1 # CPU ) print("✅ LLM model loaded successfully!") except Exception as e: print(f"⚠️ LLM model loading failed: {e}") llm_generator = None def parse_iso_datetime(timestr): """ Parse an ISO8601 datetime string (removing any trailing 'Z'). """ if timestr.endswith("Z"): timestr = timestr[:-1] return datetime.datetime.fromisoformat(timestr) def find_closest_hour_index(hour_times, current_time_str): """ Find the index in hour_times that is closest to the current_time_str. """ if not hour_times: return None dt_current = parse_iso_datetime(current_time_str) min_diff = None best_index = None for i, ht in enumerate(hour_times): dt_ht = parse_iso_datetime(ht) diff = abs((dt_ht - dt_current).total_seconds()) if min_diff is None or diff < min_diff: min_diff = diff best_index = i return best_index def get_weather_icon(code): """Map the Open-Meteo weathercode to an emoji icon.""" if code == 0: return "☀️" # Clear sky elif code in [1, 2, 3]: return "⛅" elif code in [45, 48]: return "🌫️" elif code in [51, 53, 55]: return "🌦️" elif code in [56, 57]: return "🌧️" elif code in [61, 63, 65]: return "🌧️" elif code in [66, 67]: return "🌧️" elif code in [71, 73, 75, 77]: return "❄️" elif code in [80, 81, 82]: return "🌦️" elif code in [85, 86]: return "❄️" elif code in [95, 96, 99]: return "⛈️" else: return "❓" def get_weather_description(code): """Short textual description for the weathercode.""" descriptions = { 0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast", 45: "Fog", 48: "Depositing rime fog", 51: "Light drizzle", 53: "Moderate drizzle", 55: "Dense drizzle", 56: "Freezing drizzle", 57: "Freezing drizzle", 61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain", 66: "Freezing rain", 67: "Freezing rain", 71: "Slight snow fall", 73: "Moderate snow fall", 75: "Heavy snow fall", 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 w/ slight hail", 99: "Thunderstorm w/ heavy hail" } return descriptions.get(code, "Unknown") def reverse_geocode(lat, lon): """ Use Photon (via geopy) to convert latitude and longitude into a human-readable address. If the geocoding fails, returns a fallback string with the coordinates. """ try: location = photon_geolocator.reverse((lat, lon), exactly_one=True) if location: return location.address except (GeocoderTimedOut, GeocoderServiceError) as e: print("Photon reverse geocode error:", e) return f"Lat: {lat}, Lon: {lon}" # ----------------------------- # LLM-Powered Personalized Recommendations # ----------------------------- def generate_personalized_recommendations(weather_summary, location_address, critical_days, warning_days): """ Generate personalized agricultural recommendations using LLM based on weather and location. """ if llm_generator is None: return None try: # Extract region info from location region_parts = location_address.split(',') region = region_parts[-1].strip() if len(region_parts) > 0 else "your region" # Determine weather condition weather_condition = "cloudy conditions" if critical_days: if len(critical_days) > 3: weather_condition = "severe weather alerts" else: weather_condition = "critical weather conditions" elif warning_days: weather_condition = "warning-level weather" # Create a better structured prompt prompt = f"""Agricultural advice for farmers in {region}: Weather: {weather_condition} expected Days affected: {len(critical_days) + len(warning_days)} days Farming recommendations: 1. Crop care:""" # Generate recommendations response = llm_generator( prompt, max_new_tokens=80, num_return_sequences=1, temperature=0.8, do_sample=True, pad_token_id=50256, repetition_penalty=1.5 # Reduce repetition ) generated_text = response[0]['generated_text'] # Extract only the generated part recommendations = generated_text[len(prompt):].strip() # Clean up the output lines = recommendations.split('\n') clean_lines = [] seen = set() for line in lines[:5]: # Max 5 lines line = line.strip() # Skip empty, repetitive, or nonsensical lines if line and len(line) > 10 and line not in seen: # Check for repetition patterns if not any(line.count(word) > 2 for word in line.split()): clean_lines.append(line) seen.add(line) if clean_lines: recommendations = ' '.join(clean_lines) # Limit length if len(recommendations) > 180: recommendations = recommendations[:177] + "..." return recommendations else: # Fallback to rule-based if LLM output is poor return generate_rule_based_recommendations(weather_condition, region, critical_days, warning_days) except Exception as e: print(f"LLM generation error: {e}") return generate_rule_based_recommendations("cloudy conditions", "your region", critical_days, warning_days) def generate_rule_based_recommendations(weather_condition, region, critical_days, warning_days): """ Fallback rule-based recommendations when LLM fails or produces poor output. """ if critical_days and len(critical_days) > 0: return f"For {region}: Secure crops and equipment. Postpone spraying. Monitor drainage systems. Harvest ready crops before severe weather." elif warning_days and len(warning_days) > 0: if "cloudy" in weather_condition.lower() or "overcast" in weather_condition.lower(): return f"For {region}: Reduce irrigation due to lower evaporation. Monitor for fungal diseases. Apply preventive fungicides if needed." else: return f"For {region}: Adjust irrigation schedule. Monitor soil moisture. Delay non-essential field operations." else: return f"For {region}: Continue normal farming operations. Monitor weather updates regularly." # ----------------------------- # Twilio WhatsApp Integration # ----------------------------- def check_and_collect_alerts(forecast_list): """ Check the forecast for hazardous weather conditions and collect detailed alert messages. """ alerts = [] critical_days = [] warning_days = [] for day in forecast_list: day_alerts = [] severity = "INFO" # Temperature Analysis if day.get("tmax") and day.get("tmin"): tmax = day["tmax"] tmin = day["tmin"] if tmax > 40: day_alerts.append(f"🌡️ Extreme Heat: {tmax}°C - High risk of crop stress and water loss") severity = "CRITICAL" elif tmax > 35: day_alerts.append(f"🌡️ High Temperature: {tmax}°C - Increase irrigation frequency") severity = "WARNING" if tmin < 5: day_alerts.append(f"❄️ Frost Risk: {tmin}°C - Protect sensitive crops") severity = "CRITICAL" elif tmin < 10: day_alerts.append(f"🌡️ Cold Night: {tmin}°C - Monitor young plants") severity = "WARNING" # Temperature swing temp_diff = tmax - tmin if temp_diff > 20: day_alerts.append(f"📊 Large temperature swing: {temp_diff}°C - May stress plants") # Weather Condition Analysis desc = day.get("desc", "").lower() if "thunderstorm" in desc or "heavy" in desc: day_alerts.append(f"⛈️ Severe Weather: {day['desc']} - Secure equipment, delay spraying") severity = "CRITICAL" elif "rain" in desc or "drizzle" in desc: day_alerts.append(f"🌧️ Rainfall Expected: {day['desc']} - Postpone irrigation, avoid field work") severity = "WARNING" elif "overcast" in desc or "cloudy" in desc: day_alerts.append(f"☁️ Cloudy Conditions: {day['desc']} - Reduced photosynthesis, monitor for diseases") severity = "INFO" # Compile day alert if any conditions met if day_alerts: day_header = f"\n*{day['day_name']} ({day['date_str']})*" if severity == "CRITICAL": day_header = f"🚨 {day_header} - CRITICAL" critical_days.append(day['day_name']) elif severity == "WARNING": day_header = f"⚠️ {day_header} - WARNING" warning_days.append(day['day_name']) alert_text = day_header + "\n" + "\n".join(f" • {alert}" for alert in day_alerts) # Add temperature range if day.get("tmax") and day.get("tmin"): alert_text += f"\n 📈 Temp Range: {day['tmin']}°C - {day['tmax']}°C" if day.get("morning_temp"): alert_text += f"\n 🌅 Morning: {day['morning_temp']}°C" if day.get("evening_temp"): alert_text += f"\n 🌆 Evening: {day['evening_temp']}°C" alerts.append(alert_text) return alerts, critical_days, warning_days def send_whatsapp_message(message, location, location_address, critical_days, warning_days): """ Send a WhatsApp message using Twilio API with enhanced agricultural insights. """ google_maps_url = f"https://www.google.com/maps?q={location[0]},{location[1]}" # Build concise severity summary severity_summary = "" if critical_days: severity_summary += f"🚨 {len(critical_days)} CRITICAL: {', '.join(critical_days[:3])}\n" if warning_days: severity_summary += f"⚠️ {len(warning_days)} WARNING: {', '.join(warning_days[:3])}\n" # Generate personalized LLM recommendations (shortened) weather_summary = f"{len(critical_days)} critical, {len(warning_days)} warning days" llm_recommendations = generate_personalized_recommendations( weather_summary, location_address, critical_days, warning_days ) # Shorten LLM recommendations if too long if llm_recommendations and len(llm_recommendations) > 200: llm_recommendations = llm_recommendations[:197] + "..." # Build concise message message_content = ( f"🌾 *WEATHER ALERT* 🌾\n\n" f"{severity_summary}\n" ) # Add condensed forecast (max 3 days) alert_lines = message.strip().split('\n\n')[:3] for alert in alert_lines: # Shorten each alert lines = alert.split('\n') if lines: message_content += f"{lines[0]}\n" # Just the header if len(lines) > 1: message_content += f"{lines[1][:80]}\n" # First detail only message_content += "\n" # Add AI recommendations if available if llm_recommendations: message_content += f"🤖 *AI ADVICE:*\n{llm_recommendations}\n\n" # Add critical actions only if critical_days: message_content += ( f"🚨 *URGENT:*\n" f"• Secure equipment\n" f"• Harvest ready crops\n" f"• Protect livestock\n\n" ) elif warning_days: message_content += ( f"⚠️ *ACTIONS:*\n" f"• Adjust irrigation\n" f"• Monitor soil moisture\n" f"• Delay field work\n\n" ) # Add location message_content += ( f"📍 {location_address}\n" f"🗺️ {google_maps_url}\n\n" f"_Weather Forecast for Farmers_" ) # Ensure under 1600 characters if len(message_content) > 1590: message_content = message_content[:1587] + "..." account_sid = 'ACe45f7038c5338a153d1126ca6d547c84' auth_token = '48b9eea898885ef395d48edc74924340' client = Client(account_sid, auth_token) try: msg = client.messages.create( from_='whatsapp:+14155238886', body=message_content, to='whatsapp:+919763059811' ) print(f"✅ WhatsApp sent! SID: {msg.sid}, Length: {len(message_content)} chars") except Exception as e: print(f"❌ WhatsApp error: {e}") print(f"Message length was: {len(message_content)} characters") @app.route("/", methods=["GET", "POST"]) def index(): # Default coordinates default_lat = 18.5196 default_lon = 73.8553 if request.method == "POST": try: lat = float(request.form.get("lat", default_lat)) lon = float(request.form.get("lon", default_lon)) except ValueError: lat, lon = default_lat, default_lon else: lat = float(request.args.get("lat", default_lat)) lon = float(request.args.get("lon", default_lon)) location_address = reverse_geocode(lat, lon) # Call Open-Meteo API for forecast data url = "https://api.open-meteo.com/v1/forecast" params = { "latitude": lat, "longitude": lon, "hourly": ( "temperature_2m,relative_humidity_2m,precipitation," "cloudcover,windspeed_10m,pressure_msl,soil_moisture_3_to_9cm,uv_index" ), "daily": ( "weathercode,temperature_2m_max,temperature_2m_min," "sunrise,sunset,uv_index_max" ), "current_weather": True, "forecast_days": 10, "timezone": "auto" } resp = requests.get(url, params=params) data = resp.json() timezone = data.get("timezone", "Local") current_weather = data.get("current_weather", {}) current_temp = current_weather.get("temperature") current_time = current_weather.get("time") current_code = current_weather.get("weathercode") current_icon = get_weather_icon(current_code) current_desc = get_weather_description(current_code) current_wind_speed = current_weather.get("windspeed", 0.0) current_wind_dir = current_weather.get("winddirection", 0) if current_time: dt_current = parse_iso_datetime(current_time) current_time_formatted = dt_current.strftime("%A, %b %d, %Y %I:%M %p") else: current_time_formatted = "" hourly_data = data.get("hourly", {}) hour_times = hourly_data.get("time", []) hour_temp = hourly_data.get("temperature_2m", []) hour_humidity = hourly_data.get("relative_humidity_2m", []) hour_precip = hourly_data.get("precipitation", []) hour_clouds = hourly_data.get("cloudcover", []) hour_wind = hourly_data.get("windspeed_10m", []) hour_pressure = hourly_data.get("pressure_msl", []) hour_soil = hourly_data.get("soil_moisture_3_to_9cm", []) hour_uv = hourly_data.get("uv_index", []) current_index = None if current_time: current_index = find_closest_hour_index(hour_times, current_time) feels_like = current_temp if current_index is not None and current_index < len(hour_humidity): h = hour_humidity[current_index] feels_like = round(current_temp - 0.2 * (100 - h) / 10, 1) today_highlights = {} if current_index is not None: today_highlights["humidity"] = hour_humidity[current_index] if current_index < len(hour_humidity) else None today_highlights["precipitation"] = hour_precip[current_index] if current_index < len(hour_precip) else None today_highlights["clouds"] = hour_clouds[current_index] if current_index < len(hour_clouds) else None today_highlights["windspeed"] = hour_wind[current_index] if current_index < len(hour_wind) else None today_highlights["pressure"] = hour_pressure[current_index] if current_index < len(hour_pressure) else None today_highlights["soil_moisture"] = hour_soil[current_index] if current_index < len(hour_soil) else None today_highlights["uv_index"] = hour_uv[current_index] if current_index < len(hour_uv) else None else: for k in ["humidity", "precipitation", "cloudcover", "windspeed", "pressure", "soil_moisture", "uv_index"]: today_highlights[k] = None daily_data = data.get("daily", {}) daily_sunrise = daily_data.get("sunrise", []) daily_sunset = daily_data.get("sunset", []) if len(daily_sunrise) > 0: today_highlights["sunrise"] = daily_sunrise[0][11:16] else: today_highlights["sunrise"] = None if len(daily_sunset) > 0: today_highlights["sunset"] = daily_sunset[0][11:16] else: today_highlights["sunset"] = None daily_times = daily_data.get("time", []) daily_codes = daily_data.get("weathercode", []) daily_tmax = daily_data.get("temperature_2m_max", []) daily_tmin = daily_data.get("temperature_2m_min", []) forecast_list = [] def get_hour_temp(date_str, hour_str): target = date_str + "T" + hour_str + ":00" best_idx = None best_diff = None dt_target = parse_iso_datetime(target) for i, ht in enumerate(hour_times): dt_ht = parse_iso_datetime(ht) diff = abs((dt_ht - dt_target).total_seconds()) if best_diff is None or diff < best_diff: best_diff = diff best_idx = i if best_idx is not None and best_idx < len(hour_temp): return hour_temp[best_idx] return None for i in range(len(daily_times)): date_str = daily_times[i] dt_obj = parse_iso_datetime(date_str) day_name = dt_obj.strftime("%A") short_date = dt_obj.strftime("%b %d") code = daily_codes[i] if i < len(daily_codes) else None icon = get_weather_icon(code) desc = get_weather_description(code) tmax = daily_tmax[i] if i < len(daily_tmax) else None tmin = daily_tmin[i] if i < len(daily_tmin) else None avg_temp = round((tmax + tmin) / 2, 1) if tmax is not None and tmin is not None else None morning_temp = get_hour_temp(date_str, "09") evening_temp = get_hour_temp(date_str, "21") sr = daily_sunrise[i][11:16] if i < len(daily_sunrise) else None ss = daily_sunset[i][11:16] if i < len(daily_sunset) else None forecast_list.append({ "day_name": day_name, "date_str": short_date, "icon": icon, "desc": desc, "avg_temp": avg_temp, "morning_temp": morning_temp, "evening_temp": evening_temp, "sunrise": sr, "sunset": ss, "tmax": tmax, "tmin": tmin }) alerts, critical_days, warning_days = check_and_collect_alerts(forecast_list) # Generate AI recommendations for frontend display ai_recommendations = None if alerts or critical_days or warning_days: weather_summary = f"{len(critical_days)} critical days, {len(warning_days)} warning days" ai_recommendations = generate_personalized_recommendations( weather_summary, location_address, critical_days, warning_days ) # Send WhatsApp alerts if needed if alerts: alert_message = "\n".join(alerts) send_whatsapp_message(alert_message, (lat, lon), location_address, critical_days, warning_days) alerts_sent = True else: alerts_sent = False return render_template( "index.html", lat=lat, lon=lon, location_address=location_address, current_temp=current_temp, current_icon=current_icon, current_desc=current_desc, current_time=current_time_formatted, current_wind_speed=current_wind_speed, current_wind_dir=current_wind_dir, feels_like=feels_like, today_highlights=today_highlights, forecast_list=forecast_list, timezone=timezone, alerts_sent=alerts_sent, ai_recommendations=ai_recommendations, critical_days=critical_days, warning_days=warning_days ) if __name__ == "__main__": app.run(debug=True,port=5001)