krushimitravit's picture
Upload 5 files
6bdc836 verified
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)