Esmaeilkianii commited on
Commit
a31acf3
·
verified ·
1 Parent(s): e7ef59a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -606
app.py CHANGED
@@ -19,21 +19,13 @@ import altair as alt
19
  from streamlit_option_menu import option_menu
20
  from streamlit_lottie import st_lottie
21
  import requests
22
- import hydralit_components as hc
23
- from streamlit_extras.colored_header import colored_header
24
- from streamlit_extras.metric_cards import style_metric_cards
25
- from streamlit_extras.chart_container import chart_container
26
- from streamlit_extras.add_vertical_space import add_vertical_space
27
- from streamlit_card import card
28
- import pydeck as pdk
29
- import math
30
  from sklearn.ensemble import RandomForestRegressor
31
  from sklearn.cluster import KMeans
32
  from tensorflow.keras.models import Sequential
33
  from tensorflow.keras.layers import LSTM, Dense
34
  from sklearn.preprocessing import MinMaxScaler
35
 
36
- # Page configuration with custom theme
37
  st.set_page_config(
38
  page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
39
  page_icon="🌿",
@@ -41,125 +33,40 @@ st.set_page_config(
41
  initial_sidebar_state="expanded"
42
  )
43
 
44
- # Custom CSS with modern design, animations, and theme support
45
  st.markdown("""
46
  <style>
47
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
48
-
49
- * {
50
- font-family: 'Vazirmatn', sans-serif !important;
51
- transition: all 0.3s ease;
52
- }
53
-
54
- .main {
55
- background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%);
56
- }
57
-
58
- .dark-theme .main {
59
- background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%);
60
- color: #ecf0f1;
61
- }
62
-
63
- .main-header {
64
- background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
65
- padding: 1.5rem;
66
- border-radius: 12px;
67
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
68
- margin-bottom: 2rem;
69
- position: relative;
70
- overflow: hidden;
71
- animation: header-glow 3s infinite alternate;
72
- }
73
-
74
- @keyframes header-glow {
75
- 0% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.1); }
76
- 100% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.3); }
77
- }
78
-
79
- .main-header h1, .main-header p {
80
- color: white;
81
- z-index: 1;
82
- position: relative;
83
- }
84
-
85
- .stcard {
86
- border-radius: 12px;
87
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
88
- transition: transform 0.3s ease, box-shadow 0.3s ease;
89
- }
90
-
91
- .stcard:hover {
92
- transform: translateY(-5px);
93
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
94
- }
95
-
96
- .dark-theme .stcard {
97
- background: #34495e;
98
- color: #ecf0f1;
99
- }
100
-
101
- .stButton>button {
102
- border-radius: 50px;
103
- padding: 0.5rem 1.5rem;
104
- font-weight: 600;
105
- transition: all 0.3s ease;
106
- }
107
-
108
- .primary-btn {
109
- background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
110
- color: white;
111
- }
112
-
113
- .dark-theme .primary-btn {
114
- background: linear-gradient(90deg, #27ae60 0%, #219653 100%);
115
- }
116
-
117
- .metric-card {
118
- background: white;
119
- border-radius: 12px;
120
- padding: 1.5rem;
121
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
122
- text-align: center;
123
- }
124
-
125
- .dark-theme .metric-card {
126
- background: #34495e;
127
- color: #ecf0f1;
128
- }
129
-
130
- .map-container {
131
- border-radius: 12px;
132
- overflow: hidden;
133
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
134
- }
135
-
136
- .animate-load {
137
- animation: fadeIn 0.5s ease forwards;
138
- }
139
-
140
- @keyframes fadeIn {
141
- 0% { opacity: 0; transform: translateY(20px); }
142
- 100% { opacity: 1; transform: translateY(0); }
143
- }
144
-
145
- .rtl {
146
- direction: rtl;
147
- text-align: right;
148
- }
149
  </style>
150
  """, unsafe_allow_html=True)
151
 
152
- # Theme selection
153
  if 'theme' not in st.session_state:
154
  st.session_state.theme = 'light'
155
-
156
  theme = st.sidebar.selectbox("انتخاب تم", ["روشن", "تیره"], index=0 if st.session_state.theme == 'light' else 1)
157
  st.session_state.theme = 'light' if theme == "روشن" else 'dark'
158
  theme_class = "dark-theme" if st.session_state.theme == 'dark' else ""
159
-
160
  st.markdown(f'<body class="{theme_class}">', unsafe_allow_html=True)
161
 
162
- # Earth Engine Initialization
163
  @st.cache_resource
164
  def initialize_earth_engine():
165
  try:
@@ -177,661 +84,337 @@ def initialize_earth_engine():
177
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
178
  "universe_domain": "googleapis.com"
179
  }
180
-
181
- credentials_file = 'ee-esmaeilkiani13877-cfdea6eaf411.json'
182
- with open(credentials_file, 'w') as f:
183
  json.dump(credentials_dict, f)
184
-
185
- credentials = ee.ServiceAccountCredentials(service_account, credentials_file)
186
  ee.Initialize(credentials)
187
-
188
- os.remove(credentials_file)
189
  return True
190
  except Exception as e:
191
  st.error(f"خطا در اتصال به Earth Engine: {e}")
192
  return False
193
 
194
- # Load data
195
  @st.cache_data
196
- def load_farm_data():
197
  try:
198
- df = pd.read_csv("پایگاه داده (1).csv")
199
- return df
200
  except Exception as e:
201
- st.error(f"خطا در بارگذاری داده‌های مزارع: {e}")
202
  return pd.DataFrame()
203
 
204
  @st.cache_data
205
- def load_coordinates_data():
206
- try:
207
- df = pd.read_csv("farm_coordinates.csv")
208
- return df
209
- except Exception as e:
210
- st.error(f"خطا در بارگذاری داده‌های مختصات: {e}")
211
- return pd.DataFrame()
212
-
213
- # Load animation JSON
214
- @st.cache_data
215
- def load_lottie_url(url: str):
216
  r = requests.get(url)
217
- if r.status_code != 200:
218
- return None
219
- return r.json()
220
 
221
- # Weather data function
222
- def get_weather_data(lat, lon, api_key):
223
  url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
224
  response = requests.get(url)
225
- if response.status_code == 200:
226
- return response.json()
227
- return None
228
 
229
- # Estimate water requirement
230
- def estimate_water_requirement(farm_id, date_str):
231
- api_key = "ed47316a45379e2221a75f813229fb46"
232
  farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
233
- lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
234
- weather_data = get_weather_data(lat, lon, api_key)
235
  if weather_data:
236
  temperature = weather_data['main']['temp']
237
  humidity = weather_data['main']['humidity']
238
- water_requirement = (temperature - 20) * 0.5 + (100 - humidity) * 0.1
239
- return max(0, water_requirement)
240
  return None
241
 
242
- # Create Earth Engine map with heatmap option
243
- def create_ee_map(farm_id, date_str, layer_type="NDVI", heatmap=False):
244
- try:
245
- farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
246
- lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
247
-
248
- m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
249
-
250
- date_obj = datetime.strptime(date_str, '%Y-%m-%d')
251
- start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d')
252
- end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d')
253
-
254
- region = ee.Geometry.Point([lon, lat]).buffer(1500)
255
-
256
- s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
257
- .filterDate(start_date, end_date) \
258
- .filterBounds(region) \
259
- .sort('CLOUDY_PIXEL_PERCENTAGE') \
260
- .first()
261
-
262
- if layer_type == "NDVI":
263
- index = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
264
- viz_params = {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']}
265
- legend_title = 'شاخص پوشش گیاهی (NDVI)'
266
- elif layer_type == "NDMI":
267
- index = s2.normalizedDifference(['B8', 'B11']).rename('NDMI')
268
- viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']}
269
- legend_title = 'شاخص رطوبت (NDMI)'
270
- elif layer_type == "EVI":
271
- nir = s2.select('B8')
272
- red = s2.select('B4')
273
- blue = s2.select('B2')
274
- index = nir.subtract(red).multiply(2.5).divide(nir.add(red.multiply(6)).subtract(blue.multiply(7.5)).add(1)).rename('EVI')
275
- viz_params = {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']}
276
- legend_title = 'شاخص پیشرفته گیاهی (EVI)'
277
- elif layer_type == "NDWI":
278
- index = s2.normalizedDifference(['B3', 'B8']).rename('NDWI')
279
- viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']}
280
- legend_title = 'شاخص آب (NDWI)'
281
- elif layer_type == "SoilMoisture":
282
- s1 = ee.ImageCollection('COPERNICUS/S1_GRD') \
283
- .filterDate(start_date, end_date) \
284
- .filterBounds(region) \
285
- .sort('system:time_start') \
286
- .first()
287
- index = s1.select('VV').rename('SoilMoisture')
288
- viz_params = {'min': -25, 'max': -5, 'palette': ['#00008b', '#add8e6', '#ffffff']}
289
- legend_title = 'رطوبت خاک (Soil Moisture)'
290
-
291
- if heatmap:
292
- map_id_dict = ee.Image(index).getMapId({'min': viz_params['min'], 'max': viz_params['max'], 'palette': viz_params['palette'], 'opacity': 0.7})
293
- folium.TileLayer(
294
- tiles=map_id_dict['tile_fetcher'].url_format,
295
- attr='Google Earth Engine Heatmap',
296
- name=f'{layer_type} Heatmap',
297
- overlay=True,
298
- control=True
299
- ).add_to(m)
300
- else:
301
- map_id_dict = ee.Image(index).getMapId(viz_params)
302
- folium.TileLayer(
303
- tiles=map_id_dict['tile_fetcher'].url_format,
304
- attr='Google Earth Engine',
305
- name=layer_type,
306
- overlay=True,
307
- control=True
308
- ).add_to(m)
309
-
310
- folium.Marker([lat, lon], popup=f'مزرعه {farm_id}', tooltip=f'مزرعه {farm_id}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
311
- folium.Circle([lat, lon], radius=1500, color='green', fill=True, fill_color='green', fill_opacity=0.1).add_to(m)
312
- folium.LayerControl().add_to(m)
313
-
314
- legend_html = f'''
315
- <div style="position: fixed; bottom: 50px; right: 50px; border: 2px solid grey; z-index: 9999; background-color: white; padding: 10px; border-radius: 5px; direction: rtl;">
316
- <div style="font-size: 14px; font-weight: bold; margin-bottom: 5px;">{legend_title}</div>
317
- <div style="display: flex; align-items: center; margin-bottom: 5px;">
318
- <div style="background: {viz_params['palette'][0]}; width: 20px; height: 20px; margin-left: 5px;"></div>
319
- <span>کم</span>
320
- </div>
321
- <div style="display: flex; align-items: center; margin-bottom: 5px;">
322
- <div style="background: {viz_params['palette'][2]}; width: 20px; height: 20px; margin-left: 5px;"></div>
323
- <span>متوسط</span>
324
- </div>
325
- <div style="display: flex; align-items: center;">
326
- <div style="background: {viz_params['palette'][-1]}; width: 20px; height: 20px; margin-left: 5px;"></div>
327
- <span>زیاد</span>
328
- </div>
329
- </div>
330
- '''
331
- m.get_root().html.add_child(folium.Element(legend_html))
332
-
333
- return m
334
- except Exception as e:
335
- st.error(f"خطا در ایجاد نقشه: {e}")
336
- return None
337
 
338
- # Generate mock growth data
339
  def generate_mock_growth_data(farm_data, selected_variety="all", selected_age="all"):
340
  weeks = list(range(1, 23))
341
- filtered_farms = farm_data
342
- if selected_variety != "all":
343
- filtered_farms = filtered_farms[filtered_farms['واریته'] == selected_variety]
344
- if selected_age != "all":
345
- filtered_farms = filtered_farms[filtered_farms['سن'] == selected_age]
346
-
347
- farm_growth_data = []
348
- for _, farm in filtered_farms.iterrows():
349
- base_height = np.random.uniform(50, 100)
350
- growth_rate = np.random.uniform(5, 15)
351
- growth_data = {
352
- 'farm_id': farm['مزرعه'],
353
- 'variety': farm['واریته'],
354
- 'age': farm['سن'],
355
- 'weeks': weeks,
356
- 'heights': [round(base_height + growth_rate * week) for week in weeks]
357
- }
358
- farm_growth_data.append(growth_data)
359
-
360
- if farm_growth_data:
361
- avg_heights = [round(sum(farm['heights'][week-1] for farm in farm_growth_data) / len(farm_growth_data)) for week in weeks]
362
- avg_growth_data = {
363
- 'farm_id': 'میانگین',
364
- 'variety': 'همه',
365
- 'age': 'همه',
366
- 'weeks': weeks,
367
- 'heights': avg_heights
368
- }
369
- return {'individual': farm_growth_data, 'average': avg_growth_data}
370
- return {
371
- 'individual': [],
372
- 'average': {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': [0] * len(weeks)}
373
- }
374
 
375
- # Calculate farm statistics and detect stress
376
  def calculate_farm_stats(farm_id, layer_type="NDVI"):
377
- if layer_type == "NDVI":
378
- mean = round(np.random.uniform(0.6, 0.8), 2)
379
- min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
380
- max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
381
- std_dev = round(np.random.uniform(0.05, 0.15), 2)
382
- elif layer_type == "NDMI":
383
- mean = round(np.random.uniform(0.3, 0.5), 2)
384
- min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
385
- max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
386
- std_dev = round(np.random.uniform(0.05, 0.15), 2)
387
- elif layer_type == "EVI":
388
- mean = round(np.random.uniform(0.4, 0.6), 2)
389
- min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
390
- max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
391
- std_dev = round(np.random.uniform(0.05, 0.15), 2)
392
- elif layer_type == "NDWI":
393
- mean = round(np.random.uniform(-0.1, 0.1), 2)
394
- min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
395
- max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
396
- std_dev = round(np.random.uniform(0.05, 0.15), 2)
397
- elif layer_type == "SoilMoisture":
398
- mean = round(np.random.uniform(-20, -10), 2)
399
- min_val = round(mean - np.random.uniform(5, 10), 2)
400
- max_val = round(mean + np.random.uniform(5, 10), 2)
401
- std_dev = round(np.random.uniform(2, 5), 2)
402
-
403
- hist_data = np.random.normal(mean, std_dev, 1000)
404
- hist_data = np.clip(hist_data, min_val, max_val)
405
-
406
- stress_alerts = []
407
- if layer_type == "NDVI" and mean < 0.3:
408
- stress_alerts.append("تنش سلامتی: NDVI پایین نشان‌دهنده پوشش گیاهی ضعیف است.")
409
- if layer_type == "NDMI" and mean < 0.1:
410
- stress_alerts.append("تنش آبی: NDMI پایین نشان‌دهنده کمبود رطوبت است.")
411
- if layer_type == "SoilMoisture" and mean < -20:
412
- stress_alerts.append("تنش خاک: رطوبت خاک بسیار پایین است.")
413
-
414
- return {
415
- 'mean': mean, 'min': min_val, 'max': max_val, 'std_dev': std_dev,
416
- 'histogram_data': hist_data, 'stress_alerts': stress_alerts
417
  }
 
 
 
 
 
 
 
 
 
418
 
419
- # Time-series data generation
420
  def generate_time_series_data(farm_id, layer_type, period="weekly"):
421
- dates = pd.date_range(end=datetime.now(), periods=30 if period == "weekly" else 90 if period == "monthly" else 365, freq='W' if period == "weekly" else 'M' if period == "monthly" else 'Q')
422
- if layer_type == "NDVI":
423
- values = np.random.normal(0.7, 0.1, len(dates))
424
- elif layer_type == "NDMI":
425
- values = np.random.normal(0.4, 0.1, len(dates))
426
- elif layer_type == "EVI":
427
- values = np.random.normal(0.5, 0.1, len(dates))
428
- elif layer_type == "NDWI":
429
- values = np.random.normal(0, 0.1, len(dates))
430
- elif layer_type == "SoilMoisture":
431
- values = np.random.normal(-15, 5, len(dates))
432
- return pd.DataFrame({'Date': dates, layer_type: values})
433
 
434
- # Advanced prediction with Random Forest and LSTM
435
- def predict_growth_advanced(farm_id, historical_data):
436
  weeks = historical_data['Week'].values.reshape(-1, 1)
437
  heights = historical_data['Height'].values
438
-
439
- # Random Forest
440
- rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
441
  rf_model.fit(weeks, heights)
442
  future_weeks = np.array(range(16, 23)).reshape(-1, 1)
443
  rf_predictions = rf_model.predict(future_weeks)
444
 
445
- # LSTM
446
  scaler = MinMaxScaler()
447
  scaled_heights = scaler.fit_transform(heights.reshape(-1, 1))
448
- X, y = [], []
449
- for i in range(3, len(scaled_heights)):
450
- X.append(scaled_heights[i-3:i])
451
- y.append(scaled_heights[i])
452
- X, y = np.array(X), np.array(y)
453
-
454
- lstm_model = Sequential([
455
- LSTM(50, activation='relu', input_shape=(3, 1), return_sequences=True),
456
- LSTM(50, activation='relu'),
457
- Dense(1)
458
- ])
459
  lstm_model.compile(optimizer='adam', loss='mse')
460
- lstm_model.fit(X, y, epochs=50, batch_size=1, verbose=0)
461
-
462
  lstm_input = scaled_heights[-3:].reshape((1, 3, 1))
463
- lstm_predictions = []
464
- for _ in range(7):
465
- pred = lstm_model.predict(lstm_input, verbose=0)
466
- lstm_predictions.append(pred[0, 0])
467
- lstm_input = np.append(lstm_input[:, 1:, :], pred.reshape(1, 1, 1), axis=1)
468
- lstm_predictions = scaler.inverse_transform(np.array(lstm_predictions).reshape(-1, 1)).flatten()
469
-
470
  return rf_predictions, lstm_predictions
471
 
472
- # Cluster farms
473
  def cluster_farms(farm_data):
474
- features = farm_data[['مساحت', 'سن']].astype(float)
475
- kmeans = KMeans(n_clusters=3, random_state=42)
 
476
  farm_data['Cluster'] = kmeans.fit_predict(features)
477
  return farm_data, kmeans.cluster_centers_
478
 
479
- # Initialize Earth Engine
480
  ee_initialized = initialize_earth_engine()
481
- farm_df = load_farm_data()
482
- coordinates_df = load_coordinates_data()
483
-
484
- # Load animations
485
  lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json')
486
  lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json')
487
  lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json')
488
-
489
- # Session state
490
  if 'heights_df' not in st.session_state:
491
  st.session_state.heights_df = pd.DataFrame(columns=['Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3', 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen', 'Variety', 'Age', 'Area', 'Channel', 'Administration'])
492
 
493
- # Main header
494
- st.markdown('<div class="main-header animate-load">', unsafe_allow_html=True)
495
- st.markdown('<h1>سامانه هوشمند پایش مزارع نیشکر دهخدا</h1>', unsafe_allow_html=True)
496
- st.markdown('<p>پلتفرم جامع مدیریت، پایش و تحلیل داده‌های مزارع نیشکر با استفاده از تصاویر ماهواره‌ای و هوش مصنوعی</p>', unsafe_allow_html=True)
497
- st.markdown('</div>', unsafe_allow_html=True)
498
-
499
- # Navigation menu
500
  selected = option_menu(
501
- menu_title=None,
502
- options=["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل داده‌ها", "گزارش‌گیری", "تنظیمات"],
503
- icons=["speedometer2", "map", "pencil-square", "graph-up", "file-earmark-text", "gear"],
504
- default_index=0,
505
- orientation="horizontal",
506
- styles={
507
- "container": {"padding": "0!important", "background-color": "transparent", "margin-bottom": "20px"},
508
- "icon": {"color": "#1a8754", "font-size": "18px"},
509
- "nav-link": {"font-size": "16px", "text-align": "center", "margin":"0px", "--hover-color": "#e9f7ef", "border-radius": "10px"},
510
- "nav-link-selected": {"background-color": "#1a8754", "color": "white", "font-weight": "600"},
511
- }
512
  )
513
 
514
- # Dashboard
515
  if selected == "داشبورد":
516
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
517
  col1, col2, col3, col4 = st.columns(4)
 
 
518
 
519
- with col1:
520
- st.markdown('<div class="metric-card">', unsafe_allow_html=True)
521
- st.markdown(f'<div class="metric-value">{len(farm_df)}</div>', unsafe_allow_html=True)
522
- st.markdown('<div class="metric-label">تعداد مزارع</div>', unsafe_allow_html=True)
523
- st.markdown('</div>', unsafe_allow_html=True)
524
-
525
- with col2:
526
- active_farms = int(len(farm_df) * 0.85)
527
- st.markdown('<div class="metric-card">', unsafe_allow_html=True)
528
- st.markdown(f'<div class="metric-value">{active_farms}</div>', unsafe_allow_html=True)
529
- st.markdown('<div class="metric-label">مزارع فعال</div>', unsafe_allow_html=True)
530
- st.markdown('</div>', unsafe_allow_html=True)
531
-
532
- with col3:
533
- avg_height = 175
534
- st.markdown('<div class="metric-card">', unsafe_allow_html=True)
535
- st.markdown(f'<div class="metric-value">{avg_height} cm</div>', unsafe_allow_html=True)
536
- st.markdown('<div class="metric-label">میانگین ارتفاع</div>', unsafe_allow_html=True)
537
- st.markdown('</div>', unsafe_allow_html=True)
538
-
539
- with col4:
540
- avg_moisture = 68
541
- st.markdown('<div class="metric-card">', unsafe_allow_html=True)
542
- st.markdown(f'<div class="metric-value">{avg_moisture}%</div>', unsafe_allow_html=True)
543
- st.markdown('<div class="metric-label">میانگین رطوبت</div>', unsafe_allow_html=True)
544
- st.markdown('</div>', unsafe_allow_html=True)
545
-
546
- # Interactive Dashboard Filters
547
- st.markdown("### فیلترهای داشبورد")
548
  col_filter1, col_filter2, col_filter3 = st.columns(3)
549
- with col_filter1:
550
- variety_filter = st.multiselect("واریته", ["همه"] + list(farm_df['واریته'].unique()), default="همه")
551
- with col_filter2:
552
- age_filter = st.multiselect("سن", ["همه"] + list(farm_df['سن'].unique()), default="همه")
553
- with col_filter3:
554
- channel_filter = st.multiselect("کانال", ["همه"] + list(farm_df['کانال'].unique()), default="همه")
555
-
556
- filtered_df = farm_df
557
- if "همه" not in variety_filter:
558
- filtered_df = filtered_df[filtered_df['واریته'].isin(variety_filter)]
559
- if "همه" not in age_filter:
560
- filtered_df = filtered_df[filtered_df['سن'].isin(age_filter)]
561
- if "همه" not in channel_filter:
562
- filtered_df = filtered_df[filtered_df['کانال'].isin(channel_filter)]
563
 
564
  tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "داده‌ها"])
565
-
566
  with tab1:
567
- st.markdown("### توزیع واریته‌ها و سن محصول")
568
  col1, col2 = st.columns(2)
569
- with col1:
570
- variety_counts = filtered_df['واریته'].value_counts().reset_index()
571
- variety_counts.columns = ['واریته', 'تعداد']
572
- fig = px.pie(variety_counts, values='تعداد', names='واریته', title='توزیع واریته‌ها', color_discrete_sequence=px.colors.sequential.Greens_r)
573
- st.plotly_chart(fig, use_container_width=True)
574
- with col2:
575
- age_counts = filtered_df['سن'].value_counts().reset_index()
576
- age_counts.columns = ['سن', 'تعداد']
577
- fig = px.pie(age_counts, values='تعداد', names='سن', title='توزیع سن محصول', color_discrete_sequence=px.colors.sequential.Blues_r)
578
- st.plotly_chart(fig, use_container_width=True)
579
-
580
- st.markdown("### اطلاعات کلی مزارع")
581
- total_area = filtered_df['مساحت'].astype(float).sum()
582
  col1, col2, col3 = st.columns(3)
583
- col1.metric("تعداد کل مزارع", f"{len(filtered_df)}")
584
- col2.metric("مساحت کل (هکتار)", f"{total_area:.2f}")
585
- col3.metric("تعداد کانال‌ها", f"{filtered_df['کانال'].nunique()}")
586
- st_lottie(lottie_farm, height=300, key="farm_animation")
587
-
588
  with tab2:
589
- st.markdown("### نقشه مزارع")
590
  if not coordinates_df.empty:
591
  m = folium.Map(location=[31.45, 48.72], zoom_start=12, tiles='CartoDB positron')
592
  for _, farm in coordinates_df.iterrows():
593
- lat, lon = farm['عرض جغرافیایی'], farm['طول جغرافیایی']
594
- name = farm['مزرعه']
595
- farm_info = filtered_df[filtered_df['مزرعه'] == name]
596
- if not farm_info.empty:
597
- popup_text = f"""
598
- <div style="direction: rtl;">
599
- <h4>مزرعه {name}</h4>
600
- <p>واریته: {farm_info['واریته'].iloc[0]}</p>
601
- <p>سن: {farm_info['سن'].iloc[0]}</p>
602
- <p>مساحت: {farm_info['مساحت'].iloc[0]} هکتار</p>
603
- </div>
604
- """
605
- folium.Marker([lat, lon], popup=folium.Popup(popup_text, max_width=300), tooltip=f"مزرعه {name}", icon=folium.Icon(color='green', icon='leaf')).add_to(m)
606
  st.markdown('<div class="map-container">', unsafe_allow_html=True)
607
  folium_static(m, width=1000, height=600)
608
  st.markdown('</div>', unsafe_allow_html=True)
609
-
610
  with tab3:
611
- st.markdown("### نمودار رشد هفتگی")
612
  growth_data = generate_mock_growth_data(filtered_df)
613
  chart_tab1, chart_tab2 = st.tabs(["میانگین رشد", "رشد مزارع فردی"])
614
- with chart_tab1:
615
- fig = go.Figure()
616
- fig.add_trace(go.Scatter(x=growth_data['average']['weeks'], y=growth_data['average']['heights'], mode='lines+markers', name='میانگین رشد', line=dict(color='#1a8754', width=3)))
617
- fig.update_layout(title='میانگین رشد هفتگی', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)', font=dict(family='Vazirmatn'))
618
- st.plotly_chart(fig, use_container_width=True)
619
- with chart_tab2:
620
- fig = go.Figure()
621
- for i, farm_data in enumerate(growth_data['individual'][:5]):
622
- fig.add_trace(go.Scatter(x=farm_data['weeks'], y=farm_data['heights'], mode='lines+markers', name=f"مزرعه {farm_data['farm_id']}"))
623
- fig.update_layout(title='رشد هفتگی مزارع فردی', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)', font=dict(family='Vazirmatn'))
624
  st.plotly_chart(fig, use_container_width=True)
625
-
626
- with tab4:
627
- st.markdown("### داده‌های مزارع")
628
- st.dataframe(filtered_df, use_container_width=True, height=400, hide_index=True)
629
  st.markdown('</div>', unsafe_allow_html=True)
630
 
631
- # Map Page
632
  elif selected == "نقشه مزارع":
633
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
634
  st.markdown("## نقشه مزارع با شاخص‌های ماهواره‌ای")
635
  col1, col2 = st.columns([1, 3])
636
  with col1:
637
- st.markdown('<div class="glass-card">', unsafe_allow_html=True)
638
  selected_farm = st.selectbox("انتخاب مزرعه", options=coordinates_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
639
  selected_date = st.date_input("انتخاب تاریخ", value=datetime.now())
640
- selected_layer = st.selectbox("انتخاب شاخص", options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], format_func=lambda x: {"NDVI": "شاخص پوشش گیاهی (NDVI)", "NDMI": "شاخص رطوبت (NDMI)", "EVI": "شاخص پیشرفته گیاهی (EVI)", "NDWI": "شاخص آب (NDWI)", "SoilMoisture": "رطوبت خاک (Soil Moisture)"}[x])
641
- heatmap_toggle = st.checkbox("نمایش نقشه حرارتی", value=False)
642
- generate_map = st.button("تولید نقشه", type="primary", use_container_width=True)
643
- st.markdown('</div>', unsafe_allow_html=True)
644
-
645
  with col2:
646
  map_tab, stats_tab = st.tabs(["نقشه", "آمار و تحلیل"])
647
  with map_tab:
648
  if generate_map or 'last_map' not in st.session_state:
649
  with st.spinner('در حال تولید نقشه...'):
650
- m = create_ee_map(selected_farm, selected_date.strftime('%Y-%m-%d'), selected_layer, heatmap=heatmap_toggle)
651
  if m:
652
  st.session_state.last_map = m
653
  folium_static(m, width=800, height=600)
654
- st.success(f"نقشه {selected_layer} برای مزرعه {selected_farm} تولید شد.")
655
  elif 'last_map' in st.session_state:
656
  folium_static(st.session_state.last_map, width=800, height=600)
657
-
658
  with stats_tab:
659
  if 'last_map' in st.session_state:
660
  stats = calculate_farm_stats(selected_farm, selected_layer)
661
  col1, col2, col3, col4 = st.columns(4)
662
- with col1: st.metric(f"میانگین {selected_layer}", stats["mean"])
663
- with col2: st.metric(f"حداکثر {selected_layer}", stats["max"])
664
- with col3: st.metric(f"حداقل {selected_layer}", stats["min"])
665
- with col4: st.metric("انحراف معیار", stats["std_dev"])
666
-
667
- if stats['stress_alerts']:
668
- for alert in stats['stress_alerts']:
669
- st.warning(alert)
670
-
671
- fig = px.histogram(x=stats["histogram_data"], nbins=20, title=f"توزیع مقادیر {selected_layer}", labels={"x": f"مقدار {selected_layer}", "y": "فراوانی"})
672
- st.plotly_chart(fig, use_container_width=True)
673
-
674
  st.markdown("### تحلیل سری زمانی")
675
  period = st.selectbox("بازه زمانی", ["weekly", "monthly", "seasonal"], format_func=lambda x: {"weekly": "هفتگی", "monthly": "ماهانه", "seasonal": "فصلی"}[x])
676
  ts_data = generate_time_series_data(selected_farm, selected_layer, period)
677
- fig = px.line(ts_data, x='Date', y=selected_layer, title=f"روند تغییرات {selected_layer}")
678
- st.plotly_chart(fig, use_container_width=True)
679
  st.markdown('</div>', unsafe_allow_html=True)
680
 
681
- # Data Entry Page
682
  elif selected == "ورود اطلاعات":
683
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
684
- st.markdown("## ورود اطلاعات روزانه مزارع")
685
  tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"])
686
  with tab1:
687
  col1, col2 = st.columns(2)
688
- with col1:
689
- selected_week = st.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)], format_func=lambda x: f"هفته {x}")
690
- with col2:
691
- selected_day = st.selectbox("انتخاب روز", ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه"])
692
-
693
  filtered_farms = farm_df[farm_df['روز'] == selected_day]
694
  data_key = f"data_{selected_week}_{selected_day}"
695
  if data_key not in st.session_state:
696
- st.session_state[data_key] = pd.DataFrame({
697
- 'مزرعه': filtered_farms['مزرعه'],
698
- 'ایستگاه 1': [0] * len(filtered_farms),
699
- 'ایستگاه 2': [0] * len(filtered_farms),
700
- 'ایستگاه 3': [0] * len(filtered_farms),
701
- 'ایستگاه 4': [0] * len(filtered_farms),
702
- 'ایستگاه 5': [0] * len(filtered_farms),
703
- 'چاهک 1': [0] * len(filtered_farms),
704
- 'چاهک 2': [0] * len(filtered_farms),
705
- 'رطوبت غلاف': [0] * len(filtered_farms),
706
- 'نیتروژن': [0] * len(filtered_farms),
707
- 'میانگین ارتفاع': [0] * len(filtered_farms)
708
- })
709
-
710
  edited_df = st.data_editor(st.session_state[data_key], use_container_width=True)
711
- for i in range(len(edited_df)):
712
- stations = [edited_df.iloc[i][f'ایستگاه {j}'] for j in range(1, 6)]
713
- valid_stations = [s for s in stations if s > 0]
714
- if valid_stations:
715
- edited_df.iloc[i, edited_df.columns.get_loc('میانگین ارتفاع')] = round(sum(valid_stations) / len(valid_stations), 1)
716
  st.session_state[data_key] = edited_df
717
-
718
  if st.button("ذخیره اطلاعات", type="primary"):
719
  new_data = edited_df.copy()
720
- new_data['Farm_ID'] = new_data['مزرعه']
721
- new_data['Week'] = int(selected_week)
722
- new_data['Measurement_Date'] = (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d')
723
- new_data['Height'] = new_data['میانگین ارتفاع']
724
  new_data = new_data.merge(farm_df[['مزرعه', 'واریته', 'سن', 'مساحت', 'کانال', 'اداره']], left_on='Farm_ID', right_on='مزرعه', how='left')
725
  st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
726
  st.success(f"داده‌های هفته {selected_week} ذخیره شدند.")
727
  with tab2:
728
- uploaded_file = st.file_uploader("فایل اکسل خود را آپلود کنید", type=["xlsx", "xls", "csv"])
729
  if uploaded_file:
730
  df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
731
  st.dataframe(df)
732
  if st.button("ذخیره فایل"): st.success("فایل ذخیره شد.")
733
  st.markdown('</div>', unsafe_allow_html=True)
734
 
735
- # Data Analysis Page
736
  elif selected == "تحلیل داده‌ها":
737
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
738
  st.markdown("## تحلیل هوشمند داده‌ها")
739
  tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریته‌ها", "تحلیل رطوبت", "پیش‌بینی"])
740
-
741
  with tab1:
742
  growth_data = generate_mock_growth_data(farm_df)
743
- chart_data = []
744
- for farm_data in growth_data['individual']:
745
- for i, week in enumerate(farm_data['weeks']):
746
- chart_data.append({'Farm': farm_data['farm_id'], 'Week': week, 'Height': farm_data['heights'][i]})
747
- chart_df = pd.DataFrame(chart_data)
748
- chart = alt.Chart(chart_df).mark_line(point=True).encode(x='Week:Q', y='Height:Q', color='Farm:N').interactive()
749
- st.altair_chart(chart, use_container_width=True)
750
-
751
  with tab2:
752
  variety_heights = {variety: np.random.normal(150, 20, 100) for variety in farm_df['واریته'].unique()}
753
- fig = go.Figure()
754
- for variety in variety_heights:
755
- fig.add_trace(go.Box(y=variety_heights[variety], name=variety))
756
- fig.update_layout(title='مقایسه ارتفاع بر اساس واریته', yaxis_title='ارتفاع (سانتی‌متر)', font=dict(family="Vazirmatn"))
757
  st.plotly_chart(fig, use_container_width=True)
758
-
759
  with tab3:
760
- farms = farm_df['مزرعه'].unique()[:10]
761
- dates = pd.date_range(end=datetime.now(), periods=30)
762
- moisture_data = [{'Farm': farm, 'Date': date, 'Moisture': max(0, min(100, np.random.uniform(50, 80) + np.random.normal(0, 5)))} for farm in farms for date in dates]
763
- moisture_df = pd.DataFrame(moisture_data)
764
- fig = px.line(moisture_df, x='Date', y='Moisture', color='Farm', title='روند رطوبت مزارع')
765
-
766
- # Correlation analysis
767
- correlation_data = [{'Moisture': m, 'Height': 100 + m * 1.5 + np.random.normal(0, 20)} for m in moisture_df['Moisture']]
768
- corr_df = pd.DataFrame(correlation_data)
769
- fig_corr = px.scatter(corr_df, x='Moisture', y='Height', title='همبستگی رطوبت و ارتفاع', trendline='ols')
770
- st.plotly_chart(fig, use_container_width=True)
771
- st.plotly_chart(fig_corr, use_container_width=True)
772
  st.info(f"ضریب همبستگی: {corr_df['Moisture'].corr(corr_df['Height']):.2f}")
773
-
774
  with tab4:
775
  selected_farm = st.selectbox("انتخاب مزرعه", options=farm_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
776
- weeks = list(range(1, 16))
777
- heights = [50 + i * 10 + np.random.normal(0, 5) for i in weeks]
778
- historical_df = pd.DataFrame({'Week': weeks, 'Height': heights})
779
- rf_preds, lstm_preds = predict_growth_advanced(selected_farm, historical_df)
780
- future_weeks = list(range(16, 23))
781
-
782
- fig = go.Figure()
783
- fig.add_trace(go.Scatter(x=weeks, y=heights, mode='lines+markers', name='داده‌های تاریخی'))
784
- fig.add_trace(go.Scatter(x=future_weeks, y=rf_preds, mode='lines+markers', name='پیش‌بینی Random Forest', line=dict(dash='dash')))
785
- fig.add_trace(go.Scatter(x=future_weeks, y=lstm_preds, mode='lines+markers', name='پیش‌بینی LSTM', line=dict(dash='dot')))
786
- fig.update_layout(title=f'پیش‌بینی رشد مزرعه {selected_farm}', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)', font=dict(family='Vazirmatn'))
787
  st.plotly_chart(fig, use_container_width=True)
788
-
789
- # Clustering
790
  clustered_df, centers = cluster_farms(farm_df)
791
- fig = px.scatter(clustered_df, x='مساحت', y='سن', color='Cluster', title='گروه‌بندی مزارع', labels={'مساحت': 'مساحت (هکتار)', 'سن': 'سن (سال)'})
792
- st.plotly_chart(fig, use_container_width=True)
793
  st.markdown('</div>', unsafe_allow_html=True)
794
 
795
- # Reporting Page
796
  elif selected == "گزارش‌گیری":
797
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
798
  st.markdown("## گزارش‌گیری پیشرفته")
799
- start_date = st.date_input("تاریخ شروع", value=datetime.now() - timedelta(days=30))
800
- end_date = st.date_input("تاریخ پایان", value=datetime.now())
801
  report_type = st.selectbox("نوع گزارش", ["گزارش کلی", "گزارش رشد", "گزارش رطوبت", "گزارش مقایسه‌ای واریته‌ها"])
802
  if st.button("تولید گزارش"):
803
  with st.spinner('در حال تولید گزارش...'):
804
- time.sleep(2)
805
  if report_type == "گزارش کلی":
806
- st.markdown("### گزارش کلی وضعیت مزارع")
807
  col1, col2 = st.columns(2)
808
- with col1:
809
- fig = px.pie(values=farm_df['اداره'].value_counts().values, names=farm_df['اداره'].value_counts().index, title='توزیع مزارع بر اساس اداره')
810
- st.plotly_chart(fig)
811
- with col2:
812
- weeks = list(range(1, 23))
813
- heights = [100 + i * 5 + np.random.normal(0, 10) for i in weeks]
814
- fig = px.line(x=weeks, y=heights, title='روند رشد کلی مزارع', labels={'x': 'هفته', 'y': 'ارتفاع (سانتی‌متر)'})
815
- st.plotly_chart(fig)
816
  st.markdown('</div>', unsafe_allow_html=True)
817
 
818
- # Settings Page
819
  elif selected == "تنظیمات":
820
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
821
- st.markdown("## تنظیمات سیستم")
822
  tab1, tab2 = st.tabs(["تنظیمات کاربری", "تنظیمات سیستم"])
823
- with tab1:
824
- user_name = st.text_input("نام کاربری", value="کاربر نمونه")
825
- user_email = st.text_input("ایمیل", value="[email protected]")
826
- with tab2:
827
- system_language = st.selectbox("زبان سیستم", ["فارسی", "English", "العربية"])
828
- date_format = st.selectbox("فرمت تاریخ", ["YYYY/MM/DD", "DD/MM/YYYY", "MM/DD/YYYY"])
829
  st.markdown('</div>', unsafe_allow_html=True)
830
 
831
- # Footer
832
  st.markdown("""
833
  <footer style="position: fixed; left: 0; bottom: 0; width: 100%; background-color: #1a8754; color: white; text-align: center; padding: 10px 0;">
834
- <p>© سامانه هوشمند پایش مزارع نیشکر کشت و صنعت دهخدا | طراحی شده توسط تیم مطالعات کاربردی توسعه</p>
835
  </footer>
836
  """, unsafe_allow_html=True)
837
  st.markdown('</body>', unsafe_allow_html=True)
 
19
  from streamlit_option_menu import option_menu
20
  from streamlit_lottie import st_lottie
21
  import requests
 
 
 
 
 
 
 
 
22
  from sklearn.ensemble import RandomForestRegressor
23
  from sklearn.cluster import KMeans
24
  from tensorflow.keras.models import Sequential
25
  from tensorflow.keras.layers import LSTM, Dense
26
  from sklearn.preprocessing import MinMaxScaler
27
 
28
+ # --- Configuration ---
29
  st.set_page_config(
30
  page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
31
  page_icon="🌿",
 
33
  initial_sidebar_state="expanded"
34
  )
35
 
36
+ # --- Custom CSS ---
37
  st.markdown("""
38
  <style>
39
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
40
+ * { font-family: 'Vazirmatn', sans-serif !important; transition: all 0.3s ease; }
41
+ .main { background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%); }
42
+ .dark-theme .main { background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%); color: #ecf0f1; }
43
+ .main-header { background: linear-gradient(90deg, #1a8754 0%, #115740 100%); padding: 1.5rem; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; animation: header-glow 3s infinite alternate; }
44
+ @keyframes header-glow { 0% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.1); } 100% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.3); } }
45
+ .main-header h1, .main-header p { color: white; }
46
+ .stcard { border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; }
47
+ .stcard:hover { transform: translateY(-5px); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1); }
48
+ .dark-theme .stcard { background: #34495e; color: #ecf0f1; }
49
+ .stButton>button { border-radius: 50px; padding: 0.5rem 1.5rem; font-weight: 600; transition: all 0.3s ease; }
50
+ .primary-btn { background: linear-gradient(90deg, #1a8754 0%, #115740 100%); color: white; }
51
+ .dark-theme .primary-btn { background: linear-gradient(90deg, #27ae60 0%, #219653 100%); }
52
+ .metric-card { background: white; border-radius: 12px; padding: 1.5rem; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); text-align: center; }
53
+ .dark-theme .metric-card { background: #34495e; color: #ecf0f1; }
54
+ .map-container { border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); }
55
+ .animate-load { animation: fadeIn 0.5s ease forwards; }
56
+ @keyframes fadeIn { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } }
57
+ .rtl { direction: rtl; text-align: right; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  </style>
59
  """, unsafe_allow_html=True)
60
 
61
+ # --- Theme Selection ---
62
  if 'theme' not in st.session_state:
63
  st.session_state.theme = 'light'
 
64
  theme = st.sidebar.selectbox("انتخاب تم", ["روشن", "تیره"], index=0 if st.session_state.theme == 'light' else 1)
65
  st.session_state.theme = 'light' if theme == "روشن" else 'dark'
66
  theme_class = "dark-theme" if st.session_state.theme == 'dark' else ""
 
67
  st.markdown(f'<body class="{theme_class}">', unsafe_allow_html=True)
68
 
69
+ # --- Utility Functions ---
70
  @st.cache_resource
71
  def initialize_earth_engine():
72
  try:
 
84
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
85
  "universe_domain": "googleapis.com"
86
  }
87
+ with open('ee_credentials.json', 'w') as f:
 
 
88
  json.dump(credentials_dict, f)
89
+ credentials = ee.ServiceAccountCredentials(service_account, 'ee_credentials.json')
 
90
  ee.Initialize(credentials)
91
+ os.remove('ee_credentials.json')
 
92
  return True
93
  except Exception as e:
94
  st.error(f"خطا در اتصال به Earth Engine: {e}")
95
  return False
96
 
 
97
  @st.cache_data
98
+ def load_data(file_name, error_message):
99
  try:
100
+ return pd.read_csv(file_name)
 
101
  except Exception as e:
102
+ st.error(f"{error_message}: {e}")
103
  return pd.DataFrame()
104
 
105
  @st.cache_data
106
+ def load_lottie_url(url):
 
 
 
 
 
 
 
 
 
 
107
  r = requests.get(url)
108
+ return r.json() if r.status_code == 200 else None
 
 
109
 
110
+ def get_weather_data(lat, lon, api_key="ed47316a45379e2221a75f813229fb46"):
 
111
  url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
112
  response = requests.get(url)
113
+ return response.json() if response.status_code == 200 else None
 
 
114
 
115
+ def estimate_water_requirement(farm_id, date_str, coordinates_df):
 
 
116
  farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
117
+ weather_data = get_weather_data(farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی'])
 
118
  if weather_data:
119
  temperature = weather_data['main']['temp']
120
  humidity = weather_data['main']['humidity']
121
+ return max(0, (temperature - 20) * 0.5 + (100 - humidity) * 0.1)
 
122
  return None
123
 
124
+ def create_ee_map(farm_id, date_str, coordinates_df, layer_type="NDVI", heatmap=False):
125
+ farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
126
+ lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
127
+ m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
128
+ date_obj = datetime.strptime(date_str, '%Y-%m-%d')
129
+ start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d')
130
+ end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d')
131
+ region = ee.Geometry.Point([lon, lat]).buffer(1500)
132
+ s2 = ee.ImageCollection('COPERNICUS/S2_SR').filterDate(start_date, end_date).filterBounds(region).sort('CLOUDY_PIXEL_PERCENTAGE').first()
133
+
134
+ layers = {
135
+ "NDVI": (s2.normalizedDifference(['B8', 'B4']).rename('NDVI'), {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']}, 'شاخص پوشش گیاهی (NDVI)'),
136
+ "NDMI": (s2.normalizedDifference(['B8', 'B11']).rename('NDMI'), {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']}, 'شاخص رطوبت (NDMI)'),
137
+ "EVI": (s2.normalizedDifference(['B8', 'B4']).multiply(2.5).divide(s2.select('B8').add(s2.select('B4').multiply(6)).subtract(s2.select('B2').multiply(7.5)).add(1)).rename('EVI'), {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']}, 'شاخص پیشرفته گیاهی (EVI)'),
138
+ "NDWI": (s2.normalizedDifference(['B3', 'B8']).rename('NDWI'), {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']}, 'شاخص آب (NDWI)'),
139
+ "SoilMoisture": (ee.ImageCollection('COPERNICUS/S1_GRD').filterDate(start_date, end_date).filterBounds(region).sort('system:time_start').first().select('VV').rename('SoilMoisture'), {'min': -25, 'max': -5, 'palette': ['#00008b', '#add8e6', '#ffffff']}, 'رطوبت خاک (Soil Moisture)')
140
+ }
141
+
142
+ index, viz_params, legend_title = layers[layer_type]
143
+ map_id_dict = ee.Image(index).getMapId(viz_params if not heatmap else {**viz_params, 'opacity': 0.7})
144
+ folium.TileLayer(tiles=map_id_dict['tile_fetcher'].url_format, attr='Google Earth Engine', name=layer_type, overlay=True, control=True).add_to(m)
145
+ folium.Marker([lat, lon], popup=f'مزرعه {farm_id}', tooltip=f'مزرعه {farm_id}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
146
+ folium.Circle([lat, lon], radius=1500, color='green', fill=True, fill_opacity=0.1).add_to(m)
147
+ folium.LayerControl().add_to(m)
148
+
149
+ legend_html = f'''
150
+ <div style="position: fixed; bottom: 50px; right: 50px; border: 2px solid grey; z-index: 9999; background-color: white; padding: 10px; border-radius: 5px; direction: rtl;">
151
+ <div style="font-size: 14px; font-weight: bold; margin-bottom: 5px;">{legend_title}</div>
152
+ <div style="display: flex; align-items: center; margin-bottom: 5px;"><div style="background: {viz_params['palette'][0]}; width: 20px; height: 20px; margin-left: 5px;"></div><span>کم</span></div>
153
+ <div style="display: flex; align-items: center; margin-bottom: 5px;"><div style="background: {viz_params['palette'][2]}; width: 20px; height: 20px; margin-left: 5px;"></div><span>متوسط</span></div>
154
+ <div style="display: flex; align-items: center;"><div style="background: {viz_params['palette'][-1]}; width: 20px; height: 20px; margin-left: 5px;"></div><span>زیاد</span></div>
155
+ </div>
156
+ '''
157
+ m.get_root().html.add_child(folium.Element(legend_html))
158
+ return m
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
 
160
  def generate_mock_growth_data(farm_data, selected_variety="all", selected_age="all"):
161
  weeks = list(range(1, 23))
162
+ filtered_farms = farm_data[(farm_data['واریته'] == selected_variety) if selected_variety != "all" else farm_data.index] \
163
+ [(farm_data['سن'] == selected_age) if selected_age != "all" else farm_data.index]
164
+ farm_growth_data = [
165
+ {'farm_id': farm['مزرعه'], 'variety': farm['واریته'], 'age': farm['سن'], 'weeks': weeks,
166
+ 'heights': [round(np.random.uniform(50, 100) + np.random.uniform(5, 15) * week) for week in weeks]}
167
+ for _, farm in filtered_farms.iterrows()
168
+ ]
169
+ avg_heights = [round(np.mean([farm['heights'][week-1] for farm in farm_growth_data])) if farm_growth_data else 0 for week in weeks]
170
+ return {'individual': farm_growth_data, 'average': {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': avg_heights}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
 
172
  def calculate_farm_stats(farm_id, layer_type="NDVI"):
173
+ stats_ranges = {
174
+ "NDVI": (0.6, 0.8, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, 0.3, "تنش سلامتی: NDVI پایین"),
175
+ "NDMI": (0.3, 0.5, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, 0.1, "تنش آبی: NDMI پایین"),
176
+ "EVI": (0.4, 0.6, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, None, None),
177
+ "NDWI": (-0.1, 0.1, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, None, None),
178
+ "SoilMoisture": (-20, -10, 5, 10, 5, 10, 2, 5, -20, "تنش خاک: رطوبت پایین")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
180
+ mean_range, min_adj1, min_adj2, max_adj1, max_adj2, std_range = stats_ranges[layer_type][:6]
181
+ mean = round(np.random.uniform(*mean_range), 2)
182
+ min_val = round(mean - np.random.uniform(min_adj1, min_adj2), 2)
183
+ max_val = round(mean + np.random.uniform(max_adj1, max_adj2), 2)
184
+ std_dev = round(np.random.uniform(*std_range), 2)
185
+ hist_data = np.clip(np.random.normal(mean, std_dev, 1000), min_val, max_val)
186
+ stress_threshold, stress_message = stats_ranges[layer_type][8:]
187
+ stress_alerts = [stress_message] if stress_threshold and mean < stress_threshold else []
188
+ return {'mean': mean, 'min': min_val, 'max': max_val, 'std_dev': std_dev, 'histogram_data': hist_data, 'stress_alerts': stress_alerts}
189
 
 
190
  def generate_time_series_data(farm_id, layer_type, period="weekly"):
191
+ periods = {"weekly": 30, "monthly": 90, "seasonal": 365}
192
+ freqs = {"weekly": 'W', "monthly": 'M', "seasonal": 'Q'}
193
+ dates = pd.date_range(end=datetime.now(), periods=periods[period], freq=freqs[period])
194
+ ranges = {"NDVI": (0.7, 0.1), "NDMI": (0.4, 0.1), "EVI": (0.5, 0.1), "NDWI": (0, 0.1), "SoilMoisture": (-15, 5)}
195
+ return pd.DataFrame({'Date': dates, layer_type: np.random.normal(*ranges[layer_type], len(dates))})
 
 
 
 
 
 
 
196
 
197
+ def predict_growth_advanced(historical_data):
 
198
  weeks = historical_data['Week'].values.reshape(-1, 1)
199
  heights = historical_data['Height'].values
200
+ rf_model = RandomForestRegressor(n_estimators=50, random_state=42) # Reduced trees for speed
 
 
201
  rf_model.fit(weeks, heights)
202
  future_weeks = np.array(range(16, 23)).reshape(-1, 1)
203
  rf_predictions = rf_model.predict(future_weeks)
204
 
 
205
  scaler = MinMaxScaler()
206
  scaled_heights = scaler.fit_transform(heights.reshape(-1, 1))
207
+ X = np.array([scaled_heights[i-3:i] for i in range(3, len(scaled_heights))])
208
+ y = scaled_heights[3:]
209
+ lstm_model = Sequential([LSTM(20, activation='relu', input_shape=(3, 1)), Dense(1)]) # Simplified LSTM
 
 
 
 
 
 
 
 
210
  lstm_model.compile(optimizer='adam', loss='mse')
211
+ lstm_model.fit(X, y, epochs=20, batch_size=1, verbose=0) # Reduced epochs
 
212
  lstm_input = scaled_heights[-3:].reshape((1, 3, 1))
213
+ lstm_predictions = [scaler.inverse_transform(lstm_model.predict(lstm_input, verbose=0)).flatten()[0] for _ in range(7)]
 
 
 
 
 
 
214
  return rf_predictions, lstm_predictions
215
 
 
216
  def cluster_farms(farm_data):
217
+ # Clean data: Convert to numeric, replace invalid values with NaN, then fill with median
218
+ features = farm_data[['مساحت', 'سن']].apply(pd.to_numeric, errors='coerce').fillna({'مساحت': farm_data['مساحت'].median(), 'سن': farm_data['سن'].median()})
219
+ kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) # Added n_init for explicit control
220
  farm_data['Cluster'] = kmeans.fit_predict(features)
221
  return farm_data, kmeans.cluster_centers_
222
 
223
+ # --- Initialization ---
224
  ee_initialized = initialize_earth_engine()
225
+ farm_df = load_data("پایگاه داده (1).csv", "خطا در بارگذاری داده‌های مزارع")
226
+ coordinates_df = load_data("farm_coordinates.csv", "خطا در بارگذاری داده‌های مختصات")
 
 
227
  lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json')
228
  lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json')
229
  lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json')
 
 
230
  if 'heights_df' not in st.session_state:
231
  st.session_state.heights_df = pd.DataFrame(columns=['Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3', 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen', 'Variety', 'Age', 'Area', 'Channel', 'Administration'])
232
 
233
+ # --- Main UI ---
234
+ st.markdown('<div class="main-header animate-load"><h1>سامانه هوشمند پایش مزارع نیشکر دهخدا</h1><p>پلتفرم جامع مدیریت و پایش مزارع نیشکر</p></div>', unsafe_allow_html=True)
 
 
 
 
 
235
  selected = option_menu(
236
+ menu_title=None, options=["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل داده‌ها", "گزارش‌گیری", "تنظیمات"],
237
+ icons=["speedometer2", "map", "pencil-square", "graph-up", "file-earmark-text", "gear"], default_index=0, orientation="horizontal",
238
+ styles={"container": {"padding": "0!important", "background-color": "transparent", "margin-bottom": "20px"}, "icon": {"color": "#1a8754", "font-size": "18px"},
239
+ "nav-link": {"font-size": "16px", "text-align": "center", "margin": "0px", "--hover-color": "#e9f7ef", "border-radius": "10px"}, "nav-link-selected": {"background-color": "#1a8754", "color": "white", "font-weight": "600"}}
 
 
 
 
 
 
 
240
  )
241
 
242
+ # --- Dashboard ---
243
  if selected == "داشبورد":
244
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
245
  col1, col2, col3, col4 = st.columns(4)
246
+ for col, value, label in [(col1, len(farm_df), "تعداد مزارع"), (col2, int(len(farm_df) * 0.85), "مزارع فعال"), (col3, 175, "میانگین ارتفاع (cm)"), (col4, 68, "میانگین رطوبت (%)")]:
247
+ col.markdown(f'<div class="metric-card"><div class="metric-value">{value}</div><div class="metric-label">{label}</div></div>', unsafe_allow_html=True)
248
 
249
+ st.markdown("### فیلترها")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  col_filter1, col_filter2, col_filter3 = st.columns(3)
251
+ filters = [col_filter1.multiselect("واریته", ["همه"] + list(farm_df['واریته'].unique()), default="همه"),
252
+ col_filter2.multiselect("سن", ["همه"] + list(farm_df['سن'].unique()), default="همه"),
253
+ col_filter3.multiselect("کانال", ["همه"] + list(farm_df['کانال'].unique()), default="همه")]
254
+ filtered_df = farm_df[([(v in filters[0]) for v in farm_df['واریته']] if "همه" not in filters[0] else farm_df.index) &
255
+ ([(a in filters[1]) for a in farm_df['سن']] if "همه" not in filters[1] else farm_df.index) &
256
+ ([(c in filters[2]) for c in farm_df['کانال']] if "همه" not in filters[2] else farm_df.index)]
 
 
 
 
 
 
 
 
257
 
258
  tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "داده‌ها"])
 
259
  with tab1:
 
260
  col1, col2 = st.columns(2)
261
+ with col1: st.plotly_chart(px.pie(filtered_df['واریته'].value_counts().reset_index(), values='count', names='واریته', title='توزیع واریته‌ها', color_discrete_sequence=px.colors.sequential.Greens_r), use_container_width=True)
262
+ with col2: st.plotly_chart(px.pie(filtered_df['سن'].value_counts().reset_index(), values='count', names='سن', title='توزیع سن محصول', color_discrete_sequence=px.colors.sequential.Blues_r), use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
263
  col1, col2, col3 = st.columns(3)
264
+ col1.metric("تعداد مزارع", len(filtered_df))
265
+ col2.metric("مساحت کل (هکتار)", f"{filtered_df['مساحت'].astype(float).sum():.2f}")
266
+ col3.metric("تعداد کانال‌ها", filtered_df['کانال'].nunique())
267
+ st_lottie(lottie_farm, height=300)
 
268
  with tab2:
 
269
  if not coordinates_df.empty:
270
  m = folium.Map(location=[31.45, 48.72], zoom_start=12, tiles='CartoDB positron')
271
  for _, farm in coordinates_df.iterrows():
272
+ if farm['مزرعه'] in filtered_df['مزرعه'].values:
273
+ farm_info = filtered_df[filtered_df['مزرعه'] == farm['مزرعه']].iloc[0]
274
+ popup_text = f"<div style='direction: rtl;'><h4>مزرعه {farm['مزرعه']}</h4><p>واریته: {farm_info['واریته']}</p><p>سن: {farm_info['سن']}</p><p>مساحت: {farm_info['مساحت']} هکتار</p></div>"
275
+ folium.Marker([farm['عرض جغرافیایی'], farm['طول جغرافیایی']], popup=folium.Popup(popup_text, max_width=300), tooltip=f"مزرعه {farm['مزرعه']}", icon=folium.Icon(color='green', icon='leaf')).add_to(m)
 
 
 
 
 
 
 
 
 
276
  st.markdown('<div class="map-container">', unsafe_allow_html=True)
277
  folium_static(m, width=1000, height=600)
278
  st.markdown('</div>', unsafe_allow_html=True)
 
279
  with tab3:
 
280
  growth_data = generate_mock_growth_data(filtered_df)
281
  chart_tab1, chart_tab2 = st.tabs(["میانگین رشد", "رشد مزارع فردی"])
282
+ with chart_tab1: st.plotly_chart(go.Figure(go.Scatter(x=growth_data['average']['weeks'], y=growth_data['average']['heights'], mode='lines+markers', name='میانگین رشد', line=dict(color='#1a8754', width=3))).update_layout(title='میانگین رشد هفتگی', xaxis_title='هفته', yaxis_title='ارتفاع (cm)', font=dict(family='Vazirmatn')), use_container_width=True)
283
+ with chart_tab2:
284
+ fig = go.Figure([go.Scatter(x=farm_data['weeks'], y=farm_data['heights'], mode='lines+markers', name=f"مزرعه {farm_data['farm_id']}") for farm_data in growth_data['individual'][:5]])
285
+ fig.update_layout(title='رشد هفتگی مزارع فردی', xaxis_title='هفته', yaxis_title='ارتفاع (cm)', font=dict(family='Vazirmatn'))
 
 
 
 
 
 
286
  st.plotly_chart(fig, use_container_width=True)
287
+ with tab4: st.dataframe(filtered_df, use_container_width=True, height=400, hide_index=True)
 
 
 
288
  st.markdown('</div>', unsafe_allow_html=True)
289
 
290
+ # --- Map ---
291
  elif selected == "نقشه مزارع":
292
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
293
  st.markdown("## نقشه مزارع با شاخص‌های ماهواره‌ای")
294
  col1, col2 = st.columns([1, 3])
295
  with col1:
 
296
  selected_farm = st.selectbox("انتخاب مزرعه", options=coordinates_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
297
  selected_date = st.date_input("انتخاب تاریخ", value=datetime.now())
298
+ selected_layer = st.selectbox("انتخاب شاخص", options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], format_func=lambda x: {"NDVI": "NDVI", "NDMI": "NDMI", "EVI": "EVI", "NDWI": "NDWI", "SoilMoisture": "رطوبت خاک"}[x])
299
+ heatmap_toggle = st.checkbox("نمایش نقشه حرارتی")
300
+ generate_map = st.button("تولید نقشه", type="primary")
 
 
301
  with col2:
302
  map_tab, stats_tab = st.tabs(["نقشه", "آمار و تحلیل"])
303
  with map_tab:
304
  if generate_map or 'last_map' not in st.session_state:
305
  with st.spinner('در حال تولید نقشه...'):
306
+ m = create_ee_map(selected_farm, selected_date.strftime('%Y-%m-%d'), coordinates_df, selected_layer, heatmap_toggle)
307
  if m:
308
  st.session_state.last_map = m
309
  folium_static(m, width=800, height=600)
310
+ st.success(f"نقشه {selected_layer} تولید شد.")
311
  elif 'last_map' in st.session_state:
312
  folium_static(st.session_state.last_map, width=800, height=600)
 
313
  with stats_tab:
314
  if 'last_map' in st.session_state:
315
  stats = calculate_farm_stats(selected_farm, selected_layer)
316
  col1, col2, col3, col4 = st.columns(4)
317
+ for col, label, value in [(col1, f"میانگین {selected_layer}", stats["mean"]), (col2, f"حداکثر {selected_layer}", stats["max"]), (col3, f"حداقل {selected_layer}", stats["min"]), (col4, "انحراف معیار", stats["std_dev"])]:
318
+ col.metric(label, value)
319
+ for alert in stats['stress_alerts']: st.warning(alert)
320
+ st.plotly_chart(px.histogram(x=stats["histogram_data"], nbins=20, title=f"توزیع {selected_layer}", labels={"x": selected_layer, "y": "فراوانی"}), use_container_width=True)
 
 
 
 
 
 
 
 
321
  st.markdown("### تحلیل سری زمانی")
322
  period = st.selectbox("بازه زمانی", ["weekly", "monthly", "seasonal"], format_func=lambda x: {"weekly": "هفتگی", "monthly": "ماهانه", "seasonal": "فصلی"}[x])
323
  ts_data = generate_time_series_data(selected_farm, selected_layer, period)
324
+ st.plotly_chart(px.line(ts_data, x='Date', y=selected_layer, title=f"روند {selected_layer}"), use_container_width=True)
 
325
  st.markdown('</div>', unsafe_allow_html=True)
326
 
327
+ # --- Data Entry ---
328
  elif selected == "ورود اطلاعات":
329
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
330
+ st.markdown("## ورود اطلاعات روزانه")
331
  tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"])
332
  with tab1:
333
  col1, col2 = st.columns(2)
334
+ selected_week, selected_day = col1.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)], format_func=lambda x: f"هفته {x}"), col2.selectbox("انتخاب روز", ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه"])
 
 
 
 
335
  filtered_farms = farm_df[farm_df['روز'] == selected_day]
336
  data_key = f"data_{selected_week}_{selected_day}"
337
  if data_key not in st.session_state:
338
+ st.session_state[data_key] = pd.DataFrame({col: [0 if col != 'مزرعه' else farm for farm in filtered_farms['مزرعه']] for col in ['مزرعه', 'ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'چاهک 1', 'چاهک 2', 'رطوبت غلاف', 'نیتروژن', 'میانگین ارتفاع']})
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  edited_df = st.data_editor(st.session_state[data_key], use_container_width=True)
340
+ edited_df['میانگین ارتفاع'] = [round(np.mean([s for s in row[1:6] if s > 0])) if any(s > 0 for s in row[1:6]) else 0 for row in edited_df.iloc[:, 1:6].values]
 
 
 
 
341
  st.session_state[data_key] = edited_df
 
342
  if st.button("ذخیره اطلاعات", type="primary"):
343
  new_data = edited_df.copy()
344
+ new_data['Farm_ID'], new_data['Week'], new_data['Measurement_Date'], new_data['Height'] = new_data['مزرعه'], int(selected_week), (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d'), new_data['میانگین ارتفاع']
 
 
 
345
  new_data = new_data.merge(farm_df[['مزرعه', 'واریته', 'سن', 'مساحت', 'کانال', 'اداره']], left_on='Farm_ID', right_on='مزرعه', how='left')
346
  st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
347
  st.success(f"داده‌های هفته {selected_week} ذخیره شدند.")
348
  with tab2:
349
+ uploaded_file = st.file_uploader("فایل اکسل یا CSV", type=["xlsx", "xls", "csv"])
350
  if uploaded_file:
351
  df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
352
  st.dataframe(df)
353
  if st.button("ذخیره فایل"): st.success("فایل ذخیره شد.")
354
  st.markdown('</div>', unsafe_allow_html=True)
355
 
356
+ # --- Data Analysis ---
357
  elif selected == "تحلیل داده‌ها":
358
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
359
  st.markdown("## تحلیل هوشمند داده‌ها")
360
  tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریته‌ها", "تحلیل رطوبت", "پیش‌بینی"])
 
361
  with tab1:
362
  growth_data = generate_mock_growth_data(farm_df)
363
+ chart_df = pd.DataFrame([{'Farm': farm['farm_id'], 'Week': week, 'Height': height} for farm in growth_data['individual'] for week, height in zip(farm['weeks'], farm['heights'])])
364
+ st.altair_chart(alt.Chart(chart_df).mark_line(point=True).encode(x='Week:Q', y='Height:Q', color='Farm:N').interactive(), use_container_width=True)
 
 
 
 
 
 
365
  with tab2:
366
  variety_heights = {variety: np.random.normal(150, 20, 100) for variety in farm_df['واریته'].unique()}
367
+ fig = go.Figure([go.Box(y=heights, name=variety) for variety, heights in variety_heights.items()])
368
+ fig.update_layout(title='مقایسه ارتفاع بر اساس واریته', yaxis_title='ارتفاع (cm)', font=dict(family="Vazirmatn"))
 
 
369
  st.plotly_chart(fig, use_container_width=True)
 
370
  with tab3:
371
+ moisture_df = pd.DataFrame([{'Farm': farm, 'Date': date, 'Moisture': max(0, min(100, np.random.uniform(50, 80) + np.random.normal(0, 5)))} for farm in farm_df['مزرعه'].unique()[:10] for date in pd.date_range(end=datetime.now(), periods=30)])
372
+ st.plotly_chart(px.line(moisture_df, x='Date', y='Moisture', color='Farm', title='روند رطوبت مزارع'), use_container_width=True)
373
+ corr_df = pd.DataFrame({'Moisture': moisture_df['Moisture'], 'Height': moisture_df['Moisture'] * 1.5 + np.random.normal(0, 20, len(moisture_df))})
374
+ st.plotly_chart(px.scatter(corr_df, x='Moisture', y='Height', title='همبستگی رطوبت و ارتفاع', trendline='ols'), use_container_width=True)
 
 
 
 
 
 
 
 
375
  st.info(f"ضریب همبستگی: {corr_df['Moisture'].corr(corr_df['Height']):.2f}")
 
376
  with tab4:
377
  selected_farm = st.selectbox("انتخاب مزرعه", options=farm_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
378
+ historical_df = pd.DataFrame({'Week': range(1, 16), 'Height': [50 + i * 10 + np.random.normal(0, 5) for i in range(15)]})
379
+ rf_preds, lstm_preds = predict_growth_advanced(historical_df)
380
+ fig = go.Figure([go.Scatter(x=historical_df['Week'], y=historical_df['Height'], mode='lines+markers', name='داده‌های تاریخی'),
381
+ go.Scatter(x=range(16, 23), y=rf_preds, mode='lines+markers', name='پیش‌بینی RF', line=dict(dash='dash')),
382
+ go.Scatter(x=range(16, 23), y=lstm_preds, mode='lines+markers', name='پیش‌بینی LSTM', line=dict(dash='dot'))])
383
+ fig.update_layout(title=f'پیش‌بینی رشد مزرعه {selected_farm}', xaxis_title='هفته', yaxis_title='ارتفاع (cm)', font=dict(family='Vazirmatn'))
 
 
 
 
 
384
  st.plotly_chart(fig, use_container_width=True)
 
 
385
  clustered_df, centers = cluster_farms(farm_df)
386
+ st.plotly_chart(px.scatter(clustered_df, x='مساحت', y='سن', color='Cluster', title='گروه‌بندی مزارع', labels={'مساحت': 'مساحت (هکتار)', 'سن': 'سن (سال)'}), use_container_width=True)
 
387
  st.markdown('</div>', unsafe_allow_html=True)
388
 
389
+ # --- Reporting ---
390
  elif selected == "گزارش‌گیری":
391
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
392
  st.markdown("## گزارش‌گیری پیشرفته")
393
+ start_date, end_date = st.date_input("تاریخ شروع", value=datetime.now() - timedelta(days=30)), st.date_input("تاریخ پایان", value=datetime.now())
 
394
  report_type = st.selectbox("نوع گزارش", ["گزارش کلی", "گزارش رشد", "گزارش رطوبت", "گزارش مقایسه‌ای واریته‌ها"])
395
  if st.button("تولید گزارش"):
396
  with st.spinner('در حال تولید گزارش...'):
397
+ time.sleep(1) # Reduced for speed
398
  if report_type == "گزارش کلی":
399
+ st.markdown("### گزارش کلی")
400
  col1, col2 = st.columns(2)
401
+ with col1: st.plotly_chart(px.pie(values=farm_df['اداره'].value_counts().values, names=farm_df['اداره'].value_counts().index, title='توزیع مزارع بر اساس اداره'))
402
+ with col2: st.plotly_chart(px.line(x=range(1, 23), y=[100 + i * 5 + np.random.normal(0, 10) for i in range(22)], title='روند رشد کلی', labels={'x': 'هفته', 'y': 'ارتفاع (cm)'}))
 
 
 
 
 
 
403
  st.markdown('</div>', unsafe_allow_html=True)
404
 
405
+ # --- Settings ---
406
  elif selected == "تنظیمات":
407
  st.markdown('<div class="animate-load">', unsafe_allow_html=True)
408
+ st.markdown("## تنظیمات")
409
  tab1, tab2 = st.tabs(["تنظیمات کاربری", "تنظیمات سیستم"])
410
+ with tab1: st.text_input("نام کاربری", value="کاربر نمونه"), st.text_input("ایمیل", value="[email protected]")
411
+ with tab2: st.selectbox("زبان سیستم", ["فارسی", "English", "العربية"]), st.selectbox("فرمت تاریخ", ["YYYY/MM/DD", "DD/MM/YYYY", "MM/DD/YYYY"])
 
 
 
 
412
  st.markdown('</div>', unsafe_allow_html=True)
413
 
414
+ # --- Footer ---
415
  st.markdown("""
416
  <footer style="position: fixed; left: 0; bottom: 0; width: 100%; background-color: #1a8754; color: white; text-align: center; padding: 10px 0;">
417
+ <p>© سامانه هوشمند پایش مزارع نیشکر دهخدا | طراحی شده توسط تیم مطالعات کاربردی توسعه</p>
418
  </footer>
419
  """, unsafe_allow_html=True)
420
  st.markdown('</body>', unsafe_allow_html=True)