Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -28,11 +28,12 @@ from streamlit_extras.add_vertical_space import add_vertical_space
|
|
28 |
from streamlit_card import card
|
29 |
import pydeck as pdk
|
30 |
import math
|
|
|
31 |
|
32 |
# Page configuration with custom theme
|
33 |
st.set_page_config(
|
34 |
-
page_title="سامانه
|
35 |
-
page_icon="
|
36 |
layout="wide",
|
37 |
initial_sidebar_state="expanded"
|
38 |
)
|
@@ -195,34 +196,77 @@ def initialize_earth_engine():
|
|
195 |
|
196 |
# Load data
|
197 |
@st.cache_data
|
198 |
-
def
|
199 |
try:
|
200 |
-
|
201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
except Exception as e:
|
203 |
-
st.error(f"خطا در بارگذاری
|
204 |
return pd.DataFrame()
|
205 |
|
206 |
-
|
207 |
-
def
|
208 |
try:
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
|
|
|
|
|
|
|
|
214 |
|
215 |
-
#
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
return None
|
221 |
-
return r.json()
|
222 |
|
223 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
def create_ee_map(farm_id, date_str, layer_type="NDVI"):
|
225 |
try:
|
|
|
226 |
farm_row = coordinates_df[coordinates_df['نام'] == farm_id].iloc[0]
|
227 |
lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
|
228 |
|
@@ -318,9 +362,10 @@ def create_ee_map(farm_id, date_str, layer_type="NDVI"):
|
|
318 |
st.error(f"خطا در ایجاد نقشه: {e}")
|
319 |
return None
|
320 |
|
321 |
-
# Calculate statistics for a farm using GEE
|
322 |
def calculate_farm_stats(farm_id, date_str, layer_type="NDVI"):
|
323 |
try:
|
|
|
324 |
farm_row = coordinates_df[coordinates_df['نام'] == farm_id].iloc[0]
|
325 |
lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
|
326 |
region = ee.Geometry.Point([lon, lat]).buffer(1500)
|
@@ -372,96 +417,27 @@ def calculate_farm_stats(farm_id, date_str, layer_type="NDVI"):
|
|
372 |
st.error(f"خطا در محاسبه آمار: {e}")
|
373 |
return None
|
374 |
|
375 |
-
# تابع برای محاسبات خودکار
|
376 |
-
def calculate_metrics(df):
|
377 |
-
# محاسبه میانگین ارتف��ع مزرعه
|
378 |
-
station_cols = ['ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5']
|
379 |
-
df['ارتفاع هفته جاری مزرعه'] = df[station_cols].apply(
|
380 |
-
lambda row: np.mean([x for x in row if x != 0 and pd.notnull(x)]) if any(x != 0 and pd.notnull(x) for x in row) else 0, axis=1
|
381 |
-
)
|
382 |
-
|
383 |
-
# محاسبه رشد هفتگی (فرض میکنیم ستون "هفته" وجود دارد)
|
384 |
-
df['رشد هفته جاری'] = df['ارتفاع هفته جاری مزرعه'] - df['ارتفاع هفته گذشته مزرعه'].fillna(0)
|
385 |
-
|
386 |
-
# محاسبه انحراف نیتروژن
|
387 |
-
df['نیتروژن انحراف'] = df['نیتروژن فعلی'] - df['نیتروژن استاندارد فعلی'].fillna(0)
|
388 |
-
|
389 |
-
# محاسبه انحراف رطوبت
|
390 |
-
df['رطوبت انحراف'] = df['رطوبت غلاف فعلی'] - df['رطوبت استاندارد فعلی'].fillna(0)
|
391 |
-
|
392 |
-
# محاسبه نسبت مساحت کراپ لاگ (فرض بر این است که مساحت کراپ لاگ برابر با مساحت زیرمجموعه است)
|
393 |
-
df['نسبت مساحت کراپ لاگ'] = df['مساحت زیرمجموعه'] / df['مساحت داشت'].replace(0, np.nan)
|
394 |
-
|
395 |
-
return df
|
396 |
-
|
397 |
-
# تابع برای تولید گزارش PDF
|
398 |
-
def generate_pdf_report(df, report_type, week=None, day=None):
|
399 |
-
pdf = FPDF()
|
400 |
-
pdf.add_page()
|
401 |
-
pdf.add_font('Vazir', '', 'Vazirmatn-Regular.ttf', uni=True)
|
402 |
-
pdf.set_font('Vazir', '', 12)
|
403 |
-
|
404 |
-
if report_type == "روزانه":
|
405 |
-
title = f"گزارش روزانه - تاریخ: {day}"
|
406 |
-
elif report_type == "هفتگی":
|
407 |
-
title = f"گزارش هفتگی - هفته: {week}"
|
408 |
-
else:
|
409 |
-
title = "گزارش تجمیعی"
|
410 |
-
|
411 |
-
pdf.cell(200, 10, title, ln=True, align='C')
|
412 |
-
pdf.ln(10)
|
413 |
-
|
414 |
-
# سرستونها
|
415 |
-
cols = df.columns.tolist()
|
416 |
-
pdf.set_font('Vazir', '', 10)
|
417 |
-
for col in cols:
|
418 |
-
pdf.cell(40, 10, col, border=1)
|
419 |
-
pdf.ln()
|
420 |
-
|
421 |
-
# دادهها
|
422 |
-
for index, row in df.iterrows():
|
423 |
-
for col in cols:
|
424 |
-
pdf.cell(40, 10, str(row[col]) if pd.notnull(row[col]) else "0", border=1)
|
425 |
-
pdf.ln()
|
426 |
-
|
427 |
-
# ذخیره PDF در حافظه
|
428 |
-
pdf_output = pdf.output(dest='S').encode('latin1')
|
429 |
-
return pdf_output
|
430 |
-
|
431 |
-
# Initialize Earth Engine
|
432 |
-
ee_initialized = initialize_earth_engine()
|
433 |
-
|
434 |
# Load data
|
435 |
-
|
436 |
-
coordinates_df = load_coordinates_data()
|
437 |
|
438 |
-
#
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
# Create session state for storing data with Persian column names
|
444 |
-
if 'heights_df' not in st.session_state:
|
445 |
-
st.session_state.heights_df = pd.DataFrame(columns=[
|
446 |
-
'ردیف', 'نماینده', 'کانال', 'اداره', 'تولید', 'مساحت داشت', 'مساحت زیرمجموعه', 'واریته', 'سن',
|
447 |
-
'ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'ارتفاع هفته جاری مزرعه',
|
448 |
-
'ارتفاع هفته گذشته مزرعه', 'رشد هفته جاری', 'رشد هفته گذشته', 'نیتروژن فعلی', 'نیتروژن استاندارد فعلی',
|
449 |
-
'نیتروژن قبلی', 'نیتروژن استاندارد قبلی', 'رطوبت غلاف فعلی', 'رطوبت استاندارد فعلی', 'رطوبت غلاف قبلی',
|
450 |
-
'رطوبت استاندارد قبلی', 'چاهک 1', 'تاریخ قرائت', 'چاهک 2', 'تاریخ قرائت2', 'نیتروژن انحراف', 'رطوبت انحراف', 'نسبت مساحت کراپ لاگ'
|
451 |
])
|
452 |
|
453 |
# Main header
|
454 |
st.markdown('<div class="main-header">', unsafe_allow_html=True)
|
455 |
-
st.markdown('<h1>سامانه
|
456 |
-
st.markdown('<p>پلتفرم جامع
|
457 |
st.markdown('</div>', unsafe_allow_html=True)
|
458 |
|
459 |
# Navigation menu
|
460 |
selected = option_menu(
|
461 |
menu_title=None,
|
462 |
-
options=["
|
463 |
-
icons=["
|
464 |
-
menu_icon="cast",
|
465 |
default_index=0,
|
466 |
orientation="horizontal",
|
467 |
styles={
|
@@ -472,97 +448,24 @@ selected = option_menu(
|
|
472 |
}
|
473 |
)
|
474 |
|
475 |
-
# Dashboard
|
476 |
-
if selected == "داشبورد":
|
477 |
-
col1, col2, col3, col4 = st.columns(4)
|
478 |
-
|
479 |
-
with col1:
|
480 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{len(farm_df)}</div><div class="metric-label">تعداد مزارع</div></div>', unsafe_allow_html=True)
|
481 |
-
|
482 |
-
with col2:
|
483 |
-
active_farms = int(len(farm_df) * 0.85)
|
484 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{active_farms}</div><div class="metric-label">مزارع فعال</div></div>', unsafe_allow_html=True)
|
485 |
-
|
486 |
-
with col3:
|
487 |
-
avg_height = st.session_state.heights_df['ارتفاع هفته جاری مزرعه'].mean() if not st.session_state.heights_df.empty else 0
|
488 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{avg_height:.1f} cm</div><div class="metric-label">میانگین ارتفاع</div></div>', unsafe_allow_html=True)
|
489 |
-
|
490 |
-
with col4:
|
491 |
-
avg_moisture = st.session_state.heights_df['رطوبت غلاف فعلی'].mean() if not st.session_state.heights_df.empty else 0
|
492 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{avg_moisture:.1f}%</div><div class="metric-label">میانگین رطوبت</div></div>', unsafe_allow_html=True)
|
493 |
-
|
494 |
-
tab1, tab2 = st.tabs(["نمای کلی", "نمودارها"])
|
495 |
-
|
496 |
-
with tab1:
|
497 |
-
st.markdown("### اطلاعات کلی مزارع")
|
498 |
-
total_area = farm_df['مساحت داشت'].astype(float).sum() if 'مساحت داشت' in farm_df.columns else 0
|
499 |
-
col1, col2, col3 = st.columns(3)
|
500 |
-
col1.metric("تعداد کل مزارع", f"{len(farm_df)}")
|
501 |
-
col2.metric("مساحت کل (هکتار)", f"{total_area:.2f}")
|
502 |
-
col3.metric("تعداد کانالها", f"{farm_df['کانال'].nunique() if 'کانال' in farm_df.columns else 0}")
|
503 |
-
|
504 |
-
with tab2:
|
505 |
-
if not st.session_state.heights_df.empty:
|
506 |
-
fig = px.line(st.session_state.heights_df, x='هفته', y='ارتفاع هفته جاری مزرعه', title='روند ارتفاع هفتگی', labels={'هفته': 'هفته', 'ارتفاع هفته جاری مزرعه': 'ارتفاع (سانتیمتر)'})
|
507 |
-
st.plotly_chart(fig, use_container_width=True)
|
508 |
-
else:
|
509 |
-
st.warning("دادهای برای نمایش وجود ندارد.")
|
510 |
-
|
511 |
-
# Map Page
|
512 |
-
elif selected == "نقشه مزارع":
|
513 |
-
st.markdown("## نقشه مزارع با شاخصهای ماهوارهای")
|
514 |
-
col1, col2 = st.columns([1, 3])
|
515 |
-
|
516 |
-
with col1:
|
517 |
-
selected_farm = st.selectbox("انتخاب مزرعه", options=coordinates_df['نام'].tolist(), index=0)
|
518 |
-
selected_date = st.date_input("انتخاب تاریخ", value=datetime.now())
|
519 |
-
selected_layer = st.selectbox("انتخاب شاخص", ["NDVI", "NDMI", "EVI", "NDWI"])
|
520 |
-
if st.button("تولید نقشه"):
|
521 |
-
with st.spinner('در حال تولید نقشه...'):
|
522 |
-
m = create_ee_map(selected_farm, selected_date.strftime('%Y-%m-%d'), selected_layer)
|
523 |
-
if m:
|
524 |
-
folium_static(m, width=800, height=600)
|
525 |
-
st.success("نقشه با موفقیت تولید شد!")
|
526 |
-
else:
|
527 |
-
st.error("خطا در تولید نقشه!")
|
528 |
-
|
529 |
-
with col2:
|
530 |
-
if 'last_map' in st.session_state:
|
531 |
-
folium_static(st.session_state.last_map, width=800, height=600)
|
532 |
-
stats = calculate_farm_stats(selected_farm, selected_date.strftime('%Y-%m-%d'), selected_layer)
|
533 |
-
if stats:
|
534 |
-
col1, col2, col3, col4 = st.columns(4)
|
535 |
-
with col1:
|
536 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{stats["mean"]:.2f}</div><div class="metric-label">میانگین {selected_layer}</div></div>', unsafe_allow_html=True)
|
537 |
-
with col2:
|
538 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{stats["max"]:.2f}</div><div class="metric-label">حداکثر {selected_layer}</div></div>', unsafe_allow_html=True)
|
539 |
-
with col3:
|
540 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{stats["min"]:.2f}</div><div class="metric-label">حداقل {selected_layer}</div></div>', unsafe_allow_html=True)
|
541 |
-
with col4:
|
542 |
-
st.markdown(f'<div class="metric-card"><div class="metric-value">{stats["std_dev"]:.2f}</div><div class="metric-label">انحراف معیار</div></div>', unsafe_allow_html=True)
|
543 |
-
|
544 |
# Data Entry Page
|
545 |
-
|
546 |
-
st.markdown("## ورود اطلاعات
|
547 |
tabs = st.tabs(["ورود دستی", "آپلود فایل"])
|
548 |
|
549 |
with tabs[0]:
|
550 |
-
st.markdown("### ورود
|
551 |
-
|
552 |
-
|
553 |
-
day = st.selectbox("انتخاب روز", days, key="day_manual")
|
554 |
|
555 |
if st.button("نمایش دادهها"):
|
556 |
-
filtered_df = st.session_state.
|
557 |
-
|
558 |
-
(st.session_state.heights_df['هفته'] == week)
|
559 |
].copy()
|
560 |
if filtered_df.empty:
|
561 |
-
|
562 |
-
filtered_df = pd.DataFrame(columns=st.session_state.heights_df.columns)
|
563 |
filtered_df.loc[0] = [None] * len(filtered_df.columns)
|
564 |
-
filtered_df['
|
565 |
-
filtered_df['تاریخ قرائت'] = day
|
566 |
st.session_state.filtered_df = filtered_df
|
567 |
|
568 |
if 'filtered_df' in st.session_state:
|
@@ -570,39 +473,21 @@ elif selected == "ورود اطلاعات":
|
|
570 |
st.session_state.filtered_df,
|
571 |
num_rows="dynamic",
|
572 |
column_config={
|
573 |
-
"
|
574 |
-
"
|
575 |
-
"
|
576 |
-
"
|
577 |
-
"
|
578 |
-
"
|
579 |
-
"
|
580 |
-
"
|
581 |
-
"
|
582 |
-
"
|
583 |
-
"
|
584 |
-
"
|
585 |
-
"
|
586 |
-
"
|
587 |
-
"
|
588 |
-
"ارتفاع هفته گذشته مزرعه": st.column_config.NumberColumn("ارتفاع هفته گذشته مزرعه", min_value=0.0, step=0.1, required=True),
|
589 |
-
"رشد هفته جاری": st.column_config.NumberColumn("رشد هفته جاری", min_value=0.0, step=0.1, disabled=True),
|
590 |
-
"رشد هفته گذشته": st.column_config.NumberColumn("رشد هفته گذشته", min_value=0.0, step=0.1, required=True),
|
591 |
-
"نیتروژن فعلی": st.column_config.NumberColumn("نیتروژن فعلی", min_value=0.0, step=0.1, required=True),
|
592 |
-
"نیتروژن استاندارد فعلی": st.column_config.NumberColumn("نیتروژن استاندارد فعلی", min_value=0.0, step=0.1, required=True),
|
593 |
-
"نیتروژن قبلی": st.column_config.NumberColumn("نیتروژن قبلی", min_value=0.0, step=0.1, required=True),
|
594 |
-
"نیتروژن استاندارد قبلی": st.column_config.NumberColumn("نیتروژن استاندارد قبلی", min_value=0.0, step=0.1, required=True),
|
595 |
-
"رطوبت غلاف فعلی": st.column_config.NumberColumn("رطوبت غلاف فعلی", min_value=0.0, step=0.1, required=True),
|
596 |
-
"رطوبت استاندارد فعلی": st.column_config.NumberColumn("رطوبت استاندارد فعلی", min_value=0.0, step=0.1, required=True),
|
597 |
-
"رطوبت غلاف قبلی": st.column_config.NumberColumn("رطوبت غلاف قبلی", min_value=0.0, step=0.1, required=True),
|
598 |
-
"رطوبت استاندارد قبلی": st.column_config.NumberColumn("رطوبت استاندارد قبلی", min_value=0.0, step=0.1, required=True),
|
599 |
-
"چاهک 1": st.column_config.NumberColumn("چاهک 1", min_value=0.0, step=0.1, required=True),
|
600 |
-
"چاهک 2": st.column_config.NumberColumn("چاهک 2", min_value=0.0, step=0.1, required=True),
|
601 |
-
"تاریخ قرائت": st.column_config.DateColumn("تاریخ قرائت", format="YYYY-MM-DD", required=True),
|
602 |
-
"تاریخ قرائت2": st.column_config.DateColumn("تاریخ قرائت2", format="YYYY-MM-DD", required=True),
|
603 |
-
"نیتروژن انحراف": st.column_config.NumberColumn("نیتروژن انحراف", min_value=-100.0, max_value=100.0, disabled=True),
|
604 |
-
"رطوبت انحراف": st.column_config.NumberColumn("رطوبت انحراف", min_value=-100.0, max_value=100.0, disabled=True),
|
605 |
-
"نسبت مساحت کراپ لاگ": st.column_config.NumberColumn("نسبت مساحت کراپ لاگ", min_value=0.0, max_value=10.0, disabled=True),
|
606 |
},
|
607 |
use_container_width=True,
|
608 |
hide_index=True
|
@@ -610,167 +495,117 @@ elif selected == "ورود اطلاعات":
|
|
610 |
|
611 |
if st.button("محاسبه و ذخیره"):
|
612 |
calculated_df = calculate_metrics(edited_df)
|
613 |
-
st.session_state.
|
614 |
-
[st.session_state.
|
615 |
ignore_index=True
|
616 |
)
|
617 |
-
st.success("دادهها با موفقیت
|
618 |
st.session_state.filtered_df = calculated_df
|
619 |
|
620 |
with tabs[1]:
|
621 |
-
st.markdown("### آپلود فایل
|
622 |
-
uploaded_file = st.file_uploader("فایل
|
623 |
-
|
624 |
if uploaded_file is not None:
|
625 |
try:
|
626 |
-
df = pd.
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
'
|
631 |
-
|
632 |
-
'
|
633 |
-
'اداره': 'اداره',
|
634 |
-
'تولید': 'تولید',
|
635 |
-
'مساحت داشت': 'مساحت داشت',
|
636 |
-
'مساحت زیرمجموعه': 'مساحت زیرمجموعه',
|
637 |
-
'واریته': 'واریته',
|
638 |
-
'سن': 'سن',
|
639 |
-
'ایستگاه 1': 'ایستگاه 1',
|
640 |
-
'ایستگاه 2': 'ایستگاه 2',
|
641 |
-
'ایستگاه 3': 'ایستگاه 3',
|
642 |
-
'ایستگاه 4': 'ایستگاه 4',
|
643 |
-
'ایستگاه 5': 'ایستگاه 5',
|
644 |
-
'ارتفاع هفته جاری مزرعه': 'ارتفاع هفته جاری مزرعه',
|
645 |
-
'ارتفاع هفته گذشته مزرعه': 'ارتفاع هفته گذشته مزرعه',
|
646 |
-
'رشد هفته جاری': 'رشد هفته جاری',
|
647 |
-
'رشد هفته گذشته': 'رشد هفته گذشته',
|
648 |
-
'نیتروژن فعلی': 'نیتروژن فعلی',
|
649 |
-
'نیتروژن استاندارد فعلی': 'نیتروژن استاندارد فعلی',
|
650 |
-
'نیتروژن قبلی': 'نیتروژن قبلی',
|
651 |
-
'نیتروژن استاندارد قبلی': 'نیتروژن استاندارد قبلی',
|
652 |
-
'رطوبت غلاف فعلی': 'رطوبت غلاف فعلی',
|
653 |
-
'رطوبت استاندارد فعلی': 'رطوبت استاندارد فعلی',
|
654 |
-
'رطوبت غلاف قبلی': 'رطوبت غلاف قبلی',
|
655 |
-
'رطوبت استاندارد قبلی': 'رطوبت استاندارد قبلی',
|
656 |
-
'چاهک 1': 'چاهک 1',
|
657 |
-
'تاریخ قرائت': 'تاریخ قرائت',
|
658 |
-
'چاهک 2': 'چاهک 2',
|
659 |
-
'تاریخ قرائت2': 'تاریخ قرائت2'
|
660 |
-
}
|
661 |
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
# Required columns for the upload file
|
670 |
-
required_upload_columns = ['ردیف', 'نماینده', 'کانال', 'اداره', 'تولید', 'مساحت داشت', 'مساحت زیرمجموعه', 'واریته', 'سن',
|
671 |
-
'ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'ارتفاع هفته جاری مزرعه',
|
672 |
-
'ارتفاع هفته گذشته مزرعه', 'رشد هفته جاری', 'رشد هفته گذشته', 'نیتروژن فعلی', 'نیتروژن استاندارد فعلی',
|
673 |
-
'نیتروژن قبلی', 'نیتروژن استاندارد قبلی', 'رطوبت غلاف فعلی', 'رطوبت استاندارد فعلی', 'رطوبت غلاف قبلی',
|
674 |
-
'رطوبت استاندارد قبلی', 'چاهک 1', 'تاریخ قرائت', 'چاهک 2', 'تاریخ قرائت2']
|
675 |
-
|
676 |
-
# Check for missing required columns
|
677 |
-
missing_columns = [col for col in required_upload_columns if col not in mapped_df.columns]
|
678 |
-
if missing_columns:
|
679 |
-
st.error(f"ستونهای زیر در فایل یافت نشدند: {', '.join(missing_columns)}")
|
680 |
-
else:
|
681 |
-
# Convert date format if necessary (assuming Persian date format like 1403/02/22)
|
682 |
-
def convert_persian_date(date_str):
|
683 |
-
try:
|
684 |
-
parts = str(date_str).split('/')
|
685 |
-
if len(parts) == 3:
|
686 |
-
year, month, day = map(int, parts)
|
687 |
-
return f"{year}-{month:02d}-{day:02d}"
|
688 |
-
return date_str
|
689 |
-
except:
|
690 |
-
return date_str
|
691 |
-
|
692 |
-
mapped_df['تاریخ قرائت'] = mapped_df['تاریخ قرائت'].apply(convert_persian_date)
|
693 |
-
mapped_df['تاریخ قرائت2'] = mapped_df['تاریخ قرائت2'].apply(convert_persian_date)
|
694 |
-
mapped_df['تاریخ قرائت'] = pd.to_datetime(mapped_df['تاریخ قرائت'], errors='coerce')
|
695 |
-
mapped_df['تاریخ قرائت2'] = pd.to_datetime(mapped_df['تاریخ قرائت2'], errors='coerce')
|
696 |
-
|
697 |
-
# Ensure numeric columns
|
698 |
-
numeric_cols = ['کانال', 'اداره', 'تولید', 'مساحت داشت', 'مساحت زیرمجموعه', 'ایستگاه 1', 'ایستگاه 2',
|
699 |
-
'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'ارتفاع هفته جاری مزرعه', 'ارتفاع هفته گذشته مزرعه',
|
700 |
-
'رشد هفته جاری', 'رشد هفته گذشته', 'نیتروژن فعلی', 'نیتروژن استاندارد فعلی', 'نیتروژن قبلی',
|
701 |
-
'نیتروژن استاندارد قبلی', 'رطوبت غلاف فعلی', 'رطوبت استاندارد فعلی', 'رطوبت غلاف قبلی',
|
702 |
-
'رطوبت استاندارد قبلی', 'چاهک 1', 'چاهک 2']
|
703 |
-
for col in numeric_cols:
|
704 |
-
mapped_df[col] = pd.to_numeric(mapped_df[col], errors='coerce')
|
705 |
-
|
706 |
-
# Calculate metrics
|
707 |
-
calculated_df = calculate_metrics(mapped_df)
|
708 |
-
|
709 |
-
# Append to session state
|
710 |
-
st.session_state.heights_df = pd.concat(
|
711 |
-
[st.session_state.heights_df, calculated_df[~calculated_df.index.isin(st.session_state.heights_df.index)]],
|
712 |
-
ignore_index=True
|
713 |
-
)
|
714 |
-
st.success("دادهها از فایل با موفقیت بارگذاری و پردازش شدند!")
|
715 |
-
st.dataframe(calculated_df)
|
716 |
except Exception as e:
|
717 |
st.error(f"خطا در پردازش فایل: {e}")
|
718 |
|
719 |
st.markdown("### دادههای فعلی")
|
720 |
-
if not st.session_state.
|
721 |
-
st.dataframe(st.session_state.
|
722 |
else:
|
723 |
st.info("دادهای برای نمایش وجود ندارد.")
|
724 |
|
725 |
# Data Analysis Page
|
726 |
-
|
727 |
st.markdown("## تحلیل دادهها")
|
728 |
-
if not st.session_state.
|
729 |
-
st.markdown("###
|
730 |
-
fig = px.line(st.session_state.
|
731 |
st.plotly_chart(fig, use_container_width=True)
|
732 |
|
733 |
-
st.markdown("###
|
734 |
-
fig = px.scatter(st.session_state.
|
735 |
st.plotly_chart(fig, use_container_width=True)
|
736 |
else:
|
737 |
st.warning("دادهای برای تحلیل وجود ندارد.")
|
738 |
|
739 |
# Reporting Page
|
740 |
-
|
741 |
st.markdown("## گزارشگیری")
|
742 |
report_type = st.selectbox("نوع گزارش", ["روزانه", "هفتگی", "تجمیعی"])
|
743 |
|
744 |
if report_type == "روزانه":
|
745 |
-
|
746 |
-
|
|
|
747 |
elif report_type == "هفتگی":
|
748 |
-
|
749 |
-
|
|
|
|
|
|
|
750 |
else:
|
751 |
-
report_df = st.session_state.
|
752 |
|
753 |
st.dataframe(report_df)
|
754 |
|
755 |
if st.button("تولید گزارش PDF"):
|
756 |
-
pdf_data = generate_pdf_report(report_df, report_type,
|
757 |
b64 = base64.b64encode(pdf_data).decode('utf-8')
|
758 |
href = f'<a href="data:application/pdf;base64,{b64}" download="report.pdf">دانلود گزارش PDF</a>'
|
759 |
st.markdown(href, unsafe_allow_html=True)
|
760 |
|
761 |
-
#
|
762 |
-
|
763 |
-
st.
|
764 |
-
st.
|
765 |
-
|
766 |
-
# ذخیره دائمی پایگاه داده
|
767 |
-
if st.button("ذخیره پایگاه داده"):
|
768 |
-
st.session_state.heights_df.to_csv("پایگاه داده.csv", index=False)
|
769 |
-
st.success("پایگاه داده با موفقیت ذخیره شد!")
|
770 |
|
771 |
# Footer
|
772 |
st.markdown("""
|
773 |
<footer style="position: relative; bottom: 0; width: 100%; background-color: #1a8754; color: white; text-align: center; padding: 1rem; margin-top: 2rem; border-top: 2px solid rgba(255, 255, 255, 0.1);">
|
774 |
-
<p>© 2025 سامانه
|
775 |
</footer>
|
776 |
-
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
from streamlit_card import card
|
29 |
import pydeck as pdk
|
30 |
import math
|
31 |
+
from jdatetime import date as jdate
|
32 |
|
33 |
# Page configuration with custom theme
|
34 |
st.set_page_config(
|
35 |
+
page_title="سامانه مدیریت دادهها",
|
36 |
+
page_icon="📊",
|
37 |
layout="wide",
|
38 |
initial_sidebar_state="expanded"
|
39 |
)
|
|
|
196 |
|
197 |
# Load data
|
198 |
@st.cache_data
|
199 |
+
def load_data():
|
200 |
try:
|
201 |
+
uploaded_file = st.file_uploader("آپلود فایل CSV", type=["csv"], key="data_uploader")
|
202 |
+
if uploaded_file is not None:
|
203 |
+
df = pd.read_csv(uploaded_file)
|
204 |
+
# تبدیل تاریخ شمسی به میلادی (فرض میکنیم تاریخها به فرمت 1403/02/22 هستند)
|
205 |
+
df['تاریخ'] = pd.to_datetime(df['تاریخ'].apply(convert_persian_date), errors='coerce')
|
206 |
+
return df
|
207 |
+
else:
|
208 |
+
return pd.DataFrame()
|
209 |
except Exception as e:
|
210 |
+
st.error(f"خطا در بارگذاری دادهها: {e}")
|
211 |
return pd.DataFrame()
|
212 |
|
213 |
+
# تبدیل تاریخ شمسی به میلادی
|
214 |
+
def convert_persian_date(date_str):
|
215 |
try:
|
216 |
+
parts = str(date_str).split('/')
|
217 |
+
if len(parts) == 3:
|
218 |
+
year, month, day = map(int, parts)
|
219 |
+
persian_date = jdate(year, month, day)
|
220 |
+
gregorian_date = persian_date.togregorian()
|
221 |
+
return gregorian_date.strftime('%Y-%m-%d')
|
222 |
+
return date_str
|
223 |
+
except:
|
224 |
+
return date_str
|
225 |
|
226 |
+
# محاسبات
|
227 |
+
def calculate_metrics(df):
|
228 |
+
# اضافه کردن ستون هفته از تاریخ (اختیاری، برای گزارشگیری)
|
229 |
+
df['هفته'] = df['تاریخ'].dt.isocalendar().week
|
230 |
+
return df
|
|
|
|
|
231 |
|
232 |
+
# تولید گزارش PDF
|
233 |
+
def generate_pdf_report(df, report_type, date=None, week=None):
|
234 |
+
pdf = FPDF()
|
235 |
+
pdf.add_page()
|
236 |
+
pdf.add_font('Vazir', '', 'Vazirmatn-Regular.ttf', uni=True)
|
237 |
+
pdf.set_font('Vazir', '', 12)
|
238 |
+
|
239 |
+
if report_type == "روزانه":
|
240 |
+
title = f"گزارش روزانه - تاریخ: {date.strftime('%Y-%m-%d') if date else 'بدون تاریخ'}"
|
241 |
+
elif report_type == "هفتگی":
|
242 |
+
title = f"گزارش هفتگی - هفته: {week}"
|
243 |
+
else:
|
244 |
+
title = "گزارش تجمیعی"
|
245 |
+
|
246 |
+
pdf.cell(200, 10, title, ln=True, align='C')
|
247 |
+
pdf.ln(10)
|
248 |
+
|
249 |
+
# سرستونها
|
250 |
+
cols = df.columns.tolist()
|
251 |
+
pdf.set_font('Vazir', '', 10)
|
252 |
+
for col in cols:
|
253 |
+
pdf.cell(40, 10, col, border=1)
|
254 |
+
pdf.ln()
|
255 |
+
|
256 |
+
# دادهها
|
257 |
+
for index, row in df.iterrows():
|
258 |
+
for col in cols:
|
259 |
+
pdf.cell(40, 10, str(row[col]) if pd.notnull(row[col]) else "0", border=1)
|
260 |
+
pdf.ln()
|
261 |
+
|
262 |
+
# ذخیره PDF در حافظه
|
263 |
+
pdf_output = pdf.output(dest='S').encode('latin1')
|
264 |
+
return pdf_output
|
265 |
+
|
266 |
+
# Create Earth Engine map (optional, can be removed if not needed)
|
267 |
def create_ee_map(farm_id, date_str, layer_type="NDVI"):
|
268 |
try:
|
269 |
+
# Assuming coordinates_df is loaded; if not needed, this function can be removed
|
270 |
farm_row = coordinates_df[coordinates_df['نام'] == farm_id].iloc[0]
|
271 |
lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
|
272 |
|
|
|
362 |
st.error(f"خطا در ایجاد نقشه: {e}")
|
363 |
return None
|
364 |
|
365 |
+
# Calculate statistics for a farm using GEE (optional, can be removed if not needed)
|
366 |
def calculate_farm_stats(farm_id, date_str, layer_type="NDVI"):
|
367 |
try:
|
368 |
+
# Assuming coordinates_df is loaded; if not needed, this function can be removed
|
369 |
farm_row = coordinates_df[coordinates_df['نام'] == farm_id].iloc[0]
|
370 |
lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
|
371 |
region = ee.Geometry.Point([lon, lat]).buffer(1500)
|
|
|
417 |
st.error(f"خطا در محاسبه آمار: {e}")
|
418 |
return None
|
419 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
# Load data
|
421 |
+
data_df = load_data()
|
|
|
422 |
|
423 |
+
# Create session state for storing data
|
424 |
+
if 'data_df' not in st.session_state:
|
425 |
+
st.session_state.data_df = pd.DataFrame(columns=[
|
426 |
+
'شماره', 'تاریخ', 'تعداد', 'مبلغ', 'وضعیت', 'توضیحات', 'نوع پرداخت', 'شماره فاکتور',
|
427 |
+
'شماره سفارش', 'کد مشتری', 'نام مشتری', 'شماره پیگیری', 'تاریخ تحویل', 'مقدار تخفیف', 'تاریخ ثبت'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
428 |
])
|
429 |
|
430 |
# Main header
|
431 |
st.markdown('<div class="main-header">', unsafe_allow_html=True)
|
432 |
+
st.markdown('<h1>سامانه مدیریت دادهها</h1>', unsafe_allow_html=True)
|
433 |
+
st.markdown('<p>پلتفرم جامع ورود، مدیریت و گزارشگیری دادهها</p>', unsafe_allow_html=True)
|
434 |
st.markdown('</div>', unsafe_allow_html=True)
|
435 |
|
436 |
# Navigation menu
|
437 |
selected = option_menu(
|
438 |
menu_title=None,
|
439 |
+
options=["ورود اطلاعات", "تحلیل دادهها", "گزارشگیری"],
|
440 |
+
icons=["pencil-square", "graph-up", "file-earmark-text"],
|
|
|
441 |
default_index=0,
|
442 |
orientation="horizontal",
|
443 |
styles={
|
|
|
448 |
}
|
449 |
)
|
450 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
451 |
# Data Entry Page
|
452 |
+
if selected == "ورود اطلاعات":
|
453 |
+
st.markdown("## ورود اطلاعات")
|
454 |
tabs = st.tabs(["ورود دستی", "آپلود فایل"])
|
455 |
|
456 |
with tabs[0]:
|
457 |
+
st.markdown("### ورود دادهها به صورت دستی")
|
458 |
+
dates = st.session_state.data_df['تاریخ'].unique()
|
459 |
+
date = st.selectbox("انتخاب تاریخ", pd.to_datetime(dates).strftime('%Y/%m/%d').tolist() if len(dates) > 0 else ["1403/02/22"], key="date_manual")
|
|
|
460 |
|
461 |
if st.button("نمایش دادهها"):
|
462 |
+
filtered_df = st.session_state.data_df[
|
463 |
+
st.session_state.data_df['تاریخ'] == pd.to_datetime(date, format='%Y/%m/%d')
|
|
|
464 |
].copy()
|
465 |
if filtered_df.empty:
|
466 |
+
filtered_df = pd.DataFrame(columns=st.session_state.data_df.columns)
|
|
|
467 |
filtered_df.loc[0] = [None] * len(filtered_df.columns)
|
468 |
+
filtered_df['تاریخ'] = pd.to_datetime(date, format='%Y/%m/%d')
|
|
|
469 |
st.session_state.filtered_df = filtered_df
|
470 |
|
471 |
if 'filtered_df' in st.session_state:
|
|
|
473 |
st.session_state.filtered_df,
|
474 |
num_rows="dynamic",
|
475 |
column_config={
|
476 |
+
"شماره": st.column_config.NumberColumn("شماره", required=True),
|
477 |
+
"تاریخ": st.column_config.DateColumn("تاریخ", format="YYYY/MM/DD", required=True),
|
478 |
+
"تعداد": st.column_config.NumberColumn("تعداد", min_value=0, step=1, required=True),
|
479 |
+
"مبلغ": st.column_config.NumberColumn("مبلغ", min_value=0, step=1, required=True),
|
480 |
+
"وضعیت": st.column_config.TextColumn("وضعیت", required=True),
|
481 |
+
"توضیحات": st.column_config.TextColumn("توضیحات"),
|
482 |
+
"نوع پرداخت": st.column_config.TextColumn("نوع پرداخت", required=True),
|
483 |
+
"شماره فاکتور": st.column_config.TextColumn("شماره فاکتور", required=True),
|
484 |
+
"شماره سفارش": st.column_config.TextColumn("شماره سفارش", required=True),
|
485 |
+
"کد مشتری": st.column_config.TextColumn("کد مشتری", required=True),
|
486 |
+
"نام مشتری": st.column_config.TextColumn("نام مشتری", required=True),
|
487 |
+
"شماره پیگیری": st.column_config.TextColumn("شماره پیگیری"),
|
488 |
+
"تاریخ تحویل": st.column_config.DateColumn("تاریخ تحویل", format="YYYY/MM/DD"),
|
489 |
+
"مقدار تخفیف": st.column_config.NumberColumn("مقدار تخفیف", min_value=0, step=1),
|
490 |
+
"تاریخ ثبت": st.column_config.DateColumn("تاریخ ثبت", format="YYYY/MM/DD"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
491 |
},
|
492 |
use_container_width=True,
|
493 |
hide_index=True
|
|
|
495 |
|
496 |
if st.button("محاسبه و ذخیره"):
|
497 |
calculated_df = calculate_metrics(edited_df)
|
498 |
+
st.session_state.data_df = pd.concat(
|
499 |
+
[st.session_state.data_df, calculated_df[~calculated_df.index.isin(st.session_state.data_df.index)]],
|
500 |
ignore_index=True
|
501 |
)
|
502 |
+
st.success("دادهها با موفقیت ذخیره شدند!")
|
503 |
st.session_state.filtered_df = calculated_df
|
504 |
|
505 |
with tabs[1]:
|
506 |
+
st.markdown("### آپلود فایل")
|
507 |
+
uploaded_file = st.file_uploader("آپلود فایل CSV", type=["csv"])
|
|
|
508 |
if uploaded_file is not None:
|
509 |
try:
|
510 |
+
df = pd.read_csv(uploaded_file)
|
511 |
+
# تبدیل تاریخ شمسی به میلادی
|
512 |
+
df['تاریخ'] = pd.to_datetime(df['تاریخ'].apply(convert_persian_date), errors='coerce')
|
513 |
+
if 'تاریخ تحویل' in df.columns:
|
514 |
+
df['تاریخ تحویل'] = pd.to_datetime(df['تاریخ تحویل'].apply(convert_persian_date), errors='coerce')
|
515 |
+
if 'تاریخ ثبت' in df.columns:
|
516 |
+
df['تاریخ ثبت'] = pd.to_datetime(df['تاریخ ثبت'].apply(convert_persian_date), errors='coerce')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
517 |
|
518 |
+
calculated_df = calculate_metrics(df)
|
519 |
+
st.session_state.data_df = pd.concat(
|
520 |
+
[st.session_state.data_df, calculated_df[~calculated_df.index.isin(st.session_state.data_df.index)]],
|
521 |
+
ignore_index=True
|
522 |
+
)
|
523 |
+
st.success("دادهها از فایل با موفقیت بارگذاری شدند!")
|
524 |
+
st.dataframe(st.session_state.data_df)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
525 |
except Exception as e:
|
526 |
st.error(f"خطا در پردازش فایل: {e}")
|
527 |
|
528 |
st.markdown("### دادههای فعلی")
|
529 |
+
if not st.session_state.data_df.empty:
|
530 |
+
st.dataframe(st.session_state.data_df)
|
531 |
else:
|
532 |
st.info("دادهای برای نمایش وجود ندارد.")
|
533 |
|
534 |
# Data Analysis Page
|
535 |
+
if selected == "تحلیل دادهها":
|
536 |
st.markdown("## تحلیل دادهها")
|
537 |
+
if not st.session_state.data_df.empty:
|
538 |
+
st.markdown("### روند مبلغ بر اساس تاریخ")
|
539 |
+
fig = px.line(st.session_state.data_df, x='تاریخ', y='مبلغ', title='روند مبلغ')
|
540 |
st.plotly_chart(fig, use_container_width=True)
|
541 |
|
542 |
+
st.markdown("### رابطه تعداد و مبلغ")
|
543 |
+
fig = px.scatter(st.session_state.data_df, x='تعداد', y='مبلغ', color='وضعیت', title='رابطه تعداد و مبلغ')
|
544 |
st.plotly_chart(fig, use_container_width=True)
|
545 |
else:
|
546 |
st.warning("دادهای برای تحلیل وجود ندارد.")
|
547 |
|
548 |
# Reporting Page
|
549 |
+
if selected == "گزارشگیری":
|
550 |
st.markdown("## گزارشگیری")
|
551 |
report_type = st.selectbox("نوع گزارش", ["روزانه", "هفتگی", "تجمیعی"])
|
552 |
|
553 |
if report_type == "روزانه":
|
554 |
+
dates = st.session_state.data_df['تاریخ'].unique()
|
555 |
+
date = st.selectbox("��نتخاب تاریخ", pd.to_datetime(dates).strftime('%Y/%m/%d').tolist() if len(dates) > 0 else ["1403/02/22"])
|
556 |
+
report_df = st.session_state.data_df[st.session_state.data_df['تاریخ'] == pd.to_datetime(date, format='%Y/%m/%d')]
|
557 |
elif report_type == "هفتگی":
|
558 |
+
# محاسبه هفته از تاریخ
|
559 |
+
st.session_state.data_df['هفته'] = st.session_state.data_df['تاریخ'].dt.isocalendar().week
|
560 |
+
weeks = st.session_state.data_df['هفته'].unique()
|
561 |
+
week = st.selectbox("انتخاب هفته", sorted(weeks) if len(weeks) > 0 else [1])
|
562 |
+
report_df = st.session_state.data_df[st.session_state.data_df['هفته'] == week]
|
563 |
else:
|
564 |
+
report_df = st.session_state.data_df
|
565 |
|
566 |
st.dataframe(report_df)
|
567 |
|
568 |
if st.button("تولید گزارش PDF"):
|
569 |
+
pdf_data = generate_pdf_report(report_df, report_type, date if report_type == "روزانه" else None, week if report_type == "هفتگی" else None)
|
570 |
b64 = base64.b64encode(pdf_data).decode('utf-8')
|
571 |
href = f'<a href="data:application/pdf;base64,{b64}" download="report.pdf">دانلود گزارش PDF</a>'
|
572 |
st.markdown(href, unsafe_allow_html=True)
|
573 |
|
574 |
+
# ذخیره دائمی دادهها
|
575 |
+
if st.button("ذخیره دادهها"):
|
576 |
+
st.session_state.data_df.to_csv("دادهها.csv", index=False, date_format='%Y/%m/%d')
|
577 |
+
st.success("دادهها با موفقیت ذخیره شدند!")
|
|
|
|
|
|
|
|
|
|
|
578 |
|
579 |
# Footer
|
580 |
st.markdown("""
|
581 |
<footer style="position: relative; bottom: 0; width: 100%; background-color: #1a8754; color: white; text-align: center; padding: 1rem; margin-top: 2rem; border-top: 2px solid rgba(255, 255, 255, 0.1);">
|
582 |
+
<p>© 2025 سامانه مدیریت دادهها</p>
|
583 |
</footer>
|
584 |
+
""", unsafe_allow_html=True)
|
585 |
+
|
586 |
+
# Initialize Earth Engine (optional, can be removed if not needed)
|
587 |
+
ee_initialized = initialize_earth_engine()
|
588 |
+
|
589 |
+
# Load coordinates data (optional, can be removed if not needed)
|
590 |
+
@st.cache_data
|
591 |
+
def load_coordinates_data():
|
592 |
+
try:
|
593 |
+
df = pd.read_csv("farm_coordinates.csv")
|
594 |
+
return df
|
595 |
+
except Exception as e:
|
596 |
+
st.error(f"خطا در بارگذاری دادههای مختصات: {e}")
|
597 |
+
return pd.DataFrame()
|
598 |
+
|
599 |
+
coordinates_df = load_coordinates_data()
|
600 |
+
|
601 |
+
# Load animations (optional, can be removed if not needed)
|
602 |
+
@st.cache_data
|
603 |
+
def load_lottie_url(url: str):
|
604 |
+
r = requests.get(url)
|
605 |
+
if r.status_code != 200:
|
606 |
+
return None
|
607 |
+
return r.json()
|
608 |
+
|
609 |
+
lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json')
|
610 |
+
lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json')
|
611 |
+
lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json')
|