Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	| """ | |
| تطبيق وحدة التسعير المتكاملة | |
| """ | |
| 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 | |
| import random | |
| import os | |
| import time | |
| import io | |
| from modules.pricing.services.standard_pricing import StandardPricing | |
| from modules.pricing.services.unbalanced_pricing import UnbalancedPricing | |
| from modules.pricing.services.local_content_calculator import LocalContentCalculator | |
| from modules.pricing.services.price_prediction import PricePrediction | |
| from utils.excel_handler import export_to_excel | |
| from utils.helpers import format_number, format_currency | |
| class PricingApp: | |
| """وحدة التسعير المتكاملة""" | |
| def __init__(self): | |
| """تهيئة وحدة التسعير المتكاملة""" | |
| self.pricing_methods = [ | |
| "التسعير القياسي", | |
| "التسعير غير المتزن", | |
| "التسعير التنافسي", | |
| "التسعير الموجه بالربحية" | |
| ] | |
| # تهيئة خدمات التسعير | |
| self.standard_pricing = StandardPricing() | |
| self.unbalanced_pricing = UnbalancedPricing() | |
| self.local_content = LocalContentCalculator() | |
| self.price_prediction = PricePrediction() | |
| def render(self): | |
| """عرض واجهة وحدة التسعير""" | |
| st.markdown("<h1 class='module-title'>وحدة التسعير المتكاملة</h1>", unsafe_allow_html=True) | |
| tabs = st.tabs([ | |
| "إنشاء تسعير جديد", | |
| "تحليل سعر البند", | |
| "نموذج التسعير الشامل", | |
| "التسعير غير المتزن", | |
| "المحتوى المحلي" | |
| ]) | |
| with tabs[0]: | |
| self._render_new_pricing_tab() | |
| with tabs[1]: | |
| self._render_item_analysis_tab() | |
| with tabs[2]: | |
| self._render_comprehensive_pricing_tab() | |
| with tabs[3]: | |
| self._render_unbalanced_pricing_tab() | |
| with tabs[4]: | |
| self._render_local_content_tab() | |
| def _render_item_analysis_tab(self): | |
| """عرض تبويب تحليل سعر البند""" | |
| st.markdown("### تحليل سعر البند") | |
| # التحقق من وجود تسعير حالي | |
| if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: | |
| st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") | |
| return | |
| # اختيار البند للتحليل | |
| if 'current_pricing' in st.session_state and st.session_state.current_pricing is not None: | |
| items = st.session_state.current_pricing['items'] | |
| item_options = items['رقم البند'].tolist() | |
| selected_item = st.selectbox("اختر البند للتحليل", item_options) | |
| if selected_item: | |
| item_data = items[items['رقم البند'] == selected_item].iloc[0] | |
| st.markdown(f"### تحليل البند: {selected_item}") | |
| st.markdown(f"**وصف البند**: {item_data['وصف البند']}") | |
| st.markdown(f"**الوحدة**: {item_data['الوحدة']}") | |
| st.markdown(f"**الكمية**: {item_data['الكمية']}") | |
| st.markdown(f"**سعر الوحدة**: {item_data['سعر الوحدة']:,.2f} ريال") | |
| # تحليل مكونات السعر | |
| st.markdown("### تحليل مكونات السعر") | |
| # عناصر التكلفة الافتراضية | |
| cost_components = { | |
| 'المواد': 0.6, # 60% من التكلفة | |
| 'العمالة': 0.25, # 25% من التكلفة | |
| 'المعدات': 0.1, # 10% من التكلفة | |
| 'نفقات عامة': 0.05 # 5% من التكلفة | |
| } | |
| # حساب تكلفة كل عنصر | |
| unit_price = item_data['سعر الوحدة'] | |
| component_values = {k: v * unit_price for k, v in cost_components.items()} | |
| # عرض مكونات التكلفة في جدول | |
| components_df = pd.DataFrame({ | |
| 'العنصر': component_values.keys(), | |
| 'نسبة من التكلفة': [f"{v*100:.1f}%" for v in cost_components.values()], | |
| 'القيمة (ريال)': [f"{v:,.2f}" for v in component_values.values()] | |
| }) | |
| st.table(components_df) | |
| # رسم بياني لمكونات التكلفة | |
| fig = px.pie( | |
| names=list(component_values.keys()), | |
| values=list(component_values.values()), | |
| title='توزيع مكونات التكلفة' | |
| ) | |
| st.plotly_chart(fig) | |
| # تحليل تاريخي للأسعار | |
| st.markdown("### تحليل تاريخي للأسعار") | |
| # بيانات تاريخية افتراضية | |
| historical_data = { | |
| 'التاريخ': ['2020-01', '2020-07', '2021-01', '2021-07', '2022-01', '2022-07', '2023-01', '2023-07'], | |
| 'السعر': [ | |
| unit_price * 0.7, | |
| unit_price * 0.75, | |
| unit_price * 0.8, | |
| unit_price * 0.85, | |
| unit_price * 0.9, | |
| unit_price * 0.95, | |
| unit_price, | |
| unit_price * 1.05 | |
| ] | |
| } | |
| hist_df = pd.DataFrame(historical_data) | |
| # رسم بياني للتحليل التاريخي | |
| fig = px.line( | |
| hist_df, | |
| x='التاريخ', | |
| y='السعر', | |
| title='تطور سعر الوحدة عبر الزمن', | |
| markers=True | |
| ) | |
| st.plotly_chart(fig) | |
| # المقارنة مع الأسعار المرجعية | |
| st.markdown("### المقارنة مع الأسعار المرجعية") | |
| # بيانات مرجعية افتراضية | |
| reference_data = { | |
| 'المصدر': ['قاعدة البيانات الداخلية', 'دليل الأسعار الاسترشادي', 'متوسط أسعار السوق', 'أسعار المشاريع المماثلة'], | |
| 'السعر المرجعي': [ | |
| unit_price * 0.95, | |
| unit_price * 1.05, | |
| unit_price * 1.1, | |
| unit_price * 0.9 | |
| ] | |
| } | |
| ref_df = pd.DataFrame(reference_data) | |
| ref_df['الفرق عن السعر الحالي'] = ref_df['السعر المرجعي'] - unit_price | |
| ref_df['نسبة الفرق'] = (ref_df['الفرق عن السعر الحالي'] / unit_price * 100).round(2).astype(str) + '%' | |
| st.table(ref_df) | |
| def _render_new_pricing_tab(self): | |
| """عرض تبويب إنشاء تسعير جديد""" | |
| st.markdown("### إنشاء تسعير جديد") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| tender_name = st.text_input("اسم المناقصة") | |
| client = st.text_input("الجهة المالكة") | |
| pricing_method = st.selectbox("طريقة التسعير", self.pricing_methods) | |
| with col2: | |
| tender_number = st.text_input("رقم المناقصة") | |
| location = st.text_input("الموقع") | |
| submission_date = st.date_input("تاريخ التقديم") | |
| # خيارات بيانات البنود | |
| st.markdown("### بيانات البنود") | |
| data_source = st.radio( | |
| "مصدر بيانات البنود", | |
| ["إدخال يدوي", "استيراد من Excel", "استيراد من وحدة تحليل المستندات"] | |
| ) | |
| if data_source == "إدخال يدوي": | |
| # ضبط CSS لتحسين ظهور الواجهة العربية | |
| st.markdown(""" | |
| <style> | |
| input, .stTextArea textarea { | |
| direction: rtl; | |
| text-align: right; | |
| font-family: 'Arial', 'Tahoma', sans-serif !important; | |
| } | |
| .stTextInput > div > div > input { | |
| text-align: right; | |
| direction: rtl; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # تهيئة قائمة الوحدات المتاحة | |
| unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"] | |
| # إنشاء بيانات افتراضية إذا لم تكن موجودة | |
| if 'manual_items' not in st.session_state: | |
| # إنشاء DataFrame فارغ | |
| manual_items = pd.DataFrame(columns=[ | |
| 'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي' | |
| ]) | |
| # إضافة بضعة صفوف افتراضية | |
| default_items = pd.DataFrame({ | |
| 'رقم البند': ["A1", "A2", "A3", "A4", "A5"], | |
| 'وصف البند': [ | |
| "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", | |
| "توريد وتركيب حديد التسليح للأساسات", | |
| "أعمال العزل المائي للأساسات", | |
| "أعمال الردم والدك للأساسات", | |
| "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة" | |
| ], | |
| 'الوحدة': ["م3", "طن", "م2", "م3", "م3"], | |
| 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0], | |
| 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0], | |
| 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0] | |
| }) | |
| manual_items = pd.concat([manual_items, default_items]) | |
| st.session_state.manual_items = manual_items | |
| # عرض واجهة إدخال البنود | |
| st.markdown("### إدخال تفاصيل البنود") | |
| # التحقق من استخدام طريقة الإدخال البسيطة | |
| use_simple_input = st.checkbox("استخدام طريقة الإدخال البسيطة", value=True) | |
| if use_simple_input: | |
| # عرض البنود الحالية كجدول للعرض فقط | |
| st.markdown("### جدول البنود الحالية") | |
| st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True) | |
| # إضافة بند جديد | |
| st.markdown("### إضافة بند جديد") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| new_id = st.text_input("رقم البند", value=f"A{len(st.session_state.manual_items)+1}") | |
| new_desc = st.text_area("وصف البند", value="") | |
| with col2: | |
| new_unit = st.selectbox("الوحدة", options=unit_options) | |
| new_qty = st.number_input("الكمية", value=0.0, min_value=0.0, format="%.2f") | |
| new_price = st.number_input("سعر الوحدة", value=0.0, min_value=0.0, format="%.2f") | |
| new_total = new_qty * new_price | |
| st.info(f"إجمالي البند الجديد: {new_total:,.2f} ريال") | |
| if st.button("إضافة البند"): | |
| # التحقق من صحة البيانات | |
| if new_id and new_desc and new_qty > 0: | |
| # إنشاء صف جديد | |
| new_row = pd.DataFrame({ | |
| 'رقم البند': [new_id], | |
| 'وصف البند': [new_desc], | |
| 'الوحدة': [new_unit], | |
| 'الكمية': [float(new_qty)], | |
| 'سعر الوحدة': [float(new_price)], | |
| 'الإجمالي': [float(new_total)] | |
| }) | |
| # إضافة الصف إلى DataFrame | |
| st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True) | |
| st.success("تم إضافة البند بنجاح!") | |
| st.rerun() | |
| else: | |
| st.error("يرجى ملء جميع الحقول المطلوبة: رقم البند، الوصف، والكمية يجب أن تكون أكبر من صفر.") | |
| # تعديل البنود الحالية | |
| st.markdown("### تعديل البنود الحالية") | |
| # تحديد البند المراد تعديله | |
| item_to_edit = st.selectbox( | |
| "اختر البند للتعديل", | |
| options=st.session_state.manual_items['رقم البند'].tolist(), | |
| format_func=lambda x: f"{x}: {st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == x]['وصف البند'].values[0][:30]}..." | |
| ) | |
| if item_to_edit: | |
| # الحصول على مؤشر الصف للبند المحدد | |
| idx = st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == item_to_edit].index[0] | |
| row = st.session_state.manual_items.loc[idx] | |
| # إنشاء نموذج تعديل | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| edited_id = st.text_input("رقم البند (تعديل)", value=row['رقم البند'], key="edit_id") | |
| edited_desc = st.text_area("وصف البند (تعديل)", value=row['وصف البند'], key="edit_desc") | |
| with col2: | |
| edited_unit = st.selectbox( | |
| "الوحدة (تعديل)", | |
| options=unit_options, | |
| index=unit_options.index(row['الوحدة']) if row['الوحدة'] in unit_options else 0, | |
| key="edit_unit" | |
| ) | |
| edited_qty = st.number_input("الكمية (تعديل)", value=float(row['الكمية']), min_value=0.0, format="%.2f", key="edit_qty") | |
| edited_price = st.number_input("سعر الوحدة (تعديل)", value=float(row['سعر الوحدة']), min_value=0.0, format="%.2f", key="edit_price") | |
| edited_total = edited_qty * edited_price | |
| st.info(f"إجمالي البند بعد التعديل: {edited_total:,.2f} ريال") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("حفظ التعديلات"): | |
| # تحديث البند | |
| st.session_state.manual_items.at[idx, 'رقم البند'] = edited_id | |
| st.session_state.manual_items.at[idx, 'وصف البند'] = edited_desc | |
| st.session_state.manual_items.at[idx, 'الوحدة'] = edited_unit | |
| st.session_state.manual_items.at[idx, 'الكمية'] = edited_qty | |
| st.session_state.manual_items.at[idx, 'سعر الوحدة'] = edited_price | |
| st.session_state.manual_items.at[idx, 'الإجمالي'] = edited_total | |
| st.success("تم تحديث البند بنجاح!") | |
| st.rerun() | |
| with col2: | |
| if st.button("حذف هذا البند"): | |
| st.session_state.manual_items = st.session_state.manual_items.drop(idx).reset_index(drop=True) | |
| st.warning("تم حذف البند!") | |
| st.rerun() | |
| # المجموع الكلي | |
| total = st.session_state.manual_items['الإجمالي'].sum() | |
| st.metric("المجموع الكلي", f"{total:,.2f} ريال") | |
| # جعل هذه البيانات متاحة للاستخدام في الخطوات التالية | |
| edited_items = st.session_state.manual_items.copy() | |
| else: | |
| # عرض رسالة توضح أن طريقة الإدخال البسيطة هي الأفضل | |
| st.warning("لتجنب مشاكل عدم التوافق في أنواع البيانات، يُفضل استخدام طريقة الإدخال البسيطة.") | |
| # محاولة استخدام المحرر القياسي مع معالجة الأخطاء | |
| try: | |
| # تحويل البيانات إلى الأنواع المناسبة | |
| for col in st.session_state.manual_items.columns: | |
| if col in ['رقم البند', 'وصف البند', 'الوحدة']: | |
| st.session_state.manual_items[col] = st.session_state.manual_items[col].astype(str) | |
| # عرض المحرر (للقراءة فقط) | |
| st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True) | |
| # إنشاء نظام تعديل منفصل | |
| st.markdown("### تعديل أسعار الوحدات") | |
| for idx, row in st.session_state.manual_items.iterrows(): | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| st.text(f"{row['رقم البند']}: {row['وصف البند'][:50]}") | |
| with col2: | |
| price = st.number_input( | |
| f"سعر الوحدة ({row['الوحدة']})", | |
| value=float(row['سعر الوحدة']), | |
| min_value=0.0, | |
| key=f"price_{idx}" | |
| ) | |
| # تحديث السعر والإجمالي | |
| st.session_state.manual_items.at[idx, 'سعر الوحدة'] = price | |
| st.session_state.manual_items.at[idx, 'الإجمالي'] = price * row['الكمية'] | |
| # المجموع الكلي | |
| total = st.session_state.manual_items['الإجمالي'].sum() | |
| st.metric("المجموع الكلي", f"{total:,.2f} ريال") | |
| # جعل هذه البيانات متاحة للاستخدام في الخطوات التالية | |
| edited_items = st.session_state.manual_items.copy() | |
| except Exception as e: | |
| st.error(f"حدث خطأ: {str(e)}") | |
| st.info("يرجى استخدام طريقة الإدخال البسيطة لتجنب هذه المشكلة.") | |
| elif data_source == "استيراد من Excel": | |
| uploaded_file = st.file_uploader("رفع ملف Excel", type=["xlsx", "xls"]) | |
| if uploaded_file is not None: | |
| st.success("تم رفع الملف بنجاح") | |
| # محاكاة قراءة الملف | |
| st.markdown("### معاينة البيانات المستوردة") | |
| # إنشاء بيانات افتراضية | |
| import_items = pd.DataFrame({ | |
| 'رقم البند': ["A1", "A2", "A3", "A4", "A5", "A6", "A7"], | |
| 'وصف البند': [ | |
| "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", | |
| "توريد وتركيب حديد التسليح للأساسات", | |
| "أعمال العزل المائي للأساسات", | |
| "أعمال الردم والدك للأساسات", | |
| "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", | |
| "توريد وتركيب حديد التسليح للأعمدة", | |
| "أعمال البلوك للجدران" | |
| ], | |
| 'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"], | |
| 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0, 10.0, 400.0], | |
| 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], | |
| 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] | |
| }) | |
| st.dataframe(import_items) | |
| if st.button("استيراد البيانات"): | |
| st.session_state.manual_items = import_items.copy() | |
| st.session_state.manual_items_modified = True | |
| st.success("تم استيراد البيانات بنجاح!") | |
| st.rerun() | |
| else: # استيراد من وحدة تحليل المستندات | |
| available_documents = [ | |
| "كراسة شروط مشروع توسعة مستشفى الملك فهد", | |
| "جدول كميات صيانة محطات المياه", | |
| "مخططات إنشاء مدرسة ثانوية" | |
| ] | |
| selected_doc = st.selectbox("اختر المستند", available_documents) | |
| if st.button("استيراد البيانات من تحليل المستند"): | |
| # محاكاة استيراد البيانات | |
| with st.spinner("جاري استيراد البيانات..."): | |
| time.sleep(2) | |
| # إنشاء بيانات افتراضية | |
| doc_items = pd.DataFrame({ | |
| 'رقم البند': ["A1", "A2", "A3", "A4", "A5", "A6", "A7"], | |
| 'وصف البند': [ | |
| "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", | |
| "توريد وتركيب حديد التسليح للأساسات", | |
| "أعمال العزل المائي للأساسات", | |
| "أعمال الردم والدك للأساسات", | |
| "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", | |
| "توريد وتركيب حديد التسليح للأعمدة", | |
| "أعمال البلوك للجدران" | |
| ], | |
| 'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"], | |
| 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0, 10.0, 400.0], | |
| 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], | |
| 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] | |
| }) | |
| st.session_state.manual_items = doc_items.copy() | |
| st.success("تم استيراد البيانات من تحليل المستند بنجاح!") | |
| st.dataframe(doc_items) | |
| # زر بدء التسعير | |
| if st.button("بدء التسعير"): | |
| # تحقق من صحة البيانات | |
| if 'manual_items' in st.session_state and not st.session_state.manual_items.empty: | |
| # التأكد من حساب الإجمالي قبل الحفظ | |
| st.session_state.manual_items['الإجمالي'] = st.session_state.manual_items['الكمية'] * st.session_state.manual_items['سعر الوحدة'] | |
| # حفظ بيانات التسعير الحالي | |
| st.session_state.current_pricing = { | |
| 'name': tender_name, | |
| 'number': tender_number, | |
| 'client': client, | |
| 'location': location, | |
| 'method': pricing_method, | |
| 'submission_date': submission_date, | |
| 'items': st.session_state.manual_items.copy(), | |
| 'status': 'جديد', | |
| 'created_at': datetime.now() | |
| } | |
| # الانتقال إلى تبويب نموذج التسعير الشامل | |
| st.success("تم إنشاء التسعير بنجاح! يمكنك الانتقال إلى نموذج التسعير الشامل.") | |
| else: | |
| st.error("يرجى إدخال بيانات البنود أولاً.") | |
| def _render_comprehensive_pricing_tab(self): | |
| """عرض تبويب نموذج التسعير الشامل""" | |
| st.markdown("### نموذج التسعير الشامل") | |
| # التحقق من وجود تسعير حالي | |
| if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: | |
| st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") | |
| return | |
| # عرض معلومات التسعير الحالي | |
| pricing = st.session_state.current_pricing | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("اسم المناقصة", pricing['name']) | |
| st.metric("الجهة المالكة", pricing['client']) | |
| with col2: | |
| st.metric("رقم المناقصة", pricing['number']) | |
| st.metric("تاريخ التقديم", pricing['submission_date'].strftime("%Y-%m-%d")) | |
| with col3: | |
| st.metric("طريقة التسعير", pricing['method']) | |
| st.metric("الموقع", pricing['location']) | |
| # عرض البنود والتسعير | |
| st.markdown("### بنود التسعير") | |
| items = pricing['items'].copy() | |
| # إضافة أسعار الوحدة للمحاكاة | |
| if 'سعر الوحدة' in items.columns and (items['سعر الوحدة'] == 0).all(): | |
| items['سعر الوحدة'] = [ | |
| round(random.uniform(1000, 3000), 2), # الخرسانة | |
| round(random.uniform(5000, 7000), 2), # الحديد | |
| round(random.uniform(100, 200), 2), # العزل | |
| round(random.uniform(50, 100), 2), # الردم | |
| round(random.uniform(1200, 3500), 2), # الخرسانة للأعمدة | |
| ] | |
| if len(items) > 5: | |
| for i in range(5, len(items)): | |
| items.at[i, 'سعر الوحدة'] = round(random.uniform(500, 5000), 2) | |
| # حساب الإجمالي | |
| items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] | |
| # عرض البنود | |
| st.dataframe(items, use_container_width=True, hide_index=True) | |
| # ✅ التوصية الذكية باستخدام OpenAI | |
| with st.expander("🔍 توليد توصية ذكية باستخدام AI"): | |
| if st.button("🔍 توليد توصية ذكية باستخدام AI", use_container_width=True): | |
| import openai | |
| import os | |
| client = openai.OpenAI(api_key=os.environ.get("ai")) | |
| items_df = items.copy() | |
| prompt = f"""قم بتحليل الجدول التالي للبنود في مشروع إنشاء، وقدم توصية ذكية لتحسين التسعير وضمان التوازن المالي. الجدول يحتوي على البنود، الكميات، الأسعار، والإجماليات:\n\n{items_df.to_string(index=False)}\n\nالتوصية:\n""" | |
| try: | |
| with st.spinner("جاري توليد التوصية..."): | |
| response = client.chat.completions.create( | |
| model="gpt-4", | |
| messages=[ | |
| {"role": "system", "content": "أنت خبير في تسعير مشاريع البناء والبنية التحتية."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| temperature=0.4, | |
| max_tokens=500 | |
| ) | |
| recommendation = response.choices[0].message.content | |
| st.success("تم توليد التوصية بنجاح!") | |
| st.markdown("#### التوصية الذكية:") | |
| st.info(recommendation) | |
| except Exception as e: | |
| st.error(f"حدث خطأ أثناء الاتصال بنموذج OpenAI: {e}") | |
| st.info("يجب التأكد من تثبيت أحدث إصدار من مكتبة OpenAI: `pip install openai --upgrade`") | |
| # واجهة تعديل أسعار الوحدات | |
| st.markdown("### تعديل أسعار الوحدات") | |
| # تقسيم البنود إلى مجموعتين للعرض | |
| col1, col2 = st.columns(2) | |
| half = len(items) // 2 + len(items) % 2 | |
| with col1: | |
| for idx in range(half): | |
| if idx < len(items): | |
| row = items.iloc[idx] | |
| price = st.number_input( | |
| f"{row['رقم البند']}: {row['وصف البند'][:30]}... ({row['الوحدة']})", | |
| value=float(row['سعر الوحدة']), | |
| min_value=0.0, | |
| key=f"price1_{idx}" | |
| ) | |
| items.at[idx, 'سعر الوحدة'] = price | |
| items.at[idx, 'الإجمالي'] = price * items.at[idx, 'الكمية'] | |
| with col2: | |
| for idx in range(half, len(items)): | |
| row = items.iloc[idx] | |
| price = st.number_input( | |
| f"{row['رقم البند']}: {row['وصف البند'][:30]}... ({row['الوحدة']})", | |
| value=float(row['سعر الوحدة']), | |
| min_value=0.0, | |
| key=f"price2_{idx}" | |
| ) | |
| items.at[idx, 'سعر الوحدة'] = price | |
| items.at[idx, 'الإجمالي'] = price * items.at[idx, 'الكمية'] | |
| # حساب وعرض إجماليات التسعير | |
| total_price = items['الإجمالي'].sum() | |
| st.markdown("### إجماليات التسعير") | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("إجمالي التكاليف المباشرة", f"{total_price:,.2f} ريال") | |
| with col2: | |
| overhead_percentage = st.slider("نسبة المصاريف العامة والأرباح (%)", 5, 30, 15) | |
| overhead_value = total_price * overhead_percentage / 100 | |
| st.metric("المصاريف العامة والأرباح", f"{overhead_value:,.2f} ريال") | |
| with col3: | |
| grand_total = total_price + overhead_value | |
| st.metric("الإجمالي النهائي", f"{grand_total:,.2f} ريال") | |
| # رسم بياني لتوزيع التكاليف | |
| st.markdown("### تحليل التكاليف") | |
| # حساب النسب المئوية لكل بند | |
| pie_data = items.copy() | |
| pie_data['نسبة من إجمالي التكاليف'] = pie_data['الإجمالي'] / total_price * 100 | |
| fig = px.pie( | |
| pie_data, | |
| values='نسبة من إجمالي التكاليف', | |
| names='وصف البند', | |
| title='توزيع التكاليف حسب البنود', | |
| hole=0.4 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # أزرار العمليات | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| if st.button("حفظ التسعير"): | |
| # تحديث بيانات التسعير الحالي | |
| st.session_state.current_pricing['items'] = items.copy() | |
| st.success("تم حفظ التسعير بنجاح!") | |
| with col2: | |
| if st.button("تصدير إلى Excel"): | |
| st.success("تم تصدير التسعير إلى Excel بنجاح!") | |
| with col3: | |
| if st.button("تحليل المخاطر المالية"): | |
| st.success("تم إرسال الطلب إلى وحدة تحليل المخاطر!") | |
| def _render_unbalanced_pricing_tab(self): | |
| """عرض تبويب التسعير غير المتزن""" | |
| st.markdown("### التسعير غير المتزن") | |
| # التحقق من وجود تسعير حالي | |
| if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: | |
| st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") | |
| return | |
| # شرح التسعير غير المتزن | |
| with st.expander("ما هو التسعير غير المتزن؟", expanded=False): | |
| st.markdown(""" | |
| **التسعير غير المتزن** هو استراتيجية تسعير تقوم على توزيع التكاليف بين بنود المناقصة بشكل غير متساوٍ، مع الحفاظ على إجمالي قيمة العطاء. | |
| ### استراتيجيات التسعير غير المتزن: | |
| 1. **التحميل الأمامي (Front Loading)**: زيادة أسعار البنود المبكرة في المشروع للحصول على تدفق نقدي أفضل في بداية المشروع. | |
| 2. **التحميل الخلفي (Back Loading)**: زيادة أسعار البنود المتأخرة في المشروع. | |
| 3. **تحميل البنود المؤكدة**: زيادة أسعار البنود التي من المؤكد تنفيذها بالكميات المحددة. | |
| 4. **تخفيض أسعار البنود المحتملة**: تخفيض أسعار البنود التي قد تزيد كمياتها أثناء التنفيذ. | |
| ### مزايا التسعير غير المتزن: | |
| - تحسين التدفق النقدي للمشروع. | |
| - تعظيم الربحية في حالة التغييرات والأوامر التغييرية. | |
| - زيادة فرص الفوز بالمناقصة. | |
| ### مخاطر التسعير غير المتزن: | |
| - قد يتم رفض العطاء إذا كان عدم التوازن واضحاً. | |
| - قد تتأثر السمعة سلباً إذا تم استخدامه بشكل مفرط. | |
| - قد يؤدي إلى خسائر إذا لم يتم تنفيذ البنود ذات الأسعار العالية. | |
| """) | |
| # عرض بنود التسعير الحالي | |
| items = st.session_state.current_pricing['items'].copy() | |
| # إضافة عمود إستراتيجية التسعير | |
| if 'إستراتيجية التسعير' not in items.columns: | |
| items['إستراتيجية التسعير'] = 'متوازن' | |
| st.markdown("### إستراتيجية التسعير غير المتزن") | |
| # اختيار الإستراتيجية | |
| strategy = st.selectbox( | |
| "اختر إستراتيجية التسعير", | |
| [ | |
| "تحميل أمامي (Front Loading)", | |
| "تحميل البنود المؤكدة", | |
| "تخفيض البنود المحتمل زيادتها", | |
| "إستراتيجية مخصصة" | |
| ] | |
| ) | |
| # تطبيق الإستراتيجية المختارة | |
| if strategy == "تحميل أمامي (Front Loading)": | |
| # محاكاة تحميل أمامي | |
| items_count = len(items) | |
| early_items = items.iloc[:items_count//3].index | |
| middle_items = items.iloc[items_count//3:2*items_count//3].index | |
| late_items = items.iloc[2*items_count//3:].index | |
| # تطبيق الزيادة والنقصان | |
| for idx in early_items: | |
| items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.3 # زيادة 30% | |
| items.at[idx, 'إستراتيجية التسعير'] = 'زيادة' | |
| for idx in middle_items: | |
| items.at[idx, 'إستراتيجية التسعير'] = 'متوازن' | |
| for idx in late_items: | |
| items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.7 # نقص 30% | |
| items.at[idx, 'إستراتيجية التسعير'] = 'نقص' | |
| elif strategy == "تحميل البنود المؤكدة": | |
| # محاكاة - اعتبار بعض البنود مؤكدة | |
| confirmed_items = [0, 2, 4] # الأصفار-مستندة | |
| variable_items = [idx for idx in range(len(items)) if idx not in confirmed_items] | |
| # تطبيق الزيادة والنقصان | |
| for idx in confirmed_items: | |
| if idx < len(items): | |
| items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.25 # زيادة 25% | |
| items.at[idx, 'إستراتيجية التسعير'] = 'زيادة' | |
| for idx in variable_items: | |
| if idx < len(items): | |
| items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.85 # نقص 15% | |
| items.at[idx, 'إستراتيجية التسعير'] = 'نقص' | |
| elif strategy == "تخفيض البنود المحتمل زيادتها": | |
| # محاكاة - اعتبار بعض البنود محتمل زيادتها | |
| variable_items = [1, 3] # الأصفار-مستندة | |
| other_items = [idx for idx in range(len(items)) if idx not in variable_items] | |
| # تطبيق الزيادة والنقصان | |
| for idx in variable_items: | |
| if idx < len(items): | |
| items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.7 # نقص 30% | |
| items.at[idx, 'إستراتيجية التسعير'] = 'نقص' | |
| for idx in other_items: | |
| if idx < len(items): | |
| items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.15 # زيادة 15% | |
| items.at[idx, 'إستراتيجية التسعير'] = 'زيادة' | |
| else: # إستراتيجية مخصصة | |
| st.markdown("### تعديل أسعار البنود يدوياً") | |
| st.markdown("قم بتعديل أسعار البنود وإستراتيجية التسعير يدوياً.") | |
| # حساب الإجمالي بعد التعديل | |
| items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] | |
| # تعيين ألوان للإستراتيجيات | |
| def highlight_strategy(val): | |
| if val == 'زيادة': | |
| return 'background-color: #a8e6cf' | |
| elif val == 'نقص': | |
| return 'background-color: #ff9aa2' | |
| return '' | |
| # عرض الجدول مع تنسيق | |
| st.markdown("### بنود التسعير غير المتزن") | |
| styled_items = items.style.applymap(highlight_strategy, subset=['إستراتيجية التسعير']) | |
| st.dataframe(styled_items, use_container_width=True) | |
| # المقارنة بين التسعير المتوازن وغير المتوازن | |
| st.markdown("### مقارنة التسعير المتوازن وغير المتوازن") | |
| original_items = st.session_state.current_pricing['items'].copy() | |
| original_total = original_items['الإجمالي'].sum() | |
| unbalanced_total = items['الإجمالي'].sum() | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("إجمالي التسعير المتوازن", f"{original_total:,.2f} ريال") | |
| with col2: | |
| st.metric("إجمالي التسعير غير المتوازن", f"{unbalanced_total:,.2f} ريال") | |
| with col3: | |
| diff = unbalanced_total - original_total | |
| st.metric("الفرق", f"{diff:,.2f} ريال", delta=f"{diff/original_total*100:.1f}%") | |
| # المعايرة للحفاظ على إجمالي التسعير | |
| if abs(diff) > 1: # إذا كان هناك فرق كبير | |
| if st.button("معايرة الأسعار للحفاظ على إجمالي التسعير"): | |
| # تعديل الأسعار للحفاظ على إجمالي التكلفة | |
| adjustment_factor = original_total / unbalanced_total | |
| items['سعر الوحدة'] = items['سعر الوحدة'] * adjustment_factor | |
| items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] | |
| st.success(f"تم تعديل الأسعار للحفاظ على إجمالي التسعير الأصلي ({original_total:,.2f} ريال)") | |
| st.dataframe(items, use_container_width=True) | |
| # رسم بياني للمقارنة | |
| st.markdown("### تحليل بصري للتسعير غير المتوازن") | |
| # إعداد البيانات للرسم البياني | |
| chart_data = pd.DataFrame({ | |
| 'وصف البند': original_items['وصف البند'], | |
| 'التسعير المتوازن': original_items['الإجمالي'], | |
| 'التسعير غير المتوازن': items['الإجمالي'] | |
| }) | |
| # رسم بياني شريطي للمقارنة | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar( | |
| x=chart_data['وصف البند'], | |
| y=chart_data['التسعير المتوازن'], | |
| name='التسعير المتوازن', | |
| marker_color='rgb(55, 83, 109)' | |
| )) | |
| fig.add_trace(go.Bar( | |
| x=chart_data['وصف البند'], | |
| y=chart_data['التسعير غير المتوازن'], | |
| name='التسعير غير المتوازن', | |
| marker_color='rgb(26, 118, 255)' | |
| )) | |
| fig.update_layout( | |
| title='مقارنة بين التسعير المتوازن وغير المتوازن', | |
| xaxis_tickfont_size=14, | |
| yaxis=dict( | |
| title='الإجمالي (ريال)', | |
| titlefont_size=16, | |
| tickfont_size=14, | |
| ), | |
| legend=dict( | |
| x=0, | |
| y=1.0, | |
| bgcolor='rgba(255, 255, 255, 0)', | |
| bordercolor='rgba(255, 255, 255, 0)' | |
| ), | |
| barmode='group', | |
| bargap=0.15, | |
| bargroupgap=0.1 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # زر حفظ التسعير غير المتوازن | |
| if st.button("حفظ التسعير غير المتوازن"): | |
| st.session_state.current_pricing['items'] = items.copy() | |
| st.session_state.current_pricing['method'] = "التسعير غير المتزن" | |
| st.success("تم حفظ التسعير غير المتوازن بنجاح!") | |
| def _render_local_content_tab(self): | |
| """عرض تبويب المحتوى المحلي""" | |
| st.markdown("### تحليل المحتوى المحلي") | |
| # التحقق من وجود تسعير حالي | |
| if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: | |
| st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") | |
| return | |
| # شرح المحتوى المحلي | |
| with st.expander("ما هو المحتوى المحلي؟", expanded=False): | |
| st.markdown(""" | |
| **المحتوى المحلي** هو نسبة المنتجات والخدمات والقوى العاملة المحلية المستخدمة في المشروع. يهدف إلى زيادة مساهمة المنتجات والخدمات المحلية في المشاريع. | |
| ### مكونات المحتوى المحلي: | |
| 1. **المنتجات**: المنتجات والمواد المصنعة محلياً. | |
| 2. **الخدمات**: الخدمات المقدمة من شركات محلية. | |
| 3. **القوى العاملة**: العمالة والكوادر الفنية والإدارية المحلية. | |
| ### أهمية المحتوى المحلي: | |
| - تعزيز الاقتصاد المحلي وخلق فرص عمل. | |
| - تحقيق أهداف رؤية 2030 في زيادة المحتوى المحلي. | |
| - التأهل للمشاريع الحكومية التي تتطلب نسبة محتوى محلي محددة. | |
| - الحصول على حوافز وأفضلية في المناقصات الحكومية. | |
| ### متطلبات المحتوى المحلي: | |
| - نسبة المحتوى المحلي للقوى العاملة: 80% | |
| - نسبة المحتوى المحلي للمنتجات: 70% | |
| - نسبة المحتوى المحلي للخدمات: 60% | |
| """) | |
| # عرض لوحة إدخال بيانات المحتوى المحلي | |
| st.markdown("### بيانات المحتوى المحلي") | |
| # التبويبات لأنواع المحتوى المحلي | |
| lc_tabs = st.tabs(["المنتجات", "الخدمات", "القوى العاملة", "التحليل"]) | |
| with lc_tabs[0]: # المنتجات | |
| st.markdown("#### بيانات المنتجات") | |
| # إنشاء بيانات افتراضية للمنتجات إذا لم تكن موجودة | |
| if 'local_content_products' not in st.session_state: | |
| st.session_state.local_content_products = pd.DataFrame({ | |
| 'المنتج': [ | |
| "خرسانة مسلحة", | |
| "حديد تسليح", | |
| "بلوك خرساني", | |
| "عزل مائي", | |
| "دهانات" | |
| ], | |
| 'الكمية': [250, 25, 400, 500, 600], | |
| 'سعر_الوحدة': [1200, 6000, 200, 100, 50], | |
| 'التكلفة_الإجمالية': [300000, 150000, 80000, 50000, 30000], | |
| 'نسبة_المحتوى_المحلي': [0.95, 0.70, 0.98, 0.60, 0.80] | |
| }) | |
| # حساب التكلفة الإجمالية | |
| st.session_state.local_content_products['التكلفة_الإجمالية'] = st.session_state.local_content_products['الكمية'] * st.session_state.local_content_products['سعر_الوحدة'] | |
| # عرض جدول البنود مع إمكانية التعديل | |
| edited_products = st.data_editor( | |
| st.session_state.local_content_products, | |
| use_container_width=True, | |
| hide_index=True, | |
| num_rows="dynamic" | |
| ) | |
| st.session_state.local_content_products = edited_products | |
| # عرض ملخص المنتجات | |
| total_products_cost = edited_products['التكلفة_الإجمالية'].sum() | |
| avg_local_content = (edited_products['التكلفة_الإجمالية'] * edited_products['نسبة_المحتوى_المحلي']).sum() / total_products_cost if total_products_cost > 0 else 0 | |
| st.markdown(f""" | |
| **إجمالي تكلفة المنتجات**: {total_products_cost:,.2f} ريال | |
| **متوسط نسبة المحتوى المحلي للمنتجات**: {avg_local_content*100:.2f}% | |
| **المستهدف**: 70% | |
| **الحالة**: {"✅ ملتزم" if avg_local_content >= 0.7 else "❌ غير ملتزم"} | |
| """) | |
| with lc_tabs[1]: # الخدمات | |
| st.markdown("#### بيانات الخدمات") | |
| # إنشاء بيانات افتراضية للخدمات إذا لم تكن موجودة | |
| if 'local_content_services' not in st.session_state: | |
| st.session_state.local_content_services = pd.DataFrame({ | |
| 'الخدمة': [ | |
| "تصميم معماري", | |
| "إشراف هندسي", | |
| "خدمات نقل", | |
| "خدمات أمن وسلامة", | |
| "صيانة ونظافة" | |
| ], | |
| 'التكلفة': [100000, 120000, 50000, 30000, 20000], | |
| 'نسبة_المحتوى_المحلي': [0.90, 0.85, 0.90, 0.95, 0.95] | |
| }) | |
| # عرض جدول الخدمات مع إمكانية التعديل | |
| edited_services = st.data_editor( | |
| st.session_state.local_content_services, | |
| use_container_width=True, | |
| hide_index=True, | |
| num_rows="dynamic" | |
| ) | |
| st.session_state.local_content_services = edited_services | |
| # عرض ملخص الخدمات | |
| total_services_cost = edited_services['التكلفة'].sum() | |
| avg_local_content = (edited_services['التكلفة'] * edited_services['نسبة_المحتوى_المحلي']).sum() / total_services_cost if total_services_cost > 0 else 0 | |
| st.markdown(f""" | |
| **إجمالي تكلفة الخدمات**: {total_services_cost:,.2f} ريال | |
| **متوسط نسبة المحتوى المحلي للخدمات**: {avg_local_content*100:.2f}% | |
| **المستهدف**: 60% | |
| **الحالة**: {"✅ ملتزم" if avg_local_content >= 0.6 else "❌ غير ملتزم"} | |
| """) | |
| with lc_tabs[2]: # القوى العاملة | |
| st.markdown("#### بيانات القوى العاملة") | |
| # إنشاء بيانات افتراضية للقوى العاملة إذا لم تكن موجودة | |
| if 'local_content_labor' not in st.session_state: | |
| st.session_state.local_content_labor = pd.DataFrame({ | |
| 'فئة_العمالة': [ | |
| "مهندسون", | |
| "فنيون", | |
| "عمال بناء", | |
| "إداريون", | |
| "مشرفون" | |
| ], | |
| 'العدد': [5, 10, 30, 3, 4], | |
| 'الراتب_الشهري': [15000, 8000, 3000, 10000, 12000], | |
| 'المدة_بالأشهر': [12, 12, 12, 12, 12], | |
| 'نسبة_المحتوى_المحلي': [0.75, 0.65, 0.60, 0.90, 0.80] | |
| }) | |
| # حساب التكلفة الإجمالية | |
| st.session_state.local_content_labor['التكلفة_الإجمالية'] = st.session_state.local_content_labor['العدد'] * st.session_state.local_content_labor['الراتب_الشهري'] * st.session_state.local_content_labor['المدة_بالأشهر'] | |
| # عرض جدول القوى العاملة مع إمكانية التعديل | |
| edited_labor = st.data_editor( | |
| st.session_state.local_content_labor, | |
| use_container_width=True, | |
| hide_index=True, | |
| num_rows="dynamic" | |
| ) | |
| # إعادة حساب التكلفة الإجمالية بعد التعديل | |
| edited_labor['التكلفة_الإجمالية'] = edited_labor['العدد'] * edited_labor['الراتب_الشهري'] * edited_labor['المدة_بالأشهر'] | |
| st.session_state.local_content_labor = edited_labor | |
| # عرض ملخص القوى العاملة | |
| total_labor_cost = edited_labor['التكلفة_الإجمالية'].sum() | |
| avg_local_content = (edited_labor['التكلفة_الإجمالية'] * edited_labor['نسبة_المحتوى_المحلي']).sum() / total_labor_cost if total_labor_cost > 0 else 0 | |
| st.markdown(f""" | |
| **إجمالي تكلفة القوى العاملة**: {total_labor_cost:,.2f} ريال | |
| **متوسط نسبة المحتوى المحلي للقوى العاملة**: {avg_local_content*100:.2f}% | |
| **المستهدف**: 80% | |
| **الحالة**: {"✅ ملتزم" if avg_local_content >= 0.8 else "❌ غير ملتزم"} | |
| """) | |
| with lc_tabs[3]: # التحليل | |
| st.markdown("#### تحليل المحتوى المحلي") | |
| # حساب المحتوى المحلي الإجمالي | |
| try: | |
| # تجميع بيانات تحليل المحتوى المحلي | |
| products_cost = st.session_state.local_content_products['التكلفة_الإجمالية'].sum() | |
| products_local_content = (st.session_state.local_content_products['التكلفة_الإجمالية'] * st.session_state.local_content_products['نسبة_المحتوى_المحلي']).sum() / products_cost if products_cost > 0 else 0 | |
| services_cost = st.session_state.local_content_services['التكلفة'].sum() | |
| services_local_content = (st.session_state.local_content_services['التكلفة'] * st.session_state.local_content_services['نسبة_المحتوى_المحلي']).sum() / services_cost if services_cost > 0 else 0 | |
| labor_cost = st.session_state.local_content_labor['التكلفة_الإجمالية'].sum() | |
| labor_local_content = (st.session_state.local_content_labor['التكلفة_الإجمالية'] * st.session_state.local_content_labor['نسبة_المحتوى_المحلي']).sum() / labor_cost if labor_cost > 0 else 0 | |
| # حساب الوزن النسبي لكل مكون | |
| total_cost = products_cost + services_cost + labor_cost | |
| products_weight = products_cost / total_cost if total_cost > 0 else 0 | |
| services_weight = services_cost / total_cost if total_cost > 0 else 0 | |
| labor_weight = labor_cost / total_cost if total_cost > 0 else 0 | |
| # حساب المحتوى المحلي الإجمالي | |
| total_local_content = (products_local_content * products_weight) + (services_local_content * services_weight) + (labor_local_content * labor_weight) | |
| # عرض ملخص المحتوى المحلي | |
| st.markdown("### ملخص المحتوى المحلي") | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("إجمالي التكاليف", f"{total_cost:,.2f} ريال") | |
| with col2: | |
| st.metric("نسبة المحتوى المحلي الإجمالية", f"{total_local_content*100:.2f}%") | |
| with col3: | |
| target_local_content = 0.7 # 70% | |
| st.metric("الحالة", "ملتزم" if total_local_content >= target_local_content else "غير ملتزم", delta=f"{(total_local_content - target_local_content)*100:.2f}%") | |
| # عرض رسم بياني للمقارنة | |
| st.markdown("### تحليل بصري للمحتوى المحلي") | |
| # رسم بياني شريطي لنسب المحتوى المحلي | |
| categories = ['المنتجات', 'الخدمات', 'القوى العاملة', 'الإجمالي'] | |
| actual_values = [products_local_content * 100, services_local_content * 100, labor_local_content * 100, total_local_content * 100] | |
| target_values = [70, 60, 80, 70] # المستهدفات | |
| # تهيئة البيانات للرسم البياني | |
| chart_data = pd.DataFrame({ | |
| 'الفئة': categories, | |
| 'النسبة الفعلية': actual_values, | |
| 'النسبة المستهدفة': target_values | |
| }) | |
| # رسم بياني شريطي للمقارنة | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar( | |
| x=chart_data['الفئة'], | |
| y=chart_data['النسبة الفعلية'], | |
| name='النسبة الفعلية', | |
| marker_color='rgb(26, 118, 255)' | |
| )) | |
| fig.add_trace(go.Bar( | |
| x=chart_data['الفئة'], | |
| y=chart_data['النسبة المستهدفة'], | |
| name='النسبة المستهدفة', | |
| marker_color='rgb(55, 83, 109)' | |
| )) | |
| fig.update_layout( | |
| title='مقارنة بين النسب الفعلية والمستهدفة للمحتوى المحلي', | |
| xaxis_tickfont_size=14, | |
| yaxis=dict( | |
| title='النسبة %', | |
| titlefont_size=16, | |
| tickfont_size=14, | |
| ), | |
| legend=dict( | |
| x=0, | |
| y=1.0, | |
| bgcolor='rgba(255, 255, 255, 0)', | |
| bordercolor='rgba(255, 255, 255, 0)' | |
| ), | |
| barmode='group', | |
| bargap=0.15, | |
| bargroupgap=0.1 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # عرض توصيات لتحسين نسبة المحتوى المحلي | |
| st.markdown("### توصيات لتحسين نسبة المحتوى المحلي") | |
| recommendations = [] | |
| if products_local_content < 0.7: | |
| recommendations.append("- زيادة نسبة المحتوى المحلي للمنتجات من خلال:") | |
| recommendations.append(" - البحث عن موردين محليين للمنتجات ذات النسبة المنخفضة") | |
| recommendations.append(" - استبدال المنتجات المستوردة ببدائل محلية") | |
| recommendations.append(" - التعاون مع المصانع المحلية لتوطين صناعة المنتجات") | |
| if services_local_content < 0.6: | |
| recommendations.append("- زيادة نسبة المحتوى المحلي للخدمات من خلال:") | |
| recommendations.append(" - التعاقد مع شركات خدمات محلية") | |
| recommendations.append(" - تحويل الخدمات المستعان بها من الخارج إلى شركات محلية") | |
| recommendations.append(" - تأهيل الشركات المحلية لتقديم الخدمات المطلوبة") | |
| if labor_local_content < 0.8: | |
| recommendations.append("- زيادة نسبة المحتوى المحلي للقوى العاملة من خلال:") | |
| recommendations.append(" - زيادة توظيف الكوادر المحلية") | |
| recommendations.append(" - تدريب وتأهيل العمالة المحلية") | |
| recommendations.append(" - استبدال العمالة الأجنبية بكوادر محلية تدريجياً") | |
| if total_local_content < 0.7: | |
| recommendations.append("- زيادة نسبة المحتوى المحلي الإجمالية من خلال:") | |
| recommendations.append(" - إعادة توزيع الميزانية لصالح المكونات ذات النسبة العالية من المحتوى المحلي") | |
| recommendations.append(" - وضع خطة مرحلية لزيادة المحتوى المحلي") | |
| recommendations.append(" - التعاون مع اللجنة المحلية لزيادة المحتوى المحلي") | |
| if recommendations: | |
| for rec in recommendations: | |
| st.markdown(rec) | |
| else: | |
| st.success("تهانينا! نسبة المحتوى المحلي متوافقة مع المتطلبات.") | |
| # حساب تأثير المحتوى المحلي على التسعير | |
| st.markdown("### تأثير المحتوى المحلي على التسعير") | |
| # تحديد عامل تعديل السعر بناءً على نسبة المحتوى المحلي | |
| price_adjustment_factor = 1.0 | |
| if total_local_content >= 0.9: | |
| price_adjustment_factor = 0.92 # خصم 8% للمحتوى المحلي العالي جداً | |
| price_discount = "8%" | |
| elif total_local_content >= 0.8: | |
| price_adjustment_factor = 0.94 # خصم 6% للمحتوى المحلي العالي | |
| price_discount = "6%" | |
| elif total_local_content >= 0.7: | |
| price_adjustment_factor = 0.96 # خصم 4% للمحتوى المحلي المتوسط | |
| price_discount = "4%" | |
| elif total_local_content >= 0.6: | |
| price_adjustment_factor = 0.98 # خصم 2% للمحتوى المحلي المنخفض | |
| price_discount = "2%" | |
| else: | |
| price_adjustment_factor = 1.0 # لا خصم | |
| price_discount = "0%" | |
| # عرض تأثير المحتوى المحلي على التسعير | |
| original_total = st.session_state.current_pricing['items']['الإجمالي'].sum() | |
| adjusted_total = original_total * price_adjustment_factor | |
| discount_amount = original_total - adjusted_total | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("إجمالي التسعير الأصلي", f"{original_total:,.2f} ريال") | |
| with col2: | |
| st.metric("نسبة الخصم بسبب المحتوى المحلي", price_discount) | |
| with col3: | |
| st.metric("إجمالي التسعير بعد الخصم", f"{adjusted_total:,.2f} ريال", delta=f"-{discount_amount:,.2f} ريال") | |
| # أزرار العمليات | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("حفظ تحليل المحتوى المحلي"): | |
| # حفظ بيانات المحتوى المحلي في التسعير الحالي | |
| st.session_state.current_pricing['local_content'] = { | |
| 'products': st.session_state.local_content_products.copy(), | |
| 'services': st.session_state.local_content_services.copy(), | |
| 'labor': st.session_state.local_content_labor.copy(), | |
| 'total_local_content': total_local_content, | |
| 'price_adjustment_factor': price_adjustment_factor | |
| } | |
| st.success("تم حفظ تحليل المحتوى المحلي بنجاح!") | |
| with col2: | |
| if st.button("تصدير تقرير المحتوى المحلي"): | |
| st.success("تم تصدير تقرير المحتوى المحلي بنجاح!") | |
| except Exception as e: | |
| st.error(f"حدث خطأ أثناء تحليل المحتوى المحلي: {str(e)}") | |
| st.warning("تأكد من إدخال بيانات المحتوى المحلي بشكل صحيح في التبويبات السابقة.") |