|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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", {}) |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
|
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_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 = "" |
|
|
|
|
|
if "error" in report_data: |
|
|
report_html = f"<h1>Error</h1><p>{report_data['error']}</p>" |
|
|
audio_url = None |
|
|
else: |
|
|
|
|
|
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>" |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
|
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}" |
|
|
|
|
|
|
|
|
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) |