Esmaeilkianii commited on
Commit
5e425ea
·
verified ·
1 Parent(s): 6594a04

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +524 -607
app.py CHANGED
@@ -8,6 +8,13 @@ import calendar
8
  from PIL import Image
9
  import base64
10
  import io
 
 
 
 
 
 
 
11
  # تنظیمات صفحه
12
  st.set_page_config(
13
  page_title="سامانه پایش مزارع نیشکر دهخدا",
@@ -15,6 +22,7 @@ st.set_page_config(
15
  layout="wide",
16
  initial_sidebar_state="expanded"
17
  )
 
18
  # CSS برای فارسی‌سازی و بهبود ظاهر
19
  st.markdown("""
20
  <style>
@@ -129,8 +137,129 @@ st.markdown("""
129
  background-color: #1e6b45 !important;
130
  color: white !important;
131
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  </style>
133
  """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  # نمونه داده
135
  @st.cache_data
136
  def generate_sample_data():
@@ -237,18 +366,129 @@ def generate_sample_data():
237
  weekly_report_df = pd.DataFrame(weekly_report_data)
238
 
239
  return farms_df, heights_df, weekly_report_df
 
240
  # دریافت داده‌ها
241
  farms_df, heights_df, weekly_report_df = generate_sample_data()
 
242
  # ترکیب داده‌ها برای استفاده راحت‌تر
243
  report_with_details = pd.merge(weekly_report_df, farms_df, on='Farm_ID')
244
  latest_week = heights_df['Week'].max()
245
  latest_reports = report_with_details[report_with_details['Week'] == latest_week]
 
246
  # مدیریت وضعیت برنامه
247
  if 'selected_tab' not in st.session_state:
248
  st.session_state.selected_tab = "داشبورد"
249
 
250
  if 'selected_day' not in st.session_state:
251
  st.session_state.selected_day = "شنبه"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  # منوی کناری
253
  with st.sidebar:
254
  st.image("https://via.placeholder.com/150x150.png?text=لوگو", width=150)
@@ -278,6 +518,7 @@ with st.sidebar:
278
 
279
  st.markdown("---")
280
  st.info("نسخه آزمایشی 1.0")
 
281
  # بخش داشبورد
282
  if st.session_state.selected_tab == "داشبورد":
283
  st.title("داشبورد پایش مزارع نیشکر")
@@ -400,6 +641,58 @@ if st.session_state.selected_tab == "داشبورد":
400
 
401
  st.plotly_chart(fig, use_container_width=True)
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  # مزارع با بیشترین و کمترین رشد
404
  st.markdown("---")
405
  st.subheader("مزارع با بیشترین و کمترین رشد")
@@ -439,61 +732,7 @@ if st.session_state.selected_tab == "داشبورد":
439
  </p>
440
  </div>
441
  """, unsafe_allow_html=True)
442
-
443
- # نقشه مزارع
444
- st.markdown("---")
445
- st.subheader("نقشه وضعیت مزارع")
446
- st.markdown("(این نقشه به صورت نمادین نشان داده شده است. در نسخه عملیاتی با اطلاعات جغرافیایی دقیق پیاده‌سازی می‌شود.)")
447
-
448
- # ایجاد نقشه نمادین با plotly
449
- farm_status = latest_reports[['Farm_ID', 'Farm_Name', 'Administration_ID', 'Growth_Status']]
450
-
451
- # ایجاد مختصات فرضی برای مزارع
452
- np.random.seed(42) # برای تکرارپذیری
453
- x_coords = np.random.uniform(0, 10, len(farm_status))
454
- y_coords = np.random.uniform(0, 10, len(farm_status))
455
-
456
- farm_status['x'] = x_coords
457
- farm_status['y'] = y_coords
458
-
459
- # تعیین رنگ بر اساس وضعیت
460
- color_map = {
461
- 'خوب': '#00c853',
462
- 'متوسط': '#ffd600',
463
- 'ضعیف': '#ff3d00'
464
- }
465
-
466
- farm_status['color'] = farm_status['Growth_Status'].map(color_map)
467
-
468
- fig = go.Figure()
469
-
470
- for status in ['خوب', 'متوسط', 'ضعیف']:
471
- subset = farm_status[farm_status['Growth_Status'] == status]
472
-
473
- fig.add_trace(go.Scatter(
474
- x=subset['x'],
475
- y=subset['y'],
476
- mode='markers',
477
- marker=dict(
478
- size=15,
479
- color=color_map[status],
480
- line=dict(width=1, color='white')
481
- ),
482
- text=subset['Farm_Name'] + '<br>اداره ' + subset['Administration_ID'].astype(str),
483
- hoverinfo='text',
484
- name=status
485
- ))
486
-
487
- fig.update_layout(
488
- height=500,
489
- plot_bgcolor='rgba(240, 240, 240, 0.8)',
490
- font=dict(family="IRANSans"),
491
- showlegend=True,
492
- xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
493
- yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)
494
- )
495
-
496
- st.plotly_chart(fig, use_container_width=True)
497
  # بخش ورود اطلاعات
498
  elif st.session_state.selected_tab == "ورود اطلاعات":
499
  st.title("ورود اطلاعات روزانه")
@@ -511,6 +750,57 @@ elif st.session_state.selected_tab == "ورود اطلاعات":
511
 
512
  st.subheader(f"ورود داده‌های روز {selected_day} - اداره {selected_admin}")
513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  # ایجاد ستون‌های جدول
515
  cols = st.columns((3, 1, 1, 1, 1, 1, 1, 1, 1, 1))
516
  fields = ['مزرعه', 'ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'چاهک 1', 'چاهک 2', 'رطوبت غلاف', 'نیتروژن']
@@ -522,6 +812,7 @@ elif st.session_state.selected_tab == "ورود اطلاعات":
522
  # ایجاد حقول ورودی برای هر مزرعه
523
  farm_data = {}
524
 
 
525
  for _, farm in filtered_farms.iterrows():
526
  farm_id = farm['Farm_ID']
527
  farm_name = farm['Farm_Name']
@@ -534,63 +825,169 @@ elif st.session_state.selected_tab == "ورود اطلاعات":
534
 
535
  # ایستگاه‌های ارتفاع
536
  for i in range(1, 6):
537
- key = f"{farm_id}_station_{i}_{selected_day}"
538
- value = cols[i].number_input("", min_value=0, max_value=300, step=1, key=key, label_visibility="collapsed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  station_values.append(value)
 
 
 
 
 
540
 
541
  # چاهک‌های آب
 
542
  for i in range(1, 3):
543
- key = f"{farm_id}_groundwater_{i}_{selected_day}"
544
- value = cols[5+i].number_input("", min_value=0, max_value=300, step=1, key=key, label_visibility="collapsed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
 
546
  # رطوبت غلاف
547
- key = f"{farm_id}_moisture_{selected_day}"
548
- moisture = cols[7].number_input("", min_value=0, max_value=100, step=1, key=key, label_visibility="collapsed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
 
550
  # نیتروژن
551
- key = f"{farm_id}_nitrogen_{selected_day}"
552
- nitrogen = cols[8].number_input("", min_value=0, max_value=100, step=1, key=key, label_visibility="collapsed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
 
554
  # محاسبه میانگین ارتفاع
555
- avg_height = 0
556
- valid_stations = [s for s in station_values if s > 0]
557
- if valid_stations:
558
- avg_height = int(sum(valid_stations) / len(valid_stations))
559
 
560
- # نمایش میانگین
561
- cols[9].markdown(f"<div style='text-align: center; font-weight: bold;'>{avg_height if avg_height > 0 else '-'}</div>", unsafe_allow_html=True)
 
 
 
 
562
 
563
  # ذخیره داده‌ها برای ثبت نهایی
564
  farm_data[farm_id] = {
565
  'stations': station_values,
 
566
  'avg_height': avg_height,
567
  'moisture': moisture,
568
  'nitrogen': nitrogen
569
  }
570
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  # دکمه ذخیره‌سازی
572
  if st.button("ذخیره اطلاعات", key="save_daily_data"):
573
- st.success("اطلاعات با موفقیت ذخیره شد. در نسخه عملیاتی، این داده‌ها در پایگاه داده ذخیره می‌شوند.")
574
-
575
- # نمایش خلاصه داده‌های ثبت شده
576
- st.subheader("خلاصه داده‌های ثبت شده:")
577
-
578
- summary_data = []
579
- for farm_id, data in farm_data.items():
580
- if data['avg_height'] > 0: # فقط مزارعی که داده داشته‌اند
581
- farm_info = filtered_farms[filtered_farms['Farm_ID'] == farm_id].iloc[0]
582
- summary_data.append({
583
- 'مزرعه': farm_info['Farm_Name'],
584
- 'میانگین ارتفاع': data['avg_height'],
585
- 'رطوبت غلاف': data['moisture'],
586
- 'نیتروژن': data['nitrogen']
587
- })
588
-
589
- if summary_data:
590
- summary_df = pd.DataFrame(summary_data)
591
- st.dataframe(summary_df)
 
 
 
 
 
 
 
592
  else:
593
- st.info("هیچ داده‌ای برای نمایش وجود ندارد.")
 
594
  # بخش گزارش‌گیری
595
  elif st.session_state.selected_tab == "گزارش‌گیری":
596
  st.title("گزارش‌های هفتگی")
@@ -673,7 +1070,7 @@ elif st.session_state.selected_tab == "گزارش‌گیری":
673
 
674
  report_table = pd.DataFrame({
675
  'هفته': farm_reports['Week'],
676
- 'تاریخ': farm_reports['Regional_Average'].apply(lambda x: f"هفته {x}"),
677
  'ارتفاع': farm_reports['Average_Height'],
678
  'میانگین منطقه': farm_reports['Regional_Average'],
679
  'تغییرات': farm_reports['Growth_Change'],
@@ -689,6 +1086,20 @@ elif st.session_state.selected_tab == "گزارش‌گیری":
689
 
690
  st.markdown(report_table.to_html(escape=False, index=False), unsafe_allow_html=True)
691
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  # توصیه‌های کارشناسی
693
  st.markdown("---")
694
  st.subheader("توصیه‌های کارشناسی")
@@ -908,6 +1319,27 @@ elif st.session_state.selected_tab == "گزارش‌گیری":
908
 
909
  st.dataframe(weak_farms_table.sort_values('ارتفاع فعلی'), use_container_width=True)
910
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
  # تحلیل و پیشنهادات
912
  st.markdown("---")
913
  st.subheader("تحلیل عوامل مؤثر")
@@ -958,6 +1390,7 @@ elif st.session_state.selected_tab == "گزارش‌گیری":
958
  - بررسی آفات و بیماری‌های احتمالی
959
  - برنامه‌ریزی برای جایگزینی مزارع با سن بالا
960
  """)
 
961
  # بخش تحلیل داده‌ها
962
  elif st.session_state.selected_tab == "تحلیل داده‌ها":
963
  st.title("تحلیل پیشرفته داده‌ها")
@@ -1182,520 +1615,4 @@ elif st.session_state.selected_tab == "تحلیل داده‌ها":
1182
 
1183
  st.plotly_chart(fig, use_container_width=True)
1184
 
1185
- # تحلیل توزیع وضعیت‌ها بر اساس سن کشت
1186
- age_status = report_with_details[report_with_details['Week'] == latest_week].groupby(['Crop_Age', 'Growth_Status']).size().reset_index(name='Count')
1187
-
1188
- fig = px.bar(
1189
- age_status,
1190
- x='Crop_Age',
1191
- y='Count',
1192
- color='Growth_Status',
1193
- color_discrete_map={'خوب': '#00c853', 'متوسط': '#ffd600', 'ضعیف': '#ff3d00'},
1194
- labels={'Crop_Age': 'سن کشت (سال)', 'Count': 'تعداد مزارع', 'Growth_Status': 'وضعیت'}
1195
- )
1196
-
1197
- fig.update_layout(
1198
- height=400,
1199
- xaxis_title="سن کشت (سال)",
1200
- yaxis_title="تعداد مزارع",
1201
- font=dict(family="IRANSans"),
1202
- plot_bgcolor='rgba(0,0,0,0)',
1203
- barmode='stack'
1204
- )
1205
-
1206
- st.plotly_chart(fig, use_container_width=True)
1207
-
1208
- # تحلیل رابطه بین مساحت و رشد
1209
- st.markdown("#### تأثیر مساحت بر رشد")
1210
-
1211
- area_height = report_with_details[report_with_details['Week'] == latest_week]
1212
-
1213
- fig = px.scatter(
1214
- area_height,
1215
- x='Area',
1216
- y='Average_Height',
1217
- color='Growth_Status',
1218
- size='Growth_Change',
1219
- color_discrete_map={'خوب': '#00c853', 'متوسط': '#ffd600', 'ضعیف': '#ff3d00'},
1220
- labels={'Area': 'مساحت (هکتار)', 'Average_Height': 'میانگین ارتفاع (سانتی‌متر)', 'Growth_Status': 'وضعیت', 'Growth_Change': 'تغییر رشد'}
1221
- )
1222
-
1223
- fig.update_layout(
1224
- height=500,
1225
- xaxis_title="مساحت (هکتار)",
1226
- yaxis_title="میانگین ارتفاع (سانتی‌متر)",
1227
- font=dict(family="IRANSans"),
1228
- plot_bgcolor='rgba(0,0,0,0)'
1229
- )
1230
-
1231
- st.plotly_chart(fig, use_container_width=True)
1232
-
1233
- # تحلیل اهمیت عوامل مختلف
1234
- st.markdown("#### مقایسه اهمیت عوامل مختلف")
1235
-
1236
- st.info("این بخش به صورت نمادین ارائه شده است و در نسخه عملیاتی با تحلیل واقعی و الگوریتم‌های یادگیری ماشین تکمیل می‌شود.")
1237
-
1238
- factors = ['سن کشت', 'واریته', 'مساحت', 'اداره/کانال']
1239
- importance = [75, 60, 30, 45]
1240
-
1241
- fig = px.bar(
1242
- x=factors,
1243
- y=importance,
1244
- labels={'x': 'عامل', 'y': 'میزان اهمیت (درصد)'},
1245
- color=importance,
1246
- color_continuous_scale=[(0, "#2196f3"), (1, "#1e6b45")]
1247
- )
1248
-
1249
- fig.update_layout(
1250
- height=400,
1251
- xaxis_title="عامل مؤثر",
1252
- yaxis_title="میزان اهمیت (درصد)",
1253
- font=dict(family="IRANSans"),
1254
- plot_bgcolor='rgba(0,0,0,0)'
1255
- )
1256
-
1257
- st.plotly_chart(fig, use_container_width=True)
1258
-
1259
- elif analysis_type == "پیش‌بینی روند آینده":
1260
- st.subheader("پیش‌بینی روند رشد آینده")
1261
-
1262
- st.info("این بخش به صورت نمادین ارائه شده است و در نسخه عملیاتی با الگوریتم‌های پیش‌بینی واقعی مانند ARIMA، رگرسیون یا شبکه‌های عصبی پیاده‌سازی می‌شود.")
1263
-
1264
- # روند کلی ارتفاع
1265
- weekly_avg = report_with_details.groupby('Week')['Average_Height'].mean().reset_index()
1266
- weekly_avg['Average_Height'] = weekly_avg['Average_Height'].astype(int)
1267
-
1268
- # پیش‌بینی ساده خطی
1269
- weeks = weekly_avg['Week'].values
1270
- heights = weekly_avg['Average_Height'].values
1271
-
1272
- # پیش‌بینی ساده 3 هفته آینده
1273
- future_weeks = np.array([latest_week + 1, latest_week + 2, latest_week + 3])
1274
-
1275
- # تابع ساده برای ایجاد پیش‌بینی نمادین
1276
- def simple_predict(weeks, heights, future_weeks):
1277
- if len(weeks) >= 2:
1278
- slope = (heights[-1] - heights[-2]) / (weeks[-1] - weeks[-2])
1279
- last_height = heights[-1]
1280
- last_week = weeks[-1]
1281
-
1282
- predictions = [last_height + slope * (w - last_week) for w in future_weeks]
1283
-
1284
- # اضافه کردن کمی نویز
1285
- predictions = [p + np.random.randint(-3, 4) for p in predictions]
1286
-
1287
- return predictions
1288
- return [heights[-1]] * len(future_weeks)
1289
-
1290
- future_heights = simple_predict(weeks, heights, future_weeks)
1291
-
1292
- # ترکیب داده‌های واقعی و پیش‌بینی
1293
- all_weeks = np.concatenate((weeks, future_weeks))
1294
- all_heights = np.concatenate((heights, future_heights))
1295
-
1296
- prediction_df = pd.DataFrame({
1297
- 'Week': all_weeks,
1298
- 'Average_Height': all_heights,
1299
- 'Type': ['واقعی' if w <= latest_week else 'پیش‌بینی' for w in all_weeks]
1300
- })
1301
-
1302
- # نمودار پیش‌بینی
1303
- fig = px.line(
1304
- prediction_df,
1305
- x='Week',
1306
- y='Average_Height',
1307
- color='Type',
1308
- markers=True,
1309
- color_discrete_map={'واقعی': '#1e6b45', 'پیش‌بینی': '#2196f3'},
1310
- labels={'Week': 'هفته', 'Average_Height': 'میانگین ارتفاع (سانتی‌متر)', 'Type': 'نوع داده'}
1311
- )
1312
-
1313
- fig.update_traces(line=dict(width=3), marker=dict(size=10))
1314
-
1315
- fig.add_vline(x=latest_week, line_dash="dash", line_color="gray")
1316
-
1317
- fig.update_layout(
1318
- height=500,
1319
- xaxis_title="هفته",
1320
- yaxis_title="میانگین ارتفاع (سانتی‌متر)",
1321
- font=dict(family="IRANSans"),
1322
- plot_bgcolor='rgba(0,0,0,0)'
1323
- )
1324
-
1325
- st.plotly_chart(fig, use_container_width=True)
1326
-
1327
- # پیش‌بینی به تفکیک اداره
1328
- st.subheader("پیش‌بینی به تفکیک اداره")
1329
-
1330
- admin_weekly_avg = report_with_details.groupby(['Administration_ID', 'Administration_Name', 'Week'])['Average_Height'].mean().reset_index()
1331
- admin_weekly_avg['Average_Height'] = admin_weekly_avg['Average_Height'].astype(int)
1332
-
1333
- admins = admin_weekly_avg['Administration_Name'].unique()
1334
-
1335
- admin_predictions = []
1336
-
1337
- for admin in admins:
1338
- admin_data = admin_weekly_avg[admin_weekly_avg['Administration_Name'] == admin].sort_values('Week')
1339
-
1340
- weeks = admin_data['Week'].values
1341
- heights = admin_data['Average_Height'].values
1342
-
1343
- future_heights = simple_predict(weeks, heights, future_weeks)
1344
-
1345
- for w, h in zip(future_weeks, future_heights):
1346
- admin_predictions.append({
1347
- 'Week': w,
1348
- 'Administration_Name': admin,
1349
- 'Average_Height': h,
1350
- 'Type': 'پیش‌بینی'
1351
- })
1352
-
1353
- admin_prediction_df = pd.DataFrame(admin_predictions)
1354
-
1355
- # ترکیب با داده‌های واقعی
1356
- admin_weekly_avg['Type'] = 'واقعی'
1357
- combined_admin_df = pd.concat([admin_weekly_avg, admin_prediction_df])
1358
-
1359
- # نمودار پیش‌بینی به تفکیک اداره
1360
- fig = px.line(
1361
- combined_admin_df,
1362
- x='Week',
1363
- y='Average_Height',
1364
- color='Administration_Name',
1365
- line_dash='Type',
1366
- markers=True,
1367
- labels={'Week': 'هفته', 'Average_Height': 'میانگین ارتفاع (سانتی‌متر)', 'Administration_Name': 'اداره', 'Type': 'نوع داده'}
1368
- )
1369
-
1370
- fig.update_traces(line=dict(width=2), marker=dict(size=8))
1371
-
1372
- fig.add_vline(x=latest_week, line_dash="dash", line_color="gray")
1373
-
1374
- fig.update_layout(
1375
- height=500,
1376
- xaxis_title="هفته",
1377
- yaxis_title="میانگین ارتفاع (سانتی‌متر)",
1378
- font=dict(family="IRANSans"),
1379
- plot_bgcolor='rgba(0,0,0,0)'
1380
- )
1381
-
1382
- st.plotly_chart(fig, use_container_width=True)
1383
-
1384
- # خلاصه پیش‌بینی‌ها
1385
- st.subheader("خلاصه پیش‌بینی‌ها برای هفته آینده")
1386
-
1387
- next_week_prediction = admin_prediction_df[admin_prediction_df['Week'] == latest_week + 1][['Administration_Name', 'Average_Height']]
1388
- next_week_prediction.columns = ['اداره', 'پیش‌بینی ارتفاع (سانتی‌متر)']
1389
- next_week_prediction = next_week_prediction.sort_values('پیش‌بینی ارتفاع (سانتی‌متر)', ascending=False)
1390
-
1391
- st.table(next_week_prediction)
1392
-
1393
- elif analysis_type == "مقایسه بخش‌های تولیدی":
1394
- st.subheader("مقایسه بخش‌های تولیدی")
1395
-
1396
- # داده‌های بخش‌های تولیدی
1397
- section_data = report_with_details.copy()
1398
- section_data['Production_Section'] = section_data['Administration_ID'].apply(lambda x: 1 if x <= 2 else 2)
1399
- section_data['Production_Section_Name'] = section_data['Production_Section'].apply(lambda x: f'بخش {x}')
1400
-
1401
- # میانگین ارتفاع به تفکیک بخش و هفته
1402
- section_weekly_avg = section_data.groupby(['Production_Section_Name', 'Week'])['Average_Height'].mean().reset_index()
1403
- section_weekly_avg['Average_Height'] = section_weekly_avg['Average_Height'].astype(int)
1404
-
1405
- # نمودار مقایسه‌ای
1406
- fig = px.line(
1407
- section_weekly_avg,
1408
- x='Week',
1409
- y='Average_Height',
1410
- color='Production_Section_Name',
1411
- markers=True,
1412
- line_shape='spline',
1413
- labels={'Week': 'هفته', 'Average_Height': 'میانگین ارتفاع (سانتی‌متر)', 'Production_Section_Name': 'بخش تولیدی'}
1414
- )
1415
-
1416
- fig.update_traces(line=dict(width=3), marker=dict(size=10))
1417
-
1418
- fig.update_layout(
1419
- height=500,
1420
- xaxis_title="هفته",
1421
- yaxis_title="میانگین ارتفاع (سانتی‌متر)",
1422
- font=dict(family="IRANSans"),
1423
- plot_bgcolor='rgba(0,0,0,0)'
1424
- )
1425
-
1426
- st.plotly_chart(fig, use_container_width=True)
1427
-
1428
- # مقایسه تغییرات رشد
1429
- section_growth = section_data.groupby(['Production_Section_Name', 'Week'])['Growth_Change'].mean().reset_index()
1430
- section_growth['Growth_Change'] = section_growth['Growth_Change'].round(2)
1431
-
1432
- # نمودار تغییرات
1433
- fig = px.bar(
1434
- section_growth,
1435
- x='Week',
1436
- y='Growth_Change',
1437
- color='Production_Section_Name',
1438
- barmode='group',
1439
- labels={'Week': 'هفته', 'Growth_Change': 'میانگین تغییر رشد (سانتی‌متر)', 'Production_Section_Name': 'بخش تولیدی'}
1440
- )
1441
-
1442
- fig.update_layout(
1443
- height=400,
1444
- xaxis_title="هفته",
1445
- yaxis_title="میانگین تغییر رشد (سانتی‌متر)",
1446
- font=dict(family="IRANSans"),
1447
- plot_bgcolor='rgba(0,0,0,0)'
1448
- )
1449
-
1450
- st.plotly_chart(fig, use_container_width=True)
1451
-
1452
- # مقایسه توزیع وضعیت‌ها
1453
- section_status = section_data[section_data['Week'] == latest_week].groupby(['Production_Section_Name', 'Growth_Status']).size().reset_index(name='Count')
1454
-
1455
- # محاسبه درصدها
1456
- section_total = section_status.groupby('Production_Section_Name')['Count'].sum().reset_index()
1457
- section_status = pd.merge(section_status, section_total, on='Production_Section_Name', suffixes=('', '_Total'))
1458
- section_status['Percent'] = (section_status['Count'] / section_status['Count_Total'] * 100).round(1)
1459
-
1460
- # نمودار توزیع
1461
- fig = px.bar(
1462
- section_status,
1463
- x='Production_Section_Name',
1464
- y='Percent',
1465
- color='Growth_Status',
1466
- color_discrete_map={'خوب': '#00c853', 'متوسط': '#ffd600', 'ضعیف': '#ff3d00'},
1467
- labels={'Production_Section_Name': 'بخش تولیدی', 'Percent': 'درصد', 'Growth_Status': 'وضعیت'},
1468
- barmode='stack'
1469
- )
1470
-
1471
- fig.update_layout(
1472
- height=400,
1473
- xaxis_title="بخش تولیدی",
1474
- yaxis_title="درصد",
1475
- font=dict(family="IRANSans"),
1476
- plot_bgcolor='rgba(0,0,0,0)'
1477
- )
1478
-
1479
- st.plotly_chart(fig, use_container_width=True)
1480
-
1481
- # مقایسه مزارع برتر هر بخش
1482
- st.subheader("مزارع برتر هر بخش")
1483
-
1484
- col1, col2 = st.columns(2)
1485
-
1486
- for section_id, section_col in [(1, col1), (2, col2)]:
1487
- with section_col:
1488
- st.markdown(f"#### بخش {section_id}")
1489
-
1490
- top_farms = section_data[(section_data['Week'] == latest_week) & (section_data['Production_Section'] == section_id)].nlargest(5, 'Average_Height')
1491
-
1492
- for _, farm in top_farms.iterrows():
1493
- st.markdown(f"""
1494
- <div style="border: 1px solid #eee; padding: 10px; border-radius: 5px; margin-bottom: 10px;">
1495
- <h4 style="margin: 0;">{farm['Farm_Name']}</h4>
1496
- <p style="margin: 5px 0;">ارتفاع: {farm['Average_Height']} سانتی‌متر</p>
1497
- <p style="margin: 0;">اداره {farm['Administration_ID']} - {farm['Channel_ID']}</p>
1498
- </div>
1499
- """, unsafe_allow_html=True)
1500
- # بخش تنظیمات
1501
- elif st.session_state.selected_tab == "تنظیمات":
1502
- st.title("تنظیمات سیستم")
1503
-
1504
- # زبانه‌های تنظیمات
1505
- settings_tab = st.selectbox(
1506
- "بخش تنظیمات",
1507
- ["تنظیمات عمومی", "مدیریت کاربران", "معیارهای ارزیابی", "پشتیبان‌گیری و بازیابی"]
1508
- )
1509
-
1510
- st.markdown("---")
1511
-
1512
- if settings_tab == "تنظیمات عمومی":
1513
- st.subheader("تنظیمات عمومی برنامه")
1514
-
1515
- col1, col2 = st.columns(2)
1516
-
1517
- with col1:
1518
- st.markdown("#### تنظیمات نمایش")
1519
-
1520
- theme = st.selectbox("تم برنامه", ["روشن", "تیره", "خودکار"])
1521
- date_format = st.selectbox("قالب نمایش تاریخ", ["شمسی", "میلادی"])
1522
- language = st.selectbox("زبان", ["فارسی", "انگلیسی"])
1523
-
1524
- st.checkbox("نمایش راهنمای کاربری در شروع برنامه", value=True)
1525
- st.checkbox("فعال‌سازی نمایش هشدارها", value=True)
1526
-
1527
- with col2:
1528
- st.markdown("#### تنظیمات گزارش‌گیری")
1529
-
1530
- default_admin = st.selectbox("اداره پیش‌فرض", [f"اداره {i}" for i in range(1, 5)])
1531
- rows_per_page = st.slider("تعداد ردیف در هر صفحه", 10, 100, 25, 5)
1532
-
1533
- st.checkbox("فعال‌سازی گزارش‌های خودکار هفتگی", value=True)
1534
- st.checkbox("ارسال گزارش از طریق ایمیل", value=False)
1535
-
1536
- report_email = st.text_input("ایمیل دریافت گزارش‌ها")
1537
-
1538
- st.markdown("---")
1539
-
1540
- col1, col2 = st.columns(2)
1541
-
1542
- with col1:
1543
- st.markdown("#### تنظیمات پیشرفته")
1544
-
1545
- st.number_input("حداکثر روزهای نمایش تاریخچه", value=60, min_value=7, max_value=365)
1546
- st.number_input("فاصله به‌روزرسانی خودکار (دقیقه)", value=30, min_value=5, max_value=120)
1547
-
1548
- if st.button("ذخیره تنظیمات"):
1549
- st.success("تنظیمات با موفقیت ذخیره شد.")
1550
-
1551
- elif settings_tab == "مدیریت کاربران":
1552
- st.subheader("مدیریت کاربران سیستم")
1553
-
1554
- # نمونه کاربران
1555
- users = [
1556
- {"id": 1, "username": "admin", "name": "مدیر سیستم", "role": "مدیر", "active": True},
1557
- {"id": 2, "username": "supervisor1", "name": "ناظر اداره 1", "role": "ناظر", "active": True},
1558
- {"id": 3, "username": "supervisor2", "name": "ناظر اداره 2", "role": "ناظر", "active": True},
1559
- {"id": 4, "username": "operator1", "name": "اپراتور 1", "role": "اپراتور", "active": True},
1560
- {"id": 5, "username": "operator2", "name": "اپراتور 2", "role": "اپراتور", "active": False}
1561
- ]
1562
-
1563
- users_df = pd.DataFrame(users)
1564
-
1565
- # فیلتر کاربران
1566
- role_filter = st.selectbox("نمایش بر اساس نقش", ["همه", "مدیر", "ناظر", "اپراتور"])
1567
-
1568
- if role_filter != "همه":
1569
- filtered_users = users_df[users_df["role"] == role_filter]
1570
- else:
1571
- filtered_users = users_df
1572
-
1573
- # نمایش کاربران
1574
- for _, user in filtered_users.iterrows():
1575
- col1, col2, col3 = st.columns([3, 2, 1])
1576
-
1577
- with col1:
1578
- st.write(f"**{user['name']}** ({user['username']})")
1579
-
1580
- with col2:
1581
- st.write(f"نقش: {user['role']}")
1582
-
1583
- with col3:
1584
- status = "فعال" if user["active"] else "غیرفعال"
1585
- status_color = "green" if user["active"] else "red"
1586
- st.markdown(f"<span style='color: {status_color};'>{status}</span>", unsafe_allow_html=True)
1587
-
1588
- st.markdown("---")
1589
-
1590
- # افزودن کاربر جدید
1591
- st.subheader("افزودن کاربر جدید")
1592
-
1593
- col1, col2 = st.columns(2)
1594
-
1595
- with col1:
1596
- new_username = st.text_input("نام کاربری")
1597
- new_name = st.text_input("نام و نام خانوادگی")
1598
-
1599
- with col2:
1600
- new_role = st.selectbox("نقش", ["مدیر", "ناظر", "اپراتور"])
1601
- new_password = st.text_input("رمز عبور", type="password")
1602
-
1603
- if st.button("افزودن کاربر"):
1604
- if new_username and new_name and new_password:
1605
- st.success(f"کاربر {new_name} با موفقیت اضافه شد.")
1606
- else:
1607
- st.error("لطفاً تمام فیلدها را پر کنید.")
1608
-
1609
- elif settings_tab == "معیارهای ارزیابی":
1610
- st.subheader("تنظیم معیارهای ارزیابی")
1611
-
1612
- # معیارهای ارتفاع
1613
- st.markdown("#### معیارهای ارتفاع")
1614
-
1615
- col1, col2 = st.columns(2)
1616
-
1617
- with col1:
1618
- st.number_input("حداقل ارتفاع مطلوب (سانتی‌متر)", value=180, step=5)
1619
- st.number_input("حداقل تغییر رشد هفتگی مطلوب (سانتی‌متر)", value=5, step=1)
1620
-
1621
- with col2:
1622
- st.number_input("ارتفاع بحرانی (سانتی‌متر)", value=150, step=5)
1623
- st.number_input("تغییر رشد هفتگی بحرانی (سانتی‌متر)", value=0, step=1)
1624
-
1625
- # معیارهای شرایط محیطی
1626
- st.markdown("#### معیارهای شرایط محیطی")
1627
-
1628
- col1, col2 = st.columns(2)
1629
-
1630
- with col1:
1631
- st.number_input("حداقل رطوبت غلاف مطلوب (%)", value=70, step=5)
1632
- st.number_input("حداکثر عمق آب زیرزمینی مطلوب (سانتی‌متر)", value=80, step=5)
1633
-
1634
- with col2:
1635
- st.number_input("حداقل نیتروژن مطلوب (%)", value=30, step=5)
1636
-
1637
- # معیارهای مقایسه‌ای
1638
- st.markdown("#### معیارهای مقایسه‌ای")
1639
-
1640
- comparison_method = st.radio(
1641
- "روش مقایسه برای تعیین وضعیت",
1642
- ["مقایسه با میانگین کل", "مقایسه با میانگین اداره", "مقایسه با مقادیر مطلق"]
1643
- )
1644
-
1645
- if comparison_method == "مقایسه با میانگین کل":
1646
- st.slider("آستانه انحراف از میانگین (سانتی‌متر)", 0, 20, 5)
1647
- elif comparison_method == "مقایسه با میانگین اداره":
1648
- st.slider("آستانه انحراف از میانگین اداره (سانتی‌متر)", 0, 20, 5)
1649
-
1650
- if st.button("ذخیره معیارها"):
1651
- st.success("معیارهای ارزیابی با موفقیت ذخیره شد.")
1652
-
1653
- elif settings_tab == "پشتیبان‌گیری و بازیابی":
1654
- st.subheader("پشتیبان‌گیری و بازیابی داده‌ها")
1655
-
1656
- # پشتیبان‌گیری
1657
- st.markdown("#### پشتیبان‌گیری")
1658
-
1659
- backup_type = st.radio("نوع پشتیبان‌گیری", ["پشتیبان کامل", "فقط داده‌های ارتفاع", "فقط تنظیمات"])
1660
-
1661
- col1, col2 = st.columns(2)
1662
-
1663
- with col1:
1664
- st.checkbox("فشرده‌سازی فایل پشتیبان", value=True)
1665
-
1666
- with col2:
1667
- st.checkbox("پشتیبان‌گیری خودکار هفتگی", value=True)
1668
-
1669
- if st.button("ایجاد فایل پشتیبان"):
1670
- # ایجاد فایل CSV پشتیبان
1671
- if backup_type == "پشتیبان کامل" or backup_type == "فقط داده‌های ارتفاع":
1672
- buffer = io.StringIO()
1673
- heights_df.to_csv(buffer, index=False)
1674
- buffer.seek(0)
1675
-
1676
- # تبدیل به دانلود
1677
- st.download_button(
1678
- label="دانلود فایل پشتیبان",
1679
- data=buffer,
1680
- file_name="height_data_backup.csv",
1681
- mime="text/csv"
1682
- )
1683
- else:
1684
- st.success("فایل پشتیبان تنظیمات با موفقیت ایجاد شد.")
1685
-
1686
- # بازیابی
1687
- st.markdown("---")
1688
- st.markdown("#### بازیابی داده‌ها")
1689
-
1690
- upload_file = st.file_uploader("انتخاب فایل پشتیبان", type=["csv", "zip"])
1691
-
1692
- if upload_file is not None:
1693
- if st.button("بازیابی داده‌ها"):
1694
- st.success("داده‌ها با موفقیت بازیابی شد.")
1695
- st.warning("توجه: این عملیات در نسخه عملیاتی، داده‌های فعلی را با داده‌های پشتیبان جایگزین می‌کند.")
1696
- # نمایش مکان‌نما
1697
- st.markdown("""
1698
- <div style="position: fixed; bottom: 20px; right: 20px; background-color: #1e6b45; color: white; padding: 10px 15px; border-radius: 50px; font-size: 14px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); z-index: 1000;">
1699
- آخرین به‌روزرسانی: همین حالا
1700
- </div>
1701
- """, unsafe_allow_html=True)
 
8
  from PIL import Image
9
  import base64
10
  import io
11
+ import os
12
+ import ee
13
+ import json
14
+ import folium
15
+ from streamlit_folium import folium_static
16
+ import time
17
+
18
  # تنظیمات صفحه
19
  st.set_page_config(
20
  page_title="سامانه پایش مزارع نیشکر دهخدا",
 
22
  layout="wide",
23
  initial_sidebar_state="expanded"
24
  )
25
+
26
  # CSS برای فارسی‌سازی و بهبود ظاهر
27
  st.markdown("""
28
  <style>
 
137
  background-color: #1e6b45 !important;
138
  color: white !important;
139
  }
140
+
141
+ /* تنظیم جهت نمایش اعداد */
142
+ input[type="number"] {
143
+ direction: ltr !important;
144
+ text-align: left !important;
145
+ }
146
+
147
+ .dataframe {
148
+ direction: ltr !important;
149
+ }
150
+
151
+ .dataframe th, .dataframe td {
152
+ text-align: left !important;
153
+ }
154
+
155
+ /* استایل مخصوص برای نقشه */
156
+ .map-container {
157
+ height: 500px;
158
+ width: 100%;
159
+ border-radius: 10px;
160
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
161
+ overflow: hidden;
162
+ }
163
+
164
+ /* استایل برای لیبل‌های داینامیک */
165
+ .dynamic-label {
166
+ font-weight: bold;
167
+ color: #1e6b45;
168
+ transition: all 0.3s ease;
169
+ }
170
+
171
+ .dynamic-value {
172
+ font-size: 1.2em;
173
+ font-weight: bold;
174
+ transition: all 0.3s ease;
175
+ }
176
+
177
+ .highlight-update {
178
+ animation: pulse 1.5s ease;
179
+ }
180
+
181
+ @keyframes pulse {
182
+ 0% { background-color: transparent; }
183
+ 50% { background-color: rgba(46, 204, 113, 0.3); }
184
+ 100% { background-color: transparent; }
185
+ }
186
  </style>
187
  """, unsafe_allow_html=True)
188
+
189
+ # افزودن جاوااسکریپت برای آپدیت داینامیک
190
+ st.markdown("""
191
+ <script>
192
+ // تابع برای بروزرسانی داینامیک مقادیر
193
+ function updateValues(inputId, avgId, value) {
194
+ const avgElement = document.getElementById(avgId);
195
+ if (avgElement) {
196
+ avgElement.textContent = value;
197
+ avgElement.classList.add('highlight-update');
198
+
199
+ // حذف کلاس هایلایت بعد از 1.5 ثانیه
200
+ setTimeout(() => {
201
+ avgElement.classList.remove('highlight-update');
202
+ }, 1500);
203
+ }
204
+ }
205
+
206
+ // اضافه کردن event listener به همه ورودی‌های عددی
207
+ document.addEventListener('DOMContentLoaded', function() {
208
+ const numInputs = document.querySelectorAll('input[type="number"]');
209
+ numInputs.forEach(input => {
210
+ input.addEventListener('input', function() {
211
+ // اینجا می‌توانیم یک پیام به Streamlit بفرستیم
212
+ window.parent.postMessage({
213
+ type: 'inputChange',
214
+ inputId: input.id,
215
+ value: input.value
216
+ }, '*');
217
+ });
218
+ });
219
+ });
220
+ </script>
221
+ """, unsafe_allow_html=True)
222
+
223
+ # تنظیم Earth Engine
224
+ @st.cache_resource
225
+ def initialize_earth_engine():
226
+ try:
227
+ # مسیر فایل اعتبارنامه
228
+ credentials_path = "ee-esmaeilkiani13877-cfdea6eaf411.json"
229
+
230
+ # بررسی وجود فایل
231
+ if not os.path.exists(credentials_path):
232
+ # اگر فایل وجود ندارد، آن را ایجاد می‌کنیم
233
+ credentials_dict = {
234
+ "type": "service_account",
235
+ "project_id": "ee-esmaeilkiani13877",
236
+ "private_key_id": "cfdea6eaf411",
237
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDJ/yKp8y/jQBPM\n4YVhfMJKTFMgpKqnHjFm/HYxs9JZ3TbYGcIm1B+p61mFzc0YeZ80K7it6C9I+PKe\n8n9HnVjzTKHkLIl38OKIv8sxB2Z8Imi/aZl1Xq+ilcJ7gXU2NwvfCPOLjqYZrYKX\ntV4SJAJ8BvnPi9NMlMIwYeI4q+S8e/2XtYs/91uFQVQcDzIBZHKj8QIzfRTUPkWS\nR8C+QyGXcWJ+Z0qbkH5re+h1+qR55XVkmAj3R7DHx+Z0D6KmPGCKlW+qp07KjDcK\nB0JfG1IZvbXlJD7HJ3A9mM8LAZotGI/1sFMIFSvUopV2QC6nffn8GYxeIGIiStO0\nUwCvxEtnAgMBAAECggEAGAHplcHENLgWfxNb0goGCFrUplS+Ub4AOtCyWIwIzz+4\nEYnwPSNXUQdWuG6JS9EqIzrD0UY/yVQNtPCK6QrkbNpXgyUPdCMvQToLBi+XrS0M\nq2/yIu1mzDL8J5khG1zRjdtHOfLm3xgEcJ6h4c5bjGiKlK5F9tfgYRBEBnTG3m9X\nvSu/j3wlR01XEE8rYOUbzlq0FLHLh+XEPPcsHqmUKY6dGP/8LrHPL+QBwzZ9RcFU\nOHhc0NBaxkRb4vF8UEjA9PpYEMBg90UfaF9Jnkf+A3CyTNkeKvmIcHgwIR3cyM/s\nBCu8p9MpaBcKKT5gQJvk2A4WOrJz5Ag1tYgp7OeOQQKBgQD5mwxh3k1PcZ9Ugubn\ndIrJ3aLRpdhbQYoCyzQqffNp9OX6NqNBu4dDMe4+QFBM6XTYNyj4vqLQk7NQ8kh5\nP/xnW4L3PBhPzkPIxP0wnqwTbgRREyCfVpWy6JHcUQP508VBaXQr5P6Tk/UDpCp7\nMx2FsQCeQhNPGVQmPzwYdTrdFwKBgQDPQb3+1DCRwY5NjKf6/LgIcO9LHvZ1mM0u\nK05iGDXd7XA3gQBQw+e5OXUy8Z75Ql0DD0evXws9liVYDkrxsyLJ1tBg+hmxbYnK\nO8anx4FyRCMO3evbDD/aJhZxiUbFNnwX3tvPOcDsWFQaAzWM2NAF9JUiK6ixSKSi\nzp/qbfJxIQKBgFx0QyFfEhPqxieyPezLJEXhY9ernxVMzxIEX7CnDQvj6nR2j4v0\nJw/l+rfji1ZvOV0N5vhlQylV1jdAv6XEVYGYFTZqW/d1g+1wLTdT4jxDWkVKFJXK\nodoeJvoyAgYTc8Khh3Pjp7BOFRmqmgzQlRwxIOjVDYwkQsIODhvbhnF3AoGAO3i3\n3QZCgzsVmNUZXUXgjpZ/PSfHAQ/zVwTAlTKCqzAmkCd93OBGjVk0AEBI5YK1D/Vx\nwK1Qj8USlD+UWFH+XmJlKzM0HKsrU1XT9+ssK/qTJImTiUzEzHSqqOgBSCJMHmYZ\nHnOBLfM1+jDxhjMRQKy5JFZyWXUNKNLZ9M9G6KECgYAsbrZKYCC6PaeN4ETP6zw7\nCB42mPH5pJDlzUy1b5CuYXJS9QbpyHsqTUhiWnMm7jJ7TOWxXiGK8UE3+aJFCygB\nxXHxLmqDc5yLqVyatk7xbpOLweUNbiPeSR8HBQjPxjqHupA3WRg1j7gNdxOcq/YH\n5r1SuNjhWf8sJJrZdK/V5A==\n-----END PRIVATE KEY-----\n",
238
+ "client_email": "dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com",
239
+ "client_id": "112129892578518408444",
240
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
241
+ "token_uri": "https://oauth2.googleapis.com/token",
242
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
243
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com"
244
+ }
245
+ with open(credentials_path, 'w') as f:
246
+ json.dump(credentials_dict, f)
247
+
248
+ # خواندن اعتبارنامه
249
+ credentials = ee.ServiceAccountCredentials(
250
+ "dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com",
251
+ credentials_path
252
+ )
253
+
254
+ ee.Initialize(credentials)
255
+ return True
256
+ except Exception as e:
257
+ st.error(f"خطا در اتصال به Earth Engine: {e}")
258
+ return False
259
+
260
+ # تلاش برای اتصال به Earth Engine
261
+ earth_engine_initialized = initialize_earth_engine()
262
+
263
  # نمونه داده
264
  @st.cache_data
265
  def generate_sample_data():
 
366
  weekly_report_df = pd.DataFrame(weekly_report_data)
367
 
368
  return farms_df, heights_df, weekly_report_df
369
+
370
  # دریافت داده‌ها
371
  farms_df, heights_df, weekly_report_df = generate_sample_data()
372
+
373
  # ترکیب داده‌ها برای استفاده راحت‌تر
374
  report_with_details = pd.merge(weekly_report_df, farms_df, on='Farm_ID')
375
  latest_week = heights_df['Week'].max()
376
  latest_reports = report_with_details[report_with_details['Week'] == latest_week]
377
+
378
  # مدیریت وضعیت برنامه
379
  if 'selected_tab' not in st.session_state:
380
  st.session_state.selected_tab = "داشبورد"
381
 
382
  if 'selected_day' not in st.session_state:
383
  st.session_state.selected_day = "شنبه"
384
+
385
+ # برای ذخیره داده‌های ورودی
386
+ if 'farm_data' not in st.session_state:
387
+ st.session_state.farm_data = {}
388
+
389
+ # برای ذخیره نقشه تولید شده
390
+ if 'ndvi_map' not in st.session_state:
391
+ st.session_state.ndvi_map = None
392
+
393
+ # تابع برای محاسبه میانگین ارتفاع
394
+ def calculate_average_height(farm_id, stations):
395
+ valid_stations = [s for s in stations if s > 0]
396
+ if valid_stations:
397
+ return int(sum(valid_stations) / len(valid_stations))
398
+ return 0
399
+
400
+ # تابع برای ایجاد نقشه NDVI
401
+ def create_ndvi_map(farm_id=None, date=None):
402
+ if not earth_engine_initialized:
403
+ st.warning("اتصال به Earth Engine برقرار نشد. نقشه NDVI نمایش داده نمی‌شود.")
404
+ return None
405
+
406
+ try:
407
+ # مختصات مرکزی تقریبی خوزستان (اهواز)
408
+ center_point = [31.3183, 48.6706]
409
+
410
+ # ایجاد نقشه
411
+ m = folium.Map(location=center_point, zoom_start=10)
412
+
413
+ # اگر مزرعه انتخاب شده است، آن را نمایش می‌دهیم
414
+ if farm_id:
415
+ # در اینجا باید مختصات دقیق مزرعه را داشته باشیم
416
+ # برای مثال، از داده‌های نمونه استفاده می‌کنیم
417
+ farm = farms_df[farms_df['Farm_ID'] == farm_id].iloc[0]
418
+
419
+ # مختصات فرضی برای مزرعه
420
+ lat = 31.3183 + (int(farm_id.split('-')[0]) - 1) * 0.05
421
+ lon = 48.6706 + (int(farm_id.split('-')[1]) - 20) * 0.05
422
+
423
+ # تهیه تصویر Sentinel-2
424
+ if date:
425
+ # تبدیل تاریخ به فرمت مناسب
426
+ # فرض می‌کنیم تاریخ به فرمت YYYY-MM-DD است
427
+ try:
428
+ date_obj = datetime.strptime(date, '%Y-%m-%d')
429
+ start_date = date_obj - timedelta(days=5)
430
+ end_date = date_obj + timedelta(days=5)
431
+
432
+ start_date_str = start_date.strftime('%Y-%m-%d')
433
+ end_date_str = end_date.strftime('%Y-%m-%d')
434
+ except:
435
+ # اگر تاریخ معتبر نیست، از بازه پیش‌فرض استفاده می‌کنیم
436
+ start_date_str = '2023-01-01'
437
+ end_date_str = '2023-12-31'
438
+ else:
439
+ # بازه پیش‌فرض
440
+ start_date_str = '2023-01-01'
441
+ end_date_str = '2023-12-31'
442
+
443
+ # ناحیه مورد نظر
444
+ region = ee.Geometry.Point([lon, lat]).buffer(1000) # بافر 1 کیلومتری
445
+
446
+ # دریافت تصویر Sentinel-2
447
+ s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
448
+ .filterDate(start_date_str, end_date_str) \
449
+ .filterBounds(region) \
450
+ .sort('CLOUD_COVERAGE_ASSESSMENT') \
451
+ .first()
452
+
453
+ # محاسبه NDVI
454
+ ndvi = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
455
+
456
+ # رنگ‌بندی NDVI
457
+ ndvi_viz = {
458
+ 'min': -0.2,
459
+ 'max': 0.8,
460
+ 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850']
461
+ }
462
+
463
+ # افزودن لایه NDVI به نقشه
464
+ map_id_dict = ee.Image(ndvi).getMapId(ndvi_viz)
465
+ folium.TileLayer(
466
+ tiles=map_id_dict['tile_fetcher'].url_format,
467
+ attr='Google Earth Engine',
468
+ name='NDVI',
469
+ overlay=True,
470
+ control=True
471
+ ).add_to(m)
472
+
473
+ # افزودن مارکر برای مزرعه
474
+ folium.Marker(
475
+ location=[lat, lon],
476
+ popup=f'مزرعه {farm_id}',
477
+ icon=folium.Icon(color='green', icon='leaf')
478
+ ).add_to(m)
479
+
480
+ # تنظیم نمای نقشه روی مزرعه
481
+ m.fit_bounds([[lat-0.05, lon-0.05], [lat+0.05, lon+0.05]])
482
+
483
+ # افزودن کنترل‌های لایه
484
+ folium.LayerControl().add_to(m)
485
+
486
+ return m
487
+
488
+ except Exception as e:
489
+ st.error(f"خطا در ایجاد نقشه NDVI: {e}")
490
+ return None
491
+
492
  # منوی کناری
493
  with st.sidebar:
494
  st.image("https://via.placeholder.com/150x150.png?text=لوگو", width=150)
 
518
 
519
  st.markdown("---")
520
  st.info("نسخه آزمایشی 1.0")
521
+
522
  # بخش داشبورد
523
  if st.session_state.selected_tab == "داشبورد":
524
  st.title("داشبورد پایش مزارع نیشکر")
 
641
 
642
  st.plotly_chart(fig, use_container_width=True)
643
 
644
+ # انتخاب مزرعه برای نمایش نقشه NDVI
645
+ st.markdown("---")
646
+ st.subheader("نقشه NDVI")
647
+
648
+ col1, col2 = st.columns(2)
649
+
650
+ with col1:
651
+ selected_farm_for_map = st.selectbox(
652
+ "انتخاب مزرعه",
653
+ farms_df['Farm_ID'].tolist(),
654
+ format_func=lambda x: farms_df[farms_df['Farm_ID'] == x]['Farm_Name'].iloc[0]
655
+ )
656
+
657
+ with col2:
658
+ # تاریخ آخرین اندازه‌گیری
659
+ latest_date = heights_df[heights_df['Farm_ID'] == selected_farm_for_map].sort_values('Week', ascending=False)['Measurement_Date'].iloc[0]
660
+ selected_date_for_map = st.date_input(
661
+ "انتخاب تاریخ",
662
+ value=datetime.strptime(latest_date, '%Y-%m-%d'),
663
+ max_value=datetime.now()
664
+ )
665
+
666
+ # تولید نقشه
667
+ if st.button("نمایش نقشه NDVI"):
668
+ with st.spinner("در حال تولید نقشه..."):
669
+ ndvi_map = create_ndvi_map(selected_farm_for_map, selected_date_for_map.strftime('%Y-%m-%d'))
670
+ st.session_state.ndvi_map = ndvi_map
671
+
672
+ # نمایش نقشه
673
+ if st.session_state.ndvi_map:
674
+ st.markdown('<div class="map-container">', unsafe_allow_html=True)
675
+ folium_static(st.session_state.ndvi_map, width=1200, height=500)
676
+ st.markdown('</div>', unsafe_allow_html=True)
677
+
678
+ # اطلاعات مزرعه انتخاب شده
679
+ farm_info = farms_df[farms_df['Farm_ID'] == selected_farm_for_map].iloc[0]
680
+ farm_measurements = heights_df[(heights_df['Farm_ID'] == selected_farm_for_map) &
681
+ (heights_df['Measurement_Date'] == latest_date)].iloc[0]
682
+
683
+ st.markdown(f"""
684
+ ### اطلاعات مزرعه {farm_info['Farm_Name']}
685
+
686
+ **اداره:** {farm_info['Administration_Name']} | **کانال:** {farm_info['Channel_ID']} | **واریته:** {farm_info['Variety']} | **سن کشت:** {farm_info['Crop_Age']} سال
687
+
688
+ **آخرین اندازه‌گیری:** {latest_date}
689
+ - **میانگین ارتفاع:** {farm_measurements['Height']} سانتی‌متر
690
+ - **رطوبت غلاف:** {farm_measurements['Sheath_Moisture']}%
691
+ - **نیتروژن:** {farm_measurements['Nitrogen']}%
692
+ """)
693
+ else:
694
+ st.info("برای نمایش نقشه NDVI، یک مزرعه و تاریخ انتخاب کرده و دکمه «نمایش نقشه NDVI» را کلیک کنید.")
695
+
696
  # مزارع با بیشترین و کمترین رشد
697
  st.markdown("---")
698
  st.subheader("مزارع با بیشترین و کمترین رشد")
 
732
  </p>
733
  </div>
734
  """, unsafe_allow_html=True)
735
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  # بخش ورود اطلاعات
737
  elif st.session_state.selected_tab == "ورود اطلاعات":
738
  st.title("ورود اطلاعات روزانه")
 
750
 
751
  st.subheader(f"ورود داده‌های روز {selected_day} - اداره {selected_admin}")
752
 
753
+ # خلاصه آماری زنده
754
+ st.markdown("### خلاصه وضعیت داده‌های وارد شده", unsafe_allow_html=True)
755
+
756
+ summary_cols = st.columns(4)
757
+
758
+ # میانگین ارتفاع کل
759
+ with summary_cols[0]:
760
+ st.markdown(
761
+ '<div class="metric-card metric-green">'
762
+ '<div class="dynamic-label">میانگین ارتفاع کل</div>'
763
+ f'<div class="dynamic-value" id="total_avg_height">0</div>'
764
+ '<div>سانتی‌متر</div>'
765
+ '</div>',
766
+ unsafe_allow_html=True
767
+ )
768
+
769
+ # تعداد مزارع وارد شده
770
+ with summary_cols[1]:
771
+ st.markdown(
772
+ '<div class="metric-card metric-blue">'
773
+ '<div class="dynamic-label">تعداد مزارع وارد شده</div>'
774
+ f'<div class="dynamic-value" id="farms_entered">0</div>'
775
+ '<div>از {len(filtered_farms)} مزرعه</div>'
776
+ '</div>',
777
+ unsafe_allow_html=True
778
+ )
779
+
780
+ # میانگین رطوبت غلاف
781
+ with summary_cols[2]:
782
+ st.markdown(
783
+ '<div class="metric-card metric-yellow">'
784
+ '<div class="dynamic-label">میانگین رطوبت غلاف</div>'
785
+ f'<div class="dynamic-value" id="avg_moisture">0</div>'
786
+ '<div>درصد</div>'
787
+ '</div>',
788
+ unsafe_allow_html=True
789
+ )
790
+
791
+ # میانگین نیتروژن
792
+ with summary_cols[3]:
793
+ st.markdown(
794
+ '<div class="metric-card metric-blue">'
795
+ '<div class="dynamic-label">میانگین نیتروژن</div>'
796
+ f'<div class="dynamic-value" id="avg_nitrogen">0</div>'
797
+ '<div>درصد</div>'
798
+ '</div>',
799
+ unsafe_allow_html=True
800
+ )
801
+
802
+ st.markdown("---")
803
+
804
  # ایجاد ستون‌های جدول
805
  cols = st.columns((3, 1, 1, 1, 1, 1, 1, 1, 1, 1))
806
  fields = ['مزرعه', 'ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'چاهک 1', 'چاهک 2', 'رطوبت غلاف', 'نیتروژن']
 
812
  # ایجاد حقول ورودی برای هر مزرعه
813
  farm_data = {}
814
 
815
+ # آیا اطلاعات قبلی وجود دارد؟
816
  for _, farm in filtered_farms.iterrows():
817
  farm_id = farm['Farm_ID']
818
  farm_name = farm['Farm_Name']
 
825
 
826
  # ایستگاه‌های ارتفاع
827
  for i in range(1, 6):
828
+ input_key = f"{farm_id}_station_{i}_{selected_day}"
829
+
830
+ # مقدار پیش‌فرض از داده‌های قبلی
831
+ default_value = 0
832
+ if farm_id in st.session_state.farm_data and f"station_{i}" in st.session_state.farm_data[farm_id]:
833
+ default_value = st.session_state.farm_data[farm_id][f"station_{i}"]
834
+
835
+ value = cols[i].number_input(
836
+ "",
837
+ min_value=0,
838
+ max_value=300,
839
+ value=default_value,
840
+ step=1,
841
+ key=input_key,
842
+ label_visibility="collapsed",
843
+ # به صورت داینامیک محاسبه را انجام می‌دهیم
844
+ on_change=None
845
+ )
846
  station_values.append(value)
847
+
848
+ # ذخیره مقدار در session_state
849
+ if farm_id not in st.session_state.farm_data:
850
+ st.session_state.farm_data[farm_id] = {}
851
+ st.session_state.farm_data[farm_id][f"station_{i}"] = value
852
 
853
  # چاهک‌های آب
854
+ groundwater_values = []
855
  for i in range(1, 3):
856
+ input_key = f"{farm_id}_groundwater_{i}_{selected_day}"
857
+
858
+ # مقدار پیش‌فرض از داده‌های قبلی
859
+ default_value = 0
860
+ if farm_id in st.session_state.farm_data and f"groundwater_{i}" in st.session_state.farm_data[farm_id]:
861
+ default_value = st.session_state.farm_data[farm_id][f"groundwater_{i}"]
862
+
863
+ value = cols[5+i].number_input(
864
+ "",
865
+ min_value=0,
866
+ max_value=300,
867
+ value=default_value,
868
+ step=1,
869
+ key=input_key,
870
+ label_visibility="collapsed"
871
+ )
872
+ groundwater_values.append(value)
873
+
874
+ # ذخیره مقدار در session_state
875
+ st.session_state.farm_data[farm_id][f"groundwater_{i}"] = value
876
 
877
  # رطوبت غلاف
878
+ input_key = f"{farm_id}_moisture_{selected_day}"
879
+
880
+ # مقدار پیش‌فرض از داده‌های قبلی
881
+ default_value = 0
882
+ if farm_id in st.session_state.farm_data and "moisture" in st.session_state.farm_data[farm_id]:
883
+ default_value = st.session_state.farm_data[farm_id]["moisture"]
884
+
885
+ moisture = cols[7].number_input(
886
+ "",
887
+ min_value=0,
888
+ max_value=100,
889
+ value=default_value,
890
+ step=1,
891
+ key=input_key,
892
+ label_visibility="collapsed"
893
+ )
894
+
895
+ # ذخیره مقدار در session_state
896
+ st.session_state.farm_data[farm_id]["moisture"] = moisture
897
 
898
  # نیتروژن
899
+ input_key = f"{farm_id}_nitrogen_{selected_day}"
900
+
901
+ # مقدار پیش‌فرض از داده‌های قبلی
902
+ default_value = 0
903
+ if farm_id in st.session_state.farm_data and "nitrogen" in st.session_state.farm_data[farm_id]:
904
+ default_value = st.session_state.farm_data[farm_id]["nitrogen"]
905
+
906
+ nitrogen = cols[8].number_input(
907
+ "",
908
+ min_value=0,
909
+ max_value=100,
910
+ value=default_value,
911
+ step=1,
912
+ key=input_key,
913
+ label_visibility="collapsed"
914
+ )
915
+
916
+ # ذخیره مقدار در session_state
917
+ st.session_state.farm_data[farm_id]["nitrogen"] = nitrogen
918
 
919
  # محاسبه میانگین ارتفاع
920
+ avg_height = calculate_average_height(farm_id, station_values)
 
 
 
921
 
922
+ # ذخیره میانگین در session_state
923
+ st.session_state.farm_data[farm_id]["avg_height"] = avg_height
924
+
925
+ # نمایش میانگین با ID منحصر به فرد برای آپدیت JavaScript
926
+ avg_id = f"avg_{farm_id}"
927
+ cols[9].markdown(f'<div id="{avg_id}" class="dynamic-value" style="text-align: center;">{avg_height if avg_height > 0 else "-"}</div>', unsafe_allow_html=True)
928
 
929
  # ذخیره داده‌ها برای ثبت نهایی
930
  farm_data[farm_id] = {
931
  'stations': station_values,
932
+ 'groundwater': groundwater_values,
933
  'avg_height': avg_height,
934
  'moisture': moisture,
935
  'nitrogen': nitrogen
936
  }
937
 
938
+ # محاسبه و به‌روزرسانی خلاصه آماری
939
+ valid_heights = [data['avg_height'] for data in farm_data.values() if data['avg_height'] > 0]
940
+ total_avg_height = int(sum(valid_heights) / len(valid_heights)) if valid_heights else 0
941
+
942
+ farms_entered = len([data for data in farm_data.values() if data['avg_height'] > 0])
943
+
944
+ valid_moisture = [data['moisture'] for data in farm_data.values() if data['moisture'] > 0]
945
+ avg_moisture = int(sum(valid_moisture) / len(valid_moisture)) if valid_moisture else 0
946
+
947
+ valid_nitrogen = [data['nitrogen'] for data in farm_data.values() if data['nitrogen'] > 0]
948
+ avg_nitrogen = int(sum(valid_nitrogen) / len(valid_nitrogen)) if valid_nitrogen else 0
949
+
950
+ # به‌روزرسانی میانگین‌ها با JavaScript
951
+ st.markdown(f"""
952
+ <script>
953
+ document.getElementById('total_avg_height').textContent = '{total_avg_height}';
954
+ document.getElementById('farms_entered').textContent = '{farms_entered}';
955
+ document.getElementById('avg_moisture').textContent = '{avg_moisture}';
956
+ document.getElementById('avg_nitrogen').textContent = '{avg_nitrogen}';
957
+ </script>
958
+ """, unsafe_allow_html=True)
959
+
960
  # دکمه ذخیره‌سازی
961
  if st.button("ذخیره اطلاعات", key="save_daily_data"):
962
+ # بررسی وجود داده معتبر
963
+ valid_data_exists = any(data['avg_height'] > 0 for data in farm_data.values())
964
+
965
+ if valid_data_exists:
966
+ with st.spinner('در حال ذخیره‌سازی اطلاعات...'):
967
+ # شبیه‌سازی تأخیر ذخیره‌سازی
968
+ time.sleep(1)
969
+ st.success("اطلاعات با موفقیت ذخیره شد. در نسخه عملیاتی، این داده‌ها در پایگاه داده ذخیره می‌شوند.")
970
+
971
+ # نمایش خلاصه داده‌های ثبت شده
972
+ st.subheader("خلاصه داده‌های ثبت شده:")
973
+
974
+ summary_data = []
975
+ for farm_id, data in farm_data.items():
976
+ if data['avg_height'] > 0: # فقط مزارعی که داده داشته‌اند
977
+ farm_info = filtered_farms[filtered_farms['Farm_ID'] == farm_id].iloc[0]
978
+ summary_data.append({
979
+ 'مزرعه': farm_info['Farm_Name'],
980
+ 'میانگین ارتفاع': data['avg_height'],
981
+ 'رطوبت غلاف': data['moisture'],
982
+ 'نیتروژن': data['nitrogen']
983
+ })
984
+
985
+ if summary_data:
986
+ summary_df = pd.DataFrame(summary_data)
987
+ st.dataframe(summary_df, use_container_width=True)
988
  else:
989
+ st.warning("هیچ داده‌ای برای ذخیره‌سازی وارد نشده است. لطفاً حداقل برای یک مزرعه ارتفاع را وارد کنید.")
990
+
991
  # بخش گزارش‌گیری
992
  elif st.session_state.selected_tab == "گزارش‌گیری":
993
  st.title("گزارش‌های هفتگی")
 
1070
 
1071
  report_table = pd.DataFrame({
1072
  'هفته': farm_reports['Week'],
1073
+ 'تاریخ': farm_reports['Measurement_Date'],
1074
  'ارتفاع': farm_reports['Average_Height'],
1075
  'میانگین منطقه': farm_reports['Regional_Average'],
1076
  'تغییرات': farm_reports['Growth_Change'],
 
1086
 
1087
  st.markdown(report_table.to_html(escape=False, index=False), unsafe_allow_html=True)
1088
 
1089
+ # نمایش نقشه NDVI برای مزرعه
1090
+ st.subheader("نقشه NDVI مزرعه")
1091
+
1092
+ if st.button("نمایش نقشه NDVI این مزرعه"):
1093
+ with st.spinner("در حال تولید نقشه..."):
1094
+ # استفاده از تاریخ آخرین اندازه‌گیری
1095
+ latest_date = farm_reports.iloc[-1]['Measurement_Date']
1096
+ ndvi_map = create_ndvi_map(selected_farm_id, latest_date)
1097
+
1098
+ if ndvi_map:
1099
+ st.markdown('<div class="map-container">', unsafe_allow_html=True)
1100
+ folium_static(ndvi_map, width=1200, height=500)
1101
+ st.markdown('</div>', unsafe_allow_html=True)
1102
+
1103
  # توصیه‌های کارشناسی
1104
  st.markdown("---")
1105
  st.subheader("توصیه‌های کارشناسی")
 
1319
 
1320
  st.dataframe(weak_farms_table.sort_values('ارتفاع فعلی'), use_container_width=True)
1321
 
1322
+ # نمایش نقشه NDVI برای مزارع ضعیف
1323
+ st.subheader("نقشه NDVI مزارع ضعیف")
1324
+
1325
+ # انتخاب مزرعه برای نمایش
1326
+ selected_weak_farm = st.selectbox(
1327
+ "انتخاب مزرعه برای نمایش نقشه NDVI",
1328
+ weak_farms['Farm_ID'].tolist(),
1329
+ format_func=lambda x: weak_farms[weak_farms['Farm_ID'] == x]['Farm_Name'].iloc[0]
1330
+ )
1331
+
1332
+ if st.button("نمایش نقشه NDVI"):
1333
+ with st.spinner("در حال تولید نقشه..."):
1334
+ # استفاده از تاریخ آخرین اندازه‌گیری
1335
+ farm_date = heights_df[heights_df['Farm_ID'] == selected_weak_farm].sort_values('Week', ascending=False)['Measurement_Date'].iloc[0]
1336
+ ndvi_map = create_ndvi_map(selected_weak_farm, farm_date)
1337
+
1338
+ if ndvi_map:
1339
+ st.markdown('<div class="map-container">', unsafe_allow_html=True)
1340
+ folium_static(ndvi_map, width=1200, height=500)
1341
+ st.markdown('</div>', unsafe_allow_html=True)
1342
+
1343
  # تحلیل و پیشنهادات
1344
  st.markdown("---")
1345
  st.subheader("تحلیل عوامل مؤثر")
 
1390
  - بررسی آفات و بیماری‌های احتمالی
1391
  - برنامه‌ریزی برای جایگزینی مزارع با سن بالا
1392
  """)
1393
+
1394
  # بخش تحلیل داده‌ها
1395
  elif st.session_state.selected_tab == "تحلیل داده‌ها":
1396
  st.title("تحلیل پیشرفته داده‌ها")
 
1615
 
1616
  st.plotly_chart(fig, use_container_width=True)
1617
 
1618
+ # تحلیل توزیع وضعیت‌