Rajkhanke007's picture
Update app.py
9239678 verified
raw
history blame
13.9 kB
import requests
import json
from flask import Flask, render_template, request, jsonify, Response, send_file
from google import genai
from gtts import gTTS
import os
app = Flask(__name__)
app.config['AUDIO_FOLDER'] = 'static/audio'
os.makedirs(app.config['AUDIO_FOLDER'], exist_ok=True)
# IMPORTANT: Replace with your actual Gemini API key
api_key = os.getenv('GEMINI_API_KEY')
if not api_key:
api_key = os.getenv('GEMINI_API_KEY_1')
if not api_key:
api_key = os.getenv('GEMINI_API_KEY_2')
client = genai.Client(api_key=api_key)
def validate_coordinates(lat, lon):
"""Validate and convert latitude and longitude to float."""
try:
return float(lat), float(lon)
except (TypeError, ValueError):
return None, None
@app.route('/')
def index():
return render_template('index.html')
@app.route('/get_weather_data', methods=['GET'])
def get_weather_data():
"""
Fetch weather data using Open-Meteo's forecast endpoint.
"""
lat = request.args.get('lat')
lon = request.args.get('lon')
lat, lon = validate_coordinates(lat, lon)
if lat is None or lon is None:
return jsonify({"error": "Invalid coordinates"}), 400
try:
forecast_url = "https://api.open-meteo.com/v1/forecast"
forecast_params = {
"latitude": lat,
"longitude": lon,
"current_weather": "true",
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum",
"hourly": "relative_humidity_2m,soil_moisture_3_to_9cm,cloudcover,windspeed_10m",
"timezone": "auto"
}
resp = requests.get(forecast_url, params=forecast_params)
resp.raise_for_status()
data = resp.json()
daily = data.get("daily", {})
hourly = data.get("hourly", {})
current = data.get("current_weather", {})
# Daily data
max_temp = daily.get("temperature_2m_max", [None])[0]
min_temp = daily.get("temperature_2m_min", [None])[0]
rain = daily.get("precipitation_sum", [None])[0]
# Hourly data (averages)
humidity_list = hourly.get("relative_humidity_2m", [])
soil_list = hourly.get("soil_moisture_3_to_9cm", [])
cloud_list = hourly.get("cloudcover", [])
avg_humidity = sum(humidity_list)/len(humidity_list) if humidity_list else None
avg_soil_moisture = sum(soil_list)/len(soil_list) if soil_list else None
avg_cloud_cover = sum(cloud_list)/len(cloud_list) if cloud_list else None
# Current weather
current_temp = current.get("temperature")
wind_speed = current.get("windspeed")
weather = {
"max_temp": max_temp, "min_temp": min_temp, "rainfall": rain,
"humidity": avg_humidity, "soil_moisture": avg_soil_moisture,
"current_temp": current_temp, "wind_speed": wind_speed, "cloud_cover": avg_cloud_cover
}
return jsonify(weather)
except Exception as e:
return jsonify({"error": str(e)}), 500
def call_gemini_api(input_data, language):
"""
Calls the Gemini API to get a pest outbreak report in a structured JSON format.
"""
prompt = f"""
Analyze the provided agricultural and weather data to generate a pest outbreak report.
Your response MUST be a single, valid JSON object and nothing else. Do not wrap it in markdown backticks.
The entire text content within the JSON must be in the '{language}' language.
This is the required JSON structure:
{{
"report_title": "Pest Outbreak Dashboard Report",
"location_info": {{
"latitude": "{input_data.get('latitude')}",
"longitude": "{input_data.get('longitude')}",
"derived_location": "A human-readable location derived from the coordinates (e.g., 'Nagpur, India')."
}},
"agricultural_inputs_analysis": "A detailed paragraph analyzing the provided agricultural inputs (crop type, growth stage, irrigation, etc.) and their potential impact on pest outbreaks.",
"pest_prediction_table": [
{{
"pest_name": "Name of the predicted pest",
"outbreak_months": "Predicted month(s) for the outbreak",
"severity": "Predicted severity level (e.g., Low, Medium, High)",
"precautionary_measures": "A short description of key precautionary measures."
}}
],
"pest_avoidance_practices": [
"A detailed, specific pest avoidance practice based on the inputs.",
"Another specific recommendation.",
"Provide 10-12 detailed bullet points."
],
"agricultural_best_practices": [
"A specific agricultural best practice based on the inputs.",
"Another specific recommendation related to crop management."
],
"predicted_pest_damage_info": "A paragraph describing the potential damage the predicted pests could cause to the specified crop."
}}
Use the following data for your analysis:
- Crop Type: {input_data.get('crop_type')}
- Sowing Date: {input_data.get('sowing_date')}
- Harvest Date: {input_data.get('harvest_date')}
- Current Growth Stage: {input_data.get('growth_stage')}
- Irrigation Frequency: {input_data.get('irrigation_freq')}
- Irrigation Method: {input_data.get('irrigation_method')}
- Soil Type: {input_data.get('soil_type')}
- Max Temp: {input_data.get('max_temp')}°C
- Min Temp: {input_data.get('min_temp')}°C
- Humidity: {input_data.get('humidity')}%
- Rainfall: {input_data.get('rain')}mm
- Soil Moisture: {input_data.get('soil_moisture')}%
- Wind Speed: {input_data.get('wind_speed')} km/h
"""
try:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt
)
json_text = response.text.strip().replace("```json", "").replace("```", "")
return json.loads(json_text)
except Exception as e:
print(f"Error calling Gemini or parsing JSON: {e}")
return {"error": "Failed to generate a valid report from the AI model. The model may have returned an unexpected format. Please try again."}
@app.route('/predict', methods=['POST'])
def predict():
form_data = request.form.to_dict()
language = form_data.get("language", "English")
report_data = call_gemini_api(form_data, language)
report_html = ""
# Check for an error from the API call
if "error" in report_data:
report_html = f"<h1>Error</h1><p>{report_data['error']}</p>"
audio_url = None
else:
# Build the HTML report dynamically from the JSON data
location = report_data.get('location_info', {})
report_html += f"<h1>{report_data.get('report_title', 'Pest Outbreak Report')}</h1>"
report_html += f"<p><strong>Location:</strong> {location.get('derived_location', 'N/A')} (Lat: {location.get('latitude', 'N/A')}, Lon: {location.get('longitude', 'N/A')})</p>"
report_html += f"<h2>Agricultural Input Parameter Analysis</h2><p>{report_data.get('agricultural_inputs_analysis', 'No analysis available.')}</p>"
report_html += "<h2>Pest Outbreak Prediction</h2><table><thead><tr><th>Pest Name</th><th>Predicted Outbreak Month(s)</th><th>Severity</th><th>Precautionary Measures</th></tr></thead><tbody>"
pest_table = report_data.get('pest_prediction_table', [])
if pest_table:
for pest in pest_table:
report_html += f"""
<tr>
<td data-label=\"Pest Name\">{pest.get('pest_name', 'N/A')}</td>
<td data-label=\"Outbreak Month(s)\">{pest.get('outbreak_months', 'N/A')}</td>
<td data-label=\"Severity\">{pest.get('severity', 'N/A')}</td>
<td data-label=\"Precautionary Measures\">{pest.get('precautionary_measures', 'N/A')}</td>
</tr>
"""
else:
report_html += '<tr><td colspan="4">No specific pest predictions available.</td></tr>'
report_html += "</tbody></table>"
report_html += "<h2>Pest Avoidance Practices</h2><ul>"
avoidance_practices = report_data.get('pest_avoidance_practices', [])
if avoidance_practices:
for practice in avoidance_practices:
report_html += f"<li>{practice}</li>"
else:
report_html += '<li>No specific avoidance practices available.</li>'
report_html += "</ul>"
report_html += "<h2>Specific Agricultural Best Practices</h2><ul>"
best_practices = report_data.get('agricultural_best_practices', [])
if best_practices:
for practice in best_practices:
report_html += f"<li>{practice}</li>"
else:
report_html += '<li>No specific agricultural best practices available.</li>'
report_html += "</ul>"
report_html += f"<h2>Potential Damage from Predicted Pests</h2><p>{report_data.get('predicted_pest_damage_info', 'No information available.')}</p>"
# Generate summary for voice (short summary)
summary = f"Pest Outbreak Report for {location.get('derived_location', 'your location')}. "
summary += report_data.get('agricultural_inputs_analysis', '')[:200] + "... "
if pest_table:
summary += f"Predicted pests: " + ', '.join([p.get('pest_name', '') for p in pest_table]) + ". "
summary += f"Severity: " + ', '.join([p.get('severity', '') for p in pest_table]) + ". "
summary += report_data.get('predicted_pest_damage_info', '')[:200]
# Generate audio file using gTTS
tts = gTTS(summary, lang='en')
audio_filename = f"pest_report_{location.get('latitude', 'lat')}_{location.get('longitude', 'lon')}.mp3"
audio_path = os.path.join(app.config['AUDIO_FOLDER'], audio_filename)
tts.save(audio_path)
audio_url = f"/static/audio/{audio_filename}"
# Build audio button HTML separately to avoid nested f-string and backslash issues
audio_player_html = ""
if audio_url:
audio_player_html = (
'<div style="text-align:center;margin-bottom:2rem;">'
'<audio id="audioSummary" class="audio-player" src="{}" controls style="width:350px;max-width:90%;margin-bottom:1rem;"></audio>'
'<br>'
'</div>'
).format(audio_url)
html_output = f"""<!DOCTYPE html>
<html lang='{language[:2]}'>
<head>
<meta charset='UTF-8'>
<title>Pest Outbreak Dashboard Report</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<style>
body {{
margin: 0; padding: 2rem; background: linear-gradient(120deg, #f7f7f7 0%, #e3f2fd 100%);
font-family: 'Segoe UI', Tahoma, sans-serif;
}}
.report-container {{
max-width: 1000px; margin: 0 auto; background-color: #ffffff;
border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); padding: 2rem;
}}
.report-container h1 {{
text-align: center; font-size: 2rem; font-weight: bold; margin-bottom: 1.5rem; color: #ffffff;
background: linear-gradient(to right, #81c784, #388e3c); padding: 1rem; border-radius: 6px;
}}
.report-container h2 {{
margin-top: 2rem; margin-bottom: 1rem; color: #2e7d32; border-bottom: 2px solid #a5d6a7;
padding-bottom: 0.5rem; font-size: 1.5rem; font-weight: 600;
}}
.report-container p {{
margin-bottom: 1rem; color: #555555; text-align: justify; line-height: 1.6;
}}
.report-container ul {{
list-style-type: none; padding-left: 0; margin-bottom: 1rem;
}}
.report-container ul li {{
padding-left: 1.5em; margin-bottom: 0.75em; position: relative; color: #555;
}}
.report-container ul li::before {{
content: '✔'; position: absolute; left: 0; color: #4caf50; font-weight: bold;
}}
.report-container table {{
width: 100%; border-collapse: collapse; margin: 1.5rem 0; box-shadow: 0 2px 4px rgba(0,0,0,0.05);
font-size: 1rem;
}}
.report-container thead tr {{
background: linear-gradient(to right, #81c784, #388e3c); color: #ffffff; text-align: left;
}}
.report-container th, .report-container td {{
border: 1px solid #e0e0e0; padding: 12px 15px; text-align: left;
}}
.report-container tbody tr:nth-child(even) {{ background-color: #f9f9f9; }}
.report-container tbody tr:hover {{ background-color: #e8f5e9; }}
.audio-player {{ width: 100%; max-width: 350px; margin: 0 auto 1rem auto; display: block; }}
.audio-summary-btn {{ font-size: 1.1rem; }}
@media (max-width: 768px) {{
body {{ padding: 0.5rem; }}
.report-container {{ padding: 0.5rem; border-radius: 6px; }}
.report-container h1 {{ font-size: 1.2rem; padding: 0.5rem; margin-bottom: 0.7rem; }}
.report-container h2 {{ font-size: 1rem; margin-top: 1rem; margin-bottom: 0.5rem; padding-bottom: 0.2rem; }}
.report-container table, .report-container tbody, .report-container tr, .report-container td {{ font-size: 0.9rem; }}
.audio-player {{ max-width: 98vw; }}
}}
@media (max-width: 480px) {{
.report-container {{ padding: 0.2rem; border-radius: 4px; }}
.report-container h1 {{ font-size: 1rem; padding: 0.3rem; margin-bottom: 0.4rem; }}
.report-container h2 {{ font-size: 0.85rem; margin-top: 0.7rem; margin-bottom: 0.3rem; padding-bottom: 0.1rem; }}
.report-container table, .report-container tbody, .report-container tr, .report-container td {{ font-size: 0.8rem; }}
.audio-player {{ max-width: 99vw; }}
}}
</style>
</head>
<body>
<div class='report-container'>{audio_player_html}
{report_html}
</div>
</body>
</html>"""
return Response(html_output, mimetype="text/html")
if __name__ == '__main__':
app.run(debug=True)