Files changed (3) hide show
  1. Dockerfile +27 -17
  2. app.py +620 -127
  3. templates/index.html +722 -159
Dockerfile CHANGED
@@ -1,17 +1,27 @@
1
- # Base image
2
- FROM python:3.9-slim
3
-
4
- # Set the working directory
5
- WORKDIR /app
6
-
7
- # Copy application files
8
- COPY . /app
9
-
10
- # Install dependencies
11
- RUN pip install --no-cache-dir -r requirements.txt
12
-
13
- # Expose the port your app runs on
14
- EXPOSE 7860
15
-
16
- # Command to run the application
17
- CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:7860", "app:app"]
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /code
4
+
5
+ # Create audio directory with proper permissions
6
+ RUN mkdir -p /code/static/audio && \
7
+ chmod 777 /code/static/audio
8
+
9
+ COPY ./requirements.txt /code/requirements.txt
10
+ COPY ./final_price_data.csv /code/final_price_data.csv
11
+ COPY ./app.py /code/app.py
12
+ COPY ./static /code/static
13
+
14
+ RUN mkdir -p /code/static/audio && \
15
+ chmod 777 /code/static/audio
16
+
17
+ RUN apt-get update && \
18
+ apt-get install -y --no-install-recommends \
19
+ espeak \
20
+ libespeak1 \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
24
+
25
+ EXPOSE 7860
26
+
27
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,127 +1,620 @@
1
- import os
2
- from flask import Flask, render_template, request, jsonify
3
- import requests
4
- import pandas as pd
5
- from datetime import datetime
6
- app = Flask(__name__)
7
-
8
- # Function to fetch data from the API
9
- def fetch_market_data(state=None, district=None, market=None, commodity=None):
10
- api_key = os.getenv("GOV_API_KEY")
11
- api_key = api_key
12
- base_url = "https://api.data.gov.in/resource/9ef84268-d588-465a-a308-a864a43d0070"
13
- params = {
14
- "api-key": api_key,
15
- "format": "json",
16
- "limit": 15000,
17
- }
18
-
19
- if state:
20
- params["filters[state.keyword]"] = state
21
- if district:
22
- params["filters[district.keyword]"] = district
23
- if market:
24
- params["filters[market.keyword]"] = market
25
- if commodity:
26
- params["filters[commodity.keyword]"] = commodity
27
-
28
- response = requests.get(base_url, params=params)
29
-
30
- if response.status_code == 200:
31
- data = response.json()
32
- records = data.get("records", [])
33
- df = pd.DataFrame(records)
34
- return df
35
- else:
36
- return pd.DataFrame()
37
-
38
- # Fetch initial state data
39
- @app.route("/")
40
- def index():
41
- # Fetch distinct states for dropdown
42
- market_data = fetch_market_data()
43
- states = market_data['state'].dropna().unique().tolist()
44
- today = datetime.today().strftime('%Y-%m-%d')
45
- return render_template('index.html', states=states, today=today)
46
-
47
- # Handle AJAX requests to filter market data
48
- @app.route("/filter_data", methods=["POST"])
49
- def filter_data():
50
- state = request.form.get("state")
51
- district = request.form.get("district")
52
- market = request.form.get("market")
53
- commodity = request.form.get("commodity")
54
-
55
- # Fetch filtered data
56
- filtered_data = fetch_market_data(state, district, market, commodity)
57
-
58
- # Generate HTML for the filtered table
59
- table_html = ""
60
- for _, row in filtered_data.iterrows():
61
- table_html += f"""
62
- <tr>
63
- <td>{row['state']}</td>
64
- <td>{row['district']}</td>
65
- <td>{row['market']}</td>
66
- <td>{row['commodity']}</td>
67
- <td>{row['variety']}</td>
68
- <td>{row['grade']}</td>
69
- <td>{row['arrival_date']}</td>
70
- <td>{row['min_price']}</td>
71
- <td>{row['max_price']}</td>
72
- <td>{row['modal_price']}</td>
73
- </tr>
74
- """
75
-
76
- # Get top 5 cheapest crops by modal price
77
- cheapest_crops = filtered_data.sort_values("modal_price", ascending=True).head(5)
78
- cheapest_html = ""
79
- for _, row in cheapest_crops.iterrows():
80
- cheapest_html += f"""
81
- <tr>
82
- <td>{row['commodity']}</td>
83
- <td>{row['modal_price']}</td>
84
- </tr>
85
- """
86
-
87
- # Get top 5 costliest crops by modal price
88
- costliest_crops = filtered_data.sort_values("modal_price", ascending=False).head(5)
89
- costliest_html = ""
90
- for _, row in costliest_crops.iterrows():
91
- costliest_html += f"""
92
- <tr>
93
- <td>{row['commodity']}</td>
94
- <td>{row['modal_price']}</td>
95
- </tr>
96
- """
97
-
98
- return jsonify({
99
- "market_html": table_html,
100
- "cheapest_html": cheapest_html,
101
- "costliest_html": costliest_html
102
- })
103
-
104
- # Handle AJAX requests for dropdown filtering
105
- @app.route("/get_districts", methods=["POST"])
106
- def get_districts():
107
- state = request.form.get("state")
108
- market_data = fetch_market_data(state=state)
109
- districts = market_data["district"].dropna().unique().tolist()
110
- return jsonify(districts)
111
-
112
- @app.route("/get_markets", methods=["POST"])
113
- def get_markets():
114
- district = request.form.get("district")
115
- market_data = fetch_market_data(district=district)
116
- markets = market_data["market"].dropna().unique().tolist()
117
- return jsonify(markets)
118
-
119
- @app.route("/get_commodities", methods=["POST"])
120
- def get_commodities():
121
- market = request.form.get("market")
122
- market_data = fetch_market_data(market=market)
123
- commodities = market_data["commodity"].dropna().unique().tolist()
124
- return jsonify(commodities)
125
-
126
- if __name__ == "__main__":
127
- app.run(debug=True, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, render_template, request, jsonify, send_file
3
+ import requests
4
+ import pandas as pd
5
+ from datetime import datetime
6
+ import plotly.express as px
7
+ import plotly.io as pio
8
+ import numpy as np
9
+ import dotenv
10
+ import json
11
+ import gtts
12
+ import uuid
13
+ from pathlib import Path
14
+
15
+ dotenv.load_dotenv()
16
+
17
+ app = Flask(__name__)
18
+
19
+ # Create audio directory if it doesn't exist
20
+ AUDIO_DIR = Path("static/audio")
21
+ AUDIO_DIR.mkdir(parents=True, exist_ok=True)
22
+
23
+ def fetch_market_data(state=None, district=None, market=None, commodity=None):
24
+ """Fetch data from the agricultural market API.
25
+ If the API fails or returns empty data, fallback to the CSV file.
26
+ Filters (state, district, market, commodity) are applied manually on CSV data.
27
+ """
28
+ api_key = "579b464db66ec23bdd000001189bbb99e979428764bdbe8fdd44ebb7"
29
+ base_url = "https://api.data.gov.in/resource/9ef84268-d588-465a-a308-a864a43d007"
30
+
31
+ params = {
32
+ "api-key": api_key,
33
+ "format": "json",
34
+ "limit": 1000,
35
+ }
36
+
37
+ if state:
38
+ params["filters[state]"] = state
39
+ if district:
40
+ params["filters[district]"] = district
41
+ if market:
42
+ params["filters[market]"] = market
43
+ if commodity:
44
+ params["filters[commodity]"] = commodity
45
+
46
+ try:
47
+ response = requests.get(base_url, params=params)
48
+ if response.status_code == 200:
49
+ data = response.json()
50
+ records = data.get("records", [])
51
+ df = pd.DataFrame(records)
52
+ else:
53
+ print(f"API Error: {response.status_code}")
54
+ raise Exception(f"API Error: {response.status_code}")
55
+ except Exception as e:
56
+ print(f"Error fetching data from API: {str(e)}. Falling back to CSV file.")
57
+ df = pd.read_csv("final_price_data.csv")
58
+ if 'min_price' not in df.columns:
59
+ rename_mapping = {
60
+ 'State': 'state',
61
+ 'District': 'district',
62
+ 'Market': 'market',
63
+ 'Commodity': 'commodity',
64
+ 'Variety': 'variety',
65
+ 'Grade': 'grade',
66
+ 'Arrival_Date': 'arrival_date',
67
+ 'Min_x0020_Price': 'min_price',
68
+ 'Max_x0020_Price': 'max_price',
69
+ 'Modal_x0020_Price': 'modal_price'
70
+ }
71
+ df.rename(columns=rename_mapping, inplace=True)
72
+
73
+ if df.empty:
74
+ print("API returned empty data. Falling back to CSV file.")
75
+ df = pd.read_csv("final_price_data.csv")
76
+ if 'min_price' not in df.columns:
77
+ rename_mapping = {
78
+ 'State': 'state',
79
+ 'District': 'district',
80
+ 'Market': 'market',
81
+ 'Commodity': 'commodity',
82
+ 'Variety': 'variety',
83
+ 'Grade': 'grade',
84
+ 'Arrival_Date': 'arrival_date',
85
+ 'Min_x0020_Price': 'min_price',
86
+ 'Max_x0020_Price': 'max_price',
87
+ 'Modal_x0020_Price': 'modal_price'
88
+ }
89
+ df.rename(columns=rename_mapping, inplace=True)
90
+
91
+ if state:
92
+ df = df[df['state'] == state]
93
+ if district:
94
+ df = df[df['district'] == district]
95
+ if market:
96
+ df = df[df['market'] == market]
97
+ if commodity:
98
+ df = df[df['commodity'] == commodity]
99
+
100
+ return df
101
+
102
+ def get_ai_insights(market_data, state, district, market=None, commodity=None, language="English"):
103
+ """Get enhanced insights from Gemini API with focus on profitable suggestions for farmers.
104
+ Supports multiple languages through the prompt.
105
+ Returns dynamic insights only. If something goes wrong, returns an empty string.
106
+ """
107
+ if not state or not district or market_data.empty:
108
+ return ""
109
+
110
+ try:
111
+ # Filter data based on provided parameters
112
+ district_data = market_data[market_data['district'] == district]
113
+ if district_data.empty:
114
+ return ""
115
+
116
+ # Apply market filter if provided
117
+ if market and not market_data[market_data['market'] == market].empty:
118
+ market_specific = True
119
+ district_data = district_data[district_data['market'] == market]
120
+ else:
121
+ market_specific = False
122
+
123
+ # Apply commodity filter if provided
124
+ if commodity and not market_data[market_data['commodity'] == commodity].empty:
125
+ commodity_specific = True
126
+ district_data = district_data[district_data['commodity'] == commodity]
127
+ else:
128
+ commodity_specific = False
129
+
130
+ # Calculate price trends
131
+ price_trends = district_data.groupby('commodity').agg({
132
+ 'modal_price': ['mean', 'min', 'max', 'std']
133
+ }).round(2)
134
+
135
+ # Using environment variable for Gemini API key
136
+ api_key = "AIzaSyBtXV2xJbrWVV57B5RWy_meKXOA59HFMeY"
137
+ if not api_key:
138
+ print("Warning: Gemini API key not set")
139
+ return ""
140
+
141
+ price_trends['price_stability'] = (price_trends['modal_price']['std'] /
142
+ price_trends['modal_price']['mean']).round(2)
143
+
144
+ district_data['arrival_date'] = pd.to_datetime(district_data['arrival_date'])
145
+ district_data['month'] = district_data['arrival_date'].dt.month
146
+ monthly_trends = district_data.groupby(['commodity', 'month'])['modal_price'].mean().round(2)
147
+
148
+ market_competition = len(district_data['market'].unique())
149
+ top_commodities = district_data.groupby('commodity')['modal_price'].mean().nlargest(5).index.tolist()
150
+
151
+ # Get min and max prices for key commodities
152
+ price_range_info = {}
153
+ for commodity in top_commodities[:3]:
154
+ comm_data = district_data[district_data['commodity'] == commodity]
155
+ if not comm_data.empty:
156
+ price_range_info[commodity] = {
157
+ 'min': comm_data['modal_price'].min(),
158
+ 'max': comm_data['modal_price'].max(),
159
+ 'avg': comm_data['modal_price'].mean()
160
+ }
161
+
162
+ # Calculate market-specific metrics if market is selected
163
+ market_details = ""
164
+ if market_specific:
165
+ market_details = f"""
166
+ Market-specific information for {market}:
167
+ - Number of commodities: {len(district_data['commodity'].unique())}
168
+ - Most expensive commodity: {district_data.groupby('commodity')['modal_price'].mean().idxmax()}
169
+ - Cheapest commodity: {district_data.groupby('commodity')['modal_price'].mean().idxmin()}
170
+ """
171
+
172
+ # Commodity-specific details if commodity is selected
173
+ commodity_details = ""
174
+ if commodity_specific:
175
+ commodity_data = district_data[district_data['commodity'] == commodity]
176
+ best_market = commodity_data.loc[commodity_data['modal_price'].idxmin()]['market']
177
+ worst_market = commodity_data.loc[commodity_data['modal_price'].idxmax()]['market']
178
+
179
+ commodity_details = f"""
180
+ Commodity-specific information for {commodity}:
181
+ - Best market to buy (lowest price): {best_market}
182
+ - Highest priced market: {worst_market}
183
+ - Price variance across markets: {commodity_data['modal_price'].std().round(2)}
184
+ """
185
+
186
+ # Improved prompt for better structured output with language support
187
+ prompt = f"""
188
+ Analyze the following agricultural market data for {district}, {state} and provide insights in {language} language.
189
+
190
+ Market data:
191
+ - Active markets: {market_competition}
192
+ - Top crops: {', '.join(top_commodities[:5])}
193
+ - Data from {len(price_trends.index)} crops and {len(monthly_trends)} monthly entries.
194
+
195
+ Price information:
196
+ {json.dumps(price_range_info, indent=2)}
197
+
198
+ {market_details}
199
+ {commodity_details}
200
+
201
+ Analyze this data and provide insights about crop market trends and profitability.
202
+ Include specific numbers from the data about prices.
203
+
204
+ Provide structured insights with clear sections. Use this exact format with bullet points:
205
+
206
+ Crop Profitability Analysis:
207
+ * [First insight about profitable crops with specific prices mentioned]
208
+ * [Second insight]
209
+
210
+ Market Price Analysis:
211
+ * [First insight about markets with specific price ranges]
212
+ * [Second insight]
213
+
214
+ Recommendations for Farmers:
215
+ * [Action item 1]
216
+ * [Action item 2]
217
+ """
218
+
219
+ api_url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent"
220
+ headers = {"Content-Type": "application/json"}
221
+
222
+ payload = {
223
+ "contents": [
224
+ {
225
+ "parts": [
226
+ {"text": prompt}
227
+ ]
228
+ }
229
+ ],
230
+ "generationConfig": {
231
+ "temperature": 0.4,
232
+ "maxOutputTokens": 1024
233
+ }
234
+ }
235
+
236
+ response = requests.post(
237
+ f"{api_url}?key={api_key}",
238
+ headers=headers,
239
+ json=payload,
240
+ timeout=20
241
+ )
242
+
243
+ if response.status_code == 200:
244
+ response_data = response.json()
245
+ if 'candidates' in response_data and len(response_data['candidates']) > 0:
246
+ content = response_data['candidates'][0]['content']
247
+ if 'parts' in content and len(content['parts']) > 0:
248
+ insights = content['parts'][0]['text']
249
+ return format_ai_insights(insights, language)
250
+ print(f"API Response issue: {response.text[:100]}")
251
+ else:
252
+ print(f"Gemini API Error: {response.status_code} - {response.text[:100]}")
253
+
254
+ return ""
255
+
256
+ except Exception as e:
257
+ print(f"Error generating insights: {str(e)}")
258
+ return ""
259
+
260
+ def extract_text_from_insights(insights_html):
261
+ """Extract pure text content from HTML insights for text-to-speech conversion."""
262
+ # Simple HTML tag removal - for production, consider using BeautifulSoup for better parsing
263
+ import re
264
+ text = re.sub(r'<.*?>', ' ', insights_html)
265
+ text = re.sub(r'\s+', ' ', text) # Remove extra whitespace
266
+ return text.strip()
267
+
268
+ def create_audio_from_text(text, language_code="en"):
269
+ """Generate audio file from text using gTTS."""
270
+ if not text:
271
+ return None
272
+
273
+ # Map UI language selection to gTTS language codes
274
+ language_map = {
275
+ "English": "en",
276
+ "Hindi": "hi",
277
+ "Tamil": "ta",
278
+ "Telugu": "te",
279
+ "Marathi": "mr",
280
+ "Bengali": "bn",
281
+ "Gujarati": "gu",
282
+ "Kannada": "kn",
283
+ "Malayalam": "ml",
284
+ "Punjabi": "pa"
285
+ }
286
+
287
+ tts_lang = language_map.get(language_code, "en")
288
+
289
+ # Generate unique filename
290
+ filename = f"{uuid.uuid4()}.mp3"
291
+ filepath = AUDIO_DIR / filename
292
+
293
+ try:
294
+ tts = gtts.gTTS(text, lang=tts_lang, slow=False)
295
+ tts.save(str(filepath))
296
+ return f"/static/audio/{filename}"
297
+ except Exception as e:
298
+ print(f"Error creating audio: {str(e)}")
299
+ return None
300
+
301
+ def create_audio_local_fallback(text, language_code="en"):
302
+ """Local fallback for TTS when network is unavailable."""
303
+ try:
304
+ # This requires pyttsx3 to be installed
305
+ import pyttsx3
306
+ engine = pyttsx3.init()
307
+
308
+ # Generate unique filename
309
+ filename = f"{uuid.uuid4()}.mp3"
310
+ filepath = AUDIO_DIR / filename
311
+
312
+ engine.save_to_file(text, str(filepath))
313
+ engine.runAndWait()
314
+
315
+ return f"/static/audio/{filename}"
316
+ except Exception as e:
317
+ print(f"Local TTS fallback failed: {str(e)}")
318
+ return None
319
+
320
+ def format_ai_insights(insights_data, language="English"):
321
+ """Format AI insights into structured HTML.
322
+ Returns an empty string if no valid insights are provided.
323
+ """
324
+ if not insights_data or not insights_data.strip():
325
+ return ""
326
+
327
+ # Process the insights text - each bullet point becomes a formatted item
328
+ formatted_content = ""
329
+
330
+ # Split by bullet points
331
+ bullet_points = insights_data.split('*')
332
+
333
+ # Filter out empty items and process each bullet point
334
+ bullet_points = [point.strip() for point in bullet_points if point.strip()]
335
+
336
+ # Check if any section headers exist in the content
337
+ sections = {}
338
+ current_section = "Recommendations"
339
+
340
+ for point in bullet_points:
341
+ if ":" in point and len(point.split(":")[0]) < 30: # Likely a section header
342
+ current_section = point.split(":")[0].strip()
343
+ # Start a new section
344
+ if current_section not in sections:
345
+ sections[current_section] = []
346
+ else:
347
+ # Add to current section
348
+ if current_section not in sections:
349
+ sections[current_section] = []
350
+ sections[current_section].append(point)
351
+
352
+ # Now build the HTML with proper sections
353
+ for section, points in sections.items():
354
+ formatted_content += f'<div class="insight-card"><h5>{section}</h5><ul class="insight-list">'
355
+ for point in points:
356
+ # Highlight prices with special styling
357
+ if "₹" in point:
358
+ # Replace price mentions with highlighted spans
359
+ parts = point.split("₹")
360
+ styled_point = parts[0]
361
+ for i in range(1, len(parts)):
362
+ # Extract the price value
363
+ price_text = parts[i].split()[0]
364
+ # Add the highlighted price and the rest of the text
365
+ styled_point += f'<span class="price-highlight">₹{price_text}</span>' + parts[i][len(price_text):]
366
+ formatted_content += f'<li>{styled_point}</li>'
367
+ else:
368
+ formatted_content += f'<li>{point}</li>'
369
+ formatted_content += '</ul></div>'
370
+
371
+ # Create the plain text version for audio generation
372
+ plain_text = f"Market Insights for {language}.\n\n"
373
+ for section, points in sections.items():
374
+ plain_text += f"{section}:\n"
375
+ for point in points:
376
+ # Clean up for speech
377
+ clean_point = point.replace("₹", " rupees ")
378
+ plain_text += f"• {clean_point}\n"
379
+ plain_text += "\n"
380
+
381
+ # Generate audio file
382
+ audio_path = create_audio_from_text(plain_text, language)
383
+ if audio_path is None:
384
+ audio_path = create_audio_local_fallback(plain_text)
385
+
386
+ # Add a wrapper for the insights with audio player
387
+ audio_player = ""
388
+ if audio_path:
389
+ audio_player = f"""
390
+ <div class="audio-player-container">
391
+ <h4>Listen to Insights</h4>
392
+ <audio id="insightsAudio" controls>
393
+ <source src="{audio_path}" type="audio/mpeg">
394
+ Your browser does not support the audio element.
395
+ </audio>
396
+ <button class="btn btn-sm btn-custom mt-2" id="playAudioBtn">
397
+ <i class="fa fa-play"></i> Play Audio
398
+ </button>
399
+ </div>
400
+ """
401
+
402
+ html = f"""
403
+ <div class="insights-header">
404
+ <h3>AI Market Insights</h3>
405
+ {audio_player}
406
+ </div>
407
+ <div class="insight-section">
408
+ {formatted_content}
409
+ </div>
410
+ """
411
+
412
+ return html
413
+
414
+ def generate_plots(df):
415
+ """Generate all plots in English"""
416
+ if df.empty:
417
+ return {}, "No data available"
418
+
419
+ price_cols = ['min_price', 'max_price', 'modal_price']
420
+ for col in price_cols:
421
+ df[col] = pd.to_numeric(df[col], errors='coerce')
422
+
423
+ colors = ["#4CAF50", "#8BC34A", "#CDDC39", "#FFC107", "#FF5722"]
424
+
425
+ df_bar = df.groupby('commodity')['modal_price'].mean().reset_index()
426
+ fig_bar = px.bar(df_bar,
427
+ x='commodity',
428
+ y='modal_price',
429
+ title="Average Price by Commodity",
430
+ color_discrete_sequence=colors)
431
+
432
+ fig_line = None
433
+ if 'commodity' in df.columns and len(df['commodity'].unique()) == 1:
434
+ df['arrival_date'] = pd.to_datetime(df['arrival_date'])
435
+ df_line = df.sort_values('arrival_date')
436
+ fig_line = px.line(df_line,
437
+ x='arrival_date',
438
+ y='modal_price',
439
+ title="Price Trend",
440
+ color_discrete_sequence=colors)
441
+
442
+ fig_box = px.box(df,
443
+ x='commodity',
444
+ y='modal_price',
445
+ title="Price Distribution",
446
+ color='commodity',
447
+ color_discrete_sequence=colors)
448
+
449
+ plots = {
450
+ 'bar': pio.to_html(fig_bar, full_html=False),
451
+ 'box': pio.to_html(fig_box, full_html=False)
452
+ }
453
+ if fig_line:
454
+ plots['line'] = pio.to_html(fig_line, full_html=False)
455
+
456
+ return plots
457
+
458
+ @app.route('/')
459
+ def index():
460
+ try:
461
+ initial_data = fetch_market_data()
462
+ states = sorted(initial_data['state'].dropna().unique()) if not initial_data.empty else []
463
+ except Exception as e:
464
+ print(f"Error fetching initial data: {str(e)}")
465
+ states = []
466
+
467
+ return render_template('index.html',
468
+ states=states,
469
+ today=datetime.today().strftime('%Y-%m-%d'))
470
+
471
+ @app.route('/filter_data', methods=['POST'])
472
+ def filter_data():
473
+ state = request.form.get('state')
474
+ district = request.form.get('district')
475
+ market = request.form.get('market')
476
+ commodity = request.form.get('commodity')
477
+ language = request.form.get('language', 'English') # Default to English
478
+
479
+ df = fetch_market_data(state, district, market, commodity)
480
+ plots = generate_plots(df)
481
+ # Pass market and commodity to get_ai_insights
482
+ insights = get_ai_insights(df, state, district, market, commodity, language) if state and district and not df.empty else ""
483
+
484
+ market_table_html = """
485
+ <div class="table-responsive">
486
+ <table class="table table-striped table-bordered">
487
+ <thead>
488
+ <tr>
489
+ <th>State</th>
490
+ <th>District</th>
491
+ <th>Market</th>
492
+ <th>Commodity</th>
493
+ <th>Variety</th>
494
+ <th>Grade</th>
495
+ <th>Arrival Date</th>
496
+ <th>Min Price</th>
497
+ <th>Max Price</th>
498
+ <th>Modal Price</th>
499
+ </tr>
500
+ </thead>
501
+ <tbody>
502
+ """
503
+
504
+ for _, row in df.iterrows():
505
+ market_table_html += f"""
506
+ <tr>
507
+ <td>{row['state']}</td>
508
+ <td>{row['district']}</td>
509
+ <td>{row['market']}</td>
510
+ <td>{row['commodity']}</td>
511
+ <td>{row['variety']}</td>
512
+ <td>{row['grade']}</td>
513
+ <td>{row['arrival_date']}</td>
514
+ <td>₹{row['min_price']}</td>
515
+ <td>₹{row['max_price']}</td>
516
+ <td>₹{row['modal_price']}</td>
517
+ </tr>
518
+ """
519
+ market_table_html += "</tbody></table></div>"
520
+
521
+ cheapest_crops = df.sort_values('modal_price', ascending=True).head(5)
522
+ cheapest_table_html = """
523
+ <div class="table-responsive">
524
+ <table class="table table-sm table-bordered">
525
+ <thead>
526
+ <tr>
527
+ <th>Commodity</th>
528
+ <th>Market</th>
529
+ <th>Modal Price</th>
530
+ </tr>
531
+ </thead>
532
+ <tbody>
533
+ """
534
+
535
+ for _, row in cheapest_crops.iterrows():
536
+ cheapest_table_html += f"""
537
+ <tr>
538
+ <td>{row['commodity']}</td>
539
+ <td>{row['market']}</td>
540
+ <td>₹{row['modal_price']}</td>
541
+ </tr>
542
+ """
543
+ cheapest_table_html += "</tbody></table></div>"
544
+
545
+ costliest_crops = df.sort_values('modal_price', ascending=False).head(5)
546
+ costliest_table_html = """
547
+ <div class="table-responsive">
548
+ <table class="table table-sm table-bordered">
549
+ <thead>
550
+ <tr>
551
+ <th>Commodity</th>
552
+ <th>Market</th>
553
+ <th>Modal Price</th>
554
+ </tr>
555
+ </thead>
556
+ <tbody>
557
+ """
558
+
559
+ for _, row in costliest_crops.iterrows():
560
+ costliest_table_html += f"""
561
+ <tr>
562
+ <td>{row['commodity']}</td>
563
+ <td>{row['market']}</td>
564
+ <td>₹{row['modal_price']}</td>
565
+ </tr>
566
+ """
567
+ costliest_table_html += "</tbody></table></div>"
568
+
569
+ market_stats = {
570
+ 'total_commodities': len(df['commodity'].unique()),
571
+ 'avg_modal_price': f"₹{df['modal_price'].mean():.2f}",
572
+ 'price_range': f"₹{df['modal_price'].min():.2f} - ₹{df['modal_price'].max():.2f}",
573
+ 'total_markets': len(df['market'].unique())
574
+ }
575
+
576
+ response = {
577
+ 'plots': plots,
578
+ 'insights': insights,
579
+ 'success': not df.empty,
580
+ 'hasStateDistrict': bool(state and district),
581
+ 'market_html': market_table_html,
582
+ 'cheapest_html': cheapest_table_html,
583
+ 'costliest_html': costliest_table_html,
584
+ 'market_stats': market_stats
585
+ }
586
+
587
+ return jsonify(response)
588
+
589
+ @app.route('/get_districts', methods=['POST'])
590
+ def get_districts():
591
+ state = request.form.get('state')
592
+ df = fetch_market_data(state=state)
593
+ districts = sorted(df['district'].dropna().unique())
594
+ return jsonify(districts)
595
+
596
+ @app.route('/get_markets', methods=['POST'])
597
+ def get_markets():
598
+ district = request.form.get('district')
599
+ df = fetch_market_data(district=district)
600
+ markets = sorted(df['market'].dropna().unique())
601
+ return jsonify(markets)
602
+
603
+ @app.route('/get_commodities', methods=['POST'])
604
+ def get_commodities():
605
+ market = request.form.get('market')
606
+ df = fetch_market_data(market=market)
607
+ commodities = sorted(df['commodity'].dropna().unique())
608
+ return jsonify(commodities)
609
+
610
+ @app.route('/static/audio/<filename>')
611
+ def serve_audio(filename):
612
+ try:
613
+ return send_file(f"static/audio/{filename}", mimetype="audio/mpeg")
614
+ except Exception as e:
615
+ print(f"Error serving audio file: {str(e)}")
616
+ return "Audio file not found", 404
617
+
618
+ if __name__ == '__main__':
619
+ pio.templates.default = "plotly_white"
620
+ app.run(debug=True, host='0.0.0.0', port=7860)
templates/index.html CHANGED
@@ -3,222 +3,785 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Crop Financial Market Analysis & Prediction</title>
7
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
 
8
  <style>
 
 
 
 
 
 
 
 
 
9
  body {
10
- background-color: #f9f9f9;
 
 
 
11
  }
 
12
  .container {
13
- margin-top: 30px;
 
 
14
  }
15
- h1 {
16
- color: #4CAF50;
17
  text-align: center;
 
 
 
 
 
 
18
  font-weight: bold;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
- .form-row {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  margin-bottom: 20px;
22
  }
23
- .table-container {
24
- border: 1px solid #4CAF50;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  border-radius: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  padding: 15px;
27
- background-color: #fff;
28
- max-height: 400px;
29
- overflow-y: auto;
30
- width: 100%;
31
  }
32
- .table-container table {
33
- width: 100%;
 
 
 
 
 
 
 
 
34
  margin-bottom: 0;
35
  }
36
- .result-section {
37
- margin-top: 30px;
 
 
 
 
38
  }
39
- .bordered-table {
40
- border: 1px solid #4CAF50;
41
- background-color: #fff;
 
 
 
 
 
42
  }
43
- .bold-header {
 
 
44
  font-weight: bold;
45
- color: #333;
46
  }
47
- th {
48
- background-color: #4CAF50;
49
- color: white;
 
50
  }
51
- .chart-container {
52
- display: flex;
53
- justify-content: space-between;
 
 
 
 
 
 
 
54
  margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
- .chart {
57
- width: 48%;
 
 
 
58
  }
59
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </head>
61
  <body>
62
  <div class="container">
63
- <h1>Crop Financial Market Analysis & Prediction</h1>
64
- <form id="filterForm">
65
- <div class="form-row">
66
- <div class="form-group col-md-3">
67
- <label for="state">State</label>
68
- <select class="form-control" id="state" name="state">
69
- <option value="">Select State</option>
70
- {% for state in states %}
71
- <option value="{{ state }}">{{ state }}</option>
72
- {% endfor %}
73
- </select>
74
- </div>
75
- <div class="form-group col-md-3">
76
- <label for="district">District</label>
77
- <select class="form-control" id="district" name="district">
78
- <option value="">Select District</option>
79
- </select>
80
- </div>
81
- <div class="form-group col-md-3">
82
- <label for="market">Market</label>
83
- <select class="form-control" id="market" name="market">
84
- <option value="">Select Market</option>
85
- </select>
 
 
 
 
 
 
 
 
 
 
 
86
  </div>
87
- <div class="form-group col-md-3">
88
- <label for="commodity">Commodity</label>
89
- <select class="form-control" id="commodity" name="commodity">
90
- <option value="">Select Commodity</option>
91
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  </div>
 
 
 
 
 
 
93
  </div>
94
- </form>
95
-
96
- <div class="result-section">
97
- <div class="table-container">
98
- <h3 class="bold-header">Market Data as of {{ today }}</h3>
99
- <div class="bordered-table">
100
- <table class="table table-bordered table-hover">
101
- <thead>
102
- <tr>
103
- <th>State</th>
104
- <th>District</th>
105
- <th>Market</th>
106
- <th>Commodity</th>
107
- <th>Variety</th>
108
- <th>Grade</th>
109
- <th>Arrival Date</th>
110
- <th>Min Price</th>
111
- <th>Max Price</th>
112
- <th>Modal Price</th>
113
- </tr>
114
- </thead>
115
- <tbody id="marketData">
116
- <!-- Market data will be populated here via AJAX -->
117
- </tbody>
118
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  </div>
120
  </div>
121
 
122
- <div class="result-section">
123
- <h3 class="bold-header">Top 5 Cheapest Crops</h3>
124
- <div class="bordered-table">
125
- <table class="table table-bordered table-hover">
126
- <thead>
127
- <tr>
128
- <th>Commodity</th>
129
- <th>Modal Price</th>
130
- </tr>
131
- </thead>
132
- <tbody id="cheapestCrops">
133
- <!-- Cheapest crops data will be populated here -->
134
- </tbody>
135
- </table>
 
 
 
 
136
  </div>
137
  </div>
138
 
139
- <div class="result-section">
140
- <h3 class="bold-header">Top 5 Costliest Crops</h3>
141
- <div class="bordered-table">
142
- <table class="table table-bordered table-hover">
143
- <thead>
144
- <tr>
145
- <th>Commodity</th>
146
- <th>Modal Price</th>
147
- </tr>
148
- </thead>
149
- <tbody id="costliestCrops">
150
- <!-- Costliest crops data will be populated here -->
151
- </tbody>
152
- </table>
153
  </div>
154
  </div>
155
  </div>
 
 
 
156
  </div>
157
 
158
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
159
  <script>
160
- // Fetch districts based on state selection
161
- $('#state').change(function() {
162
- let state = $(this).val();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  $.ajax({
164
- url: '/get_districts',
165
  method: 'POST',
166
- data: { state: state },
167
- success: function(districts) {
168
- $('#district').empty().append('<option value="">Select District</option>');
169
- districts.forEach(function(district) {
170
- $('#district').append('<option value="' + district + '">' + district + '</option>');
171
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  });
175
 
176
- // Fetch markets based on district selection
177
  $('#district').change(function() {
178
- let district = $(this).val();
179
- $.ajax({
180
- url: '/get_markets',
181
- method: 'POST',
182
- data: { district: district },
183
- success: function(markets) {
184
- $('#market').empty().append('<option value="">Select Market</option>');
185
- markets.forEach(function(market) {
186
- $('#market').append('<option value="' + market + '">' + market + '</option>');
187
  });
188
- }
189
- });
 
 
 
190
  });
191
 
192
- // Fetch commodities based on market selection
193
  $('#market').change(function() {
194
- let market = $(this).val();
195
- $.ajax({
196
- url: '/get_commodities',
197
- method: 'POST',
198
- data: { market: market },
199
- success: function(commodities) {
200
- $('#commodity').empty().append('<option value="">Select Commodity</option>');
201
- commodities.forEach(function(commodity) {
202
- $('#commodity').append('<option value="' + commodity + '">' + commodity + '</option>');
203
  });
204
- }
205
- });
 
 
 
206
  });
207
 
208
- // Filter data and update table upon form change
209
- $('#filterForm select').change(function() {
210
- let formData = $('#filterForm').serialize();
211
- $.ajax({
212
- url: '/filter_data',
213
- method: 'POST',
214
- data: formData,
215
- success: function(data) {
216
- $('#marketData').html(data.market_html);
217
- $('#cheapestCrops').html(data.cheapest_html);
218
- $('#costliestCrops').html(data.costliest_html);
219
- }
220
- });
221
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  </script>
 
 
223
  </body>
224
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Crop Market Analysis</title>
7
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
8
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
9
  <style>
10
+ :root {
11
+ --primary-color: #4CAF50;
12
+ --secondary-color: #45a049;
13
+ --background-color: #f9f9f9;
14
+ --text-color: #333;
15
+ --card-shadow: 0 2px 4px rgba(0,0,0,0.1);
16
+ --border-radius: 8px;
17
+ }
18
+
19
  body {
20
+ background-color: var(--background-color);
21
+ color: var(--text-color);
22
+ font-family: 'Arial', sans-serif;
23
+ line-height: 1.6;
24
  }
25
+
26
  .container {
27
+ max-width: 1200px;
28
+ margin: 0 auto;
29
+ padding: 20px;
30
  }
31
+
32
+ .header {
33
  text-align: center;
34
+ margin-bottom: 30px;
35
+ padding: 20px 0;
36
+ }
37
+
38
+ .header h1 {
39
+ color: var(--primary-color);
40
  font-weight: bold;
41
+ margin: 0;
42
+ font-size: 2.5rem;
43
+ }
44
+
45
+ .form-section {
46
+ background: white;
47
+ padding: 25px;
48
+ border-radius: var(--border-radius);
49
+ box-shadow: var(--card-shadow);
50
+ margin-bottom: 30px;
51
+ }
52
+
53
+ .chart-container {
54
+ background: white;
55
+ padding: 25px;
56
+ border-radius: var(--border-radius);
57
+ box-shadow: var(--card-shadow);
58
+ margin-bottom: 30px;
59
+ }
60
+
61
+ .insights-container {
62
+ background: white;
63
+ padding: 25px;
64
+ border-radius: var(--border-radius);
65
+ box-shadow: var(--card-shadow);
66
+ margin-bottom: 30px;
67
+ border-left: 5px solid var(--primary-color);
68
+ }
69
+
70
+ .insights-container h3 {
71
+ color: var(--primary-color);
72
+ margin-bottom: 20px;
73
+ }
74
+
75
+ .loading {
76
+ display: none;
77
+ text-align: center;
78
+ padding: 20px;
79
+ background: rgba(255, 255, 255, 0.9);
80
+ position: fixed;
81
+ top: 50%;
82
+ left: 50%;
83
+ transform: translate(-50%, -50%);
84
+ border-radius: var(--border-radius);
85
+ box-shadow: var(--card-shadow);
86
+ z-index: 1000;
87
+ }
88
+
89
+ .btn-custom {
90
+ background-color: var(--primary-color);
91
+ color: white;
92
+ border: none;
93
+ padding: 8px 16px;
94
+ border-radius: 4px;
95
+ transition: background-color 0.3s ease;
96
+ }
97
+
98
+ .btn-custom:hover {
99
+ background-color: var(--secondary-color);
100
+ color: white;
101
+ }
102
+
103
+ .form-control {
104
+ border-radius: 4px;
105
+ border: 1px solid #ddd;
106
+ padding: 8px 12px;
107
+ height: auto;
108
  }
109
+
110
+ .form-control:focus {
111
+ border-color: var(--primary-color);
112
+ box-shadow: 0 0 0 0.2rem rgba(76, 175, 80, 0.25);
113
+ }
114
+
115
+ label {
116
+ font-weight: 500;
117
+ margin-bottom: 8px;
118
+ color: var(--text-color);
119
+ }
120
+
121
+ #barChart, #lineChart, #boxChart {
122
+ width: 100%;
123
+ margin-bottom: 20px;
124
+ }
125
+
126
+ #aiInsights {
127
+ line-height: 1.8;
128
+ font-size: 1.1rem;
129
+ }
130
+
131
+ .alert {
132
+ border-radius: var(--border-radius);
133
+ padding: 15px 20px;
134
  margin-bottom: 20px;
135
  }
136
+
137
+ .spinner-border {
138
+ width: 3rem;
139
+ height: 3rem;
140
+ color: var(--primary-color);
141
+ }
142
+
143
+ #marketData {
144
+ height: 600px;
145
+ overflow-y: scroll;
146
+ }
147
+
148
+ .table-responsive{
149
+ height: 600px;
150
+ }
151
+
152
+ .insights-header {
153
+ background: #4CAF50;
154
+ color: white;
155
+ padding: 15px 20px;
156
+ border-radius: 8px 8px 0 0;
157
+ margin: -25px -25px 20px -25px;
158
+ }
159
+
160
+ .insights-header h3 {
161
+ margin: 0;
162
+ color: white;
163
+ }
164
+
165
+ .insight-section {
166
+ background: #f8f9fa;
167
  border-radius: 8px;
168
+ padding: 20px;
169
+ margin-bottom: 20px;
170
+ border-left: 4px solid #4CAF50;
171
+ }
172
+
173
+ .insight-section h4 {
174
+ color: #2E7D32;
175
+ margin-bottom: 15px;
176
+ font-size: 1.2rem;
177
+ font-weight: bold;
178
+ }
179
+
180
+ .insight-card {
181
+ background: white;
182
+ border-radius: 6px;
183
  padding: 15px;
184
+ margin-bottom: 15px;
185
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
 
 
186
  }
187
+
188
+ .insight-card h5 {
189
+ color: #1B5E20;
190
+ margin-bottom: 10px;
191
+ font-size: 1.1rem;
192
+ }
193
+
194
+ .insight-list {
195
+ list-style: none;
196
+ padding-left: 0;
197
  margin-bottom: 0;
198
  }
199
+
200
+ .insight-list li {
201
+ position: relative;
202
+ padding-left: 20px;
203
+ margin-bottom: 8px;
204
+ line-height: 1.5;
205
  }
206
+
207
+ .insight-list li:before {
208
+ content: "•";
209
+ color: #4CAF50;
210
+ font-size: 1.2em;
211
+ position: absolute;
212
+ left: 0;
213
+ top: -2px;
214
  }
215
+
216
+ .price-highlight {
217
+ color: #2E7D32;
218
  font-weight: bold;
 
219
  }
220
+
221
+ .percentage-up {
222
+ color: #2E7D32;
223
+ font-weight: bold;
224
  }
225
+
226
+ .percentage-down {
227
+ color: #c62828;
228
+ font-weight: bold;
229
+ }
230
+
231
+ .action-box {
232
+ background: #E8F5E9;
233
+ border-radius: 6px;
234
+ padding: 15px;
235
  margin-top: 20px;
236
+ border: 1px dashed #4CAF50;
237
+ }
238
+
239
+ .action-box h5 {
240
+ color: #2E7D32;
241
+ margin-bottom: 10px;
242
+ font-size: 1.1rem;
243
+ }
244
+
245
+ .action-list {
246
+ list-style: none;
247
+ padding-left: 0;
248
+ margin-bottom: 0;
249
+ }
250
+
251
+ .action-list li {
252
+ position: relative;
253
+ padding-left: 25px;
254
+ margin-bottom: 8px;
255
+ line-height: 1.5;
256
+ }
257
+
258
+ .action-list li:before {
259
+ content: "✓";
260
+ color: #4CAF50;
261
+ position: absolute;
262
+ left: 0;
263
+ font-weight: bold;
264
+ }
265
+
266
+ @media (max-width: 768px) {
267
+ .insights-container {
268
+ padding: 15px;
269
+ }
270
+
271
+ .insights-header {
272
+ padding: 12px 15px;
273
+ margin: -15px -15px 15px -15px;
274
+ }
275
+
276
+ .insight-section {
277
+ padding: 15px;
278
+ }
279
+ }
280
+ @media (max-width: 768px) {
281
+ .container {
282
+ padding: 10px;
283
+ }
284
+
285
+ .header h1 {
286
+ font-size: 2rem;
287
+ }
288
+
289
+ .form-row {
290
+ flex-direction: column;
291
+ }
292
+
293
+ .form-group {
294
+ margin-bottom: 15px;
295
+ }
296
+
297
+ .btn-custom {
298
+ width: 100%;
299
+ }
300
+
301
+ .insights-container {
302
+ padding: 15px;
303
+ }
304
+
305
+ #aiInsights {
306
+ font-size: 1rem;
307
+ }
308
+ }
309
+ .language-selector {
310
+ margin-top: 10px;
311
+ }
312
+
313
+ .lang-btn {
314
+ font-size: 0.85rem;
315
+ padding: 0.25rem 0.5rem;
316
+ margin: 0 2px;
317
+ border-radius: 4px;
318
+ }
319
+
320
+ .lang-btn.active {
321
+ background-color: #4CAF50;
322
+ color: white;
323
+ }
324
+
325
+ .current-language-indicator {
326
+ font-size: 0.8rem;
327
+ color: #555;
328
+ margin: 5px 0 15px 0;
329
+ }
330
+
331
+ /* For mobile responsiveness */
332
+ @media (max-width: 768px) {
333
+ .language-selector .btn-group {
334
+ display: flex;
335
+ flex-wrap: wrap;
336
+ justify-content: center;
337
+ }
338
+
339
+ .lang-btn {
340
+ margin: 2px;
341
+ flex: 0 0 auto;
342
+ }
343
+ }
344
+ select {
345
+ max-height: 200px;
346
+ overflow-y: auto;
347
+ }
348
+
349
+ .audio-player-container {
350
+ background: #e8f5e9;
351
+ padding: 12px;
352
+ border-radius: 8px;
353
+ margin: 10px 0 0 20px;
354
+ display: inline-block;
355
+ vertical-align: middle;
356
+ }
357
+
358
+ .audio-player-container h4 {
359
+ color: #2E7D32;
360
+ margin: 0 0 8px 0;
361
+ font-size: 0.9rem;
362
+ font-weight: bold;
363
+ }
364
+
365
+ .audio-player-container audio {
366
+ height: 35px;
367
+ vertical-align: middle;
368
+ }
369
+
370
+ #playAudioBtn {
371
+ background-color: #4CAF50;
372
+ color: white;
373
+ border: none;
374
+ margin-left: 5px;
375
+ font-size: 0.85em;
376
+ }
377
+
378
+ #playAudioBtn:hover {
379
+ background-color: #45a049;
380
+ }
381
+
382
+ @media (max-width: 768px) {
383
+ .insights-header {
384
+ display: flex;
385
+ flex-direction: column;
386
+ }
387
+
388
+ .audio-player-container {
389
+ margin: 10px 0 0 0;
390
+ width: 100%;
391
+ }
392
+
393
+ .audio-player-container audio {
394
+ width: 100%;
395
  }
396
+
397
+ #playAudioBtn {
398
+ display: block;
399
+ width: 100%;
400
+ margin: 5px 0 0 0;
401
  }
402
+ }
403
+
404
+ /* Add FontAwesome for play/pause icons */
405
+ .fa {
406
+ display: inline-block;
407
+ font: normal normal normal 14px/1 FontAwesome;
408
+ font-size: inherit;
409
+ text-rendering: auto;
410
+ -webkit-font-smoothing: antialiased;
411
+ -moz-osx-font-smoothing: grayscale;
412
+ }
413
+ .fa-play:before {
414
+ content: "\f04b";
415
+ }
416
+ .fa-pause:before {
417
+ content: "\f04c";
418
+ }
419
+ </style>
420
  </head>
421
  <body>
422
  <div class="container">
423
+ <div class="header">
424
+ <h1>Crop Market Analysis</h1>
425
+ </div>
426
+
427
+ <div class="form-section">
428
+ <form id="filterForm">
429
+ <div class="form-row">
430
+ <div class="form-group col-md-3">
431
+ <label for="state">State</label>
432
+ <select class="form-control" id="state" name="state">
433
+ <option value="">Select State</option>
434
+ {% for state in states %}
435
+ <option value="{{ state }}">{{ state }}</option>
436
+ {% endfor %}
437
+ </select>
438
+ </div>
439
+ <div class="form-group col-md-3">
440
+ <label for="district">District</label>
441
+ <select class="form-control" id="district" name="district" disabled>
442
+ <option value="">Select District</option>
443
+ </select>
444
+ </div>
445
+ <div class="form-group col-md-3">
446
+ <label for="market">Market</label>
447
+ <select class="form-control" id="market" name="market" disabled>
448
+ <option value="">Select Market</option>
449
+ </select>
450
+ </div>
451
+ <div class="form-group col-md-3">
452
+ <label for="commodity">Commodity</label>
453
+ <select class="form-control" id="commodity" name="commodity" disabled>
454
+ <option value="">Select Commodity</option>
455
+ </select>
456
+ </div>
457
  </div>
458
+ <div class="language-selector">
459
+ <div class="card">
460
+ <div class="card-body">
461
+ <h5 class="card-title">Select Language for AI Insights</h5>
462
+ <div class="btn-group" role="group" aria-label="Language selection">
463
+ <button type="button" class="btn btn-sm lang-btn active" data-lang="English">English</button>
464
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Hindi">हिन्दी</button>
465
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Tamil">தமிழ்</button>
466
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Telugu">తెలుగు</button>
467
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Marathi">मराठी</button>
468
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Bengali">বাংলা</button>
469
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Gujarati">ગુજરાતી</button>
470
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Kannada">ಕನ್ನಡ</button>
471
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Malayalam">മലയാളം</button>
472
+ <button type="button" class="btn btn-sm lang-btn" data-lang="Punjabi">ਪੰਜਾਬੀ</button>
473
+ </div>
474
+ <div class="current-language-indicator mt-2">
475
+ Current language: <span id="currentLanguage">English</span>
476
+ </div>
477
+ </div>
478
+ </div>
479
  </div>
480
+ </form>
481
+ </div>
482
+
483
+ <div class="loading" id="loadingIndicator">
484
+ <div class="spinner-border" role="status">
485
+ <span class="sr-only">Loading...</span>
486
  </div>
487
+ </div>
488
+
489
+ <div class="chart-container">
490
+ <div id="barChart"></div>
491
+ <div id="lineChart"></div>
492
+ <div id="boxChart"></div>
493
+ </div>
494
+ <div class="market-data-container">
495
+ <div class="row">
496
+ <div class="col-md-12 mb-4">
497
+ <div class="card">
498
+ <div class="card-header">
499
+ <h4>Market Statistics</h4>
500
+ </div>
501
+ <div class="card-body">
502
+ <div class="row">
503
+ <div class="col-md-3">
504
+ <div class="stat-item">
505
+ <h6>Total Commodities</h6>
506
+ <span id="totalCommodities"></span>
507
+ </div>
508
+ </div>
509
+ <div class="col-md-3">
510
+ <div class="stat-item">
511
+ <h6>Average Price</h6>
512
+ <span id="avgPrice"></span>
513
+ </div>
514
+ </div>
515
+ <div class="col-md-3">
516
+ <div class="stat-item">
517
+ <h6>Price Range</h6>
518
+ <span id="priceRange"></span>
519
+ </div>
520
+ </div>
521
+ <div class="col-md-3">
522
+ <div class="stat-item">
523
+ <h6>Total Markets</h6>
524
+ <span id="totalMarkets"></span>
525
+ </div>
526
+ </div>
527
+ </div>
528
+ </div>
529
+ </div>
530
  </div>
531
  </div>
532
 
533
+ <div class="row">
534
+ <div class="col-md-6 mb-4">
535
+ <div class="card">
536
+ <div class="card-header">
537
+ <h4>Top 5 Cheapest Crops</h4>
538
+ </div>
539
+ <div class="card-body" id="cheapestCrops">
540
+ </div>
541
+ </div>
542
+ </div>
543
+ <div class="col-md-6 mb-4">
544
+ <div class="card">
545
+ <div class="card-header">
546
+ <h4>Top 5 Costliest Crops</h4>
547
+ </div>
548
+ <div class="card-body" id="costliestCrops">
549
+ </div>
550
+ </div>
551
  </div>
552
  </div>
553
 
554
+ <div class="card mb-4">
555
+ <div class="card-header">
556
+ <h4>Market Data</h4>
557
+ </div>
558
+ <div class="card-body" id="marketData">
 
 
 
 
 
 
 
 
 
559
  </div>
560
  </div>
561
  </div>
562
+ <div class="insights-container">
563
+ <div id="aiInsights"></div>
564
+ </div>
565
  </div>
566
 
567
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
568
  <script>
569
+ // Add this to your JavaScript
570
+ function updateAudioPlayer(audioPath) {
571
+ if (audioPath) {
572
+ $('#audioPlayerContainer').show();
573
+ $('#insightsAudio').attr('src', audioPath);
574
+ } else {
575
+ $('#audioPlayerContainer').hide();
576
+ }
577
+ }
578
+ function showLoading() {
579
+ $('#loadingIndicator').show();
580
+ }
581
+
582
+ function hideLoading() {
583
+ $('#loadingIndicator').hide();
584
+ }
585
+
586
+ function enableSelect(selectId) {
587
+ $(`#${selectId}`).prop('disabled', false);
588
+ }
589
+
590
+ function disableSelect(selectId) {
591
+ $(`#${selectId}`).prop('disabled', true);
592
+ }
593
+
594
+ function updateContent() {
595
+ showLoading();
596
+ const formData = new FormData($('#filterForm')[0]);
597
+
598
  $.ajax({
599
+ url: '/filter_data',
600
  method: 'POST',
601
+ data: formData,
602
+ processData: false,
603
+ contentType: false,
604
+ success: function(response) {
605
+ if (response.success) {
606
+ if (response.plots.bar) $('#barChart').html(response.plots.bar);
607
+ if (response.plots.line) $('#lineChart').html(response.plots.line);
608
+ if (response.plots.box) $('#boxChart').html(response.plots.box);
609
+
610
+ $('#totalCommodities').text(response.market_stats.total_commodities);
611
+ $('#avgPrice').text(response.market_stats.avg_modal_price);
612
+ $('#priceRange').text(response.market_stats.price_range);
613
+ $('#totalMarkets').text(response.market_stats.total_markets);
614
+
615
+ $('#marketData').html(response.market_html);
616
+ $('#cheapestCrops').html(response.cheapest_html);
617
+ $('#costliestCrops').html(response.costliest_html);
618
+
619
+ if (response.hasStateDistrict) {
620
+ $('.insights-container').show();
621
+ $('#aiInsights').html(response.insights);
622
+ } else {
623
+ $('.insights-container').hide();
624
+ $('#aiInsights').html('');
625
+ }
626
+ } else {
627
+ alert('Please select both state and district to view analysis');
628
+ }
629
+ hideLoading();
630
+ },
631
+ error: function() {
632
+ alert('Error loading data');
633
+ hideLoading();
634
  }
635
  });
636
+ }
637
+
638
+ $('#state').change(function() {
639
+ const state = $(this).val();
640
+ $('#district, #market, #commodity').html('<option value="">Select</option>').prop('disabled', true);
641
+
642
+ if (state) {
643
+ showLoading();
644
+ $.post('/get_districts', { state: state }, function(districts) {
645
+ $('#district').html('<option value="">Select District</option>');
646
+ districts.forEach(district => {
647
+ $('#district').append(`<option value="${district}">${district}</option>`);
648
+ });
649
+ enableSelect('district');
650
+ hideLoading();
651
+ });
652
+ }
653
+ updateContent();
654
  });
655
 
 
656
  $('#district').change(function() {
657
+ const district = $(this).val();
658
+ $('#market, #commodity').html('<option value="">Select</option>').prop('disabled', true);
659
+
660
+ if (district) {
661
+ showLoading();
662
+ $.post('/get_markets', { district: district }, function(markets) {
663
+ $('#market').html('<option value="">Select Market</option>');
664
+ markets.forEach(market => {
665
+ $('#market').append(`<option value="${market}">${market}</option>`);
666
  });
667
+ enableSelect('market');
668
+ hideLoading();
669
+ });
670
+ }
671
+ updateContent();
672
  });
673
 
 
674
  $('#market').change(function() {
675
+ const market = $(this).val();
676
+ $('#commodity').html('<option value="">Select</option>').prop('disabled', true);
677
+
678
+ if (market) {
679
+ showLoading();
680
+ $.post('/get_commodities', { market: market }, function(commodities) {
681
+ $('#commodity').html('<option value="">Select Commodity</option>');
682
+ commodities.forEach(commodity => {
683
+ $('#commodity').append(`<option value="${commodity}">${commodity}</option>`);
684
  });
685
+ enableSelect('commodity');
686
+ hideLoading();
687
+ });
688
+ }
689
+ updateContent();
690
  });
691
 
692
+ $('#commodity').change(function() {
693
+ updateContent();
694
+ });
695
+
696
+ $(document).ready(function() {
697
+ $('.insights-container').hide();
698
+ updateContent();
 
 
 
 
 
 
699
  });
700
+
701
+ // Language handling
702
+ let currentLanguage = "English";
703
+
704
+ $('.lang-btn').click(function() {
705
+ $('.lang-btn').removeClass('active');
706
+ $(this).addClass('active');
707
+ currentLanguage = $(this).data('lang');
708
+ $('#currentLanguage').text(currentLanguage);
709
+
710
+ // If state and district are selected, update the content to reflect the new language
711
+ if ($('#state').val() && $('#district').val()) {
712
+ updateContent();
713
+ }
714
+ });
715
+
716
+ // Modify the updateContent function to include language
717
+ function updateContent() {
718
+ showLoading();
719
+ const formData = new FormData($('#filterForm')[0]);
720
+
721
+ // Add language to form data
722
+ formData.append('language', currentLanguage);
723
+
724
+ $.ajax({
725
+ url: '/filter_data',
726
+ method: 'POST',
727
+ data: formData,
728
+ processData: false,
729
+ contentType: false,
730
+ success: function(response) {
731
+ if (response.success) {
732
+ if (response.plots.bar) $('#barChart').html(response.plots.bar);
733
+ if (response.plots.line) $('#lineChart').html(response.plots.line);
734
+ if (response.plots.box) $('#boxChart').html(response.plots.box);
735
+
736
+ $('#totalCommodities').text(response.market_stats.total_commodities);
737
+ $('#avgPrice').text(response.market_stats.avg_modal_price);
738
+ $('#priceRange').text(response.market_stats.price_range);
739
+ $('#totalMarkets').text(response.market_stats.total_markets);
740
+
741
+ $('#marketData').html(response.market_html);
742
+ $('#cheapestCrops').html(response.cheapest_html);
743
+ $('#costliestCrops').html(response.costliest_html);
744
+
745
+ if (response.hasStateDistrict) {
746
+ $('.insights-container').show();
747
+ $('#aiInsights').html(response.insights);
748
+ } else {
749
+ $('.insights-container').hide();
750
+ $('#aiInsights').html('');
751
+ }
752
+ } else {
753
+ alert('Please select both state and district to view analysis');
754
+ }
755
+ hideLoading();
756
+ },
757
+ error: function() {
758
+ alert('Error loading data');
759
+ hideLoading();
760
+ }
761
+ });
762
+ }
763
+ // Add this in the <head> section after the jQuery import
764
+
765
+ $(document).on('click', '#playAudioBtn', function() {
766
+ const audioPlayer = document.getElementById('insightsAudio');
767
+
768
+ if (audioPlayer) {
769
+ if (audioPlayer.paused) {
770
+ audioPlayer.play();
771
+ $(this).html('<i class="fa fa-pause"></i> Pause Audio');
772
+ } else {
773
+ audioPlayer.pause();
774
+ $(this).html('<i class="fa fa-play"></i> Play Audio');
775
+ }
776
+ }
777
+ });
778
+
779
+ // Reset play button text when audio ends
780
+ $(document).on('ended', '#insightsAudio', function() {
781
+ $('#playAudioBtn').html('<i class="fa fa-play"></i> Play Audio');
782
+ });
783
  </script>
784
+ <script src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
785
+
786
  </body>
787
  </html>