Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
from config import GROQ_API_KEY, AIRVISUAL_API_KEY, DEFAULT_MODEL
|
16 |
+
import os
|
17 |
+
from dotenv import load_dotenv
|
18 |
+
|
19 |
+
from utils.weather_utils import get_weather, get_historical_weather, get_air_quality
|
20 |
+
from utils.pdf_utils import generate_pdf
|
21 |
+
from utils.constants import SYSTEM_PROMPTS, EXAMPLE_QUERIES, CSS_STYLE
|
22 |
+
|
23 |
+
# Load environment variables
|
24 |
+
load_dotenv()
|
25 |
+
|
26 |
+
# Get API keys from environment variables
|
27 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
28 |
+
AIRVISUAL_API_KEY = os.getenv("AIRVISUAL_API_KEY")
|
29 |
+
DEFAULT_MODEL = "llama3-70b-8192"
|
30 |
+
|
31 |
+
# === INIT Groq CLIENT ===
|
32 |
+
client = Groq(api_key=GROQ_API_KEY)
|
33 |
+
|
34 |
+
# === PAGE CONFIG ===
|
35 |
+
st.set_page_config(
|
36 |
+
page_title="🌱 AI Climate & Smart Farming Assistant",
|
37 |
+
page_icon="🌾",
|
38 |
+
layout="wide",
|
39 |
+
initial_sidebar_state="expanded"
|
40 |
+
)
|
41 |
+
|
42 |
+
# === CSS STYLING ===
|
43 |
+
st.markdown(CSS_STYLE, unsafe_allow_html=True)
|
44 |
+
|
45 |
+
# === HEADER ===
|
46 |
+
st.markdown("<h1 class='title'>🌾 AI Climate & Smart Farming Assistant</h1>", unsafe_allow_html=True)
|
47 |
+
st.markdown("<p class='subtitle'>Real-time AI insights + live weather data</p>", unsafe_allow_html=True)
|
48 |
+
st.markdown("---")
|
49 |
+
|
50 |
+
# === SIDEBAR ===
|
51 |
+
st.sidebar.header("🌟 Features")
|
52 |
+
page = st.sidebar.radio(
|
53 |
+
"Choose your tool:",
|
54 |
+
[
|
55 |
+
"AI Assistant Chat",
|
56 |
+
"Weather Data",
|
57 |
+
"Smart Farming CSV Analysis",
|
58 |
+
]
|
59 |
+
)
|
60 |
+
|
61 |
+
# === MULTI-TURN CHAT ===
|
62 |
+
if page == "AI Assistant Chat":
|
63 |
+
st.subheader("🧠 AI Climate & Farming Chat Assistant")
|
64 |
+
option = st.selectbox(
|
65 |
+
"Choose a use case:",
|
66 |
+
list(SYSTEM_PROMPTS.keys())
|
67 |
+
)
|
68 |
+
st.markdown(f"💡 *Example*: {EXAMPLE_QUERIES[option]}")
|
69 |
+
|
70 |
+
user_input = st.text_area("Enter your question or describe your situation:")
|
71 |
+
|
72 |
+
if "chat_history" not in st.session_state:
|
73 |
+
st.session_state.chat_history = []
|
74 |
+
|
75 |
+
if st.button("Send to AI") and user_input.strip():
|
76 |
+
with st.spinner("Thinking..."):
|
77 |
+
messages = [
|
78 |
+
{"role": "system", "content": SYSTEM_PROMPTS[option]},
|
79 |
+
]
|
80 |
+
# Append chat history for multi-turn
|
81 |
+
for chat in st.session_state.chat_history:
|
82 |
+
messages.append({"role": "user", "content": chat["user"]})
|
83 |
+
messages.append({"role": "assistant", "content": chat["ai"]})
|
84 |
+
# Add current user input
|
85 |
+
messages.append({"role": "user", "content": user_input})
|
86 |
+
|
87 |
+
response = client.chat.completions.create(
|
88 |
+
model=DEFAULT_MODEL,
|
89 |
+
messages=messages,
|
90 |
+
)
|
91 |
+
ai_response = response.choices[0].message.content
|
92 |
+
|
93 |
+
# Save chat
|
94 |
+
st.session_state.chat_history.append({"user": user_input, "ai": ai_response})
|
95 |
+
|
96 |
+
# Clear input box
|
97 |
+
st.rerun()
|
98 |
+
|
99 |
+
if st.session_state.chat_history:
|
100 |
+
st.markdown("### 🕘 Conversation History")
|
101 |
+
for chat in reversed(st.session_state.chat_history):
|
102 |
+
st.markdown(f"<div class='user-input'>You:</div><div>{chat['user']}</div>", unsafe_allow_html=True)
|
103 |
+
st.markdown(f"<div class='ai-response'>{chat['ai']}</div>", unsafe_allow_html=True)
|
104 |
+
|
105 |
+
# Add PDF download button
|
106 |
+
if st.button("Download Chat as PDF"):
|
107 |
+
pdf_bytes = generate_pdf(st.session_state.chat_history)
|
108 |
+
st.download_button(
|
109 |
+
label="Click to Download PDF",
|
110 |
+
data=pdf_bytes,
|
111 |
+
file_name="climate_advice.pdf",
|
112 |
+
mime="application/pdf"
|
113 |
+
)
|
114 |
+
|
115 |
+
if st.button("Clear Chat History"):
|
116 |
+
st.session_state.chat_history = []
|
117 |
+
st.rerun()
|
118 |
+
|
119 |
+
# === WEATHER DATA PAGE ===
|
120 |
+
elif page == "Weather Data":
|
121 |
+
st.subheader("🌍 Advanced Weather & Environmental Data")
|
122 |
+
|
123 |
+
location_method = st.radio(
|
124 |
+
"Choose location input method:",
|
125 |
+
["Enter City", "Select Country"]
|
126 |
+
)
|
127 |
+
|
128 |
+
location = None
|
129 |
+
if location_method == "Enter City":
|
130 |
+
location = st.text_input("Enter a city or location (e.g., Los Angeles, Delhi):")
|
131 |
+
elif location_method == "Select Country":
|
132 |
+
country = st.selectbox("Select a country:", [country.name for country in pycountry.countries])
|
133 |
+
city = st.text_input("Enter city name:")
|
134 |
+
location = f"{city}, {country}" if city else None
|
135 |
+
|
136 |
+
if location:
|
137 |
+
tab1, tab2, tab3 = st.tabs(["Current Weather", "Historical Data", "Air Quality"])
|
138 |
+
|
139 |
+
with tab1:
|
140 |
+
if st.button("Get Current Weather"):
|
141 |
+
with st.spinner("Fetching data..."):
|
142 |
+
weather_data = get_weather(location)
|
143 |
+
if weather_data is None:
|
144 |
+
st.error("Failed to fetch weather data for this location.")
|
145 |
+
else:
|
146 |
+
col1, col2 = st.columns(2)
|
147 |
+
|
148 |
+
with col1:
|
149 |
+
st.markdown(f"### Current Weather in {weather_data['location']}:")
|
150 |
+
st.write(f"- Description: {weather_data['description']}")
|
151 |
+
st.write(f"- Temperature: {weather_data['temperature_C']} °C")
|
152 |
+
st.write(f"- Humidity: {weather_data['humidity_%']} %")
|
153 |
+
st.write(f"- Wind Speed: {weather_data['wind_speed_m/s']} m/s")
|
154 |
+
|
155 |
+
with col2:
|
156 |
+
fig = go.Figure()
|
157 |
+
fig.add_trace(go.Indicator(
|
158 |
+
mode="gauge+number",
|
159 |
+
value=weather_data['temperature_C'],
|
160 |
+
title={'text': "Temperature (°C)"},
|
161 |
+
gauge={'axis': {'range': [-20, 40]},
|
162 |
+
'bar': {'color': "darkgreen"}}
|
163 |
+
))
|
164 |
+
st.plotly_chart(fig)
|
165 |
+
|
166 |
+
with tab2:
|
167 |
+
days = st.slider("Select number of days for historical data:", 1, 30, 7)
|
168 |
+
if st.button("Get Historical Weather"):
|
169 |
+
with st.spinner("Fetching historical data..."):
|
170 |
+
hist_data = get_historical_weather(location, days)
|
171 |
+
if hist_data is None:
|
172 |
+
st.error("Failed to fetch historical weather data.")
|
173 |
+
else:
|
174 |
+
daily = hist_data['daily']
|
175 |
+
df = pd.DataFrame({
|
176 |
+
'Date': pd.date_range(start=daily['time'][0], periods=len(daily['time'])),
|
177 |
+
'Max Temp': daily['temperature_2m_max'],
|
178 |
+
'Min Temp': daily['temperature_2m_min'],
|
179 |
+
'Precipitation': daily['precipitation_sum'],
|
180 |
+
'Wind Speed': daily['wind_speed_10m_max']
|
181 |
+
})
|
182 |
+
|
183 |
+
# Create temperature range plot
|
184 |
+
fig = go.Figure()
|
185 |
+
fig.add_trace(go.Scatter(
|
186 |
+
x=df['Date'],
|
187 |
+
y=df['Max Temp'],
|
188 |
+
name='Max Temperature',
|
189 |
+
line=dict(color='red')
|
190 |
+
))
|
191 |
+
fig.add_trace(go.Scatter(
|
192 |
+
x=df['Date'],
|
193 |
+
y=df['Min Temp'],
|
194 |
+
name='Min Temperature',
|
195 |
+
line=dict(color='blue'),
|
196 |
+
fill='tonexty'
|
197 |
+
))
|
198 |
+
fig.update_layout(
|
199 |
+
title='Temperature Range Over Time',
|
200 |
+
xaxis_title='Date',
|
201 |
+
yaxis_title='Temperature (°C)',
|
202 |
+
hovermode='x unified'
|
203 |
+
)
|
204 |
+
st.plotly_chart(fig)
|
205 |
+
|
206 |
+
# Create precipitation and wind speed plot
|
207 |
+
fig2 = go.Figure()
|
208 |
+
fig2.add_trace(go.Bar(
|
209 |
+
x=df['Date'],
|
210 |
+
y=df['Precipitation'],
|
211 |
+
name='Precipitation',
|
212 |
+
marker_color='lightblue'
|
213 |
+
))
|
214 |
+
fig2.add_trace(go.Scatter(
|
215 |
+
x=df['Date'],
|
216 |
+
y=df['Wind Speed'],
|
217 |
+
name='Wind Speed',
|
218 |
+
line=dict(color='orange'),
|
219 |
+
yaxis='y2'
|
220 |
+
))
|
221 |
+
fig2.update_layout(
|
222 |
+
title='Precipitation and Wind Speed',
|
223 |
+
xaxis_title='Date',
|
224 |
+
yaxis_title='Precipitation (mm)',
|
225 |
+
yaxis2=dict(
|
226 |
+
title='Wind Speed (m/s)',
|
227 |
+
overlaying='y',
|
228 |
+
side='right'
|
229 |
+
)
|
230 |
+
)
|
231 |
+
st.plotly_chart(fig2)
|
232 |
+
|
233 |
+
with tab3:
|
234 |
+
if st.button("Get Air Quality Data"):
|
235 |
+
with st.spinner("Fetching air quality data..."):
|
236 |
+
aq_data = get_air_quality(location, AIRVISUAL_API_KEY)
|
237 |
+
if aq_data is None:
|
238 |
+
st.error("Failed to fetch air quality data.")
|
239 |
+
else:
|
240 |
+
st.markdown(f"### Air Quality in {location}")
|
241 |
+
current = aq_data['current']
|
242 |
+
|
243 |
+
# Create air quality gauges
|
244 |
+
col1, col2, col3 = st.columns(3)
|
245 |
+
|
246 |
+
# Define parameters
|
247 |
+
params = {
|
248 |
+
'pm10': {'name': 'PM10 (μg/m³)', 'range': [0, 100]},
|
249 |
+
'pm2_5': {'name': 'PM2.5 (μg/m³)', 'range': [0, 50]},
|
250 |
+
'ozone': {'name': 'Ozone (μg/m³)', 'range': [0, 100]},
|
251 |
+
'nitrogen_dioxide': {'name': 'Nitrogen Dioxide (μg/m³)', 'range': [0, 100]},
|
252 |
+
'sulphur_dioxide': {'name': 'Sulphur Dioxide (μg/m³)', 'range': [0, 100]}
|
253 |
+
}
|
254 |
+
|
255 |
+
# Display gauges for first 3 parameters
|
256 |
+
for i, param in enumerate(['pm2_5', 'pm10', 'ozone']):
|
257 |
+
if param in current and current[param] is not None:
|
258 |
+
with [col1, col2, col3][i]:
|
259 |
+
fig = go.Figure(go.Indicator(
|
260 |
+
mode="gauge+number",
|
261 |
+
value=current[param],
|
262 |
+
title={'text': params[param]['name']},
|
263 |
+
gauge={'axis': {'range': params[param]['range']},
|
264 |
+
'bar': {'color': "darkgreen"}}
|
265 |
+
))
|
266 |
+
st.plotly_chart(fig)
|
267 |
+
|
268 |
+
# Display other pollutants
|
269 |
+
st.markdown("### Other Pollutants")
|
270 |
+
col1, col2 = st.columns(2)
|
271 |
+
with col1:
|
272 |
+
if 'nitrogen_dioxide' in current and current['nitrogen_dioxide'] is not None:
|
273 |
+
st.write(f"- Nitrogen Dioxide: {current['nitrogen_dioxide']} μg/m³")
|
274 |
+
with col2:
|
275 |
+
if 'sulphur_dioxide' in current and current['sulphur_dioxide'] is not None:
|
276 |
+
st.write(f"- Sulphur Dioxide: {current['sulphur_dioxide']} μg/m³")
|
277 |
+
|
278 |
+
# === SMART FARMING CSV ANALYSIS PAGE ===
|
279 |
+
elif page == "Smart Farming CSV Analysis":
|
280 |
+
st.subheader("🌱 AI-Powered Farming Data Analysis")
|
281 |
+
uploaded_file = st.file_uploader("Upload your farming dataset (CSV)", type=["csv"])
|
282 |
+
|
283 |
+
if uploaded_file:
|
284 |
+
try:
|
285 |
+
df = pd.read_csv(uploaded_file)
|
286 |
+
st.success("✅ Data loaded successfully!")
|
287 |
+
|
288 |
+
# Create tabs for different analyses
|
289 |
+
tab1, tab2 = st.tabs(["Data Explorer", "AI Insights"])
|
290 |
+
|
291 |
+
with tab1:
|
292 |
+
st.markdown("### Dataset Preview")
|
293 |
+
st.dataframe(df.head(5))
|
294 |
+
|
295 |
+
if st.checkbox("Show Summary Statistics"):
|
296 |
+
st.markdown("### Summary Statistics")
|
297 |
+
st.write(df.describe().transpose())
|
298 |
+
|
299 |
+
# Interactive visualizations
|
300 |
+
numeric_cols = df.select_dtypes(include=["float64", "int64"]).columns.tolist()
|
301 |
+
if numeric_cols:
|
302 |
+
col1, col2 = st.columns(2)
|
303 |
+
with col1:
|
304 |
+
x_axis = st.selectbox("X-Axis", numeric_cols)
|
305 |
+
with col2:
|
306 |
+
y_axis = st.selectbox("Y-Axis", numeric_cols)
|
307 |
+
|
308 |
+
if x_axis and y_axis:
|
309 |
+
fig = px.scatter(
|
310 |
+
df,
|
311 |
+
x=x_axis,
|
312 |
+
y=y_axis,
|
313 |
+
title=f"{y_axis} vs {x_axis}",
|
314 |
+
trendline="ols",
|
315 |
+
color_discrete_sequence=["#2E7D32"]
|
316 |
+
)
|
317 |
+
st.plotly_chart(fig)
|
318 |
+
|
319 |
+
# Correlation heatmap
|
320 |
+
if len(numeric_cols) > 1:
|
321 |
+
st.markdown("### Correlation Matrix")
|
322 |
+
corr = df[numeric_cols].corr()
|
323 |
+
fig = px.imshow(corr,
|
324 |
+
text_auto=True,
|
325 |
+
aspect="auto",
|
326 |
+
color_continuous_scale="Greens")
|
327 |
+
st.plotly_chart(fig)
|
328 |
+
|
329 |
+
with tab2:
|
330 |
+
st.markdown("### AI-Powered Farming Insights")
|
331 |
+
st.info("Ask specific questions about your farming data to get actionable insights")
|
332 |
+
|
333 |
+
analysis_prompt = st.text_area(
|
334 |
+
"What insights would you like? (Examples below):",
|
335 |
+
"Analyze this farming data and provide key insights:",
|
336 |
+
height=100
|
337 |
+
)
|
338 |
+
|
339 |
+
st.caption("Examples: 'Suggest optimal crops for this region', 'Identify yield patterns', "
|
340 |
+
"'Recommend irrigation improvements', 'Predict harvest timing'")
|
341 |
+
|
342 |
+
if st.button("Generate AI Insights", type="primary"):
|
343 |
+
with st.spinner("🧠 Analyzing with AI..."):
|
344 |
+
# Prepare data context
|
345 |
+
context = f"Dataset has {len(df)} rows and columns: {', '.join(df.columns)}\n"
|
346 |
+
context += f"First 3 rows:\n{df.head(3).to_string(index=False)}"
|
347 |
+
|
348 |
+
# Get AI analysis
|
349 |
+
messages = [
|
350 |
+
{
|
351 |
+
"role": "system",
|
352 |
+
"content": (
|
353 |
+
"You are an expert agricultural data scientist. Analyze farming datasets and provide: "
|
354 |
+
"1. Actionable insights for improving crop yield "
|
355 |
+
"2. Recommendations based on climate patterns "
|
356 |
+
"3. Resource optimization strategies "
|
357 |
+
"4. Sustainable farming practices "
|
358 |
+
"Use bullet points and specific numbers when possible."
|
359 |
+
)
|
360 |
+
},
|
361 |
+
{
|
362 |
+
"role": "user",
|
363 |
+
"content": f"{analysis_prompt}\n\n{context}"
|
364 |
+
}
|
365 |
+
]
|
366 |
+
|
367 |
+
response = client.chat.completions.create(
|
368 |
+
model=DEFAULT_MODEL,
|
369 |
+
messages=messages,
|
370 |
+
temperature=0.3
|
371 |
+
)
|
372 |
+
insights = response.choices[0].message.content
|
373 |
+
st.markdown(f"<div class='insight-box'>{insights}</div>", unsafe_allow_html=True)
|
374 |
+
|
375 |
+
except Exception as e:
|
376 |
+
st.error(f"❌ Error processing data: {str(e)}")
|
377 |
+
else:
|
378 |
+
st.info("👆 Upload a CSV file containing your farming data to get started")
|
379 |
+
|
380 |
+
# === FOOTER ===
|
381 |
+
st.markdown("---")
|
382 |
+
st.markdown(
|
383 |
+
"<small>🔋 Powered by <b>llama3-70b-8192</b> on Groq • Real-time data from Open-Meteo API</small>",
|
384 |
+
unsafe_allow_html=True
|
385 |
+
)
|