Spaces:
Sleeping
Sleeping
""" | |
تطبيق وحدة الموارد | |
""" | |
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import plotly.express as px | |
import plotly.graph_objects as go | |
from datetime import datetime, timedelta | |
import time | |
import io | |
import os | |
import tempfile | |
import random | |
class ResourcesApp: | |
"""وحدة إدارة الموارد""" | |
def __init__(self): | |
"""تهيئة وحدة الموارد""" | |
# تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة | |
if 'materials' not in st.session_state: | |
st.session_state.materials = [ | |
{ | |
'id': 1, | |
'name': 'خرسانة جاهزة', | |
'category': 'مواد إنشائية', | |
'unit': 'م3', | |
'price': 250, | |
'supplier': 'شركة الخرسانة الوطنية', | |
'local_content': 95, | |
'last_updated': '2024-01-15' | |
}, | |
{ | |
'id': 2, | |
'name': 'حديد تسليح', | |
'category': 'مواد إنشائية', | |
'unit': 'طن', | |
'price': 4500, | |
'supplier': 'مصنع الحديد السعودي', | |
'local_content': 45, | |
'last_updated': '2024-02-10' | |
}, | |
{ | |
'id': 3, | |
'name': 'بلوك خرساني', | |
'category': 'مواد إنشائية', | |
'unit': 'م3', | |
'price': 350, | |
'supplier': 'مصنع البلوك الحديث', | |
'local_content': 95, | |
'last_updated': '2024-01-20' | |
}, | |
{ | |
'id': 4, | |
'name': 'رمل', | |
'category': 'مواد إنشائية', | |
'unit': 'م3', | |
'price': 60, | |
'supplier': 'مؤسسة توريدات البناء', | |
'local_content': 100, | |
'last_updated': '2024-01-15' | |
}, | |
{ | |
'id': 5, | |
'name': 'بلاط سيراميك', | |
'category': 'مواد تشطيب', | |
'unit': 'م2', | |
'price': 120, | |
'supplier': 'شركة السيراميك الوطنية', | |
'local_content': 80, | |
'last_updated': '2024-02-05' | |
} | |
] | |
if 'labor' not in st.session_state: | |
st.session_state.labor = [ | |
{ | |
'id': 1, | |
'name': 'مهندس مدني', | |
'category': 'هندسة', | |
'unit': 'شهر', | |
'price': 15000, | |
'supplier': 'داخلي', | |
'local_content': 90, | |
'last_updated': '2024-01-10' | |
}, | |
{ | |
'id': 2, | |
'name': 'مهندس معماري', | |
'category': 'هندسة', | |
'unit': 'شهر', | |
'price': 14000, | |
'supplier': 'داخلي', | |
'local_content': 85, | |
'last_updated': '2024-01-10' | |
}, | |
{ | |
'id': 3, | |
'name': 'مساح', | |
'category': 'هندسة', | |
'unit': 'شهر', | |
'price': 8000, | |
'supplier': 'داخلي', | |
'local_content': 100, | |
'last_updated': '2024-01-10' | |
}, | |
{ | |
'id': 4, | |
'name': 'فني كهرباء', | |
'category': 'فني', | |
'unit': 'شهر', | |
'price': 7000, | |
'supplier': 'داخلي', | |
'local_content': 95, | |
'last_updated': '2024-01-10' | |
}, | |
{ | |
'id': 5, | |
'name': 'عامل بناء', | |
'category': 'عمالة', | |
'unit': 'يوم', | |
'price': 200, | |
'supplier': 'شركة توريد عمالة', | |
'local_content': 60, | |
'last_updated': '2024-01-20' | |
} | |
] | |
if 'equipment' not in st.session_state: | |
st.session_state.equipment = [ | |
{ | |
'id': 1, | |
'name': 'حفارة كبيرة', | |
'category': 'معدات ثقيلة', | |
'unit': 'يوم', | |
'price': 2500, | |
'supplier': 'شركة المعدات الثقيلة', | |
'local_content': 70, | |
'last_updated': '2024-01-15' | |
}, | |
{ | |
'id': 2, | |
'name': 'خلاطة خرسانة', | |
'category': 'معدات إنشائية', | |
'unit': 'يوم', | |
'price': 1800, | |
'supplier': 'مؤسسة معدات البناء', | |
'local_content': 65, | |
'last_updated': '2024-01-20' | |
}, | |
{ | |
'id': 3, | |
'name': 'رافعة برجية', | |
'category': 'معدات ثقيلة', | |
'unit': 'شهر', | |
'price': 45000, | |
'supplier': 'شركة المعدات الثقيلة', | |
'local_content': 50, | |
'last_updated': '2024-02-05' | |
}, | |
{ | |
'id': 4, | |
'name': 'مولد كهربائي', | |
'category': 'معدات مساندة', | |
'unit': 'شهر', | |
'price': 12000, | |
'supplier': 'شركة المعدات الكهربائية', | |
'local_content': 75, | |
'last_updated': '2024-01-25' | |
}, | |
{ | |
'id': 5, | |
'name': 'سقالات معدنية', | |
'category': 'معدات مساندة', | |
'unit': 'م2/شهر', | |
'price': 50, | |
'supplier': 'مؤسسة معدات البناء', | |
'local_content': 90, | |
'last_updated': '2024-01-15' | |
} | |
] | |
if 'subcontractors' not in st.session_state: | |
st.session_state.subcontractors = [ | |
{ | |
'id': 1, | |
'name': 'مؤسسة الإنشاءات المتكاملة', | |
'category': 'أعمال إنشائية', | |
'specialization': 'تنفيذ الهيكل الخرساني', | |
'rating': 4.8, | |
'city': 'الرياض', | |
'contact_person': 'محمد العتيبي', | |
'phone': '0555555555', | |
'email': '[email protected]', | |
'local_content': 85, | |
'last_updated': '2024-01-15' | |
}, | |
{ | |
'id': 2, | |
'name': 'شركة التكييف والتبريد', | |
'category': 'أعمال كهروميكانيكية', | |
'specialization': 'تركيب أنظمة التكييف والتبريد', | |
'rating': 4.5, | |
'city': 'جدة', | |
'contact_person': 'أحمد الغامدي', | |
'phone': '0566666666', | |
'email': '[email protected]', | |
'local_content': 75, | |
'last_updated': '2024-01-20' | |
}, | |
{ | |
'id': 3, | |
'name': 'مؤسسة الكهرباء الحديثة', | |
'category': 'أعمال كهروميكانيكية', | |
'specialization': 'تنفيذ الأعمال الكهربائية', | |
'rating': 4.6, | |
'city': 'الرياض', | |
'contact_person': 'فهد السويلم', | |
'phone': '0577777777', | |
'email': '[email protected]', | |
'local_content': 90, | |
'last_updated': '2024-02-05' | |
}, | |
{ | |
'id': 4, | |
'name': 'شركة المقاولات المتخصصة', | |
'category': 'أعمال تشطيبات', | |
'specialization': 'تنفيذ أعمال التشطيبات الداخلية', | |
'rating': 4.7, | |
'city': 'الدمام', | |
'contact_person': 'خالد الدوسري', | |
'phone': '0588888888', | |
'email': '[email protected]', | |
'local_content': 80, | |
'last_updated': '2024-01-25' | |
}, | |
{ | |
'id': 5, | |
'name': 'مؤسسة الصيانة والتشغيل', | |
'category': 'أعمال صيانة', | |
'specialization': 'صيانة وتشغيل المباني', | |
'rating': 4.4, | |
'city': 'الرياض', | |
'contact_person': 'عبدالله العنزي', | |
'phone': '0599999999', | |
'email': '[email protected]', | |
'local_content': 95, | |
'last_updated': '2024-02-10' | |
} | |
] | |
if 'price_history' not in st.session_state: | |
st.session_state.price_history = [ | |
# تاريخ أسعار الخرسانة الجاهزة | |
*[{'material_id': 1, 'date': (datetime.now() - timedelta(days=i*30)).strftime('%Y-%m-%d'), 'price': 250 - (i * 5) if i < 3 else 250 - 15 + (i - 2) * 10} for i in range(12)], | |
# تاريخ أسعار حديد التسليح | |
*[{'material_id': 2, 'date': (datetime.now() - timedelta(days=i*30)).strftime('%Y-%m-%d'), 'price': 4500 - (i * 100) if i < 4 else 4500 - 400 + (i - 3) * 150} for i in range(12)], | |
# تاريخ أسعار البلوك الخرساني | |
*[{'material_id': 3, 'date': (datetime.now() - timedelta(days=i*30)).strftime('%Y-%m-%d'), 'price': 350 - (i * 10) if i < 6 else 350 - 60 + (i - 5) * 15} for i in range(12)] | |
] | |
def render(self): | |
"""عرض واجهة وحدة الموارد""" | |
st.markdown("<h1 class='module-title'>وحدة إدارة الموارد</h1>", unsafe_allow_html=True) | |
tabs = st.tabs([ | |
"لوحة المعلومات", | |
"المواد", | |
"العمالة", | |
"المعدات", | |
"المقاولين من الباطن", | |
"تحليل الأسعار" | |
]) | |
with tabs[0]: | |
self._render_dashboard_tab() | |
with tabs[1]: | |
self._render_materials_tab() | |
with tabs[2]: | |
self._render_labor_tab() | |
with tabs[3]: | |
self._render_equipment_tab() | |
with tabs[4]: | |
self._render_subcontractors_tab() | |
with tabs[5]: | |
self._render_price_analysis_tab() | |
def _render_dashboard_tab(self): | |
"""عرض تبويب لوحة المعلومات""" | |
st.markdown("### لوحة معلومات إدارة الموارد") | |
# عرض مؤشرات الأداء الرئيسية | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
total_materials = len(st.session_state.materials) | |
st.metric("عدد المواد", total_materials) | |
with col2: | |
total_labor = len(st.session_state.labor) | |
st.metric("عدد موارد العمالة", total_labor) | |
with col3: | |
total_equipment = len(st.session_state.equipment) | |
st.metric("عدد المعدات", total_equipment) | |
with col4: | |
total_subcontractors = len(st.session_state.subcontractors) | |
st.metric("عدد المقاولين من الباطن", total_subcontractors) | |
# رسم بياني لتوزيع المحتوى المحلي | |
st.markdown("### المحتوى المحلي للموارد") | |
# إعداد البيانات | |
local_content_data = [] | |
# إضافة بيانات المواد | |
for material in st.session_state.materials: | |
local_content_data.append({ | |
'النوع': 'المواد', | |
'اسم المورد': material['name'], | |
'نسبة المحتوى المحلي': material['local_content'] | |
}) | |
# إضافة بيانات العمالة | |
for labor in st.session_state.labor: | |
local_content_data.append({ | |
'النوع': 'العمالة', | |
'اسم المورد': labor['name'], | |
'نسبة المحتوى المحلي': labor['local_content'] | |
}) | |
# إضافة بيانات المعدات | |
for equipment in st.session_state.equipment: | |
local_content_data.append({ | |
'النوع': 'المعدات', | |
'اسم المورد': equipment['name'], | |
'نسبة المحتوى المحلي': equipment['local_content'] | |
}) | |
# إضافة بيانات المقاولين من الباطن | |
for subcontractor in st.session_state.subcontractors: | |
local_content_data.append({ | |
'النوع': 'المقاولين من الباطن', | |
'اسم المورد': subcontractor['name'], | |
'نسبة المحتوى المحلي': subcontractor['local_content'] | |
}) | |
# تحويل البيانات إلى DataFrame | |
local_content_df = pd.DataFrame(local_content_data) | |
# حساب متوسط المحتوى المحلي لكل نوع | |
avg_local_content = local_content_df.groupby('النوع')['نسبة المحتوى المحلي'].mean().reset_index() | |
# رسم المخطط الشريطي | |
fig = px.bar( | |
avg_local_content, | |
x='النوع', | |
y='نسبة المحتوى المحلي', | |
title='متوسط نسبة المحتوى المحلي حسب نوع المورد', | |
color='النوع', | |
text_auto='.1f' | |
) | |
fig.update_traces(texttemplate='%{text}%', textposition='outside') | |
fig.add_shape( | |
type="line", | |
x0=-0.5, | |
x1=len(avg_local_content) - 0.5, | |
y0=70, # النسبة المستهدفة | |
y1=70, | |
line=dict(color="red", width=2, dash="dash"), | |
name="النسبة المستهدفة" | |
) | |
fig.add_annotation( | |
x=1, | |
y=75, | |
text=f"النسبة المستهدفة (70%)", | |
showarrow=False, | |
font=dict(color="red") | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض تنبيهات الموارد | |
st.markdown("### تنبيهات الموارد") | |
# محاكاة تنبيهات الموارد | |
alerts = [ | |
{ | |
"type": "تغير في الأسعار", | |
"resource": "حديد تسليح", | |
"message": "ارتفاع في سعر الحديد بنسبة 5% في الأسبوع الماضي", | |
"date": "2024-03-15", | |
"severity": "متوسطة" | |
}, | |
{ | |
"type": "نقص في المخزون", | |
"resource": "بلاط سيراميك", | |
"message": "انخفاض مخزون السيراميك إلى أقل من 20% من المستوى المطلوب", | |
"date": "2024-03-18", | |
"severity": "عالية" | |
}, | |
{ | |
"type": "انتهاء صلاحية عقود", | |
"resource": "مؤسسة الإنشاءات المتكاملة", | |
"message": "سينتهي العقد مع المقاول خلال 30 يوماً", | |
"date": "2024-03-10", | |
"severity": "منخفضة" | |
}, | |
{ | |
"type": "تغير في المحتوى المحلي", | |
"resource": "شركة التكييف والتبريد", | |
"message": "انخفاض نسبة المحتوى المحلي إلى أقل من النسبة المستهدفة", | |
"date": "2024-03-12", | |
"severity": "متوسطة" | |
} | |
] | |
# عرض التنبيهات | |
for alert in alerts: | |
if alert["severity"] == "عالية": | |
st.error(f"**{alert['type']}**: {alert['message']} - *{alert['resource']}* ({alert['date']})") | |
elif alert["severity"] == "متوسطة": | |
st.warning(f"**{alert['type']}**: {alert['message']} - *{alert['resource']}* ({alert['date']})") | |
else: | |
st.info(f"**{alert['type']}**: {alert['message']} - *{alert['resource']}* ({alert['date']})") | |
# عرض نظرة عامة على الأسعار | |
st.markdown("### نظرة عامة على تطور الأسعار") | |
# إعداد البيانات | |
price_history_data = [] | |
material_names = {material['id']: material['name'] for material in st.session_state.materials} | |
for entry in st.session_state.price_history: | |
material_id = entry['material_id'] | |
if material_id in material_names: | |
price_history_data.append({ | |
'المادة': material_names[material_id], | |
'التاريخ': pd.to_datetime(entry['date']), | |
'السعر': entry['price'] | |
}) | |
# تحويل البيانات إلى DataFrame | |
price_history_df = pd.DataFrame(price_history_data) | |
# رسم المخطط الخطي | |
fig = px.line( | |
price_history_df, | |
x='التاريخ', | |
y='السعر', | |
color='المادة', | |
title='تطور أسعار المواد الرئيسية خلال العام الماضي', | |
labels={'price': 'السعر (ريال)', 'date': 'التاريخ'} | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
def _render_materials_tab(self): | |
"""عرض تبويب المواد""" | |
st.markdown("### إدارة المواد") | |
# عرض أدوات البحث والتصفية | |
col1, col2 = st.columns(2) | |
with col1: | |
search_query = st.text_input("بحث في المواد", placeholder="ابحث باسم المادة أو الفئة أو المورد...") | |
with col2: | |
category_filter = st.multiselect( | |
"تصفية حسب الفئة", | |
options=list(set(material['category'] for material in st.session_state.materials)), | |
default=[] | |
) | |
# تطبيق البحث والتصفية | |
filtered_materials = st.session_state.materials | |
if search_query: | |
filtered_materials = [ | |
material for material in filtered_materials | |
if (search_query.lower() in material['name'].lower() or | |
search_query.lower() in material['category'].lower() or | |
search_query.lower() in material['supplier'].lower()) | |
] | |
if category_filter: | |
filtered_materials = [material for material in filtered_materials if material['category'] in category_filter] | |
# زر إضافة مادة جديدة | |
if st.button("إضافة مادة جديدة"): | |
st.session_state.show_material_form = True | |
# نموذج إضافة مادة جديدة | |
if st.session_state.get('show_material_form', False): | |
with st.form("add_material_form"): | |
st.markdown("#### إضافة مادة جديدة") | |
col1, col2 = st.columns(2) | |
with col1: | |
new_material_name = st.text_input("اسم المادة", key="new_material_name") | |
new_material_category = st.text_input("الفئة", key="new_material_category") | |
new_material_unit = st.text_input("وحدة القياس", key="new_material_unit") | |
with col2: | |
new_material_price = st.number_input("السعر (ريال)", min_value=0.0, key="new_material_price") | |
new_material_supplier = st.text_input("المورد", key="new_material_supplier") | |
new_material_local_content = st.slider("نسبة المحتوى المحلي (%)", 0, 100, 50, key="new_material_local_content") | |
submitted = st.form_submit_button("إضافة المادة") | |
cancel = st.form_submit_button("إلغاء") | |
if submitted and new_material_name and new_material_category and new_material_unit: | |
# إضافة المادة الجديدة | |
new_material = { | |
'id': max([material['id'] for material in st.session_state.materials], default=0) + 1, | |
'name': new_material_name, | |
'category': new_material_category, | |
'unit': new_material_unit, | |
'price': new_material_price, | |
'supplier': new_material_supplier, | |
'local_content': new_material_local_content, | |
'last_updated': datetime.now().strftime('%Y-%m-%d') | |
} | |
st.session_state.materials.append(new_material) | |
st.success(f"تمت إضافة المادة '{new_material_name}' بنجاح!") | |
st.session_state.show_material_form = False | |
st.rerun() | |
if cancel: | |
st.session_state.show_material_form = False | |
st.experimental_rerun() | |
# عرض قائمة المواد | |
if filtered_materials: | |
# تحويل البيانات إلى DataFrame | |
materials_df = pd.DataFrame(filtered_materials) | |
# تنسيق البيانات للعرض | |
display_df = materials_df.copy() | |
display_df['price'] = display_df['price'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['local_content'] = display_df['local_content'].apply(lambda x: f"{x}%") | |
# تغيير أسماء الأعمدة للعرض | |
display_df.columns = [ | |
'معرف', 'اسم المادة', 'الفئة', 'وحدة القياس', 'السعر', 'المورد', 'نسبة المحتوى المحلي', 'آخر تحديث' | |
] | |
# عرض الجدول | |
st.dataframe(display_df, use_container_width=True, hide_index=True) | |
# عرض ملخص إحصائي | |
st.markdown("#### ملخص إحصائي للمواد") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric("إجمالي عدد المواد", len(filtered_materials)) | |
with col2: | |
avg_price = sum(material['price'] for material in filtered_materials) / len(filtered_materials) | |
st.metric("متوسط سعر المواد", f"{avg_price:,.2f} ريال") | |
with col3: | |
avg_local_content = sum(material['local_content'] for material in filtered_materials) / len(filtered_materials) | |
st.metric("متوسط نسبة المحتوى المحلي", f"{avg_local_content:.1f}%") | |
# عرض مخطط توزيع المواد حسب الفئة | |
category_counts = materials_df.groupby('category').size().reset_index(name='count') | |
fig = px.pie( | |
category_counts, | |
names='category', | |
values='count', | |
title='توزيع المواد حسب الفئة' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
else: | |
st.warning("لا توجد مواد مطابقة لمعايير البحث.") | |
def _render_labor_tab(self): | |
"""عرض تبويب العمالة""" | |
st.markdown("### إدارة العمالة") | |
# عرض أدوات البحث والتصفية | |
col1, col2 = st.columns(2) | |
with col1: | |
search_query = st.text_input("بحث في العمالة", placeholder="ابحث باسم العامل أو الفئة أو المورد...") | |
with col2: | |
category_filter = st.multiselect( | |
"تصفية حسب الفئة", | |
options=list(set(labor['category'] for labor in st.session_state.labor)), | |
default=[] | |
) | |
# تطبيق البحث والتصفية | |
filtered_labor = st.session_state.labor | |
if search_query: | |
filtered_labor = [ | |
labor for labor in filtered_labor | |
if (search_query.lower() in labor['name'].lower() or | |
search_query.lower() in labor['category'].lower() or | |
search_query.lower() in labor['supplier'].lower()) | |
] | |
if category_filter: | |
filtered_labor = [labor for labor in filtered_labor if labor['category'] in category_filter] | |
# زر إضافة عامل جديد | |
if st.button("إضافة عامل جديد"): | |
st.session_state.show_labor_form = True | |
# نموذج إضافة عامل جديد | |
if st.session_state.get('show_labor_form', False): | |
with st.form("add_labor_form"): | |
st.markdown("#### إضافة عامل جديد") | |
col1, col2 = st.columns(2) | |
with col1: | |
new_labor_name = st.text_input("اسم العامل", key="new_labor_name") | |
new_labor_category = st.text_input("الفئة", key="new_labor_category") | |
new_labor_name = st.text_input("اسم العامل", key="new_labor_name") | |
new_labor_category = st.text_input("الفئة", key="new_labor_category") | |
new_labor_unit = st.text_input("وحدة القياس", key="new_labor_unit") | |
with col2: | |
new_labor_price = st.number_input("السعر (ريال)", min_value=0.0, key="new_labor_price") | |
new_labor_supplier = st.text_input("المورد", key="new_labor_supplier") | |
new_labor_local_content = st.slider("نسبة المحتوى المحلي (%)", 0, 100, 50, key="new_labor_local_content") | |
submitted = st.form_submit_button("إضافة العامل") | |
cancel = st.form_submit_button("إلغاء") | |
if submitted and new_labor_name and new_labor_category and new_labor_unit: | |
# إضافة العامل الجديد | |
new_labor = { | |
'id': max([labor['id'] for labor in st.session_state.labor], default=0) + 1, | |
'name': new_labor_name, | |
'category': new_labor_category, | |
'unit': new_labor_unit, | |
'price': new_labor_price, | |
'supplier': new_labor_supplier, | |
'local_content': new_labor_local_content, | |
'last_updated': datetime.now().strftime('%Y-%m-%d') | |
} | |
st.session_state.labor.append(new_labor) | |
st.success(f"تمت إضافة العامل '{new_labor_name}' بنجاح!") | |
st.session_state.show_labor_form = False | |
st.experimental_rerun() | |
if cancel: | |
st.session_state.show_labor_form = False | |
st.experimental_rerun() | |
# عرض قائمة العمالة | |
if filtered_labor: | |
# تحويل البيانات إلى DataFrame | |
labor_df = pd.DataFrame(filtered_labor) | |
# تنسيق البيانات للعرض | |
display_df = labor_df.copy() | |
display_df['price'] = display_df['price'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['local_content'] = display_df['local_content'].apply(lambda x: f"{x}%") | |
# تغيير أسماء الأعمدة للعرض | |
display_df.columns = [ | |
'معرف', 'اسم العامل', 'الفئة', 'وحدة القياس', 'السعر', 'المورد', 'نسبة المحتوى المحلي', 'آخر تحديث' | |
] | |
# عرض الجدول | |
st.dataframe(display_df, use_container_width=True, hide_index=True) | |
# عرض ملخص إحصائي | |
st.markdown("#### ملخص إحصائي للعمالة") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric("إجمالي عدد العمالة", len(filtered_labor)) | |
with col2: | |
avg_price = sum(labor['price'] for labor in filtered_labor) / len(filtered_labor) | |
st.metric("متوسط سعر العمالة", f"{avg_price:,.2f} ريال") | |
with col3: | |
avg_local_content = sum(labor['local_content'] for labor in filtered_labor) / len(filtered_labor) | |
st.metric("متوسط نسبة المحتوى المحلي", f"{avg_local_content:.1f}%") | |
# عرض مخطط توزيع العمالة حسب الفئة | |
category_counts = labor_df.groupby('category').size().reset_index(name='count') | |
fig = px.pie( | |
category_counts, | |
names='category', | |
values='count', | |
title='توزيع العمالة حسب الفئة' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
else: | |
st.warning("لا توجد عمالة مطابقة لمعايير البحث.") | |
def _render_equipment_tab(self): | |
"""عرض تبويب المعدات""" | |
st.markdown("### إدارة المعدات") | |
# عرض أدوات البحث والتصفية | |
col1, col2 = st.columns(2) | |
with col1: | |
search_query = st.text_input("بحث في المعدات", placeholder="ابحث باسم المعدة أو الفئة أو المورد...") | |
with col2: | |
category_filter = st.multiselect( | |
"تصفية حسب الفئة", | |
options=list(set(equipment['category'] for equipment in st.session_state.equipment)), | |
default=[] | |
) | |
# تطبيق البحث والتصفية | |
filtered_equipment = st.session_state.equipment | |
if search_query: | |
filtered_equipment = [ | |
equipment for equipment in filtered_equipment | |
if (search_query.lower() in equipment['name'].lower() or | |
search_query.lower() in equipment['category'].lower() or | |
search_query.lower() in equipment['supplier'].lower()) | |
] | |
if category_filter: | |
filtered_equipment = [equipment for equipment in filtered_equipment if equipment['category'] in category_filter] | |
# زر إضافة معدة جديدة | |
if st.button("إضافة معدة جديدة"): | |
st.session_state.show_equipment_form = True | |
# نموذج إضافة معدة جديدة | |
if st.session_state.get('show_equipment_form', False): | |
with st.form("add_equipment_form"): | |
st.markdown("#### إضافة معدة جديدة") | |
col1, col2 = st.columns(2) | |
with col1: | |
new_equipment_name = st.text_input("اسم المعدة", key="new_equipment_name") | |
new_equipment_category = st.text_input("الفئة", key="new_equipment_category") | |
new_equipment_unit = st.text_input("وحدة القياس", key="new_equipment_unit") | |
with col2: | |
new_equipment_price = st.number_input("السعر (ريال)", min_value=0.0, key="new_equipment_price") | |
new_equipment_supplier = st.text_input("المورد", key="new_equipment_supplier") | |
new_equipment_local_content = st.slider("نسبة المحتوى المحلي (%)", 0, 100, 50, key="new_equipment_local_content") | |
submitted = st.form_submit_button("إضافة المعدة") | |
cancel = st.form_submit_button("إلغاء") | |
if submitted and new_equipment_name and new_equipment_category and new_equipment_unit: | |
# إضافة المعدة الجديدة | |
new_equipment = { | |
'id': max([equipment['id'] for equipment in st.session_state.equipment], default=0) + 1, | |
'name': new_equipment_name, | |
'category': new_equipment_category, | |
'unit': new_equipment_unit, | |
'price': new_equipment_price, | |
'supplier': new_equipment_supplier, | |
'local_content': new_equipment_local_content, | |
'last_updated': datetime.now().strftime('%Y-%m-%d') | |
} | |
st.session_state.equipment.append(new_equipment) | |
st.success(f"تمت إضافة المعدة '{new_equipment_name}' بنجاح!") | |
st.session_state.show_equipment_form = False | |
st.experimental_rerun() | |
if cancel: | |
st.session_state.show_equipment_form = False | |
st.experimental_rerun() | |
# عرض قائمة المعدات | |
if filtered_equipment: | |
# تحويل البيانات إلى DataFrame | |
equipment_df = pd.DataFrame(filtered_equipment) | |
# تنسيق البيانات للعرض | |
display_df = equipment_df.copy() | |
display_df['price'] = display_df['price'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['local_content'] = display_df['local_content'].apply(lambda x: f"{x}%") | |
# تغيير أسماء الأعمدة للعرض | |
display_df.columns = [ | |
'معرف', 'اسم المعدة', 'الفئة', 'وحدة القياس', 'السعر', 'المورد', 'نسبة المحتوى المحلي', 'آخر تحديث' | |
] | |
# عرض الجدول | |
st.dataframe(display_df, use_container_width=True, hide_index=True) | |
# عرض ملخص إحصائي | |
st.markdown("#### ملخص إحصائي للمعدات") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric("إجمالي عدد المعدات", len(filtered_equipment)) | |
with col2: | |
avg_price = sum(equipment['price'] for equipment in filtered_equipment) / len(filtered_equipment) | |
st.metric("متوسط سعر المعدات", f"{avg_price:,.2f} ريال") | |
with col3: | |
avg_local_content = sum(equipment['local_content'] for equipment in filtered_equipment) / len(filtered_equipment) | |
st.metric("متوسط نسبة المحتوى المحلي", f"{avg_local_content:.1f}%") | |
# عرض مخطط توزيع المعدات حسب الفئة | |
category_counts = equipment_df.groupby('category').size().reset_index(name='count') | |
fig = px.bar( | |
category_counts, | |
x='category', | |
y='count', | |
title='توزيع المعدات حسب الفئة', | |
color='category', | |
labels={'category': 'الفئة', 'count': 'العدد'} | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
else: | |
st.warning("لا توجد معدات مطابقة لمعايير البحث.") | |
def _render_subcontractors_tab(self): | |
"""عرض تبويب المقاولين من الباطن""" | |
st.markdown("### إدارة المقاولين من الباطن") | |
# عرض أدوات البحث والتصفية | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
search_query = st.text_input("بحث في المقاولين", placeholder="ابحث باسم المقاول أو التخصص...") | |
with col2: | |
category_filter = st.multiselect( | |
"تصفية حسب الفئة", | |
options=list(set(subcontractor['category'] for subcontractor in st.session_state.subcontractors)), | |
default=[] | |
) | |
with col3: | |
city_filter = st.multiselect( | |
"تصفية حسب المدينة", | |
options=list(set(subcontractor['city'] for subcontractor in st.session_state.subcontractors)), | |
default=[] | |
) | |
# تطبيق البحث والتصفية | |
filtered_subcontractors = st.session_state.subcontractors | |
if search_query: | |
filtered_subcontractors = [ | |
subcontractor for subcontractor in filtered_subcontractors | |
if (search_query.lower() in subcontractor['name'].lower() or | |
search_query.lower() in subcontractor['specialization'].lower()) | |
] | |
if category_filter: | |
filtered_subcontractors = [subcontractor for subcontractor in filtered_subcontractors if subcontractor['category'] in category_filter] | |
if city_filter: | |
filtered_subcontractors = [subcontractor for subcontractor in filtered_subcontractors if subcontractor['city'] in city_filter] | |
# زر إضافة مقاول جديد | |
if st.button("إضافة مقاول جديد"): | |
st.session_state.show_subcontractor_form = True | |
# نموذج إضافة مقاول جديد | |
if st.session_state.get('show_subcontractor_form', False): | |
with st.form("add_subcontractor_form"): | |
st.markdown("#### إضافة مقاول جديد") | |
col1, col2 = st.columns(2) | |
with col1: | |
new_subcontractor_name = st.text_input("اسم المقاول", key="new_subcontractor_name") | |
new_subcontractor_category = st.text_input("الفئة", key="new_subcontractor_category") | |
new_subcontractor_specialization = st.text_input("التخصص", key="new_subcontractor_specialization") | |
new_subcontractor_city = st.text_input("المدينة", key="new_subcontractor_city") | |
with col2: | |
new_subcontractor_contact = st.text_input("جهة الاتصال", key="new_subcontractor_contact") | |
new_subcontractor_phone = st.text_input("رقم الهاتف", key="new_subcontractor_phone") | |
new_subcontractor_email = st.text_input("البريد الإلكتروني", key="new_subcontractor_email") | |
new_subcontractor_rating = st.slider("التقييم", 1.0, 5.0, 3.0, 0.1, key="new_subcontractor_rating") | |
new_subcontractor_local_content = st.slider("نسبة المحتوى المحلي (%)", 0, 100, 50, key="new_subcontractor_local_content") | |
submitted = st.form_submit_button("إضافة المقاول") | |
cancel = st.form_submit_button("إلغاء") | |
if submitted and new_subcontractor_name and new_subcontractor_category and new_subcontractor_specialization: | |
# إضافة المقاول الجديد | |
new_subcontractor = { | |
'id': max([subcontractor['id'] for subcontractor in st.session_state.subcontractors], default=0) + 1, | |
'name': new_subcontractor_name, | |
'category': new_subcontractor_category, | |
'specialization': new_subcontractor_specialization, | |
'rating': new_subcontractor_rating, | |
'city': new_subcontractor_city, | |
'contact_person': new_subcontractor_contact, | |
'phone': new_subcontractor_phone, | |
'email': new_subcontractor_email, | |
'local_content': new_subcontractor_local_content, | |
'last_updated': datetime.now().strftime('%Y-%m-%d') | |
} | |
st.session_state.subcontractors.append(new_subcontractor) | |
st.success(f"تمت إضافة المقاول '{new_subcontractor_name}' بنجاح!") | |
st.session_state.show_subcontractor_form = False | |
st.experimental_rerun() | |
if cancel: | |
st.session_state.show_subcontractor_form = False | |
st.experimental_rerun() | |
# عرض قائمة المقاولين | |
if filtered_subcontractors: | |
# تحويل البيانات إلى DataFrame | |
subcontractors_df = pd.DataFrame(filtered_subcontractors) | |
# تنسيق البيانات للعرض | |
display_df = subcontractors_df.copy() | |
display_df['local_content'] = display_df['local_content'].apply(lambda x: f"{x}%") | |
# تغيير أسماء الأعمدة للعرض | |
display_df.columns = [ | |
'معرف', 'اسم المقاول', 'الفئة', 'التخصص', 'التقييم', 'المدينة', | |
'جهة الاتصال', 'رقم الهاتف', 'البريد الإلكتروني', 'نسبة المحتوى المحلي', 'آخر تحديث' | |
] | |
# عرض الجدول | |
st.dataframe(display_df, use_container_width=True, hide_index=True) | |
# عرض ملخص إحصائي | |
st.markdown("#### ملخص إحصائي للمقاولين") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric("إجمالي عدد المقاولين", len(filtered_subcontractors)) | |
with col2: | |
avg_rating = sum(subcontractor['rating'] for subcontractor in filtered_subcontractors) / len(filtered_subcontractors) | |
st.metric("متوسط التقييم", f"{avg_rating:.1f}/5.0") | |
with col3: | |
avg_local_content = sum(subcontractor['local_content'] for subcontractor in filtered_subcontractors) / len(filtered_subcontractors) | |
st.metric("متوسط نسبة المحتوى المحلي", f"{avg_local_content:.1f}%") | |
# عرض مخطط توزيع المقاولين حسب الفئة | |
category_counts = subcontractors_df.groupby('category').size().reset_index(name='count') | |
fig = px.pie( | |
category_counts, | |
names='category', | |
values='count', | |
title='توزيع المقاولين حسب الفئة', | |
hole=0.4 | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض مخطط توزيع المقاولين حسب المدينة | |
city_counts = subcontractors_df.groupby('city').size().reset_index(name='count') | |
fig = px.bar( | |
city_counts, | |
x='city', | |
y='count', | |
title='توزيع المقاولين حسب المدينة', | |
color='city', | |
labels={'city': 'المدينة', 'count': 'العدد'} | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
else: | |
st.warning("لا يوجد مقاولين مطابقين لمعايير البحث.") | |
def _render_price_analysis_tab(self): | |
"""عرض تبويب تحليل الأسعار""" | |
st.markdown("### تحليل الأسعار") | |
# اختيار نوع التحليل | |
analysis_type = st.radio( | |
"نوع التحليل", | |
["تحليل أسعار المواد", "مقارنة الأسعار", "توقع الأسعار المستقبلية"], | |
horizontal=True | |
) | |
if analysis_type == "تحليل أسعار المواد": | |
self._render_material_price_analysis() | |
elif analysis_type == "مقارنة الأسعار": | |
self._render_price_comparison() | |
else: | |
self._render_price_forecast() | |
def _render_material_price_analysis(self): | |
"""عرض تحليل أسعار المواد""" | |
st.markdown("#### تحليل أسعار المواد") | |
# اختيار المواد للتحليل | |
material_options = [material['name'] for material in st.session_state.materials] | |
selected_materials = st.multiselect( | |
"اختر المواد للتحليل", | |
options=material_options, | |
default=material_options[:3] if len(material_options) >= 3 else material_options | |
) | |
if not selected_materials: | |
st.warning("الرجاء اختيار مادة واحدة على الأقل للتحليل.") | |
return | |
# إعداد البيانات للتحليل | |
material_ids = {material['name']: material['id'] for material in st.session_state.materials} | |
selected_ids = [material_ids[name] for name in selected_materials if name in material_ids] | |
price_history_data = [] | |
for entry in st.session_state.price_history: | |
if entry['material_id'] in selected_ids: | |
material_name = next((material['name'] for material in st.session_state.materials if material['id'] == entry['material_id']), "") | |
price_history_data.append({ | |
'المادة': material_name, | |
'التاريخ': pd.to_datetime(entry['date']), | |
'السعر': entry['price'] | |
}) | |
if not price_history_data: | |
st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.") | |
return | |
# تحويل البيانات إلى DataFrame | |
price_history_df = pd.DataFrame(price_history_data) | |
# عرض المخطط الخطي للأسعار | |
fig = px.line( | |
price_history_df, | |
x='التاريخ', | |
y='السعر', | |
color='المادة', | |
title='تطور أسعار المواد المختارة', | |
labels={'السعر': 'السعر (ريال)'} | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# حساب التغيرات في الأسعار | |
materials_price_changes = [] | |
for material_name in selected_materials: | |
material_prices = price_history_df[price_history_df['المادة'] == material_name].sort_values('التاريخ') | |
if len(material_prices) >= 2: | |
first_price = material_prices.iloc[0]['السعر'] | |
last_price = material_prices.iloc[-1]['السعر'] | |
price_change = last_price - first_price | |
price_change_percent = (price_change / first_price) * 100 | |
# حساب التقلب (الانحراف المعياري) | |
price_volatility = material_prices['السعر'].std() | |
materials_price_changes.append({ | |
'المادة': material_name, | |
'السعر الأول': first_price, | |
'السعر الأخير': last_price, | |
'التغير المطلق': price_change, | |
'نسبة التغير (%)': price_change_percent, | |
'التقلب (الانحراف المعياري)': price_volatility | |
}) | |
# عرض جدول التغيرات في الأسعار | |
if materials_price_changes: | |
st.markdown("#### تغيرات الأسعار خلال الفترة") | |
changes_df = pd.DataFrame(materials_price_changes) | |
# تنسيق البيانات للعرض | |
display_df = changes_df.copy() | |
display_df['السعر الأول'] = display_df['السعر الأول'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['السعر الأخير'] = display_df['السعر الأخير'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['التغير المطلق'] = display_df['التغير المطلق'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['نسبة التغير (%)'] = display_df['نسبة التغير (%)'].apply(lambda x: f"{x:.2f}%") | |
display_df['التقلب (الانحراف المعياري)'] = display_df['التقلب (الانحراف المعياري)'].apply(lambda x: f"{x:.2f}") | |
st.dataframe(display_df, use_container_width=True, hide_index=True) | |
# عرض مخطط شريطي للتغيرات في الأسعار | |
fig = px.bar( | |
changes_df, | |
x='المادة', | |
y='نسبة التغير (%)', | |
title='نسبة التغير في الأسعار', | |
color='المادة', | |
text_auto='.1f' | |
) | |
fig.update_traces(texttemplate='%{text}%', textposition='outside') | |
st.plotly_chart(fig, use_container_width=True) | |
def _render_price_comparison(self): | |
"""عرض مقارنة الأسعار""" | |
st.markdown("#### مقارنة الأسعار") | |
# اختيار نوع المورد للمقارنة | |
resource_type = st.selectbox( | |
"نوع المورد", | |
["المواد", "العمالة", "المعدات"] | |
) | |
if resource_type == "المواد": | |
resources = st.session_state.materials | |
elif resource_type == "العمالة": | |
resources = st.session_state.labor | |
else: | |
resources = st.session_state.equipment | |
# اختيار الفئة للمقارنة | |
categories = list(set([resource['category'] for resource in resources])) | |
selected_category = st.selectbox( | |
"الفئة", | |
options=["الكل"] + categories | |
) | |
# فلترة الموارد حسب الفئة | |
if selected_category != "الكل": | |
filtered_resources = [resource for resource in resources if resource['category'] == selected_category] | |
else: | |
filtered_resources = resources | |
if not filtered_resources: | |
st.warning("لا توجد موارد مطابقة للفئة المختارة.") | |
return | |
# إعداد بيانات المقارنة | |
comparison_data = [] | |
for resource in filtered_resources: | |
comparison_data.append({ | |
'الاسم': resource['name'], | |
'الفئة': resource['category'], | |
'الوحدة': resource['unit'], | |
'السعر': resource['price'], | |
'المورد': resource['supplier'], | |
'نسبة المحتوى المحلي': resource['local_content'] | |
}) | |
# تحويل البيانات إلى DataFrame | |
comparison_df = pd.DataFrame(comparison_data) | |
# عرض المخطط الشريطي للأسعار | |
fig = px.bar( | |
comparison_df, | |
x='الاسم', | |
y='السعر', | |
title=f'مقارنة أسعار {resource_type}', | |
color='الفئة' if selected_category == "الكل" else 'المورد', | |
text_auto='.2s', | |
labels={'السعر': 'السعر (ريال)'} | |
) | |
fig.update_traces(texttemplate='%{text} ريال', textposition='outside') | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض العلاقة بين السعر ونسبة المحتوى المحلي | |
fig = px.scatter( | |
comparison_df, | |
x='نسبة المحتوى المحلي', | |
y='السعر', | |
color='الفئة' if selected_category == "الكل" else None, | |
title='العلاقة بين السعر ونسبة المحتوى المحلي', | |
labels={'نسبة المحتوى المحلي': 'نسبة المحتوى المحلي (%)', 'السعر': 'السعر (ريال)'}, | |
size=[50] * len(comparison_df), | |
text='الاسم' | |
) | |
fig.update_traces(textposition='top center') | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض جدول المقارنة | |
st.markdown("#### جدول مقارنة الأسعار") | |
# تنسيق البيانات للعرض | |
display_df = comparison_df.copy() | |
display_df['السعر'] = display_df['السعر'].apply(lambda x: f"{x:,.2f} ريال") | |
display_df['نسبة المحتوى المحلي'] = display_df['نسبة المحتوى المحلي'].apply(lambda x: f"{x}%") | |
st.dataframe(display_df, use_container_width=True, hide_index=True) | |
def _render_price_forecast(self): | |
"""عرض توقع الأسعار المستقبلية""" | |
st.markdown("#### توقع الأسعار المستقبلية") | |
# اختيار المادة للتوقع | |
material_options = [material['name'] for material in st.session_state.materials] | |
selected_material = st.selectbox( | |
"اختر المادة للتوقع", | |
options=material_options | |
) | |
# اختيار فترة التوقع | |
forecast_period = st.slider( | |
"فترة التوقع (أشهر)", | |
min_value=1, | |
max_value=12, | |
value=6 | |
) | |
if not selected_material: | |
st.warning("الرجاء اختيار مادة للتوقع.") | |
return | |
# الحصول على معرف المادة | |
material_id = next((material['id'] for material in st.session_state.materials if material['name'] == selected_material), None) | |
if material_id is None: | |
st.error("المادة المحددة غير موجودة.") | |
return | |
# الحصول على بيانات الأسعار التاريخية | |
price_history_data = [] | |
for entry in st.session_state.price_history: | |
if entry['material_id'] == material_id: | |
price_history_data.append({ | |
'التاريخ': pd.to_datetime(entry['date']), | |
'السعر': entry['price'] | |
}) | |
if not price_history_data: | |
st.warning("لا توجد بيانات تاريخية كافية للمادة المحددة للقيام بالتوقع.") | |
return | |
# تحويل البيانات إلى DataFrame | |
price_history_df = pd.DataFrame(price_history_data).sort_values('التاريخ') | |
# إجراء التوقع | |
# في الواقع، ستستخدم نماذج تعلم آلي مثل ARIMA أو Prophet | |
# هنا سنستخدم توقعًا بسيطًا للأغراض التوضيحية | |
# حساب متوسط التغير الشهري | |
monthly_changes = [] | |
for i in range(1, len(price_history_df)): | |
monthly_changes.append(price_history_df.iloc[i]['السعر'] - price_history_df.iloc[i-1]['السعر']) | |
if monthly_changes: | |
avg_monthly_change = sum(monthly_changes) / len(monthly_changes) | |
else: | |
avg_monthly_change = 0 | |
# إنشاء بيانات التوقع | |
last_date = price_history_df['التاريخ'].max() | |
last_price = price_history_df.loc[price_history_df['التاريخ'] == last_date, 'السعر'].values[0] | |
forecast_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=forecast_period, freq='M') | |
forecast_prices = [last_price + (i+1) * avg_monthly_change for i in range(forecast_period)] | |
# إضافة بعض التقلبات العشوائية للتوقع | |
forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices] | |
forecast_df = pd.DataFrame({ | |
'التاريخ': forecast_dates, | |
'السعر': forecast_prices, | |
'النوع': ['توقع'] * forecast_period | |
}) | |
# دمج البيانات التاريخية والتوقع | |
historical_df = price_history_df.copy() | |
historical_df['النوع'] = ['تاريخي'] * len(historical_df) | |
combined_df = pd.concat([historical_df, forecast_df], ignore_index=True) | |
# عرض المخطط | |
fig = px.line( | |
combined_df, | |
x='التاريخ', | |
y='السعر', | |
color='النوع', | |
title=f'توقع أسعار {selected_material} للـ {forecast_period} أشهر القادمة', | |
labels={'السعر': 'السعر (ريال)'}, | |
color_discrete_map={'تاريخي': 'blue', 'توقع': 'red'} | |
) | |
# إضافة فترة الثقة حول التوقع | |
confidence = 0.1 # 10% فترة ثقة | |
upper_bound = [price * (1 + confidence) for price in forecast_prices] | |
lower_bound = [price * (1 - confidence) for price in forecast_prices] | |
fig.add_scatter( | |
x=forecast_dates, | |
y=upper_bound, | |
fill=None, | |
mode='lines', | |
line_color='rgba(255, 0, 0, 0.3)', | |
line_width=0, | |
showlegend=False | |
) | |
fig.add_scatter( | |
x=forecast_dates, | |
y=lower_bound, | |
fill='tonexty', | |
mode='lines', | |
line_color='rgba(255, 0, 0, 0.3)', | |
line_width=0, | |
name='فترة الثقة (±10%)' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض جدول التوقع | |
st.markdown("#### جدول توقع الأسعار") | |
forecast_table = forecast_df.copy() | |
forecast_table['التاريخ'] = forecast_table['التاريخ'].dt.strftime('%Y-%m') | |
forecast_table['السعر'] = forecast_table['السعر'].apply(lambda x: f"{x:,.2f} ريال") | |
forecast_table = forecast_table.drop(columns=['النوع']) | |
st.dataframe(forecast_table, use_container_width=True, hide_index=True) | |
# عرض ملخص التوقع | |
st.markdown("#### ملخص التوقع") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric( | |
"السعر الحالي", | |
f"{last_price:,.2f} ريال" | |
) | |
with col2: | |
forecasted_price = forecast_prices[-1] | |
price_change = forecasted_price - last_price | |
price_change_percent = (price_change / last_price) * 100 | |
st.metric( | |
f"السعر المتوقع بعد {forecast_period} أشهر", | |
f"{forecasted_price:,.2f} ريال", | |
delta=f"{price_change_percent:.1f}%" | |
) | |
with col3: | |
avg_forecasted_price = sum(forecast_prices) / len(forecast_prices) | |
st.metric( | |
"متوسط السعر المتوقع", | |
f"{avg_forecasted_price:,.2f} ريال" | |
) | |
# عرض ملاحظات وتوصيات | |
if price_change_percent > 10: | |
st.warning(""" | |
### توقع ارتفاع كبير في الأسعار | |
- ينصح بشراء المواد مبكراً وتخزينها إذا أمكن | |
- التفاوض على عقود توريد طويلة الأجل بأسعار ثابتة | |
- البحث عن موردين بديلين أو مواد بديلة | |
""") | |
elif price_change_percent < -10: | |
st.success(""" | |
### توقع انخفاض كبير في الأسعار | |
- ينصح بتأجيل شراء المواد إذا أمكن | |
- شراء كميات أقل والاحتفاظ بمخزون منخفض | |
- التفاوض على عقود مرنة مع الموردين | |
""") | |
else: | |
st.info(""" | |
### توقع استقرار نسبي في الأسعار | |
- يمكن الشراء حسب الاحتياج دون الحاجة لتخزين كميات كبيرة | |
- متابعة أسعار السوق بشكل دوري للتأكد من دقة التوقعات | |
""") |