Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,58 +5,28 @@ import pandas as pd
|
|
5 |
from datetime import datetime
|
6 |
import plotly.express as px
|
7 |
import plotly.io as pio
|
8 |
-
from googletrans import Translator
|
9 |
import numpy as np
|
|
|
|
|
10 |
|
11 |
-
|
12 |
-
|
13 |
-
# Initialize translator
|
14 |
-
translator = Translator()
|
15 |
|
16 |
-
|
17 |
-
MARATHI_TRANSLATIONS = {
|
18 |
-
'state': 'राज्य',
|
19 |
-
'district': 'जिल्हा',
|
20 |
-
'market': 'बाजार',
|
21 |
-
'commodity': 'पीक',
|
22 |
-
'variety': 'प्रकार',
|
23 |
-
'grade': 'श्रेणी',
|
24 |
-
'arrival_date': 'आगमन तारीख',
|
25 |
-
'min_price': 'किमान किंमत',
|
26 |
-
'max_price': 'कमाल किंमत',
|
27 |
-
'modal_price': 'सरासरी किंमत',
|
28 |
-
'Select State': 'राज्य निवडा',
|
29 |
-
'Select District': 'जिल्हा निवडा',
|
30 |
-
'Select Market': 'बाजार निवडा',
|
31 |
-
'Select Commodity': 'पीक निवडा',
|
32 |
-
'Market Data': 'बाजार माहिती',
|
33 |
-
'Top 5 Cheapest Crops': 'सर्वात स्वस्त 5 पिके',
|
34 |
-
'Top 5 Costliest Crops': 'सर्वात महाग 5 पिके'
|
35 |
-
}
|
36 |
-
|
37 |
-
def translate_to_marathi(text):
|
38 |
-
"""Translate text to Marathi"""
|
39 |
-
try:
|
40 |
-
if text in MARATHI_TRANSLATIONS:
|
41 |
-
return MARATHI_TRANSLATIONS[text]
|
42 |
-
translation = translator.translate(text, dest='mr')
|
43 |
-
return translation.text
|
44 |
-
except:
|
45 |
-
return text
|
46 |
|
47 |
def fetch_market_data(state=None, district=None, market=None, commodity=None):
|
48 |
-
"""Fetch data from the agricultural market API
|
49 |
-
|
50 |
-
|
51 |
-
|
|
|
|
|
52 |
|
53 |
params = {
|
54 |
-
"api-key":
|
55 |
"format": "json",
|
56 |
-
"limit":
|
57 |
}
|
58 |
|
59 |
-
# Add filters if provided
|
60 |
if state:
|
61 |
params["filters[state]"] = state
|
62 |
if district:
|
@@ -72,147 +42,295 @@ def fetch_market_data(state=None, district=None, market=None, commodity=None):
|
|
72 |
data = response.json()
|
73 |
records = data.get("records", [])
|
74 |
df = pd.DataFrame(records)
|
75 |
-
return df
|
76 |
else:
|
77 |
print(f"API Error: {response.status_code}")
|
78 |
-
|
79 |
except Exception as e:
|
80 |
-
print(f"Error fetching data: {str(e)}")
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
-
def get_ai_insights(market_data, state, district):
|
85 |
-
"""Get enhanced insights from
|
|
|
|
|
|
|
86 |
if not state or not district or market_data.empty:
|
87 |
return ""
|
88 |
-
|
89 |
try:
|
90 |
-
#
|
91 |
district_data = market_data[market_data['district'] == district]
|
|
|
|
|
92 |
|
93 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
price_trends = district_data.groupby('commodity').agg({
|
95 |
'modal_price': ['mean', 'min', 'max', 'std']
|
96 |
}).round(2)
|
97 |
|
98 |
-
#
|
|
|
|
|
|
|
|
|
|
|
99 |
price_trends['price_stability'] = (price_trends['modal_price']['std'] /
|
100 |
price_trends['modal_price']['mean']).round(2)
|
101 |
|
102 |
-
# Identify commodities with consistent high prices
|
103 |
-
high_value_crops = price_trends[price_trends['modal_price']['mean'] >
|
104 |
-
price_trends['modal_price']['mean'].median()]
|
105 |
-
|
106 |
-
# Get seasonal patterns
|
107 |
district_data['arrival_date'] = pd.to_datetime(district_data['arrival_date'])
|
108 |
district_data['month'] = district_data['arrival_date'].dt.month
|
109 |
monthly_trends = district_data.groupby(['commodity', 'month'])['modal_price'].mean().round(2)
|
110 |
|
111 |
-
# Market competition analysis
|
112 |
market_competition = len(district_data['market'].unique())
|
|
|
113 |
|
114 |
-
#
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
prompt = f"""
|
131 |
-
|
132 |
-
|
133 |
-
Market
|
134 |
-
-
|
135 |
-
-
|
136 |
-
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
5. Storage and Timing Recommendations:
|
162 |
-
- Which crops are worth storing for better prices?
|
163 |
-
- What are the best times to sell each major crop?
|
164 |
-
- How can farmers use price trends to time their sales?
|
165 |
-
|
166 |
-
Provide practical, actionable advice that farmers can implement immediately. Include specific numbers and percentages where relevant.
|
167 |
-
Break the response into clear sections and keep it concise but informative.
|
168 |
"""
|
169 |
-
|
170 |
-
|
|
|
|
|
171 |
payload = {
|
172 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
}
|
174 |
|
175 |
-
response = requests.post(
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
if response.status_code == 200:
|
177 |
response_data = response.json()
|
178 |
-
if
|
179 |
-
|
180 |
-
len(
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
except Exception as e:
|
191 |
print(f"Error generating insights: {str(e)}")
|
192 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
|
194 |
-
def generate_plots(df
|
195 |
-
"""Generate all plots
|
196 |
if df.empty:
|
197 |
return {}, "No data available"
|
198 |
|
199 |
-
# Convert price columns to numeric
|
200 |
price_cols = ['min_price', 'max_price', 'modal_price']
|
201 |
for col in price_cols:
|
202 |
df[col] = pd.to_numeric(df[col], errors='coerce')
|
203 |
|
204 |
-
# Color scheme
|
205 |
colors = ["#4CAF50", "#8BC34A", "#CDDC39", "#FFC107", "#FF5722"]
|
206 |
|
207 |
-
# 1. Bar Chart
|
208 |
df_bar = df.groupby('commodity')['modal_price'].mean().reset_index()
|
209 |
fig_bar = px.bar(df_bar,
|
210 |
x='commodity',
|
211 |
y='modal_price',
|
212 |
-
title=
|
213 |
color_discrete_sequence=colors)
|
214 |
|
215 |
-
# 2. Line Chart (if commodity selected)
|
216 |
fig_line = None
|
217 |
if 'commodity' in df.columns and len(df['commodity'].unique()) == 1:
|
218 |
df['arrival_date'] = pd.to_datetime(df['arrival_date'])
|
@@ -220,18 +338,16 @@ def generate_plots(df, lang='en'):
|
|
220 |
fig_line = px.line(df_line,
|
221 |
x='arrival_date',
|
222 |
y='modal_price',
|
223 |
-
title=
|
224 |
color_discrete_sequence=colors)
|
225 |
|
226 |
-
# 3. Box Plot
|
227 |
fig_box = px.box(df,
|
228 |
x='commodity',
|
229 |
y='modal_price',
|
230 |
-
title=
|
231 |
color='commodity',
|
232 |
color_discrete_sequence=colors)
|
233 |
|
234 |
-
# Convert to HTML
|
235 |
plots = {
|
236 |
'bar': pio.to_html(fig_bar, full_html=False),
|
237 |
'box': pio.to_html(fig_box, full_html=False)
|
@@ -243,27 +359,30 @@ def generate_plots(df, lang='en'):
|
|
243 |
|
244 |
@app.route('/')
|
245 |
def index():
|
246 |
-
|
247 |
-
|
248 |
-
|
|
|
|
|
|
|
|
|
249 |
return render_template('index.html',
|
250 |
-
|
251 |
-
|
252 |
|
253 |
@app.route('/filter_data', methods=['POST'])
|
254 |
def filter_data():
|
255 |
-
"""Handle data filtering, chart generation, and table generation"""
|
256 |
state = request.form.get('state')
|
257 |
district = request.form.get('district')
|
258 |
market = request.form.get('market')
|
259 |
commodity = request.form.get('commodity')
|
260 |
-
|
261 |
|
262 |
df = fetch_market_data(state, district, market, commodity)
|
263 |
-
plots = generate_plots(df
|
264 |
-
|
|
|
265 |
|
266 |
-
# Generate market data table HTML
|
267 |
market_table_html = """
|
268 |
<div class="table-responsive">
|
269 |
<table class="table table-striped table-bordered">
|
@@ -301,7 +420,6 @@ def filter_data():
|
|
301 |
"""
|
302 |
market_table_html += "</tbody></table></div>"
|
303 |
|
304 |
-
# Generate top 5 cheapest crops table
|
305 |
cheapest_crops = df.sort_values('modal_price', ascending=True).head(5)
|
306 |
cheapest_table_html = """
|
307 |
<div class="table-responsive">
|
@@ -326,7 +444,6 @@ def filter_data():
|
|
326 |
"""
|
327 |
cheapest_table_html += "</tbody></table></div>"
|
328 |
|
329 |
-
# Generate top 5 costliest crops table
|
330 |
costliest_crops = df.sort_values('modal_price', ascending=False).head(5)
|
331 |
costliest_table_html = """
|
332 |
<div class="table-responsive">
|
@@ -351,7 +468,6 @@ def filter_data():
|
|
351 |
"""
|
352 |
costliest_table_html += "</tbody></table></div>"
|
353 |
|
354 |
-
# Calculate market statistics
|
355 |
market_stats = {
|
356 |
'total_commodities': len(df['commodity'].unique()),
|
357 |
'avg_modal_price': f"₹{df['modal_price'].mean():.2f}",
|
@@ -362,7 +478,6 @@ def filter_data():
|
|
362 |
response = {
|
363 |
'plots': plots,
|
364 |
'insights': insights,
|
365 |
-
'translations': MARATHI_TRANSLATIONS if lang == 'mr' else {},
|
366 |
'success': not df.empty,
|
367 |
'hasStateDistrict': bool(state and district),
|
368 |
'market_html': market_table_html,
|
@@ -373,121 +488,8 @@ def filter_data():
|
|
373 |
|
374 |
return jsonify(response)
|
375 |
|
376 |
-
def format_ai_insights(insights_data, lang='en'):
|
377 |
-
"""Format AI insights into structured HTML with language support"""
|
378 |
-
# Translation dictionary for section headers and labels
|
379 |
-
translations = {
|
380 |
-
'AI Market Insights': 'एआय बाजार विश्लेषण',
|
381 |
-
'Immediate Market Opportunities': 'तात्काळ बाजार संधी',
|
382 |
-
'Best Profit Potential': 'सर्वोत्तम नफा क्षमता',
|
383 |
-
'Current Market Status': 'सध्याची बाजार स्थिती',
|
384 |
-
'Strategic Planning': 'धोरणात्मक नियोजन',
|
385 |
-
'High Return Crops': 'उच्च परतावा पिके',
|
386 |
-
'Recommended Crop Combinations': 'शिफारस केलेली पीक संयोजने',
|
387 |
-
'Risk Management & Market Strategy': 'जोखीम व्यवस्थापन आणि बाजार धोरण',
|
388 |
-
'Recommended Actions': 'शिफारस केलेल्या कृती',
|
389 |
-
'increase': 'वाढ',
|
390 |
-
'per kg': 'प्रति किलो',
|
391 |
-
'Most stable prices': 'सर्वात स्थिर किंमती',
|
392 |
-
'Best storage life': 'सर्वोत्तम साठवण कालावधी',
|
393 |
-
'Peak selling time': 'उच्चतम विक्री काळ',
|
394 |
-
'Plant mix of': 'पिकांचे मिश्रण लावा',
|
395 |
-
'Focus on': 'लक्ष केंद्रित करा',
|
396 |
-
'Store': 'साठवण करा',
|
397 |
-
'Aim for': 'लक्ष्य ठेवा',
|
398 |
-
'months': 'महिने'
|
399 |
-
}
|
400 |
-
|
401 |
-
def translate_text(text):
|
402 |
-
"""Translate text based on language selection"""
|
403 |
-
if lang == 'mr':
|
404 |
-
# Try to find direct translation from dictionary
|
405 |
-
for eng, mar in translations.items():
|
406 |
-
text = text.replace(eng, mar)
|
407 |
-
return text
|
408 |
-
return text
|
409 |
-
|
410 |
-
def format_price(price_text):
|
411 |
-
"""Format price with proper currency symbol and translation"""
|
412 |
-
if lang == 'mr':
|
413 |
-
return price_text.replace('₹', '₹').replace('per kg', 'प्रति किलो')
|
414 |
-
return price_text
|
415 |
-
|
416 |
-
"""Format AI insights into structured HTML"""
|
417 |
-
html = f"""
|
418 |
-
<div class="insights-header">
|
419 |
-
<h3 class="en">AI Market Insights</h3>
|
420 |
-
<h3 class="mr" style="display:none;">एआय बाजार विश्लेषण</h3>
|
421 |
-
</div>
|
422 |
-
|
423 |
-
<div class="insight-section">
|
424 |
-
<h4>Immediate Market Opportunities</h4>
|
425 |
-
<div class="insight-card">
|
426 |
-
<h5>Best Profit Potential</h5>
|
427 |
-
<ul class="insight-list">
|
428 |
-
<li>Beetroot and Bitter gourd showing <span class="percentage-up">15% increase</span> from base year</li>
|
429 |
-
<li>Bottle gourd premium quality fetching <span class="price-highlight">₹150 per kg</span></li>
|
430 |
-
</ul>
|
431 |
-
</div>
|
432 |
-
|
433 |
-
<div class="insight-card">
|
434 |
-
<h5>Current Market Status</h5>
|
435 |
-
<ul class="insight-list">
|
436 |
-
<li>Brinjal in high demand with stable price of <span class="price-highlight">₹80 per kg</span></li>
|
437 |
-
<li>Premium quality bottle gourd commanding <span class="price-highlight">₹200 per kg</span></li>
|
438 |
-
</ul>
|
439 |
-
</div>
|
440 |
-
</div>
|
441 |
-
|
442 |
-
<div class="insight-section">
|
443 |
-
<h4>Strategic Planning</h4>
|
444 |
-
<div class="insight-card">
|
445 |
-
<h5>High Return Crops</h5>
|
446 |
-
<ul class="insight-list">
|
447 |
-
<li>Cauliflower showing <span class="percentage-up">20% increase</span> from base year</li>
|
448 |
-
<li>Best planting time: Spring season for cauliflower and bottle gourd</li>
|
449 |
-
</ul>
|
450 |
-
</div>
|
451 |
-
|
452 |
-
<div class="insight-card">
|
453 |
-
<h5>Recommended Crop Combinations</h5>
|
454 |
-
<ul class="insight-list">
|
455 |
-
<li>Brinjal + Bottle gourd + Cauliflower (similar demand patterns)</li>
|
456 |
-
</ul>
|
457 |
-
</div>
|
458 |
-
</div>
|
459 |
-
|
460 |
-
<div class="insight-section">
|
461 |
-
<h4>Risk Management & Market Strategy</h4>
|
462 |
-
<div class="insight-card">
|
463 |
-
<ul class="insight-list">
|
464 |
-
<li>Most stable prices: Brinjal, Bottle gourd, Cauliflower</li>
|
465 |
-
<li>Best storage life: 6-9 months for Cauliflower, Brinjal, and Bottle gourd</li>
|
466 |
-
<li>Peak selling time for Cauliflower: March-April</li>
|
467 |
-
</ul>
|
468 |
-
</div>
|
469 |
-
</div>
|
470 |
-
|
471 |
-
<div class="action-box">
|
472 |
-
<h5>Recommended Actions</h5>
|
473 |
-
<ul class="action-list">
|
474 |
-
<li>Plant mix of beetroot, bitter gourd, bottle gourd, brinjal, and cauliflower</li>
|
475 |
-
<li>Focus on stable price markets for cauliflower and bottle gourd</li>
|
476 |
-
<li>Store cauliflower for March-April peak prices</li>
|
477 |
-
<li>Aim for premium quality grades to maximize profits</li>
|
478 |
-
</ul>
|
479 |
-
</div>
|
480 |
-
"""
|
481 |
-
if lang == 'mr':
|
482 |
-
html = translate_text(html)
|
483 |
-
# print(html
|
484 |
-
return html
|
485 |
-
|
486 |
-
return html
|
487 |
-
|
488 |
@app.route('/get_districts', methods=['POST'])
|
489 |
def get_districts():
|
490 |
-
"""Get districts for selected state"""
|
491 |
state = request.form.get('state')
|
492 |
df = fetch_market_data(state=state)
|
493 |
districts = sorted(df['district'].dropna().unique())
|
@@ -495,7 +497,6 @@ def get_districts():
|
|
495 |
|
496 |
@app.route('/get_markets', methods=['POST'])
|
497 |
def get_markets():
|
498 |
-
"""Get markets for selected district"""
|
499 |
district = request.form.get('district')
|
500 |
df = fetch_market_data(district=district)
|
501 |
markets = sorted(df['market'].dropna().unique())
|
@@ -503,9 +504,11 @@ def get_markets():
|
|
503 |
|
504 |
@app.route('/get_commodities', methods=['POST'])
|
505 |
def get_commodities():
|
506 |
-
"""Get commodities for selected market"""
|
507 |
market = request.form.get('market')
|
508 |
df = fetch_market_data(market=market)
|
509 |
commodities = sorted(df['commodity'].dropna().unique())
|
510 |
return jsonify(commodities)
|
511 |
|
|
|
|
|
|
|
|
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 |
|
12 |
+
dotenv.load_dotenv()
|
|
|
|
|
|
|
13 |
|
14 |
+
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
def fetch_market_data(state=None, district=None, market=None, commodity=None):
|
17 |
+
"""Fetch data from the agricultural market API.
|
18 |
+
If the API fails or returns empty data, fallback to the CSV file.
|
19 |
+
Filters (state, district, market, commodity) are applied manually on CSV data.
|
20 |
+
"""
|
21 |
+
api_key = "579b464db66ec23bdd000001189bbb99e979428764bdbe8fdd44ebb7"
|
22 |
+
base_url = "https://api.data.gov.in/resource/9ef84268-d588-465a-a308-a864a43d007"
|
23 |
|
24 |
params = {
|
25 |
+
"api-key": api_key,
|
26 |
"format": "json",
|
27 |
+
"limit": 1000,
|
28 |
}
|
29 |
|
|
|
30 |
if state:
|
31 |
params["filters[state]"] = state
|
32 |
if district:
|
|
|
42 |
data = response.json()
|
43 |
records = data.get("records", [])
|
44 |
df = pd.DataFrame(records)
|
|
|
45 |
else:
|
46 |
print(f"API Error: {response.status_code}")
|
47 |
+
raise Exception(f"API Error: {response.status_code}")
|
48 |
except Exception as e:
|
49 |
+
print(f"Error fetching data from API: {str(e)}. Falling back to CSV file.")
|
50 |
+
df = pd.read_csv("final_price_data.csv")
|
51 |
+
if 'min_price' not in df.columns:
|
52 |
+
rename_mapping = {
|
53 |
+
'State': 'state',
|
54 |
+
'District': 'district',
|
55 |
+
'Market': 'market',
|
56 |
+
'Commodity': 'commodity',
|
57 |
+
'Variety': 'variety',
|
58 |
+
'Grade': 'grade',
|
59 |
+
'Arrival_Date': 'arrival_date',
|
60 |
+
'Min_x0020_Price': 'min_price',
|
61 |
+
'Max_x0020_Price': 'max_price',
|
62 |
+
'Modal_x0020_Price': 'modal_price'
|
63 |
+
}
|
64 |
+
df.rename(columns=rename_mapping, inplace=True)
|
65 |
+
|
66 |
+
if df.empty:
|
67 |
+
print("API returned empty data. Falling back to CSV file.")
|
68 |
+
df = pd.read_csv("final_price_data.csv")
|
69 |
+
if 'min_price' not in df.columns:
|
70 |
+
rename_mapping = {
|
71 |
+
'State': 'state',
|
72 |
+
'District': 'district',
|
73 |
+
'Market': 'market',
|
74 |
+
'Commodity': 'commodity',
|
75 |
+
'Variety': 'variety',
|
76 |
+
'Grade': 'grade',
|
77 |
+
'Arrival_Date': 'arrival_date',
|
78 |
+
'Min_x0020_Price': 'min_price',
|
79 |
+
'Max_x0020_Price': 'max_price',
|
80 |
+
'Modal_x0020_Price': 'modal_price'
|
81 |
+
}
|
82 |
+
df.rename(columns=rename_mapping, inplace=True)
|
83 |
+
|
84 |
+
if state:
|
85 |
+
df = df[df['state'] == state]
|
86 |
+
if district:
|
87 |
+
df = df[df['district'] == district]
|
88 |
+
if market:
|
89 |
+
df = df[df['market'] == market]
|
90 |
+
if commodity:
|
91 |
+
df = df[df['commodity'] == commodity]
|
92 |
+
|
93 |
+
return df
|
94 |
|
95 |
+
def get_ai_insights(market_data, state, district, market=None, commodity=None, language="English"):
|
96 |
+
"""Get enhanced insights from Gemini API with focus on profitable suggestions for farmers.
|
97 |
+
Supports multiple languages through the prompt.
|
98 |
+
Returns dynamic insights only. If something goes wrong, returns an empty string.
|
99 |
+
"""
|
100 |
if not state or not district or market_data.empty:
|
101 |
return ""
|
102 |
+
|
103 |
try:
|
104 |
+
# Filter data based on provided parameters
|
105 |
district_data = market_data[market_data['district'] == district]
|
106 |
+
if district_data.empty:
|
107 |
+
return ""
|
108 |
|
109 |
+
# Apply market filter if provided
|
110 |
+
if market and not market_data[market_data['market'] == market].empty:
|
111 |
+
market_specific = True
|
112 |
+
district_data = district_data[district_data['market'] == market]
|
113 |
+
else:
|
114 |
+
market_specific = False
|
115 |
+
|
116 |
+
# Apply commodity filter if provided
|
117 |
+
if commodity and not market_data[market_data['commodity'] == commodity].empty:
|
118 |
+
commodity_specific = True
|
119 |
+
district_data = district_data[district_data['commodity'] == commodity]
|
120 |
+
else:
|
121 |
+
commodity_specific = False
|
122 |
+
|
123 |
+
# Calculate price trends
|
124 |
price_trends = district_data.groupby('commodity').agg({
|
125 |
'modal_price': ['mean', 'min', 'max', 'std']
|
126 |
}).round(2)
|
127 |
|
128 |
+
# Using environment variable for Gemini API key
|
129 |
+
api_key = os.environ.get('GEMINI_API_KEY')
|
130 |
+
if not api_key:
|
131 |
+
print("Warning: Gemini API key not set")
|
132 |
+
return ""
|
133 |
+
|
134 |
price_trends['price_stability'] = (price_trends['modal_price']['std'] /
|
135 |
price_trends['modal_price']['mean']).round(2)
|
136 |
|
|
|
|
|
|
|
|
|
|
|
137 |
district_data['arrival_date'] = pd.to_datetime(district_data['arrival_date'])
|
138 |
district_data['month'] = district_data['arrival_date'].dt.month
|
139 |
monthly_trends = district_data.groupby(['commodity', 'month'])['modal_price'].mean().round(2)
|
140 |
|
|
|
141 |
market_competition = len(district_data['market'].unique())
|
142 |
+
top_commodities = district_data.groupby('commodity')['modal_price'].mean().nlargest(5).index.tolist()
|
143 |
|
144 |
+
# Get min and max prices for key commodities
|
145 |
+
price_range_info = {}
|
146 |
+
for commodity in top_commodities[:3]:
|
147 |
+
comm_data = district_data[district_data['commodity'] == commodity]
|
148 |
+
if not comm_data.empty:
|
149 |
+
price_range_info[commodity] = {
|
150 |
+
'min': comm_data['modal_price'].min(),
|
151 |
+
'max': comm_data['modal_price'].max(),
|
152 |
+
'avg': comm_data['modal_price'].mean()
|
153 |
+
}
|
154 |
+
|
155 |
+
# Calculate market-specific metrics if market is selected
|
156 |
+
market_details = ""
|
157 |
+
if market_specific:
|
158 |
+
market_details = f"""
|
159 |
+
Market-specific information for {market}:
|
160 |
+
- Number of commodities: {len(district_data['commodity'].unique())}
|
161 |
+
- Most expensive commodity: {district_data.groupby('commodity')['modal_price'].mean().idxmax()}
|
162 |
+
- Cheapest commodity: {district_data.groupby('commodity')['modal_price'].mean().idxmin()}
|
163 |
+
"""
|
164 |
+
|
165 |
+
# Commodity-specific details if commodity is selected
|
166 |
+
commodity_details = ""
|
167 |
+
if commodity_specific:
|
168 |
+
commodity_data = district_data[district_data['commodity'] == commodity]
|
169 |
+
best_market = commodity_data.loc[commodity_data['modal_price'].idxmin()]['market']
|
170 |
+
worst_market = commodity_data.loc[commodity_data['modal_price'].idxmax()]['market']
|
171 |
+
|
172 |
+
commodity_details = f"""
|
173 |
+
Commodity-specific information for {commodity}:
|
174 |
+
- Best market to buy (lowest price): {best_market}
|
175 |
+
- Highest priced market: {worst_market}
|
176 |
+
- Price variance across markets: {commodity_data['modal_price'].std().round(2)}
|
177 |
+
"""
|
178 |
+
|
179 |
+
# Improved prompt for better structured output with language support
|
180 |
prompt = f"""
|
181 |
+
Analyze the following agricultural market data for {district}, {state} and provide insights in {language} language.
|
182 |
+
|
183 |
+
Market data:
|
184 |
+
- Active markets: {market_competition}
|
185 |
+
- Top crops: {', '.join(top_commodities[:5])}
|
186 |
+
- Data from {len(price_trends.index)} crops and {len(monthly_trends)} monthly entries.
|
187 |
+
|
188 |
+
Price information:
|
189 |
+
{json.dumps(price_range_info, indent=2)}
|
190 |
+
|
191 |
+
{market_details}
|
192 |
+
{commodity_details}
|
193 |
+
|
194 |
+
Analyze this data and provide insights about crop market trends and profitability.
|
195 |
+
Include specific numbers from the data about prices.
|
196 |
+
|
197 |
+
Provide structured insights with clear sections. Use this exact format with bullet points:
|
198 |
+
|
199 |
+
Crop Profitability Analysis:
|
200 |
+
* [First insight about profitable crops with specific prices mentioned]
|
201 |
+
* [Second insight]
|
202 |
+
|
203 |
+
Market Price Analysis:
|
204 |
+
* [First insight about markets with specific price ranges]
|
205 |
+
* [Second insight]
|
206 |
+
|
207 |
+
Recommendations for Farmers:
|
208 |
+
* [Action item 1]
|
209 |
+
* [Action item 2]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
"""
|
211 |
+
|
212 |
+
api_url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent"
|
213 |
+
headers = {"Content-Type": "application/json"}
|
214 |
+
|
215 |
payload = {
|
216 |
+
"contents": [
|
217 |
+
{
|
218 |
+
"parts": [
|
219 |
+
{"text": prompt}
|
220 |
+
]
|
221 |
+
}
|
222 |
+
],
|
223 |
+
"generationConfig": {
|
224 |
+
"temperature": 0.4,
|
225 |
+
"maxOutputTokens": 1024
|
226 |
+
}
|
227 |
}
|
228 |
|
229 |
+
response = requests.post(
|
230 |
+
f"{api_url}?key={api_key}",
|
231 |
+
headers=headers,
|
232 |
+
json=payload,
|
233 |
+
timeout=20
|
234 |
+
)
|
235 |
+
|
236 |
if response.status_code == 200:
|
237 |
response_data = response.json()
|
238 |
+
if 'candidates' in response_data and len(response_data['candidates']) > 0:
|
239 |
+
content = response_data['candidates'][0]['content']
|
240 |
+
if 'parts' in content and len(content['parts']) > 0:
|
241 |
+
insights = content['parts'][0]['text']
|
242 |
+
return format_ai_insights(insights)
|
243 |
+
print(f"API Response issue: {response.text[:100]}")
|
244 |
+
else:
|
245 |
+
print(f"Gemini API Error: {response.status_code} - {response.text[:100]}")
|
246 |
+
|
247 |
+
return ""
|
248 |
+
|
|
|
249 |
except Exception as e:
|
250 |
print(f"Error generating insights: {str(e)}")
|
251 |
+
return ""
|
252 |
+
|
253 |
+
def format_ai_insights(insights_data):
|
254 |
+
"""Format AI insights into structured HTML.
|
255 |
+
Returns an empty string if no valid insights are provided.
|
256 |
+
"""
|
257 |
+
if not insights_data or not insights_data.strip():
|
258 |
+
return ""
|
259 |
+
|
260 |
+
# Process the insights text - each bullet point becomes a formatted item
|
261 |
+
formatted_content = ""
|
262 |
+
|
263 |
+
# Split by bullet points
|
264 |
+
bullet_points = insights_data.split('*')
|
265 |
+
|
266 |
+
# Filter out empty items and process each bullet point
|
267 |
+
bullet_points = [point.strip() for point in bullet_points if point.strip()]
|
268 |
+
|
269 |
+
# Check if any section headers exist in the content
|
270 |
+
sections = {}
|
271 |
+
current_section = "Recommendations"
|
272 |
+
|
273 |
+
for point in bullet_points:
|
274 |
+
if ":" in point and len(point.split(":")[0]) < 30: # Likely a section header
|
275 |
+
current_section = point.split(":")[0].strip()
|
276 |
+
# Start a new section
|
277 |
+
if current_section not in sections:
|
278 |
+
sections[current_section] = []
|
279 |
+
else:
|
280 |
+
# Add to current section
|
281 |
+
if current_section not in sections:
|
282 |
+
sections[current_section] = []
|
283 |
+
sections[current_section].append(point)
|
284 |
+
|
285 |
+
# Now build the HTML with proper sections
|
286 |
+
for section, points in sections.items():
|
287 |
+
formatted_content += f'<div class="insight-card"><h5>{section}</h5><ul class="insight-list">'
|
288 |
+
for point in points:
|
289 |
+
# Highlight prices with special styling
|
290 |
+
if "₹" in point:
|
291 |
+
# Replace price mentions with highlighted spans
|
292 |
+
parts = point.split("₹")
|
293 |
+
styled_point = parts[0]
|
294 |
+
for i in range(1, len(parts)):
|
295 |
+
# Extract the price value
|
296 |
+
price_text = parts[i].split()[0]
|
297 |
+
# Add the highlighted price and the rest of the text
|
298 |
+
styled_point += f'<span class="price-highlight">₹{price_text}</span>' + parts[i][len(price_text):]
|
299 |
+
formatted_content += f'<li>{styled_point}</li>'
|
300 |
+
else:
|
301 |
+
formatted_content += f'<li>{point}</li>'
|
302 |
+
formatted_content += '</ul></div>'
|
303 |
+
|
304 |
+
# Add a wrapper for the insights
|
305 |
+
html = f"""
|
306 |
+
<div class="insights-header">
|
307 |
+
<h3>AI Market Insights</h3>
|
308 |
+
</div>
|
309 |
+
<div class="insight-section">
|
310 |
+
{formatted_content}
|
311 |
+
</div>
|
312 |
+
"""
|
313 |
+
|
314 |
+
return html
|
315 |
|
316 |
+
def generate_plots(df):
|
317 |
+
"""Generate all plots in English"""
|
318 |
if df.empty:
|
319 |
return {}, "No data available"
|
320 |
|
|
|
321 |
price_cols = ['min_price', 'max_price', 'modal_price']
|
322 |
for col in price_cols:
|
323 |
df[col] = pd.to_numeric(df[col], errors='coerce')
|
324 |
|
|
|
325 |
colors = ["#4CAF50", "#8BC34A", "#CDDC39", "#FFC107", "#FF5722"]
|
326 |
|
|
|
327 |
df_bar = df.groupby('commodity')['modal_price'].mean().reset_index()
|
328 |
fig_bar = px.bar(df_bar,
|
329 |
x='commodity',
|
330 |
y='modal_price',
|
331 |
+
title="Average Price by Commodity",
|
332 |
color_discrete_sequence=colors)
|
333 |
|
|
|
334 |
fig_line = None
|
335 |
if 'commodity' in df.columns and len(df['commodity'].unique()) == 1:
|
336 |
df['arrival_date'] = pd.to_datetime(df['arrival_date'])
|
|
|
338 |
fig_line = px.line(df_line,
|
339 |
x='arrival_date',
|
340 |
y='modal_price',
|
341 |
+
title="Price Trend",
|
342 |
color_discrete_sequence=colors)
|
343 |
|
|
|
344 |
fig_box = px.box(df,
|
345 |
x='commodity',
|
346 |
y='modal_price',
|
347 |
+
title="Price Distribution",
|
348 |
color='commodity',
|
349 |
color_discrete_sequence=colors)
|
350 |
|
|
|
351 |
plots = {
|
352 |
'bar': pio.to_html(fig_bar, full_html=False),
|
353 |
'box': pio.to_html(fig_box, full_html=False)
|
|
|
359 |
|
360 |
@app.route('/')
|
361 |
def index():
|
362 |
+
try:
|
363 |
+
initial_data = fetch_market_data()
|
364 |
+
states = sorted(initial_data['state'].dropna().unique()) if not initial_data.empty else []
|
365 |
+
except Exception as e:
|
366 |
+
print(f"Error fetching initial data: {str(e)}")
|
367 |
+
states = []
|
368 |
+
|
369 |
return render_template('index.html',
|
370 |
+
states=states,
|
371 |
+
today=datetime.today().strftime('%Y-%m-%d'))
|
372 |
|
373 |
@app.route('/filter_data', methods=['POST'])
|
374 |
def filter_data():
|
|
|
375 |
state = request.form.get('state')
|
376 |
district = request.form.get('district')
|
377 |
market = request.form.get('market')
|
378 |
commodity = request.form.get('commodity')
|
379 |
+
language = request.form.get('language', 'English') # Default to English
|
380 |
|
381 |
df = fetch_market_data(state, district, market, commodity)
|
382 |
+
plots = generate_plots(df)
|
383 |
+
# Pass market and commodity to get_ai_insights
|
384 |
+
insights = get_ai_insights(df, state, district, market, commodity, language) if state and district and not df.empty else ""
|
385 |
|
|
|
386 |
market_table_html = """
|
387 |
<div class="table-responsive">
|
388 |
<table class="table table-striped table-bordered">
|
|
|
420 |
"""
|
421 |
market_table_html += "</tbody></table></div>"
|
422 |
|
|
|
423 |
cheapest_crops = df.sort_values('modal_price', ascending=True).head(5)
|
424 |
cheapest_table_html = """
|
425 |
<div class="table-responsive">
|
|
|
444 |
"""
|
445 |
cheapest_table_html += "</tbody></table></div>"
|
446 |
|
|
|
447 |
costliest_crops = df.sort_values('modal_price', ascending=False).head(5)
|
448 |
costliest_table_html = """
|
449 |
<div class="table-responsive">
|
|
|
468 |
"""
|
469 |
costliest_table_html += "</tbody></table></div>"
|
470 |
|
|
|
471 |
market_stats = {
|
472 |
'total_commodities': len(df['commodity'].unique()),
|
473 |
'avg_modal_price': f"₹{df['modal_price'].mean():.2f}",
|
|
|
478 |
response = {
|
479 |
'plots': plots,
|
480 |
'insights': insights,
|
|
|
481 |
'success': not df.empty,
|
482 |
'hasStateDistrict': bool(state and district),
|
483 |
'market_html': market_table_html,
|
|
|
488 |
|
489 |
return jsonify(response)
|
490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
491 |
@app.route('/get_districts', methods=['POST'])
|
492 |
def get_districts():
|
|
|
493 |
state = request.form.get('state')
|
494 |
df = fetch_market_data(state=state)
|
495 |
districts = sorted(df['district'].dropna().unique())
|
|
|
497 |
|
498 |
@app.route('/get_markets', methods=['POST'])
|
499 |
def get_markets():
|
|
|
500 |
district = request.form.get('district')
|
501 |
df = fetch_market_data(district=district)
|
502 |
markets = sorted(df['market'].dropna().unique())
|
|
|
504 |
|
505 |
@app.route('/get_commodities', methods=['POST'])
|
506 |
def get_commodities():
|
|
|
507 |
market = request.form.get('market')
|
508 |
df = fetch_market_data(market=market)
|
509 |
commodities = sorted(df['commodity'].dropna().unique())
|
510 |
return jsonify(commodities)
|
511 |
|
512 |
+
if __name__ == '__main__':
|
513 |
+
app.run(debug=True, host='0.0.0.0', port=7860)
|
514 |
+
pio.templates.default = "plotly_white"
|