anasfsd123 commited on
Commit
9a38f5b
·
verified ·
1 Parent(s): 6ff6245

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +702 -0
app.py ADDED
@@ -0,0 +1,702 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from groq import Groq
3
+ import requests
4
+ import pandas as pd
5
+ from datetime import datetime, timedelta
6
+ import pycountry
7
+ from fpdf import FPDF
8
+ import io
9
+ import base64
10
+ from geopy.geocoders import Nominatim
11
+ from geopy.exc import GeocoderTimedOut
12
+ import plotly.express as px
13
+ import plotly.graph_objects as go
14
+ import unicodedata
15
+ import os
16
+ from dotenv import load_dotenv
17
+
18
+ # Load environment variables
19
+ load_dotenv()
20
+
21
+ # Get API keys from environment variables
22
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
23
+ AIRVISUAL_API_KEY = os.getenv("AIRVISUAL_API_KEY")
24
+ DEFAULT_MODEL = "llama3-70b-8192"
25
+
26
+ # === INIT Groq CLIENT ===
27
+ client = Groq(api_key=GROQ_API_KEY)
28
+
29
+ # === PAGE CONFIG ===
30
+ st.set_page_config(
31
+ page_title="🌱 AI Climate & Smart Farming Assistant",
32
+ page_icon="🌾",
33
+ layout="wide",
34
+ initial_sidebar_state="expanded"
35
+ )
36
+
37
+ # === CSS STYLING ===
38
+ st.markdown(
39
+ """
40
+ <style>
41
+ .main {
42
+ background-color: #f9f9f9;
43
+ color: #222;
44
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
45
+ }
46
+ .title {
47
+ text-align: center;
48
+ color: #2E7D32;
49
+ font-weight: 800;
50
+ }
51
+ .subtitle {
52
+ text-align: center;
53
+ font-size: 18px;
54
+ margin-bottom: 20px;
55
+ color: #4CAF50;
56
+ }
57
+ .history-box {
58
+ background-color: #e8f5e9;
59
+ padding: 10px;
60
+ margin-bottom: 10px;
61
+ border-radius: 8px;
62
+ border-left: 5px solid #66bb6a;
63
+ color: #000000;
64
+ }
65
+ .ai-response {
66
+ background-color: #c8e6c9;
67
+ padding: 10px;
68
+ margin-bottom: 15px;
69
+ border-radius: 10px;
70
+ white-space: pre-wrap;
71
+ color: #000000;
72
+ }
73
+ .user-input {
74
+ background-color: #dcedc8;
75
+ padding: 8px;
76
+ border-radius: 8px;
77
+ font-weight: bold;
78
+ margin-bottom: 5px;
79
+ color: #000000;
80
+ }
81
+ .download-button {
82
+ background-color: #4CAF50;
83
+ color: white;
84
+ padding: 10px 20px;
85
+ border-radius: 5px;
86
+ text-decoration: none;
87
+ display: inline-block;
88
+ margin: 10px 0;
89
+ }
90
+ .insight-box {
91
+ background-color: #e1f5fe;
92
+ padding: 15px;
93
+ border-radius: 10px;
94
+ margin: 15px 0;
95
+ border-left: 4px solid #0288d1;
96
+ color: #000000;
97
+ font-weight: 500;
98
+ line-height: 1.6;
99
+ }
100
+ </style>
101
+ """,
102
+ unsafe_allow_html=True
103
+ )
104
+
105
+ # === HEADER ===
106
+ st.markdown("<h1 class='title'>🌾 AI Climate & Smart Farming Assistant</h1>", unsafe_allow_html=True)
107
+ st.markdown("<p class='subtitle'>Real-time AI insights + live weather data</p>", unsafe_allow_html=True)
108
+ st.markdown("---")
109
+
110
+ # === SYSTEM PROMPTS ===
111
+ system_prompts = {
112
+ "Track Pollution": (
113
+ "You are an expert environmental scientist. "
114
+ "Help users understand pollution levels in air, water, or soil using scientific reasoning. "
115
+ "Provide actionable recommendations for improvement."
116
+ ),
117
+ "Carbon Emissions": (
118
+ "You are a sustainability advisor. "
119
+ "Estimate and explain carbon emissions, suggest reductions and eco-friendly alternatives. "
120
+ "Include cost-benefit analysis and ROI calculations."
121
+ ),
122
+ "Predict Climate Patterns": (
123
+ "You are a climate researcher. Predict or explain regional climate changes using current and historical data. "
124
+ "Include statistical analysis and confidence intervals."
125
+ ),
126
+ "Smart Farming Advice": (
127
+ "You are an AI-powered farming assistant. Help users with crop selection, irrigation, pest control, and yield optimization. "
128
+ "Focus on sustainable practices and resource efficiency."
129
+ ),
130
+ }
131
+
132
+ # === EXAMPLE QUERIES ===
133
+ example_queries = {
134
+ "Track Pollution": "e.g., What's the air quality near Lahore right now?",
135
+ "Carbon Emissions": "e.g., How can a factory reduce CO2 output sustainably?",
136
+ "Predict Climate Patterns": "e.g., What climate changes are expected in sub-Saharan Africa?",
137
+ "Smart Farming Advice": "e.g., Best crops to grow in dry conditions in Uganda?",
138
+ }
139
+
140
+ # === UTILS: API CALLS ===
141
+ def get_weather(location: str):
142
+ try:
143
+ # First, get coordinates for the location
144
+ geocoding_url = f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1"
145
+ geo_resp = requests.get(geocoding_url, timeout=10)
146
+ geo_resp.raise_for_status()
147
+ geo_data = geo_resp.json()
148
+
149
+ if not geo_data.get('results'):
150
+ return None
151
+
152
+ lat = geo_data['results'][0]['latitude']
153
+ lon = geo_data['results'][0]['longitude']
154
+ location_name = geo_data['results'][0]['name']
155
+
156
+ # Then get weather data for those coordinates
157
+ weather_url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code"
158
+ weather_resp = requests.get(weather_url, timeout=10)
159
+ weather_resp.raise_for_status()
160
+ weather_data = weather_resp.json()
161
+
162
+ # Weather code to description mapping
163
+ weather_codes = {
164
+ 0: "Clear sky",
165
+ 1: "Mainly clear",
166
+ 2: "Partly cloudy",
167
+ 3: "Overcast",
168
+ 45: "Foggy",
169
+ 48: "Depositing rime fog",
170
+ 51: "Light drizzle",
171
+ 53: "Moderate drizzle",
172
+ 55: "Dense drizzle",
173
+ 61: "Slight rain",
174
+ 63: "Moderate rain",
175
+ 65: "Heavy rain",
176
+ 71: "Slight snow",
177
+ 73: "Moderate snow",
178
+ 75: "Heavy snow",
179
+ 77: "Snow grains",
180
+ 80: "Slight rain showers",
181
+ 81: "Moderate rain showers",
182
+ 82: "Violent rain showers",
183
+ 85: "Slight snow showers",
184
+ 86: "Heavy snow showers",
185
+ 95: "Thunderstorm",
186
+ 96: "Thunderstorm with slight hail",
187
+ 99: "Thunderstorm with heavy hail"
188
+ }
189
+
190
+ current = weather_data['current']
191
+ weather_code = current['weather_code']
192
+ weather_desc = weather_codes.get(weather_code, "Unknown")
193
+
194
+ return {
195
+ "location": location_name,
196
+ "description": weather_desc,
197
+ "temperature_C": current['temperature_2m'],
198
+ "humidity_%": current['relative_humidity_2m'],
199
+ "wind_speed_m/s": current['wind_speed_10m']
200
+ }
201
+ except Exception as e:
202
+ return None
203
+
204
+ def get_historical_weather(location: str, days: int = 7):
205
+ try:
206
+ # Get coordinates
207
+ geocoding_url = f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1"
208
+ geo_resp = requests.get(geocoding_url, timeout=10)
209
+ geo_resp.raise_for_status()
210
+ geo_data = geo_resp.json()
211
+
212
+ if not geo_data.get('results'):
213
+ return None
214
+
215
+ lat = geo_data['results'][0]['latitude']
216
+ lon = geo_data['results'][0]['longitude']
217
+
218
+ # Get historical data
219
+ end_date = datetime.now()
220
+ start_date = end_date - timedelta(days=days)
221
+
222
+ weather_url = (
223
+ f"https://api.open-meteo.com/v1/forecast"
224
+ f"?latitude={lat}&longitude={lon}"
225
+ f"&start_date={start_date.strftime('%Y-%m-%d')}"
226
+ f"&end_date={end_date.strftime('%Y-%m-%d')}"
227
+ f"&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max"
228
+ )
229
+
230
+ weather_resp = requests.get(weather_url, timeout=10)
231
+ weather_resp.raise_for_status()
232
+ return weather_resp.json()
233
+ except Exception as e:
234
+ return None
235
+
236
+ def get_air_quality(location: str):
237
+ try:
238
+ # First, get coordinates for the location
239
+ geocoding_url = f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1"
240
+ geo_resp = requests.get(geocoding_url, timeout=10)
241
+ geo_resp.raise_for_status()
242
+ geo_data = geo_resp.json()
243
+
244
+ if not geo_data.get('results'):
245
+ return None
246
+
247
+ lat = geo_data['results'][0]['latitude']
248
+ lon = geo_data['results'][0]['longitude']
249
+
250
+ # Try Open-Meteo API first
251
+ aq_url = f"https://air-quality-api.open-meteo.com/v1/air-quality?latitude={lat}&longitude={lon}&current=pm10,pm2_5,ozone,nitrogen_dioxide,sulphur_dioxide"
252
+ aq_resp = requests.get(aq_url, timeout=10)
253
+
254
+ if aq_resp.status_code == 200:
255
+ aq_data = aq_resp.json()
256
+ if 'current' in aq_data:
257
+ return aq_data
258
+
259
+ # If Open-Meteo fails, try AirVisual API
260
+ airvisual_url = f"http://api.airvisual.com/v2/nearest_city?lat={lat}&lon={lon}&key={AIRVISUAL_API_KEY}"
261
+ airvisual_resp = requests.get(airvisual_url, timeout=10)
262
+
263
+ if airvisual_resp.status_code == 200:
264
+ airvisual_data = airvisual_resp.json()
265
+ if 'data' in airvisual_data and 'current' in airvisual_data['data']:
266
+ current = airvisual_data['data']['current']['pollution']
267
+ return {
268
+ 'current': {
269
+ 'pm10': current.get('p1'),
270
+ 'pm2_5': current.get('p2'),
271
+ 'ozone': current.get('o3'),
272
+ 'nitrogen_dioxide': None,
273
+ 'sulphur_dioxide': None
274
+ }
275
+ }
276
+
277
+ return None
278
+ except Exception as e:
279
+ print(f"Air quality error: {str(e)}")
280
+ return None
281
+
282
+ # === UTILS: PDF Generation ===
283
+ def clean_text_for_pdf(text):
284
+ """Clean text to be PDF-safe by removing or replacing problematic characters"""
285
+ # Normalize Unicode characters
286
+ text = unicodedata.normalize('NFKD', text)
287
+ # Replace common problematic characters
288
+ replacements = {
289
+ 'μ': 'micro',
290
+ '°': ' degrees',
291
+ '℃': 'C',
292
+ '±': '+/-',
293
+ '×': 'x',
294
+ '÷': '/',
295
+ '≤': '<=',
296
+ '≥': '>=',
297
+ '≠': '!=',
298
+ '∞': 'infinity',
299
+ '→': '->',
300
+ '←': '<-',
301
+ '↑': 'up',
302
+ '↓': 'down',
303
+ '↔': '<->',
304
+ '≈': '~=',
305
+ '∑': 'sum',
306
+ '∏': 'product',
307
+ '√': 'sqrt',
308
+ '∫': 'integral',
309
+ '∆': 'delta',
310
+ '∇': 'nabla',
311
+ '∂': 'partial',
312
+ '∝': 'proportional to',
313
+ '∞': 'infinity',
314
+ '∅': 'empty set',
315
+ '∈': 'in',
316
+ '∉': 'not in',
317
+ '⊂': 'subset',
318
+ '⊃': 'superset',
319
+ '∪': 'union',
320
+ '∩': 'intersection',
321
+ '∀': 'for all',
322
+ '∃': 'exists',
323
+ '∄': 'does not exist',
324
+ '∴': 'therefore',
325
+ '∵': 'because'
326
+ }
327
+ for char, replacement in replacements.items():
328
+ text = text.replace(char, replacement)
329
+ return text
330
+
331
+ def generate_pdf(chat_history, title="AI Climate & Farming Advice"):
332
+ pdf = FPDF()
333
+ pdf.add_page()
334
+
335
+ # Use built-in font
336
+ pdf.set_font("helvetica", "B", 16)
337
+ pdf.cell(0, 10, clean_text_for_pdf(title), ln=True, align='C')
338
+ pdf.ln(10)
339
+
340
+ # Chat history
341
+ for chat in chat_history:
342
+ # User message
343
+ pdf.set_font("helvetica", "B", 12)
344
+ pdf.cell(0, 10, "User:", ln=True)
345
+ pdf.set_font("helvetica", "", 12)
346
+ # Clean and wrap text
347
+ user_text = clean_text_for_pdf(chat["user"])
348
+ pdf.multi_cell(0, 10, user_text)
349
+ pdf.ln(5)
350
+
351
+ # AI response
352
+ pdf.set_font("helvetica", "B", 12)
353
+ pdf.cell(0, 10, "AI Response:", ln=True)
354
+ pdf.set_font("helvetica", "", 12)
355
+ # Clean and wrap text
356
+ ai_text = clean_text_for_pdf(chat["ai"])
357
+ pdf.multi_cell(0, 10, ai_text)
358
+ pdf.ln(10)
359
+
360
+ return pdf.output(dest="S").encode("latin-1", "replace")
361
+
362
+ # === UTILS: Get Country List ===
363
+ def get_country_list():
364
+ countries = [country.name for country in pycountry.countries]
365
+ return sorted(countries)
366
+
367
+ # === SIDEBAR ===
368
+ st.sidebar.header("🌟 Features")
369
+ page = st.sidebar.radio(
370
+ "Choose your tool:",
371
+ [
372
+ "AI Assistant Chat",
373
+ "Weather Data",
374
+ "Smart Farming CSV Analysis",
375
+ ]
376
+ )
377
+
378
+ # === MULTI-TURN CHAT ===
379
+ if page == "AI Assistant Chat":
380
+ st.subheader("🧠 AI Climate & Farming Chat Assistant")
381
+ option = st.selectbox(
382
+ "Choose a use case:",
383
+ list(system_prompts.keys())
384
+ )
385
+ st.markdown(f"💡 *Example*: {example_queries[option]}")
386
+
387
+ user_input = st.text_area("Enter your question or describe your situation:")
388
+
389
+ if "chat_history" not in st.session_state:
390
+ st.session_state.chat_history = []
391
+
392
+ if st.button("Send to AI") and user_input.strip():
393
+ with st.spinner("Thinking..."):
394
+ messages = [
395
+ {"role": "system", "content": system_prompts[option]},
396
+ ]
397
+ # Append chat history for multi-turn
398
+ for chat in st.session_state.chat_history:
399
+ messages.append({"role": "user", "content": chat["user"]})
400
+ messages.append({"role": "assistant", "content": chat["ai"]})
401
+ # Add current user input
402
+ messages.append({"role": "user", "content": user_input})
403
+
404
+ response = client.chat.completions.create(
405
+ model=DEFAULT_MODEL,
406
+ messages=messages,
407
+ )
408
+ ai_response = response.choices[0].message.content
409
+
410
+ # Save chat
411
+ st.session_state.chat_history.append({"user": user_input, "ai": ai_response})
412
+
413
+ # Clear input box
414
+ st.rerun()
415
+
416
+ if st.session_state.chat_history:
417
+ st.markdown("### 🕘 Conversation History")
418
+ for chat in reversed(st.session_state.chat_history):
419
+ st.markdown(f"<div class='user-input'>You:</div><div>{chat['user']}</div>", unsafe_allow_html=True)
420
+ st.markdown(f"<div class='ai-response'>{chat['ai']}</div>", unsafe_allow_html=True)
421
+
422
+ # Add PDF download button
423
+ if st.button("Download Chat as PDF"):
424
+ pdf_bytes = generate_pdf(st.session_state.chat_history)
425
+ st.download_button(
426
+ label="Click to Download PDF",
427
+ data=pdf_bytes,
428
+ file_name="climate_advice.pdf",
429
+ mime="application/pdf"
430
+ )
431
+
432
+ if st.button("Clear Chat History"):
433
+ st.session_state.chat_history = []
434
+ st.rerun()
435
+
436
+ # === WEATHER DATA PAGE ===
437
+ elif page == "Weather Data":
438
+ st.subheader("🌍 Advanced Weather & Environmental Data")
439
+
440
+ location_method = st.radio(
441
+ "Choose location input method:",
442
+ ["Enter City", "Select Country"]
443
+ )
444
+
445
+ location = None
446
+ if location_method == "Enter City":
447
+ location = st.text_input("Enter a city or location (e.g., Los Angeles, Delhi):")
448
+ elif location_method == "Select Country":
449
+ country = st.selectbox("Select a country:", get_country_list())
450
+ city = st.text_input("Enter city name:")
451
+ location = f"{city}, {country}" if city else None
452
+
453
+ if location:
454
+ tab1, tab2, tab3 = st.tabs(["Current Weather", "Historical Data", "Air Quality"])
455
+
456
+ with tab1:
457
+ if st.button("Get Current Weather"):
458
+ with st.spinner("Fetching data..."):
459
+ weather_data = get_weather(location)
460
+ if weather_data is None:
461
+ st.error("Failed to fetch weather data for this location.")
462
+ else:
463
+ col1, col2 = st.columns(2)
464
+
465
+ with col1:
466
+ st.markdown(f"### Current Weather in {weather_data['location']}:")
467
+ st.write(f"- Description: {weather_data['description']}")
468
+ st.write(f"- Temperature: {weather_data['temperature_C']} °C")
469
+ st.write(f"- Humidity: {weather_data['humidity_%']} %")
470
+ st.write(f"- Wind Speed: {weather_data['wind_speed_m/s']} m/s")
471
+
472
+ with col2:
473
+ fig = go.Figure()
474
+ fig.add_trace(go.Indicator(
475
+ mode="gauge+number",
476
+ value=weather_data['temperature_C'],
477
+ title={'text': "Temperature (°C)"},
478
+ gauge={'axis': {'range': [-20, 40]},
479
+ 'bar': {'color': "darkgreen"}}
480
+ ))
481
+ st.plotly_chart(fig)
482
+
483
+ with tab2:
484
+ days = st.slider("Select number of days for historical data:", 1, 30, 7)
485
+ if st.button("Get Historical Weather"):
486
+ with st.spinner("Fetching historical data..."):
487
+ hist_data = get_historical_weather(location, days)
488
+ if hist_data is None:
489
+ st.error("Failed to fetch historical weather data.")
490
+ else:
491
+ daily = hist_data['daily']
492
+ df = pd.DataFrame({
493
+ 'Date': pd.date_range(start=daily['time'][0], periods=len(daily['time'])),
494
+ 'Max Temp': daily['temperature_2m_max'],
495
+ 'Min Temp': daily['temperature_2m_min'],
496
+ 'Precipitation': daily['precipitation_sum'],
497
+ 'Wind Speed': daily['wind_speed_10m_max']
498
+ })
499
+
500
+ # Create temperature range plot
501
+ fig = go.Figure()
502
+ fig.add_trace(go.Scatter(
503
+ x=df['Date'],
504
+ y=df['Max Temp'],
505
+ name='Max Temperature',
506
+ line=dict(color='red')
507
+ ))
508
+ fig.add_trace(go.Scatter(
509
+ x=df['Date'],
510
+ y=df['Min Temp'],
511
+ name='Min Temperature',
512
+ line=dict(color='blue'),
513
+ fill='tonexty'
514
+ ))
515
+ fig.update_layout(
516
+ title='Temperature Range Over Time',
517
+ xaxis_title='Date',
518
+ yaxis_title='Temperature (°C)',
519
+ hovermode='x unified'
520
+ )
521
+ st.plotly_chart(fig)
522
+
523
+ # Create precipitation and wind speed plot
524
+ fig2 = go.Figure()
525
+ fig2.add_trace(go.Bar(
526
+ x=df['Date'],
527
+ y=df['Precipitation'],
528
+ name='Precipitation',
529
+ marker_color='lightblue'
530
+ ))
531
+ fig2.add_trace(go.Scatter(
532
+ x=df['Date'],
533
+ y=df['Wind Speed'],
534
+ name='Wind Speed',
535
+ line=dict(color='orange'),
536
+ yaxis='y2'
537
+ ))
538
+ fig2.update_layout(
539
+ title='Precipitation and Wind Speed',
540
+ xaxis_title='Date',
541
+ yaxis_title='Precipitation (mm)',
542
+ yaxis2=dict(
543
+ title='Wind Speed (m/s)',
544
+ overlaying='y',
545
+ side='right'
546
+ )
547
+ )
548
+ st.plotly_chart(fig2)
549
+
550
+ with tab3:
551
+ if st.button("Get Air Quality Data"):
552
+ with st.spinner("Fetching air quality data..."):
553
+ aq_data = get_air_quality(location)
554
+ if aq_data is None:
555
+ st.error("Failed to fetch air quality data.")
556
+ else:
557
+ st.markdown(f"### Air Quality in {location}")
558
+ current = aq_data['current']
559
+
560
+ # Create air quality gauges
561
+ col1, col2, col3 = st.columns(3)
562
+
563
+ # Define parameters
564
+ params = {
565
+ 'pm10': {'name': 'PM10 (μg/m³)', 'range': [0, 100]},
566
+ 'pm2_5': {'name': 'PM2.5 (μg/m³)', 'range': [0, 50]},
567
+ 'ozone': {'name': 'Ozone (μg/m³)', 'range': [0, 100]},
568
+ 'nitrogen_dioxide': {'name': 'Nitrogen Dioxide (μg/m³)', 'range': [0, 100]},
569
+ 'sulphur_dioxide': {'name': 'Sulphur Dioxide (μg/m³)', 'range': [0, 100]}
570
+ }
571
+
572
+ # Display gauges for first 3 parameters
573
+ for i, param in enumerate(['pm2_5', 'pm10', 'ozone']):
574
+ if param in current and current[param] is not None:
575
+ with [col1, col2, col3][i]:
576
+ fig = go.Figure(go.Indicator(
577
+ mode="gauge+number",
578
+ value=current[param],
579
+ title={'text': params[param]['name']},
580
+ gauge={'axis': {'range': params[param]['range']},
581
+ 'bar': {'color': "darkgreen"}}
582
+ ))
583
+ st.plotly_chart(fig)
584
+
585
+ # Display other pollutants
586
+ st.markdown("### Other Pollutants")
587
+ col1, col2 = st.columns(2)
588
+ with col1:
589
+ if 'nitrogen_dioxide' in current and current['nitrogen_dioxide'] is not None:
590
+ st.write(f"- Nitrogen Dioxide: {current['nitrogen_dioxide']} μg/m³")
591
+ with col2:
592
+ if 'sulphur_dioxide' in current and current['sulphur_dioxide'] is not None:
593
+ st.write(f"- Sulphur Dioxide: {current['sulphur_dioxide']} μg/m³")
594
+
595
+ # === SMART FARMING CSV ANALYSIS PAGE ===
596
+ elif page == "Smart Farming CSV Analysis":
597
+ st.subheader("🌱 AI-Powered Farming Data Analysis")
598
+ uploaded_file = st.file_uploader("Upload your farming dataset (CSV)", type=["csv"])
599
+
600
+ if uploaded_file:
601
+ try:
602
+ df = pd.read_csv(uploaded_file)
603
+ st.success("✅ Data loaded successfully!")
604
+
605
+ # Create tabs for different analyses
606
+ tab1, tab2 = st.tabs(["Data Explorer", "AI Insights"])
607
+
608
+ with tab1:
609
+ st.markdown("### Dataset Preview")
610
+ st.dataframe(df.head(5))
611
+
612
+ if st.checkbox("Show Summary Statistics"):
613
+ st.markdown("### Summary Statistics")
614
+ st.write(df.describe().transpose())
615
+
616
+ # Interactive visualizations
617
+ numeric_cols = df.select_dtypes(include=["float64", "int64"]).columns.tolist()
618
+ if numeric_cols:
619
+ col1, col2 = st.columns(2)
620
+ with col1:
621
+ x_axis = st.selectbox("X-Axis", numeric_cols)
622
+ with col2:
623
+ y_axis = st.selectbox("Y-Axis", numeric_cols)
624
+
625
+ if x_axis and y_axis:
626
+ fig = px.scatter(
627
+ df,
628
+ x=x_axis,
629
+ y=y_axis,
630
+ title=f"{y_axis} vs {x_axis}",
631
+ trendline="ols",
632
+ color_discrete_sequence=["#2E7D32"]
633
+ )
634
+ st.plotly_chart(fig)
635
+
636
+ # Correlation heatmap
637
+ if len(numeric_cols) > 1:
638
+ st.markdown("### Correlation Matrix")
639
+ corr = df[numeric_cols].corr()
640
+ fig = px.imshow(corr,
641
+ text_auto=True,
642
+ aspect="auto",
643
+ color_continuous_scale="Greens")
644
+ st.plotly_chart(fig)
645
+
646
+ with tab2:
647
+ st.markdown("### AI-Powered Farming Insights")
648
+ st.info("Ask specific questions about your farming data to get actionable insights")
649
+
650
+ analysis_prompt = st.text_area(
651
+ "What insights would you like? (Examples below):",
652
+ "Analyze this farming data and provide key insights:",
653
+ height=100
654
+ )
655
+
656
+ st.caption("Examples: 'Suggest optimal crops for this region', 'Identify yield patterns', "
657
+ "'Recommend irrigation improvements', 'Predict harvest timing'")
658
+
659
+ if st.button("Generate AI Insights", type="primary"):
660
+ with st.spinner("🧠 Analyzing with AI..."):
661
+ # Prepare data context
662
+ context = f"Dataset has {len(df)} rows and columns: {', '.join(df.columns)}\n"
663
+ context += f"First 3 rows:\n{df.head(3).to_string(index=False)}"
664
+
665
+ # Get AI analysis
666
+ messages = [
667
+ {
668
+ "role": "system",
669
+ "content": (
670
+ "You are an expert agricultural data scientist. Analyze farming datasets and provide: "
671
+ "1. Actionable insights for improving crop yield "
672
+ "2. Recommendations based on climate patterns "
673
+ "3. Resource optimization strategies "
674
+ "4. Sustainable farming practices "
675
+ "Use bullet points and specific numbers when possible."
676
+ )
677
+ },
678
+ {
679
+ "role": "user",
680
+ "content": f"{analysis_prompt}\n\n{context}"
681
+ }
682
+ ]
683
+
684
+ response = client.chat.completions.create(
685
+ model=DEFAULT_MODEL,
686
+ messages=messages,
687
+ temperature=0.3
688
+ )
689
+ insights = response.choices[0].message.content
690
+ st.markdown(f"<div class='insight-box'>{insights}</div>", unsafe_allow_html=True)
691
+
692
+ except Exception as e:
693
+ st.error(f"❌ Error processing data: {str(e)}")
694
+ else:
695
+ st.info("👆 Upload a CSV file containing your farming data to get started")
696
+
697
+ # === FOOTER ===
698
+ st.markdown("---")
699
+ st.markdown(
700
+ "<small>🔋 Powered by <b>llama3-70b-8192</b> on Groq • Real-time data from Open-Meteo API</small>",
701
+ unsafe_allow_html=True
702
+ )