diff --git "a/modules/ai_assistant/ai_app.py" "b/modules/ai_assistant/ai_app.py" --- "a/modules/ai_assistant/ai_app.py" +++ "b/modules/ai_assistant/ai_app.py" @@ -80,66 +80,24 @@ class ClaudeAIService: العوائد: dict: نتائج التحليل """ - try: - # الحصول على مفتاح API - api_key = self.get_api_key() - - # قراءة محتوى الملف مع ضغط الصور فقط - file_size = os.path.getsize(image_path) - _, ext = os.path.splitext(image_path) - ext = ext.lower() - - if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp'] and file_size > 5 * 1024 * 1024: - # فقط ضغط الصور العادية وليس ملفات PDF - try: - with Image.open(image_path) as img: - # حفظ الصورة المضغوطة في ذاكرة مؤقتة - compressed_img_buffer = io.BytesIO() - - # حساب معامل الجودة المناسب - quality = min(95, int((5 * 1024 * 1024 / file_size) * 100)) - - # حفظ الصورة بالجودة المحسوبة - img.save(compressed_img_buffer, format='JPEG', quality=quality) - compressed_img_buffer.seek(0) - - # استخدام البيانات المضغوطة - file_content = compressed_img_buffer.read() - logging.info(f"تم ضغط الصورة من {file_size/1024/1024:.2f} ميجابايت إلى {len(file_content)/1024/1024:.2f} ميجابايت") - except Exception as e: - logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}") - with open(image_path, 'rb') as f: - file_content = f.read() - else: - # استخدام الملف الأصلي للملفات غير الصور أو الصور الصغيرة - with open(image_path, 'rb') as f: - file_content = f.read() - - # التحقق من حجم الملف بعد القراءة - if len(file_content) > 5 * 1024 * 1024: - raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.") - - # تحويل المحتوى إلى Base64 - file_base64 = base64.b64encode(file_content).decode('utf-8') - - - except Exception as e: - logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}") - with open(image_path, 'rb') as f: - file_content = f.read() - else: - # استخدام الملف الأصلي للملفات غير الصور أو الصور الصغيرة - with open(image_path, 'rb') as f: - file_content = f.read() + try: + # الحصول على مفتاح API + api_key = self.get_api_key() + + # قراءة محتوى الملف مع ضغط الصور فقط + file_size = os.path.getsize(image_path) + _, ext = os.path.splitext(image_path) + ext = ext.lower() - # التحقق من حجم الملف بعد القراءة - if len(file_content) > 5 * 1024 * 1024: - raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.") - - + if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp'] and file_size > 5 * 1024 * 1024: + # فقط ضغط الصور العادية وليس ملفات PDF + try: + with Image.open(image_path) as img: + # حفظ الصورة المضغوطة في ذاكرة مؤقتة + compressed_img_buffer = io.BytesIO() # حساب معامل الجودة المناسب - quality = min(95, int((5 * 1024 * 1024 / file_size) * 100) + quality = min(95, int((5 * 1024 * 1024 / file_size) * 100)) # حفظ الصورة بالجودة المحسوبة img.save(compressed_img_buffer, format='JPEG', quality=quality) @@ -147,9 +105,20 @@ class ClaudeAIService: # استخدام البيانات المضغوطة file_content = compressed_img_buffer.read() - - + logging.info(f"تم ضغط الصورة من {file_size/1024/1024:.2f} ميجابايت إلى {len(file_content)/1024/1024:.2f} ميجابايت") + except Exception as e: + logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}") + with open(image_path, 'rb') as f: + file_content = f.read() + else: + # استخدام الملف الأصلي للملفات غير الصور أو الصور الصغيرة + with open(image_path, 'rb') as f: + file_content = f.read() + # التحقق من حجم الملف بعد القراءة + if len(file_content) > 5 * 1024 * 1024: + raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.") + # تحويل المحتوى إلى Base64 file_base64 = base64.b64encode(file_content).decode('utf-8') @@ -356,33 +325,17 @@ class AIAssistantApp: { "question": "كيف يمكنني تقييم المخاطر للمشروع؟", "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة." - } - ] - - # تهيئة قائمة الأسئلة والإجابات الشائعة - self.faqs = [ - { - "question": "كيف يمكنني إضافة مشروع جديد؟", - "answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة." - }, - { - "question": "ما هي خطوات تسعير المناقصة؟", - "answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية." - }, - { - "question": "كيف يتم حساب المحتوى المحلي؟", - "answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي." }, { - "question": "كيف يمكنني تصدير التقارير؟", - "answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV." + "question": "ما هي طرق التسعير المتاحة في النظام؟", + "answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة." }, { - "question": "كيف يمكنني تقييم المخاطر للمشروع؟", - "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة." + "question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟", + "answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً." } ] - + def _load_cost_prediction_model(self): """تحميل نموذج التنبؤ بالتكاليف""" # في البيئة الإنتاجية، سيتم تحميل نموذج حقيقي @@ -404,38 +357,6 @@ class AIAssistantApp: def _load_entity_recognition_model(self): """تحميل نموذج التعرف على الكيانات""" return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"} - - # تهيئة قائمة الأسئلة والإجابات الشائعة - self.faqs = [ - { - "question": "كيف يمكنني إضافة مشروع جديد؟", - "answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة." - }, - { - "question": "ما هي خطوات تسعير المناقصة؟", - "answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية." - }, - { - "question": "كيف يتم حساب المحتوى المحلي؟", - "answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي." - }, - { - "question": "كيف يمكنني تصدير التقارير؟", - "answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV." - }, - { - "question": "كيف يمكنني تقييم المخاطر للمشروع؟", - "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة." - }, - { - "question": "ما هي طرق التسعير المتاحة في النظام؟", - "answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة." - }, - { - "question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟", - "answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً." - } - ] def render(self): """عرض واجهة وحدة المساعد الذكي""" @@ -600,2745 +521,4 @@ class AIAssistantApp: self.claude_service.get_api_key() except ValueError: api_available = False - st.warning("مفتاح API لـ Claude غير متوفر. يرجى التأكد من تعيين متغير البيئة 'anthropic'.") - - if user_input and api_available: - # إضافة رسالة المستخدم إلى المحفوظات - st.session_state.ai_assistant_messages.append({"role": "user", "content": user_input}) - - # عرض محفوظات المحادثة المحدثة - with chat_container: - st.markdown(f""" -
-
- {user_input} -
-
- """, unsafe_allow_html=True) - - # معالجة الرد - with st.spinner("جاري التفكير..."): - # التحقق مما إذا كان هناك ملف مرفق - if uploaded_file: - # حفظ الملف المرفوع مؤقتاً - with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as temp_file: - temp_file.write(uploaded_file.getbuffer()) - temp_file_path = temp_file.name - - # إذا كان الملف PDF، تحويله إلى صورة - if uploaded_file.name.lower().endswith('.pdf'): - if self.pdf_conversion_available: - try: - # تحويل الصفحة الأولى فقط - images = self.convert_from_path(temp_file_path, first_page=1, last_page=1) - if images: - # حفظ الصورة بشكل مؤقت - temp_image_path = f"{temp_file_path}_image.jpg" - images[0].save(temp_image_path, 'JPEG', quality=85) - # استخدام مسار الصورة بدلاً من PDF - os.remove(temp_file_path) - temp_file_path = temp_image_path - except Exception as e: - st.error(f"فشل في تحويل ملف PDF إلى صورة: {str(e)}") - else: - st.error("تحليل ملفات PDF يتطلب تثبيت مكتبة pdf2image.") - response = "عذراً، لا يمكنني تحليل ملفات PDF في الوقت الحالي. يرجى تحويل الملف إلى صورة أو مشاركة المعلومات كنص." - - # تحليل الصورة باستخدام Claude - prompt = f"المستخدم قام برفع هذه الصورة وسأل: {user_input}\nقم بتحليل الصورة والرد على سؤال المستخدم بشكل تفصيلي." - results = self.claude_service.analyze_image(temp_file_path, prompt, model_name=selected_model) - - # حذف الملف المؤقت - try: - os.remove(temp_file_path) - except: - pass - - if "error" in results: - response = f"عذراً، حدث خطأ أثناء تحليل الملف: {results['error']}" - else: - response = results["content"] - else: - # استخدام خدمة Claude للرد على الرسائل النصية - results = self.claude_service.chat_completion(st.session_state.ai_assistant_messages, model_name=selected_model) - - if "error" in results: - response = f"عذراً، حدث خطأ أثناء معالجة طلبك: {results['error']}" - else: - response = results["content"] - - # إضافة رد المساعد إلى المحفوظات - st.session_state.ai_assistant_messages.append({"role": "assistant", "content": response}) - - # عرض رد المساعد - with chat_container: - st.markdown(f""" -
-
- {response} -
-
- """, unsafe_allow_html=True) - - # إعادة تعيين قيمة الإدخال - st.text_input("اكتب رسالتك هنا", value="", key="ai_assistant_input_reset") - - def _generate_ai_response(self, user_input, model_name="claude-3-7-sonnet"): - """توليد رد المساعد الذكي باستخدام Claude AI""" - - # التحقق من وجود مفتاح API - try: - self.claude_service.get_api_key() - except ValueError: - return "عذراً، لا يمكنني الاتصال بخدمة الذكاء الاصطناعي في الوقت الحالي. يرجى التحقق من إعدادات API." - - # البحث في الأسئلة الشائعة أولاً - for faq in self.faqs: - if any(keyword in user_input.lower() for keyword in faq["question"].lower().split()): - return f"{faq['answer']}\n\nهل تحتاج إلى مساعدة أخرى؟" - - # إنشاء محادثة لإرسالها إلى Claude - messages = [ - {"role": "user", "content": user_input} - ] - - # استدعاء خدمة Claude - results = self.claude_service.chat_completion(messages, model_name=model_name) - - if "error" in results: - # إذا فشل الاتصال، استخدم التوليد الافتراضي - logging.warning(f"فشل الاتصال بـ Claude AI: {results['error']}. استخدام التوليد الافتراضي.") - return self._generate_default_response(user_input) - else: - return results["content"] - - def _generate_default_response(self, user_input): - """توليد رد افتراضي في حالة عدم توفر Claude AI""" - - if "تسعير" in user_input or "سعر" in user_input or "تكلفة" in user_input: - return "يمكنك استخدام وحدة التنبؤ بالتكاليف لتقدير تكاليف المشروع بناءً على خصائصه. انتقل إلى تبويب 'التنبؤ بالتكاليف' وأدخل بيانات المشروع لتحصل على تقدير دقيق للتكاليف." - - elif "مخاطر" in user_input or "مخاطرة" in user_input: - return "يمكنك استخدام وحدة تحليل المخاطر لتقييم المخاطر المحتملة للمشروع. انتقل إلى تبويب 'تحليل المخاطر' وأدخل بيانات المشروع وعوامل المخاطرة لتحصل على تحليل شامل للمخاطر واستراتيجيات الاستجابة المقترحة." - - elif "مستند" in user_input or "ملف" in user_input or "وثيقة" in user_input or "مناقصة" in user_input: - return "يمكنك استخدام وحدة تحليل المستندات لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها. انتقل إلى تبويب 'تحليل المستندات' وقم بتحميل ملفات المناقصة لتحصل على تحليل تفصيلي للمستندات." - - elif "محتوى محلي" in user_input or "محلي" in user_input: - return "يمكنك استخدام وحدة المحتوى المحلي لحساب وتحسين نسبة المحتوى المحلي في مشروعك. انتقل إلى تبويب 'المحتوى المحلي' وأدخل بيانات مكونات المشروع لتحصل على تحليل شامل للمحتوى المحلي واقتراحات لتحسينه." - - elif "تقرير" in user_input or "إحصائيات" in user_input or "بيانات" in user_input: - return "يمكنك استخدام وحدة التقارير والتحليلات للحصول على تقارير تفصيلية وإحصائيات عن المشاريع. يمكنك الوصول إليها من القائمة الرئيسية للنظام." - - else: - return "شكراً لاستفسارك. يمكنني مساعدتك في تسعير المناقصات، وتحليل المخاطر، وتحليل المستندات، وحساب المحتوى المحلي. يرجى توضيح استفسارك أكثر أو اختيار أحد الخيارات في الأعلى للحصول على المساعدة المطلوبة." - - def _render_cost_prediction_tab(self): - """عرض تبويب التنبؤ بالتكاليف""" - - st.markdown("### التنبؤ بالتكاليف") - - # عرض نموذج إدخال بيانات المشروع - st.markdown("#### بيانات المشروع") - - col1, col2 = st.columns(2) - - with col1: - project_type = st.selectbox( - "نوع المشروع", - [ - "مباني سكنية", - "مباني تجارية", - "مباني حكومية", - "مراكز صحية", - "مدارس", - "بنية تحتية", - "طرق", - "جسور", - "صرف صحي", - "مياه", - "كهرباء" - ], - key="cost_project_type" - ) - - location = st.selectbox( - "الموقع", - [ - "الرياض", - "جدة", - "الدمام", - "مكة", - "المدينة", - "تبوك", - "حائل", - "عسير", - "جازان", - "نجران", - "الباحة", - "الجوف", - "القصيم" - ], - key="cost_location" - ) - - client_type = st.selectbox( - "نوع العميل", - [ - "حكومي", - "شبه حكومي", - "شركة كبيرة", - "شركة متوسطة", - "شركة صغيرة", - "أفراد" - ], - key="cost_client_type" - ) - - with col2: - area = st.number_input("المساحة (م²)", min_value=100, max_value=1000000, value=5000, key="cost_area") - - floors = st.number_input("عدد الطوابق", min_value=1, max_value=100, value=3, key="cost_floors") - - duration = st.number_input("مدة التنفيذ (شهور)", min_value=1, max_value=60, value=12, key="cost_duration") - - tender_type = st.selectbox( - "نوع المناقصة", - [ - "عامة", - "خاصة", - "أمر مباشر" - ], - key="cost_tender_type" - ) - - st.markdown("#### متغيرات إضافية") - - col1, col2, col3 = st.columns(3) - - with col1: - has_basement = st.checkbox("يتضمن بدروم", key="cost_has_basement") - has_special_finishing = st.checkbox("تشطيبات خاصة", key="cost_has_special_finishing") - - with col2: - has_landscape = st.checkbox("أعمال تنسيق المواقع", key="cost_has_landscape") - has_parking = st.checkbox("مواقف متعددة الطوابق", key="cost_has_parking") - - with col3: - has_smart_systems = st.checkbox("أنظمة ذكية", key="cost_has_smart_systems") - has_sustainability = st.checkbox("متطلبات استدامة", key="cost_has_sustainability") - - # زر التنبؤ بالتكلفة مع دعم Claude AI - col1, col2 = st.columns([1, 3]) - - with col1: - predict_button = st.button("التنبؤ بالتكلفة", use_container_width=True, key="cost_predict_button") - - with col2: - use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="cost_use_claude") - - if predict_button: - with st.spinner("جاري تحليل البيانات والتنبؤ بالتكاليف..."): - # محاكاة وقت المعالجة - time.sleep(2) - - # تجهيز البيانات للنموذج - features = { - 'project_type': project_type, - 'location': location, - 'area': area, - 'floors': floors, - 'duration_months': duration, - 'tender_type': tender_type, - 'client_type': client_type, - 'has_basement': has_basement, - 'has_special_finishing': has_special_finishing, - 'has_landscape': has_landscape, - 'has_parking': has_parking, - 'has_smart_systems': has_smart_systems, - 'has_sustainability': has_sustainability - } - - # استدعاء النموذج للتنبؤ - cost_prediction_results = self._predict_cost(features) - - # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار - if use_claude: - try: - # إنشاء نص الميزات للتحليل - features_text = f""" - بيانات المشروع: - - نوع المشروع: {project_type} - - الموقع: {location} - - المساحة: {area} م² - - عدد الطوابق: {floors} - - مدة التنفيذ: {duration} شهر - - نوع المناقصة: {tender_type} - - نوع العميل: {client_type} - - يتضمن بدروم: {'نعم' if has_basement else 'لا'} - - تشطيبات خاصة: {'نعم' if has_special_finishing else 'لا'} - - أعمال تنسيق المواقع: {'نعم' if has_landscape else 'لا'} - - مواقف متعددة الطوابق: {'نعم' if has_parking else 'لا'} - - أنظمة ذكية: {'نعم' if has_smart_systems else 'لا'} - - متطلبات استدامة: {'نعم' if has_sustainability else 'لا'} - - نتائج التنبؤ الأولية: - - التكلفة الإجمالية المقدرة: {cost_prediction_results['total_cost']:,.0f} ريال - - تكلفة المتر المربع: {cost_prediction_results['cost_per_sqm']:,.0f} ريال/م² - - تكلفة المواد: {cost_prediction_results['material_cost']:,.0f} ريال - - تكلفة العمالة: {cost_prediction_results['labor_cost']:,.0f} ريال - - تكلفة المعدات: {cost_prediction_results['equipment_cost']:,.0f} ريال - """ - - prompt = f"""تحليل بيانات مشروع وتكاليف��: - - {features_text} - - المطلوب: - 1. تحليل التكاليف المتوقعة ومعقوليتها مقارنة بمشاريع مماثلة في السوق السعودي - 2. تقديم توصيات وملاحظات لتحسين التكلفة - 3. تحديد أي مخاطر محتملة قد تؤثر على التكلفة - 4. تقديم نصائح لزيادة فعالية التكلفة - 5. تقديم رأي حول مدى تنافسية هذه التكلفة في السوق الحالي - - يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. - """ - - # استدعاء Claude للتحليل - claude_analysis = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}] - ) - - if "error" not in claude_analysis: - # إضافة تحليل Claude إلى النتائج - cost_prediction_results["claude_analysis"] = claude_analysis["content"] - except Exception as e: - st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}") - - # عرض نتائج التنبؤ - self._display_cost_prediction_results(cost_prediction_results) - - def _predict_cost(self, features): - """التنبؤ بتكاليف المشروع""" - - # في البيئة الحقيقية، سيتم استدعاء نموذج التنبؤ بالتكاليف - # محاكاة نتائج التنبؤ للعرض - - # حساب القيمة الأساسية للمتر المربع حسب نوع المشروع - base_cost_per_sqm = { - "مباني سكنية": 2500, - "مباني تجارية": 3000, - "مباني حكومية": 3500, - "مراكز صحية": 4000, - "مدارس": 3200, - "بنية تحتية": 2000, - "طرق": 1500, - "جسور": 5000, - "صرف صحي": 2200, - "مياه": 2000, - "كهرباء": 2500 - }.get(features['project_type'], 2500) - - # تطبيق معاملات التعديل حسب المتغيرات - location_factor = { - "الرياض": 1.1, - "جدة": 1.15, - "الدمام": 1.05, - "مكة": 1.2, - "المدينة": 1.1, - "تبوك": 0.95, - "حائل": 0.9, - "عسير": 0.95, - "جازان": 0.9, - "نجران": 0.85, - "الباحة": 0.9, - "الجوف": 0.85, - "القصيم": 0.9 - }.get(features['location'], 1.0) - - client_factor = { - "حكومي": 1.05, - "شبه حكومي": 1.0, - "شركة كبيرة": 0.95, - "شركة متوسطة": 0.9, - "شركة صغيرة": 0.85, - "أفراد": 0.8 - }.get(features['client_type'], 1.0) - - tender_factor = { - "عامة": 1.0, - "خاصة": 0.95, - "أمر مباشر": 0.9 - }.get(features['tender_type'], 1.0) - - # معاملات للميزات الإضافية - basement_factor = 1.1 if features['has_basement'] else 1.0 - special_finishing_factor = 1.2 if features['has_special_finishing'] else 1.0 - landscape_factor = 1.05 if features['has_landscape'] else 1.0 - parking_factor = 1.1 if features['has_parking'] else 1.0 - smart_systems_factor = 1.15 if features['has_smart_systems'] else 1.0 - sustainability_factor = 1.1 if features['has_sustainability'] else 1.0 - - # معامل لعدد الطوابق - floors_factor = 1.0 + (features['floors'] - 1) * 0.05 - - # حساب التكلفة الإجمالية - total_sqm_cost = base_cost_per_sqm * location_factor * client_factor * tender_factor * \ - basement_factor * special_finishing_factor * landscape_factor * \ - parking_factor * smart_systems_factor * sustainability_factor * \ - floors_factor - - total_cost = total_sqm_cost * features['area'] - - # حساب التكاليف المفصلة - material_cost = total_cost * 0.6 - labor_cost = total_cost * 0.25 - equipment_cost = total_cost * 0.15 - - # إضافة هامش خطأ عشوائي للمحاكاة - error_margin = 0.05 # 5% - total_cost = total_cost * (1 + np.random.uniform(-error_margin, error_margin)) - - # إعداد النتائج - results = { - "total_cost": total_cost, - "cost_per_sqm": total_cost / features['area'], - "material_cost": material_cost, - "labor_cost": labor_cost, - "equipment_cost": equipment_cost, - "breakdown": { - "structural_works": total_cost * 0.35, - "architectural_works": total_cost * 0.25, - "mep_works": total_cost * 0.25, - "site_works": total_cost * 0.1, - "general_requirements": total_cost * 0.05 - }, - "confidence_level": 0.85, # مستوى الثقة في التنبؤ - "comparison": { - "market_average": total_cost * 1.1, - "historical_projects": total_cost * 0.95 - } - } - - return results - - def _display_cost_prediction_results(self, results): - """عرض نتائج التنبؤ بالتكاليف""" - - st.markdown("### نتائج التنبؤ بالتكاليف") - - # عرض التكلفة الإجمالية وتكلفة المتر المربع - col1, col2, col3 = st.columns(3) - - with col1: - st.metric( - "التكلفة الإجمالية المتوقعة", - f"{results['total_cost']:,.0f} ريال", - delta=f"{(results['total_cost'] - results['comparison']['historical_projects']):,.0f} ريال" - ) - - with col2: - st.metric( - "تكلفة المتر المربع", - f"{results['cost_per_sqm']:,.0f} ريال/م²" - ) - - with col3: - st.metric( - "مستوى الثقة في التنبؤ", - f"{results['confidence_level'] * 100:.0f}%" - ) - - # عرض تفصيل التكاليف - st.markdown("#### تفصيل التكاليف") - - # رسم مخطط دائري للتكاليف المفصلة - fig = px.pie( - values=[ - results['material_cost'], - results['labor_cost'], - results['equipment_cost'] - ], - names=["تكلفة المواد", "تكلفة العمالة", "تكلفة المعدات"], - title="توزيع التكاليف الرئيسية" - ) - - st.plotly_chart(fig, use_container_width=True) - - # رسم مخطط شريطي لتفصيل الأعمال - breakdown_data = pd.DataFrame({ - 'فئة الأعمال': [ - "الأعمال الإنشائية", - "الأعمال المعمارية", - "الأعمال الكهروميكانيكية", - "أعمال الموقع", - "المتطلبات العامة" - ], - 'التكلفة': [ - results['breakdown']['structural_works'], - results['breakdown']['architectural_works'], - results['breakdown']['mep_works'], - results['breakdown']['site_works'], - results['breakdown']['general_requirements'] - ] - }) - - fig = px.bar( - breakdown_data, - x='فئة الأعمال', - y='التكلفة', - title="تفصيل التكاليف حسب فئة الأعمال", - text_auto='.3s' - ) - - fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside') - - st.plotly_chart(fig, use_container_width=True) - - # عرض مقارنة مع متوسط السوق - st.markdown("#### مقارنة مع متوسط السوق") - - comparison_data = pd.DataFrame({ - 'المصدر': [ - "التكلفة المتوقعة", - "متوسط السوق", - "مشاريع مماثلة سابقة" - ], - 'التكلفة': [ - results['total_cost'], - results['comparison']['market_average'], - results['comparison']['historical_projects'] - ] - }) - - fig = px.bar( - comparison_data, - x='المصدر', - y='التكلفة', - title="مقارنة التكلفة المتوقعة مع السوق", - text_auto='.3s', - color='المصدر', - color_discrete_map={ - "التكلفة المتوقعة": "#1f77b4", - "متوسط السوق": "#ff7f0e", - "مشاريع مماثلة سابقة": "#2ca02c" - } - ) - - fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside') - - st.plotly_chart(fig, use_container_width=True) - - # عرض تحليل Claude AI إذا كان متوفراً - if "claude_analysis" in results: - st.markdown("### تحليل Claude AI المتقدم") - st.info(results["claude_analysis"]) - - # عرض ملاحظات وتوصيات - st.markdown("#### ملاحظات وتوصيات") - - st.info(""" - - تم التنبؤ بالتكاليف بناءً على البيانات المدخلة ونماذج التعلم الآلي المدربة على مشاريع مماثلة. - - مستوى الثقة في التنبؤ جيد، ولكن يجب مراجعة التكاليف بشكل تفصيلي قبل اتخاذ القرار النهائي. - - تكلفة المتر المربع متوافقة مع متوسط السوق لهذا النوع من المشاريع. - - ينصح بمراجعة التصميم لتحسين التكلفة وزيادة الكفاءة. - """) - - # زر تصدير التقرير - if st.button("تصدير تقرير التكاليف"): - st.success("تم تصدير تقرير التكاليف بنجاح!") - - def _render_risk_analysis_tab(self): - """عرض تبويب تحليل المخاطر""" - - st.markdown("### تحليل المخاطر") - - # عرض نموذج إدخال بيانات المشروع للمخاطر - st.markdown("#### بيانات المشروع") - - col1, col2 = st.columns(2) - - with col1: - project_type = st.selectbox( - "نوع المشروع", - [ - "مباني سكنية", - "مباني تجارية", - "مباني حكومية", - "مراكز صحية", - "مدارس", - "بنية تحتية", - "طرق", - "جسور", - "صرف صحي", - "مياه", - "كهرباء" - ], - key="risk_project_type" - ) - - location = st.selectbox( - "الموقع", - [ - "الرياض", - "جدة", - "الدمام", - "مكة", - "المدينة", - "تبوك", - "حائل", - "عسير", - "جازان", - "نجران", - "الباحة", - "الجوف", - "القصيم" - ], - key="risk_location" - ) - - with col2: - client_type = st.selectbox( - "نوع العميل", - [ - "حكومي", - "شبه حكومي", - "شركة كبيرة", - "شركة متوسطة", - "شركة صغيرة", - "أفراد" - ], - key="risk_client_type" - ) - - tender_type = st.selectbox( - "نوع المناقصة", - [ - "عامة", - "خاصة", - "أمر مباشر" - ], - key="risk_tender_type" - ) - - st.markdown("#### عوامل المخاطرة") - - col1, col2, col3 = st.columns(3) - - with col1: - payment_terms = st.slider("شروط الدفع (1-10)", 1, 10, 5, - help="1: شروط دفع سيئة جداً، 10: شروط دفع ممتازة", - key="risk_payment_terms") - completion_deadline = st.slider("مهلة الإنجاز (1-10)", 1, 10, 5, - help="1: مهلة قصيرة جداً، 10: مهلة مريحة", - key="risk_completion_deadline") - - with col2: - penalty_clause = st.slider("شروط الغرامات (1-10)", 1, 10, 5, - help="1: غرامات مرتفعة جداً، 10: غرامات معقولة", - key="risk_penalty_clause") - technical_complexity = st.slider("التعقيد الفني (1-10)", 1, 10, 5, - help="1: بسيط جداً، 10: معقد للغاية", - key="risk_technical_complexity") - - with col3: - company_experience = st.slider("خبرة الشركة (1-10)", 1, 10, 7, - help="1: لا توجد خبرة، 10: خبرة عالية", - key="risk_company_experience") - market_volatility = st.slider("تقلبات السوق (1-10)", 1, 10, 5, - help="1: مستقر جداً، 10: متقلب للغاية", - key="risk_market_volatility") - - # زر تحليل المخاطر مع دعم Claude AI - col1, col2 = st.columns([1, 3]) - - with col1: - analyze_button = st.button("تحليل المخاطر", use_container_width=True, key="risk_analyze_button") - - with col2: - # Añadimos un key único para este checkbox - use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="risk_use_claude") - - if analyze_button: - with st.spinner("جاري تحليل المخاطر..."): - # محاكاة وقت المعالجة - time.sleep(2) - - # تجهيز البيانات للنموذج - features = { - 'project_type': project_type, - 'location': location, - 'client_type': client_type, - 'tender_type': tender_type, - 'payment_terms': payment_terms, - 'completion_deadline': completion_deadline, - 'penalty_clause': penalty_clause, - 'technical_complexity': technical_complexity, - 'company_experience': company_experience, - 'market_volatility': market_volatility - } - - # استدعاء النموذج لتحليل المخاطر - risk_analysis_results = self._analyze_risks(features) - - # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار - if use_claude: - try: - # إنشاء نص الميزات للتحليل - features_text = f""" - بيانات المشروع: - - نوع المشروع: {project_type} - - الموقع: {location} - - نوع العميل: {client_type} - - نوع المناقصة: {tender_type} - - عوامل المخاطرة: - - شروط الدفع: {payment_terms}/10 - - مهلة الإنجاز: {completion_deadline}/10 - - شروط الغرامات: {penalty_clause}/10 - - التعقيد الفني: {technical_complexity}/10 - - خبرة الشركة: {company_experience}/10 - - تقلبات السوق: {market_volatility}/10 - - ملخص التحليل الأولي: - - متوسط درجة المخاطرة: {risk_analysis_results['avg_risk_score']:.1f}/10 - - عدد المخاطر العالية: {risk_analysis_results['high_risks']} - - عدد المخاطر المتوسطة: {risk_analysis_results['medium_risks']} - - عدد المخاطر المنخفضة: {risk_analysis_results['low_risks']} - - أعلى المخاطر: - """ - - # إضافة تفاصيل أعلى المخاطر - for i, risk in enumerate(risk_analysis_results['top_risks'][:3]): - features_text += f""" - {i+1}. {risk['name']} ({risk['category']}) - - الاحتمالية: {risk['probability'] * 100:.0f}% - - التأثير: {risk['impact'] * 100:.0f}% - - درجة المخاطرة: {risk['risk_score']}/10 - """ - - prompt = f"""تحليل مخاطر مشروع: - - {features_text} - - المطلوب: - 1. تحليل عوامل المخاطرة وتأثيرها على المشروع - 2. تقديم توصيات إضافية لإدارة المخاطر - 3. اقتراح استراتيجيات استجابة للمخاطر الرئيسية - 4. تقديم نصائح لتحسين شروط العقد لتقليل المخاطر - 5. تقييم مدى ملاءمة المشروع لاستراتيجية الشركة - - يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. - """ - - # استدعاء Claude للتحليل - claude_analysis = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}] - ) - - if "error" not in claude_analysis: - # إضافة تحليل Claude إلى النتائج - risk_analysis_results["claude_analysis"] = claude_analysis["content"] - except Exception as e: - st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}") - - # عرض نتائج تحليل المخاطر - self._display_risk_analysis_results(risk_analysis_results) - - def _analyze_risks(self, features): - """تحليل مخاطر المشروع""" - - # في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المخاطر - # محاكاة نتائج تحليل المخاطر للعرض - - # تعريف قائمة من المخاطر المحتملة - potential_risks = [ - { - "id": "R-001", - "name": "غرامة تأخير مرتفعة", - "category": "مخاطر مالية", - "description": "غرامة تأخير مرتفعة تصل إلى 10% من قيمة العقد، مما قد يؤثر سلباً على ربحية المشروع في حال التأخير.", - "probability": 0.6, - "impact": 0.8, - "risk_score": 7.8, - "response_strategy": "تخطيط مفصل للمشروع مع وضع مخزون زمني مناسب وتحديد نقاط التسليم المبكر." - }, - { - "id": "R-002", - "name": "تقلبات أسعار المواد", - "category": "مخاطر السوق", - "description": "ارتفاع محتمل في أسعار المواد الخام خلال فترة تنفيذ المشروع، مما يؤثر على التكلفة الإجمالية.", - "probability": 0.7, - "impact": 0.7, - "risk_score": 7.5, - "response_strategy": "التعاقد المبكر مع الموردين وتثبيت الأسعار، أو إضافة بند تعديل سعري في العقد." - }, - { - "id": "R-003", - "name": "ضعف تدفق المدفوعات", - "category": "مخاطر مالية", - "description": "تأخر العميل في سداد المستخلصات مما يؤثر على التدفق النقدي للمشروع.", - "probability": 0.5, - "impact": 0.8, - "risk_score": 7.2, - "response_strategy": "التفاوض على شروط دفع واضحة ومواعيد محددة، وإمكانية طلب دفعة مقدمة." - }, - { - "id": "R-004", - "name": "نقص العمالة الماهرة", - "category": "مخاطر الموارد", - "description": "صعوبة توفير عمالة ماهرة لتنفيذ أجزاء محددة من المشروع.", - "probability": 0.5, - "impact": 0.6, - "risk_score": 6.5, - "response_strategy": "التخطيط المبكر للموارد البشرية وتوقيع عقود مع مقاولي الباطن المتخصصين." - }, - { - "id": "R-005", - "name": "تغييرات في نطاق العمل", - "category": "مخاطر تعاقدية", - "description": "طلبات تغيير من العميل تؤدي إلى زيادة نطاق العمل دون تعديل مناسب للتكلفة والجدول الزمني.", - "probability": 0.6, - "impact": 0.6, - "risk_score": 6.0, - "response_strategy": "تضمين آلية واضحة لإدارة التغيير في العقد وتقييم تأثير أي تغييرات على التكلفة والزمن." - }, - { - "id": "R-006", - "name": "مشاكل في الموقع", - "category": "مخاطر فنية", - "description": "ظروف موقع غير متوقعة تؤثر على تنفيذ الأعمال، مثل مشاكل في التربة أو مرافق تحت الأرض.", - "probability": 0.4, - "impact": 0.7, - "risk_score": 5.8, - "response_strategy": "إجراء دراسات واختبارات مفصلة للموقع قبل بدء التنفيذ، وتخصيص احتياطي للطوارئ." - }, - { - "id": "R-007", - "name": "تضارب في التصاميم", - "category": "مخاطر فنية", - "description": "تعارض بين مختلف تخصصات التصميم (معماري، إنشائي، كهروميكانيكي) يؤدي إلى تأخير وإعادة عمل.", - "probability": 0.4, - "impact": 0.6, - "risk_score": 5.4, - "response_strategy": "مراجعة شاملة للتصاميم قبل البدء في التنفيذ واستخدام نمذجة معلومات البناء (BIM) لكشف التعارضات." - }, - { - "id": "R-008", - "name": "تأخر الموافقات", - "category": "مخاطر تنظيمية", - "description": "تأخر في الحصول على الموافقات والتصاريح اللازمة من الجهات المختصة.", - "probability": 0.5, - "impact": 0.5, - "risk_score": 5.0, - "response_strategy": "التخطيط المبكر للتصاريح المطلوبة وبناء علاقات جيدة مع الجهات التنظيمية." - }, - { - "id": "R-009", - "name": "عدم توفر المعدات", - "category": "مخاطر الموارد", - "description": "صعوبة في توفير المعدات المتخصصة في الوقت المطلوب.", - "probability": 0.3, - "impact": 0.6, - "risk_score": 4.8, - "response_strategy": "حجز المعدات مبكراً وتوفير بدائل محتملة في حالة عدم توفر المعدات الأساسية." - }, - { - "id": "R-010", - "name": "ظروف جوية قاسية", - "category": "مخاطر خارجية", - "description": "تأثير الظروف الجوية القاسية (حرارة شديدة، أمطار غزيرة، عواصف رملية) على سير العمل.", - "probability": 0.3, - "impact": 0.5, - "risk_score": 4.5, - "response_strategy": "تخطيط الجدول الزمني مع مراعاة المواسم وإضافة مخزون زمني للظروف الجوية غير المتوقعة." - } - ] - - # حساب درجات المخاطرة بناءً على الميزات المدخلة - for risk in potential_risks: - # تعديل احتمالية حدوث المخاطر بناءً على العوامل المدخلة - if risk["id"] == "R-001": # غرامة تأخير - risk["probability"] = risk["probability"] * (10 - features["penalty_clause"]) / 10 - risk["probability"] = risk["probability"] * (10 - features["completion_deadline"]) / 10 - - elif risk["id"] == "R-002": # تقلبات أسعار المواد - risk["probability"] = risk["probability"] * features["market_volatility"] / 10 - - elif risk["id"] == "R-003": # ضعف تدفق المدفوعات - risk["probability"] = risk["probability"] * (10 - features["payment_terms"]) / 10 - - if features["client_type"] == "حكومي": - risk["probability"] = risk["probability"] * 0.6 # احتمالية أقل مع العملاء الحكوميين - elif features["client_type"] == "أفراد": - risk["probability"] = risk["probability"] * 1.3 # احتمالية أعلى مع العملاء الأفراد - - elif risk["id"] == "R-004": # نقص العمالة الماهرة - risk["probability"] = risk["probability"] * features["technical_complexity"] / 10 - - elif risk["id"] == "R-005": # تغييرات في نطاق العمل - risk["probability"] = risk["probability"] * features["technical_complexity"] / 10 - - if features["client_type"] == "حكومي": - risk["probability"] = risk["probability"] * 1.2 # احتمالية أعلى للتغييرات مع العملاء الحكوميين - - # تعديل تأثير المخاطر بناءً على العوامل المدخلة - if risk["category"] == "مخاطر فنية": - risk["impact"] = risk["impact"] * (10 - features["company_experience"]) / 10 - - # إعادة حساب درجة المخاطرة - risk["risk_score"] = round(risk["probability"] * risk["impact"] * 10, 1) - - # ترتيب المخاطر تنازلياً حسب درجة المخاطرة - sorted_risks = sorted(potential_risks, key=lambda x: x["risk_score"], reverse=True) - - # حساب عدد المخاطر حسب شدتها - high_risks = sum(1 for risk in sorted_risks if risk["risk_score"] >= 6.0) - medium_risks = sum(1 for risk in sorted_risks if 3.0 <= risk["risk_score"] < 6.0) - low_risks = sum(1 for risk in sorted_risks if risk["risk_score"] < 3.0) - - # حساب متوسط درجة المخاطرة - avg_risk_score = sum(risk["risk_score"] for risk in sorted_risks) / len(sorted_risks) - - # تجهيز النتائج - results = { - "top_risks": sorted_risks, - "high_risks": high_risks, - "medium_risks": medium_risks, - "low_risks": low_risks, - "avg_risk_score": avg_risk_score, - "risk_profile": { - "financial_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر مالية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر مالية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر مالية") > 0 else 0, - "technical_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر فنية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر فنية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر فنية") > 0 else 0, - "market_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر السوق") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر السوق") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر السوق") > 0 else 0, - "resource_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر الموارد") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر الموارد") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر الموارد") > 0 else 0, - "contract_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") > 0 else 0, - "regulatory_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") > 0 else 0 - }, - "overall_assessment": "", - "recommendation": "" - } - - # تقييم شامل للمخاطر - if avg_risk_score >= 6.0: - results["overall_assessment"] = "مشروع عالي المخاطر" - results["recommendation"] = "ينصح بإعادة التفاوض على شروط العقد أو إضافة هامش ربح أعلى لتغطية المخاطر." - elif avg_risk_score >= 4.0: - results["overall_assessment"] = "مشروع متوسط المخاطر" - results["recommendation"] = "متابعة دقيقة للمخاطر العالية ووضع خطط استجابة مفصلة لها." - else: - results["overall_assessment"] = "مشروع منخفض المخاطر" - results["recommendation"] = "مراقبة المخاطر بشكل دوري والتركيز على تحسين الأداء." - - return results - - def _display_risk_analysis_results(self, results): - """عرض نتائج تحليل المخاطر""" - - st.markdown("### نتائج تحليل المخاطر") - - # عرض ملخص تقييم المخاطر - st.markdown(f"#### التقييم العام: {results['overall_assessment']}") - - # عرض الإحصائيات الرئيسية للمخاطر - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.metric("متوسط درجة المخاطرة", f"{results['avg_risk_score']:.1f}/10") - - with col2: - st.metric("المخاطر العالية", f"{results['high_risks']}") - - with col3: - st.metric("المخاطر المتوسطة", f"{results['medium_risks']}") - - with col4: - st.metric("المخاطر المنخفضة", f"{results['low_risks']}") - - # عرض ملف المخاطر حسب الفئة - st.markdown("#### ملف المخاطر حسب الفئة") - - # تجهيز البيانات للرسم البياني - risk_profile_data = pd.DataFrame({ - 'الفئة': [ - "مخاطر مالية", - "مخاطر فنية", - "مخاطر السوق", - "مخاطر الموارد", - "مخاطر تعاقدية", - "مخاطر تنظيم��ة" - ], - 'درجة المخاطرة': [ - results['risk_profile']['financial_risk'], - results['risk_profile']['technical_risk'], - results['risk_profile']['market_risk'], - results['risk_profile']['resource_risk'], - results['risk_profile']['contract_risk'], - results['risk_profile']['regulatory_risk'] - ] - }) - - # رسم مخطط شعاعي لملف المخاطر - fig = px.line_polar( - risk_profile_data, - r='درجة المخاطرة', - theta='الفئة', - line_close=True, - range_r=[0, 10], - title="ملف المخاطر حسب الفئة" - ) - - st.plotly_chart(fig, use_container_width=True) - - # عرض المخاطر الرئيسية - st.markdown("#### المخاطر الرئيسية") - - # إنشاء جدول المخاطر - risk_table_data = [] - - for risk in results['top_risks'][:5]: # عرض أعلى 5 مخاطر فقط - risk_level = "عالية" if risk["risk_score"] >= 6.0 else "متوسطة" if risk["risk_score"] >= 3.0 else "منخفضة" - risk_color = "red" if risk_level == "عالية" else "orange" if risk_level == "متوسطة" else "green" - - risk_table_data.append({ - "المعرف": risk["id"], - "الوصف": risk["name"], - "الفئة": risk["category"], - "الاحتمالية": f"{risk['probability'] * 100:.0f}%", - "التأثير": f"{risk['impact'] * 100:.0f}%", - "درجة المخاطرة": risk["risk_score"], - "المستوى": risk_level, - "استراتيجية الاستجابة": risk["response_strategy"], - "color": risk_color - }) - - # عرض جدول المخاطر - risk_df = pd.DataFrame(risk_table_data) - - # استخدام تنسيق HTML مخصص لعرض المخاطر الرئيسية - for index, row in risk_df.iterrows(): - with st.container(): - st.markdown(f""" -
-
{row['المعرف']} - {row['الوصف']} {row['المستوى']}
-

الفئة: {row['الفئة']} | الاحتمالية: {row['الاحتمالية']} | التأثير: {row['التأثير']} | درجة المخاطرة: {row['درجة المخاطرة']}/10

-

استراتيجية الاستجابة: {row['استراتيجية الاستجابة']}

-
- """, unsafe_allow_html=True) - - # عرض توصيات عامة - st.markdown("#### التوصيات العامة") - st.info(results["recommendation"]) - - # عرض تحليل Claude AI إذا كان متوفراً - if "claude_analysis" in results: - st.markdown("### تحليل Claude AI المتقدم") - st.success(results["claude_analysis"]) - - # زر تصدير تقرير المخاطر - if st.button("تصدير تقرير المخاطر"): - st.success("تم تصدير تقرير المخاطر بنجاح!") - - def _render_document_analysis_tab(self): - """عرض تبويب تحليل المستندات""" - - st.markdown("### تحليل المستندات") - - # خيارات رفع الملفات - st.markdown("#### رفع ملفات المناقصة") - - # رفع الملفات - uploaded_files = st.file_uploader( - "اختر ملفات المناقصة للتحليل", - type=["pdf", "docx", "doc", "xls", "xlsx", "jpg", "jpeg", "png"], - accept_multiple_files=True, - key="document_analysis_files" - ) - - # اختيار نموذج التحليل - analysis_model = st.radio( - "اختر نموذج التحليل", - [ - "استخراج البنود والمواصفات", - "استخراج الشروط التعاقدية", - "تحليل الكميات", - "تحليل المتطلبات القانونية", - "تحليل شامل (يستخدم Claude AI)" - ], - horizontal=True - ) - - # زر بدء التحليل - if uploaded_files and st.button("بدء تحليل المستندات"): - with st.spinner("جاري تحليل ��لمستندات..."): - # محاكاة وقت المعالجة - time.sleep(3) - - # معالجة الملفات المرفوعة - analysis_results = self._analyze_documents(uploaded_files, analysis_model) - - # عرض نتائج التحليل - self._display_document_analysis_results(analysis_results) - - def _analyze_documents(self, files, analysis_model): - """تحليل المستندات المرفوعة""" - - # في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المستندات - # محاكاة نتائج تحليل المستندات للعرض - - # نتائج التحليل المبدئية - basic_results = { - "file_count": len(files), - "file_names": [file.name for file in files], - "file_sizes": [f"{file.size / 1024:.1f} KB" for file in files], - "file_types": [file.type or "غير محدد" for file in files], - "extracted_text_samples": {}, - "entities": [], - "tender_items": [], - "contract_terms": [], - "quantities": [], - "legal_requirements": [], - "summary": "" - } - - # محاكاة استخراج نص من الملفات - for file in files: - # استخراج عينة نصية (في البيئة الحقيقية سيتم استخراج النص الكامل) - sample_text = f"عينة نصية مستخرجة من الملف {file.name}. هذا النص لأغراض العرض فقط." - basic_results["extracted_text_samples"][file.name] = sample_text - - # محاكاة تحليل المحتوى حسب نموذج التحليل المختار - if analysis_model == "استخراج البنود والمواصفات" or analysis_model == "تحليل شامل (يستخدم Claude AI)": - basic_results["tender_items"] = [ - { - "id": "T-001", - "description": "أعمال الحفر والردم", - "unit": "م³", - "quantity": 1500, - "estimated_price": 85, - "specifications": "حفر في أي نوع من التربة بما في ذلك الصخور والردم باستخدام مواد معتمدة." - }, - { - "id": "T-002", - "description": "أعمال الخرسانة المسلحة للأساسات", - "unit": "م³", - "quantity": 750, - "estimated_price": 1200, - "specifications": "خرسانة مسلحة بقوة 30 نيوتن/مم² بعد 28 يوم، مع حديد تسليح من الفئة 60." - }, - { - "id": "T-003", - "description": "أعمال الخرسانة المسلحة للهيكل", - "unit": "م³", - "quantity": 1200, - "estimated_price": 1350, - "specifications": "خرسانة مسلحة بقوة 30 نيوتن/مم² بعد 28 يوم، مع حديد تسليح من الفئة 60." - }, - { - "id": "T-004", - "description": "أعمال الطابوق", - "unit": "م²", - "quantity": 3500, - "estimated_price": 120, - "specifications": "جدران طابوق مفرغ سمك 20 سم مع مونة إسمنتية." - }, - { - "id": "T-005", - "description": "أعمال التشطيبات الداخلية", - "unit": "م²", - "quantity": 5000, - "estimated_price": 200, - "specifications": "تشطيبات داخلية تشمل اللياسة والدهان والأرضيات حسب المواصفات المرفقة." - } - ] - - if analysis_model == "استخراج الشروط التعاقدية" or analysis_model == "تحليل شامل (يستخدم Claude AI)": - basic_results["contract_terms"] = [ - { - "id": "C-001", - "title": "مدة تنفيذ المشروع", - "description": "يجب إنجاز جميع الأعمال خلال 18 شهراً من تاريخ تسليم الموقع.", - "risk_level": "متوسط", - "notes": "مدة تنفيذ معقولة نسبياً للحجم المتوقع من الأعمال." - }, - { - "id": "C-002", - "title": "غرامة التأخير", - "description": "تفرض غرا��ة تأخير بنسبة 0.1% من قيمة العقد عن كل يوم تأخير، بحد أقصى 10% من القيمة الإجمالية للعقد.", - "risk_level": "عالي", - "notes": "غرامة مرتفعة نسبياً، تتطلب جدولة دقيقة وإدارة استباقية للمخاطر." - }, - { - "id": "C-003", - "title": "شروط الدفع", - "description": "يتم صرف المستخلصات خلال 45 يوماً من تاريخ تقديمها، مع خصم نسبة 10% كضمان حسن التنفيذ تسترد بعد فترة الضمان.", - "risk_level": "متوسط", - "notes": "فترة 45 يوماً طويلة نسبياً وقد تؤثر على التدفق النقدي." - }, - { - "id": "C-004", - "title": "التزامات المحتوى المحلي", - "description": "يجب أن لا تقل نسبة المحتوى المحلي عن 30% من إجمالي قيمة العقد.", - "risk_level": "منخفض", - "notes": "يمكن تحقيق النسبة المطلوبة من خلال توريد المواد والعمالة المحلية." - }, - { - "id": "C-005", - "title": "التغييرات والأعمال الإضافية", - "description": "يحق للمالك طلب تغييرات بنسبة ±10% من قيمة العقد دون تعديل أسعار الوحدات.", - "risk_level": "متوسط", - "notes": "نسبة معقولة، لكن يجب مراعاة احتمالية الطلبات الإضافية عند تسعير البنود." - } - ] - - if analysis_model == "تحليل الكميات" or analysis_model == "تحليل شامل (يستخدم Claude AI)": - basic_results["quantities"] = [ - { - "category": "أعمال الحفر والردم", - "volume": 1500, - "unit": "م³", - "estimated_cost": 127500 - }, - { - "category": "أعمال الخرسانة", - "volume": 1950, - "unit": "م³", - "estimated_cost": 2437500 - }, - { - "category": "أعمال الطابوق", - "volume": 3500, - "unit": "م²", - "estimated_cost": 420000 - }, - { - "category": "أعمال التشطيبات الداخلية", - "volume": 5000, - "unit": "م²", - "estimated_cost": 1000000 - }, - { - "category": "أعمال التشطيبات الخارجية", - "volume": 2200, - "unit": "م²", - "estimated_cost": 660000 - }, - { - "category": "أعمال الكهروميكانيكية", - "volume": 1, - "unit": "مقطوعية", - "estimated_cost": 1750000 - } - ] - - if analysis_model == "تحليل المتطلبات القانونية" or analysis_model == "تحليل شامل (يستخدم Claude AI)": - basic_results["legal_requirements"] = [ - { - "id": "L-001", - "title": "متطلبات التراخيص", - "description": "يجب أن يكون المقاول حاصلاً على تصنيف في الفئة الأولى في مجال المباني.", - "compliance_status": "مطلوب التحقق", - "required_documents": "شهادة التصنيف سارية المفعول" - }, - { - "id": "L-002", - "title": "متطلبات التأمين", - "description": "يجب تقديم بوليصة تأمين شاملة تغطي جميع مخاطر المشروع بقيمة لا تقل عن 100% من قيمة العقد.", - "compliance_status": "مطلوب التحقق", - "required_documents": "وثائق التأمين الشاملة" - }, - { - "id": "L-003", - "title": "متطلبات الضمان البنكي", - "description": "يجب تقديم ضمان بنكي ابتدائي بنسبة 2% من قيمة العطاء، وضمان نهائي بنسبة 5% من قيمة العقد.", - "compliance_status": "مطلوب ��لتحقق", - "required_documents": "نماذج الضمانات البنكية" - }, - { - "id": "L-004", - "title": "متطلبات السعودة", - "description": "يجب الالتزام بنسبة السعودة المطلوبة حسب برنامج نطاقات وأن يكون المقاول في النطاق الأخضر.", - "compliance_status": "مطلوب التحقق", - "required_documents": "شهادة نطاقات سارية المفعول" - }, - { - "id": "L-005", - "title": "متطلبات الزكاة والدخل", - "description": "يجب تقديم شهادة سداد الزكاة والضريبة سارية المفعول.", - "compliance_status": "مطلوب التحقق", - "required_documents": "شهادة الزكاة والدخل" - } - ] - - # إعداد ملخص التحليل - basic_results["summary"] = f""" - تم تحليل {len(files)} ملفات بإجمالي حجم {sum([file.size for file in files]) / 1024 / 1024:.2f} ميجابايت. - - نتائج التحليل الرئيسية: - - تم استخراج {len(basic_results.get('tender_items', []))} بنود رئيسية للمناقصة. - - تم تحديد {len(basic_results.get('contract_terms', []))} شروط تعاقدية هامة. - - تم تحليل الكميات لـ {len(basic_results.get('quantities', []))} فئات من الأعمال. - - تم تحديد {len(basic_results.get('legal_requirements', []))} متطلبات قانونية. - - التوصيات: - - مراجعة شروط التعاقد وخاصة البنود المتعلقة بالغرامات والدفعات. - - تدقيق جداول الكميات والتأكد من تغطية جميع البنود اللازمة للتنفيذ. - - التحقق من استيفاء جميع المتطلبات القانونية قبل تقديم العطاء. - """ - - # إضافة تحليل متقدم باستخدام Claude AI إذا تم اختياره - if analysis_model == "تحليل شامل (يستخدم Claude AI)": - try: - # إنشاء مدخلات للتحليل - analysis_input = f""" - المناقصة: تطوير مبنى إداري متعدد الطوابق - - ملفات تم تحليلها: - {', '.join(basic_results['file_names'])} - - بنود رئيسية: - - أعمال الحفر والردم: 1500 م³ - - أعمال الخرسانة المسلحة للأساسات: 750 م³ - - أعمال الخرسانة المسلحة للهيكل: 1200 م³ - - أعمال الطابوق: 3500 م² - - أعمال التشطيبات الداخلية: 5000 م² - - شروط تعاقدية رئيسية: - - مدة التنفيذ: 18 شهر - - غرامة التأخير: 0.1% يومياً بحد أقصى 10% - - شروط الدفع: 45 يوم للمستخلصات مع خصم 10% ضمان - - المحتوى المحلي: 30% كحد أدنى - - متطلبات قانونية: - - تصنيف الفئة الأولى مباني - - تأمين شامل بنسبة 100% - - ضمان بنكي ابتدائي 2% ونهائي 5% - - الالتزام بمتطلبات السعودة (النطاق الأخضر) - - من فضلك قم بتحليل هذه المناقصة وتقديم: - 1. تقييم عام للمناقصة وجاذبيتها - 2. نقاط القوة والضعف الرئيسية - 3. المخاطر المحتملة التي يجب مراعاتها - 4. توصيات للتسعير المناسب - 5. استراتيجية مقترحة للتنافس على المناقصة - """ - - # استدعاء خدمة Claude للتحليل - claude_response = self.claude_service.chat_completion( - [{"role": "user", "content": analysis_input}] - ) - - if "error" not in claude_response: - # إضافة تحليل Claude إلى النتائج - basic_results["claude_analysis"] = claude_response["content"] - except Exception as e: - logging.error(f"فشل في تحليل المستندات باستخدام Claude AI: {str(e)}") - - return basic_results - - def _display_document_analysis_results(self, results): - """عرض نتائج تحليل المستندات""" - - st.markdown("### نتائج تحليل المستندات") - - # عرض ملخص التحليل - st.markdown("#### ملخص التحليل") - st.info(results["summary"]) - - # عرض البنود المستخرجة من المناقصة إذا وجدت - if results["tender_items"]: - st.markdown("#### بنود المناقصة المستخرجة") - - # إنشاء DataFrame للبنود - items_df = pd.DataFrame(results["tender_items"]) - - # عرض الجدول بشكل منسق - st.dataframe( - items_df[["id", "description", "unit", "quantity", "estimated_price"]], - use_container_width=True - ) - - # عرض مخطط للتكاليف المقدرة - costs = [item["quantity"] * item["estimated_price"] for item in results["tender_items"]] - labels = [item["description"] for item in results["tender_items"]] - - fig = px.pie( - names=labels, - values=costs, - title="توزيع التكاليف المقدرة حسب البنود" - ) - - st.plotly_chart(fig, use_container_width=True) - - # عرض الشروط التعاقدية إذا وجدت - if results["contract_terms"]: - st.markdown("#### الشروط التعاقدية الهامة") - - # عرض كل شرط في قسم منفصل - for term in results["contract_terms"]: - risk_color = "red" if term["risk_level"] == "عالي" else "orange" if term["risk_level"] == "متوسط" else "green" - - st.markdown(f""" -
-
{term['id']} - {term['title']} مستوى الخطورة: {term['risk_level']}
-

{term['description']}

-

ملاحظات: {term['notes']}

-
- """, unsafe_allow_html=True) - - # عرض تحليل الكميات إذا وجد - if results["quantities"]: - st.markdown("#### تحليل الكميات") - - # إنشاء DataFrame للكميات - quantities_df = pd.DataFrame(results["quantities"]) - - # عرض الجدول بشكل منسق - st.dataframe(quantities_df, use_container_width=True) - - # عرض مخطط شريطي للتكاليف المقدرة - fig = px.bar( - quantities_df, - x="category", - y="estimated_cost", - title="التكاليف المقدرة حسب فئة الأعمال", - labels={"category": "فئة الأعمال", "estimated_cost": "التكلفة المقدرة (ريال)"} - ) - - fig.update_traces(text=quantities_df["estimated_cost"], textposition="outside") - - st.plotly_chart(fig, use_container_width=True) - - # عرض المتطلبات القانونية إذا وجدت - if results["legal_requirements"]: - st.markdown("#### المتطلبات القانونية") - - # عرض المتطلبات في جدول - legal_df = pd.DataFrame(results["legal_requirements"]) - - # عرض الجدول بشكل منسق - st.dataframe( - legal_df[["id", "title", "description", "compliance_status", "required_documents"]], - use_container_width=True - ) - - # عرض قائمة تحقق للمتطلبات القانونية - st.markdown("##### قائمة التحقق من المتطلبات القانونية") - - for req in results["legal_requirements"]: - st.checkbox(f"{req['title']} - {req['description']}", key=f"req_{req['id']}") - - # عرض تحليل Claude AI المتقدم إذا وجد - if "claude_analysis" in results: - st.markdown("### تحليل Claude AI المتقدم") - st.success(results["claude_analysis"]) - - # أزرار إضافية - col1, col2 = st.columns(2) - - with col1: - if st.button("تصدير تقرير تحليل المستندات"): - st.success("تم تصدير تقرير تحليل المستندات بنجاح!") - - with col2: - if st.button("استخراج جدول الكميات"): - st.success("تم استخراج جدول الكميات بنجاح!") - - def _render_local_content_tab(self): - """عرض تبويب المحتوى المحلي""" - - st.markdown("### المحتوى المحلي") - - st.markdown(""" - وحدة حساب المحتوى المحلي تساعدك في تحليل وتحسين نسبة المحتوى المحلي في مشروعك طبقاً لمتطلبات هيئة المحتوى المحلي والمشتريات الحكومية. - """) - - # عرض علامات تبويب فرعية - lc_tabs = st.tabs([ - "حساب المحتوى المحلي", - "قاعدة بيانات الموردين", - "التقارير", - "التحسين" - ]) - - with lc_tabs[0]: - self._render_lc_calculator_tab() - - with lc_tabs[1]: - self._render_lc_suppliers_tab() - - with lc_tabs[2]: - self._render_lc_reports_tab() - - with lc_tabs[3]: - self._render_lc_optimization_tab() - - def _render_lc_calculator_tab(self): - """عرض تبويب حساب المحتوى المحلي""" - - st.markdown("#### حساب المحتوى المحلي") - - # نموذج إدخال بيانات المشروع - st.markdown("##### بيانات المشروع") - - col1, col2 = st.columns(2) - - with col1: - project_name = st.text_input("اسم المشروع", "مبنى إداري الرياض") - project_value = st.number_input("القيمة الإجمالية للمشروع (ريال)", min_value=1000, value=10000000) - - with col2: - target_lc = st.slider("نسبة المحتوى المحلي المستهدفة (%)", 0, 100, 40) - calculation_method = st.selectbox( - "طريقة الحساب", - [ - "الطريقة القياسية (المدخلات)", - "طريقة القيمة المضافة", - "الطريقة المختلطة" - ] - ) - - # جدول مكونات المشروع - st.markdown("##### مكونات المشروع") - - # إعداد بيانات المكونات الافتراضية - if 'lc_components' not in st.session_state: - st.session_state.lc_components = [ - { - "id": 1, - "name": "الخرسانة المسلحة", - "category": "مواد", - "value": 3000000, - "local_content": 85, - "supplier": "شركة الإنشاءات السعودية" - }, - { - "id": 2, - "name": "الأعمال الكهربائية", - "category": "أنظمة", - "value": 1500000, - "local_content": 65, - "supplier": "مؤسسة الطاقة المتقدمة" - }, - { - "id": 3, - "name": "أعمال التكييف", - "category": "أنظمة", - "value": 1200000, - "local_content": 55, - "supplier": "شركة التبريد العالمية" - }, - { - "id": 4, - "name": "الواجهات والنوافذ", - "category": "مواد", - "value": 800000, - "local_content": 45, - "supplier": "شركة الزجاج المتطورة" - }, - { - "id": 5, - "name": "أعمال التشطيبات", - "category": "مواد وعمالة", - "value": 1200000, - "local_content": 80, - "supplier": "مؤسسة التشطيبات الحديثة" - }, - { - "id": 6, - "name": "الأثاث والتجهيزات", - "category": "أثاث", - "value": 900000, - "local_content": 30, - "supplier": "شركة الأثاث المكتبي" - }, - { - "id": 7, - "name": "أنظمة الأمن والمراقبة", - "category": "أنظمة", - "value": 600000, - "local_content": 40, - "supplier": "شركة الأنظمة الأمنية المتقدمة" - }, - { - "id": 8, - "name": "العمالة المباش��ة", - "category": "عمالة", - "value": 800000, - "local_content": 50, - "supplier": "داخلي" - } - ] - - # عرض جدول المكونات للتعديل - for i, component in enumerate(st.session_state.lc_components): - col1, col2, col3, col4, col5, col6 = st.columns([2, 1, 1, 1, 2, 1]) - - with col1: - st.session_state.lc_components[i]["name"] = st.text_input( - "المكون", - component["name"], - key=f"comp_name_{i}" - ) - - with col2: - st.session_state.lc_components[i]["category"] = st.selectbox( - "الفئة", - ["مواد", "أنظمة", "عمالة", "مواد وعمالة", "أثاث", "خدمات"], - index=["مواد", "أنظمة", "عمالة", "مواد وعمالة", "أثاث", "خدمات"].index(component["category"]), - key=f"comp_category_{i}" - ) - - with col3: - st.session_state.lc_components[i]["value"] = st.number_input( - "القيمة (ريال)", - min_value=0, - value=int(component["value"]), - key=f"comp_value_{i}" - ) - - with col4: - st.session_state.lc_components[i]["local_content"] = st.slider( - "المحتوى المحلي (%)", - 0, 100, int(component["local_content"]), - key=f"comp_lc_{i}" - ) - - with col5: - st.session_state.lc_components[i]["supplier"] = st.text_input( - "المورد", - component["supplier"], - key=f"comp_supplier_{i}" - ) - - with col6: - if st.button("حذف", key=f"delete_comp_{i}"): - st.session_state.lc_components.pop(i) - st.rerun() - - # زر إضافة مكون جديد - if st.button("إضافة مكون جديد"): - new_id = max([c["id"] for c in st.session_state.lc_components]) + 1 if st.session_state.lc_components else 1 - st.session_state.lc_components.append({ - "id": new_id, - "name": f"مكون جديد {new_id}", - "category": "مواد", - "value": 100000, - "local_content": 50, - "supplier": "غير محدد" - }) - st.rerun() - - # زر حساب المحتوى المحلي - col1, col2 = st.columns([1, 3]) - - with col1: - calculate_button = st.button("حساب المحتوى المحلي", use_container_width=True) - - with col2: - use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="lc_use_claude") - - if calculate_button: - with st.spinner("جاري حساب وتحليل المحتوى المحلي..."): - # محاكاة وقت المعالجة - time.sleep(2) - - # حساب المحتوى المحلي - lc_results = self._calculate_local_content(st.session_state.lc_components, target_lc, calculation_method) - - # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار - if use_claude: - try: - # إنشاء نص المكونات للتحليل - components_text = "" - for comp in st.session_state.lc_components: - components_text += f""" - - {comp['name']} ({comp['category']}): - القيمة: {comp['value']:,} ريال | المحتوى المحلي: {comp['local_content']}% | المورد: {comp['supplier']} - """ - - prompt = f"""تحليل وتحسين المحتوى المحلي: - - بيانات المشروع: - - اسم المشروع: {project_name} - - القيمة الإجمالية: {project_value:,} ريال - - نسبة المحتوى المحلي المستهدفة: {target_lc}% - - النسبة المحسوبة: {lc_results['total_local_content']:.1f}% - - مكونات المشروع: - {components_text} - - المطلوب: - 1. تحليل نسبة المح��وى المحلي المحسوبة ومقارنتها بالمستهدف - 2. تحديد المكونات ذات المحتوى المحلي المنخفض التي يمكن تحسينها - 3. اقتراح بدائل محلية أو استراتيجيات لزيادة المحتوى المحلي - 4. تقديم توصيات عملية لتحقيق النسبة المستهدفة - 5. تحديد أي فرص إضافية لتحسين المحتوى المحلي في المشروع - - يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. - """ - - # استدعاء Claude للتحليل - claude_analysis = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}] - ) - - if "error" not in claude_analysis: - # إضافة تحليل Claude إلى النتائج - lc_results["claude_analysis"] = claude_analysis["content"] - except Exception as e: - st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}") - - # عرض نتائج حساب المحتوى المحلي - self._display_local_content_results(lc_results, target_lc) - - def _calculate_local_content(self, components, target_lc, calculation_method): - """حساب المحتوى المحلي""" - - # حساب إجمالي قيمة المشروع - total_value = sum([comp["value"] for comp in components]) - - # حساب المحتوى المحلي الإجمالي - total_local_content_value = sum([comp["value"] * comp["local_content"] / 100 for comp in components]) - - # حساب نسبة المحتوى المحلي الإجمالية - total_local_content_percent = (total_local_content_value / total_value) * 100 if total_value > 0 else 0 - - # تحليل المحتوى المحلي حسب الفئة - categories = {} - for comp in components: - category = comp["category"] - if category not in categories: - categories[category] = { - "total_value": 0, - "local_content_value": 0 - } - - categories[category]["total_value"] += comp["value"] - categories[category]["local_content_value"] += comp["value"] * comp["local_content"] / 100 - - # حساب نسبة المحتوى المحلي لكل فئة - for category in categories: - if categories[category]["total_value"] > 0: - categories[category]["local_content_percent"] = (categories[category]["local_content_value"] / categories[category]["total_value"]) * 100 - else: - categories[category]["local_content_percent"] = 0 - - # تحديد المكونات ذات المحتوى المحلي المنخفض - low_lc_components = sorted( - [comp for comp in components if comp["local_content"] < 50], - key=lambda x: x["local_content"] - ) - - # تحديد المكونات ذات المحتوى المحلي المرتفع - high_lc_components = sorted( - [comp for comp in components if comp["local_content"] >= 80], - key=lambda x: x["local_content"], - reverse=True - ) - - # تقديم توصيات لتحسين المحتوى المحلي - improvement_recommendations = [] - - # توصيات للمكونات ذات المحتوى المحلي المنخفض - for comp in low_lc_components[:3]: # أخذ أقل 3 مكونات - improvement_recommendations.append({ - "component": comp["name"], - "current_lc": comp["local_content"], - "recommendation": f"البحث عن بدائل محلية لـ {comp['name']} التي تمثل {comp['value'] / total_value * 100:.1f}% من قيمة المشروع." - }) - - # حساب الفجوة بين المحتوى المحلي الفعلي والمستهدف - lc_gap = target_lc - total_local_content_percent - - # إعداد النتائج - results = { - "total_value": total_value, - "total_local_content_value": total_local_content_value, - "total_local_content": total_local_content_percent, - "target_lc": target_lc, - "lc_gap": lc_gap, - "categories": categories, - "low_lc_components": low_lc_components, - "high_lc_components": high_lc_components, - "improvement_recommendations": improvement_recommendations, - "calculation_method": calculation_method, - "components": components - } - - # تحديد حالة المحتوى المحلي - if lc_gap <= 0: - results["status"] = "تم تحقيق المستهدف" - results["color"] = "green" - elif lc_gap <= 5: - results["status"] = "قريب من المستهدف" - results["color"] = "orange" - else: - results["status"] = "بعيد عن المستهدف" - results["color"] = "red" - - return results - - def _display_local_content_results(self, results, target_lc): - """عرض نتائج حساب المحتوى المحلي""" - - st.markdown("### نتائج حساب المحتوى المحلي") - - # عرض نسبة المحتوى المحلي الإجمالية - col1, col2, col3 = st.columns(3) - - with col1: - st.metric( - "نسبة المحتوى المحلي الحالية", - f"{results['total_local_content']:.1f}%", - delta=f"{results['lc_gap']:.1f}%" if results['lc_gap'] < 0 else f"-{results['lc_gap']:.1f}%", - delta_color="normal" if results['lc_gap'] < 0 else "inverse" - ) - - with col2: - st.metric( - "النسبة المستهدفة", - f"{target_lc}%" - ) - - with col3: - # Aquí está el problema - no podemos usar 'green' como valor para delta_color - # En lugar de eso, usamos un texto formateado para mostrar el estado - st.markdown(f""" -
-

{results["status"]}

-
- """, unsafe_allow_html=True) - - # Alternativa sin usar delta_color - # st.metric( - # "حالة المحتوى المحلي", - # results["status"] - # ) - - - # عرض مخطط مقارنة بين النسبة الحالية والمستهدفة - comparison_data = pd.DataFrame({ - 'النوع': ['النسبة الحالية', 'النسبة المستهدفة'], - 'النسبة': [results['total_local_content'], target_lc] - }) - - fig = px.bar( - comparison_data, - x='النوع', - y='النسبة', - title="مقارنة نسبة المحتوى المحلي الحالية مع المستهدفة", - color='النوع', - color_discrete_map={ - 'النسبة الحالية': results["color"], - 'النسبة المستهدفة': 'blue' - } - ) - - fig.update_layout(yaxis_range=[0, 100]) - - st.plotly_chart(fig, use_container_width=True) - - # عرض توزيع المحتوى المحلي حسب الفئة - st.markdown("#### توزيع المحتوى المحلي حسب الفئة") - - categories_data = [] - for category, data in results["categories"].items(): - categories_data.append({ - 'الفئة': category, - 'القيمة الإجمالية': data["total_value"], - 'قيمة المحتوى المحلي': data["local_content_value"], - 'نسبة المحتوى المحلي': data["local_content_percent"] - }) - - categories_df = pd.DataFrame(categories_data) - - col1, col2 = st.columns(2) - - with col1: - fig = px.pie( - categories_df, - values='القيمة الإجمالية', - names='الفئة', - title="توزيع قيمة المشروع حسب الفئة" - ) - - st.plotly_chart(fig, use_container_width=True) - - with col2: - fig = px.bar( - categories_df, - x='الفئة', - y='نسبة المحتوى المحلي', - title="نسبة المحتوى المحلي لكل فئة", - text_auto='.1f' - ) - - fig.update_traces(texttemplate='%{text}%', textposition='outside') - fig.update_layout(yaxis_range=[0, 100]) - - st.plotly_chart(fig, use_container_width=True) - - # عرض المكونات ذات المحتوى المحلي المنخفض - st.markdown("#### المكونات ذات المحتوى المحلي المنخفض") - - if results["low_lc_components"]: - low_lc_df = pd.DataFrame([ - { - 'المكون': comp["name"], - 'الفئة': comp["category"], - 'القيمة': comp["value"], - 'نسبة المحتوى المحلي': comp["local_content"], - 'المورد': comp["supplier"] - } - for comp in results["low_lc_components"] - ]) - - st.dataframe(low_lc_df, use_container_width=True) - - # مخطط المكونات ذات المحتوى المحلي المنخفض - fig = px.bar( - low_lc_df, - x='المكون', - y='نسبة المحتوى المحلي', - color='القيمة', - title="المكونات ذات المحتوى المحلي المنخفض", - text_auto='.1f' - ) - - fig.update_traces(texttemplate='%{text}%', textposition='outside') - - st.plotly_chart(fig, use_container_width=True) - else: - st.info("لا توجد مكونات ذات محتوى محلي منخفض (أقل من 50%).") - - # عرض توصيات لتحسين المحتوى المحلي - st.markdown("#### توصيات لتحسين المحتوى المحلي") - - if results["improvement_recommendations"]: - for recommendation in results["improvement_recommendations"]: - st.markdown(f""" -
-
{recommendation['component']} (المحتوى المحلي الحالي: {recommendation['current_lc']}%)
-

{recommendation['recommendation']}

-
- """, unsafe_allow_html=True) - else: - st.success("المحتوى المحلي جيد ولا توجد توصيات للتحسين.") - - # عرض تحليل Claude AI المتقدم إذا كان متوفراً - if "claude_analysis" in results: - st.markdown("### تحليل Claude AI المتقدم") - st.info(results["claude_analysis"]) - - def _render_lc_suppliers_tab(self): - """عرض تبويب قاعدة بيانات الموردين للمحتوى المحلي""" - - st.markdown("#### قاعدة بيانات الموردين المحليين") - - # قائمة الفئات - categories = [ - "جميع الفئات", - "مواد بناء", - "أنظمة كهربائية", - "أنظمة ميكانيكية", - "تشطيبات", - "أثاث ومفروشات", - "خدمات هندسية", - "أنظمة أمنية", - "معدات وآليات" - ] - - # اختيار الفئة - selected_category = st.selectbox("فئة الموردين", categories) - - # البحث - search_query = st.text_input("البحث عن مورد") - - # إعداد قائمة الموردين - suppliers = [ - { - "id": 1, - "name": "شركة الإنشاءات السعودية", - "category": "مواد بناء", - "lc_rating": 95, - "quality_rating": 4.5, - "location": "الرياض", - "contact": "info@saudiconstruction.com", - "description": "شركة متخصصة في توريد جميع أنواع مواد البناء ذات المنشأ المحلي." - }, - { - "id": 2, - "name": "مؤسسة الطاقة المتقدمة", - "category": "أنظمة كهربائية", - "lc_rating": 85, - "quality_rating": 4.2, - "location": "جدة", - "contact": "sales@advancedpower.com", - "description": "مؤسسة متخصصة في توريد وتركيب الأنظمة الكهربائية والطاقة المتجددة." - }, - { - "id": 3, - "name": "شركة التبريد العالمية", - "category": "أنظمة ميكانيكية", - "lc_rating": 75, - "quality_rating": 4.0, - "location": "الدمام", - "contact": "info@globalcooling.com", - "description": "شركة متخصصة في أنظمة التكييف والتبريد المركزي للمشاريع الكبرى." - }, - { - "id": 4, - "name": "شركة الزجاج المتطورة", - "category": "مواد بناء", - "lc_rating": 80, - "quality_rating": 4.3, - "location": "��لرياض", - "contact": "sales@advancedglass.com", - "description": "شركة متخصصة في إنتاج وتوريد الزجاج والواجهات الزجاجية للمباني." - }, - { - "id": 5, - "name": "مؤسسة التشطيبات الحديثة", - "category": "تشطيبات", - "lc_rating": 90, - "quality_rating": 4.7, - "location": "جدة", - "contact": "info@modernfinishing.com", - "description": "مؤسسة متخصصة في أعمال التشطيبات الداخلية والخارجية بجودة عالية." - }, - { - "id": 6, - "name": "شركة الأثاث المكتبي", - "category": "أثاث ومفروشات", - "lc_rating": 70, - "quality_rating": 4.0, - "location": "الرياض", - "contact": "sales@officefurniture.com", - "description": "شركة متخصصة في تصنيع وتوريد الأثاث المكتبي والتجهيزات المكتبية." - }, - { - "id": 7, - "name": "شركة الأنظمة الأمنية المتقدمة", - "category": "أنظمة أمنية", - "lc_rating": 65, - "quality_rating": 4.1, - "location": "الدمام", - "contact": "info@advancedsecurity.com", - "description": "شركة متخصصة في أنظمة الأمن والمراقبة والإنذار للمباني والمنشآت." - }, - { - "id": 8, - "name": "شركة المعدات الهندسية", - "category": "معدات وآليات", - "lc_rating": 85, - "quality_rating": 4.5, - "location": "جدة", - "contact": "sales@engineeringequipment.com", - "description": "شركة متخصصة في توريد وصيانة المعدات الهندسية والآليات للمشاريع." - }, - { - "id": 9, - "name": "مكتب الاستشارات الهندسية", - "category": "خدمات هندسية", - "lc_rating": 100, - "quality_rating": 4.8, - "location": "الرياض", - "contact": "info@engineeringconsultants.com", - "description": "مكتب استشاري متخصص في تقديم الخدمات الهندسية والاستشارية للمشاريع." - }, - { - "id": 10, - "name": "مصنع الحديد السعودي", - "category": "مواد بناء", - "lc_rating": 100, - "quality_rating": 4.6, - "location": "جدة", - "contact": "sales@saudisteel.com", - "description": "مصنع متخصص في إنتاج وتوريد منتجات الحديد والصلب للمشاريع الإنشائية." - } - ] - - # تطبيق الفلترة حسب الفئة - if selected_category != "جميع الفئات": - filtered_suppliers = [s for s in suppliers if s["category"] == selected_category] - else: - filtered_suppliers = suppliers - - # تطبيق فلترة البحث - if search_query: - filtered_suppliers = [s for s in filtered_suppliers if search_query.lower() in s["name"].lower() or search_query.lower() in s["description"].lower()] - - # عرض الموردين - for supplier in filtered_suppliers: - with st.container(): - col1, col2 = st.columns([3, 1]) - - with col1: - st.markdown(f""" -
-
{supplier['name']} ({supplier['category']})
-

الموقع: {supplier['location']} | التواصل: {supplier['contact']}

-

تصنيف المحتوى المحلي: {supplier['lc_rating']}% | تقييم الجودة: {supplier['quality_rating']}/5

-

{supplier['description']}

-
- """, unsafe_allow_html=True) - - with col2: - st.button(f"عرض التفاصيل #{supplier['id']}", key=f"supplier_details_{supplier['id']}") - st.button(f"إضافة للمشروع #{supplier['id']}", key=f"add_supplier_{supplier['id']}") - - # زر إضافة مورد جديد - st.button("إضافة مورد جديد") - - def _render_lc_reports_tab(self): - """عرض تبويب تقارير المحتوى المحلي""" - - st.markdown("#### تقارير المحتوى المحلي") - - # اختيار نوع التقرير - report_type = st.selectbox( - "نوع التقرير", - [ - "تقرير المحتوى المحلي للمشروع الحالي", - "تقرير مقارنة المحتوى المحلي بين المشاريع", - "تقرير التطور التاريخي للمحتوى المحلي", - "تقرير الموردين ذوي المحتوى المحلي المرتفع", - "تقرير الامتثال لمتطلبات هيئة المحتوى المحلي" - ] - ) - - # عرض محاكاة للتقرير المختار - st.markdown(f"##### {report_type}") - - if report_type == "تقرير المحتوى المحلي للمشروع الحالي": - # محاكاة تقرير المشروع الحالي - project_data = pd.DataFrame({ - 'المكون': ['الخرسانة المسلحة', 'الأعمال الكهربائية', 'أعمال التكييف', 'الواجهات والنوافذ', - 'أعمال التشطيبات', 'الأثاث والتجهيزات', 'أنظمة الأمن والمراقبة', 'العمالة المباشرة'], - 'القيمة': [3000000, 1500000, 1200000, 800000, 1200000, 900000, 600000, 800000], - 'نسبة المحتوى المحلي': [85, 65, 55, 45, 80, 30, 40, 50] - }) - - # حساب قيمة المحتوى المحلي - project_data['قيمة المحتوى المحلي'] = project_data['القيمة'] * project_data['نسبة المحتوى المحلي'] / 100 - - # إضافة نسبة من إجمالي المشروع - total_value = project_data['القيمة'].sum() - project_data['نسبة من المشروع'] = project_data['القيمة'] / total_value * 100 - - # حساب النسبة الإجمالية للمحتوى المحلي - total_lc = project_data['قيمة المحتوى المحلي'].sum() / total_value * 100 - - # عرض الإجمالي - st.metric("نسبة المحتوى المحلي الإجمالية", f"{total_lc:.1f}%") - - # عرض تفاصيل المكونات - st.dataframe(project_data.style.format({ - 'القيمة': '{:,.0f} ريال', - 'قيمة المحتوى المحلي': '{:,.0f} ريال', - 'نسبة المحتوى المحلي': '{:.1f}%', - 'نسبة من المشروع': '{:.1f}%' - }), use_container_width=True) - - # مخطط توزيع المحتوى المحلي - col1, col2 = st.columns(2) - - with col1: - fig = px.pie( - project_data, - values='القيمة', - names='المكون', - title="توزيع قيمة المشروع" - ) - - st.plotly_chart(fig, use_container_width=True) - - with col2: - fig = px.pie( - project_data, - values='قيمة المحتوى المحلي', - names='المكون', - title="توزيع قيمة المحتوى المحلي" - ) - - st.plotly_chart(fig, use_container_width=True) - - # مخطط شريطي للمحتوى المحلي - fig = px.bar( - project_data, - x='المكون', - y='نسبة المحتوى المحلي', - title="نسبة المحتوى المحلي لكل مكون", - text_auto='.1f', - color='نسبة من المشروع' - ) - - fig.update_traces(texttemplate='%{text}%', textposition='outside') - - st.plotly_chart(fig, use_container_width=True) - - elif report_type == "تقرير مقارنة المحتوى المحلي بين المشاريع": - # محاكاة بيانات مقارنة المشاريع - projects_data = pd.DataFrame({ - 'المشروع': ['مبنى إداري الرياض', 'مجمع سكني جدة', 'مستشفى الدمام', 'مركز تجاري المدينة', 'فندق مكة'], - 'القيمة': [10000000, 15000000, 20000000, 12000000, 18000000], - 'نسبة المحتوى المحلي': [65, 55, 70, 60, 50], - 'سنة الإنجاز': [2022, 2022, 2023, 2023, 2024] - }) - - # عرض جدول المقارنة - st.dataframe(projects_data.style.format({ - 'القيمة': '{:,.0f} ريال', - 'نسبة المحتوى المحلي': '{:.1f}%' - }), use_container_width=True) - - # مخطط شريطي للمقارنة - fig = px.bar( - projects_data, - x='المشروع', - y='نسبة المحتوى المحلي', - title="مقارنة نسبة المحتوى المحلي بين المشاريع", - text_auto='.1f', - color='سنة الإنجاز' - ) - - fig.update_traces(texttemplate='%{text}%', textposition='outside') - - st.plotly_chart(fig, use_container_width=True) - - # مخطط فقاعي للمقارنة - fig = px.scatter( - projects_data, - x='القيمة', - y='نسبة المحتوى المحلي', - size='القيمة', - color='سنة الإنجاز', - text='المشروع', - title="العلاقة بين قيمة المشروع ونسبة المحتوى المحلي" - ) - - fig.update_traces(textposition='top center') - fig.update_layout(xaxis_title="قيمة المشروع (ريال)", yaxis_title="نسبة المحتوى المحلي (%)") - - st.plotly_chart(fig, use_container_width=True) - - elif report_type == "تقرير التطور التاريخي للمحتوى المحلي": - # محاكاة بيانات التطور التاريخي - historical_data = pd.DataFrame({ - 'السنة': [2019, 2020, 2021, 2022, 2023, 2024], - 'نسبة المحتوى المحلي': [45, 48, 52, 58, 62, 66], - 'المستهدف': [40, 45, 50, 55, 60, 65] - }) - - # عرض جدول التطور التاريخي - st.dataframe(historical_data.style.format({ - 'نسبة المحتوى المحلي': '{:.1f}%', - 'المستهدف': '{:.1f}%' - }), use_container_width=True) - - # مخطط خطي للتطور التاريخي - fig = px.line( - historical_data, - x='السنة', - y=['نسبة المحتوى المحلي', 'المستهدف'], - title="التطور التاريخي لنسبة المحتوى المحلي", - markers=True, - labels={'value': 'النسبة (%)', 'variable': ''} - ) - - fig.update_layout(legend_title_text='') - - st.plotly_chart(fig, use_container_width=True) - - # مخطط شريطي للمقارنة بين الفعلي والمستهدف - historical_data['الفرق'] = historical_data['نسبة المحتوى المحلي'] - historical_data['المستهدف'] - - fig = px.bar( - historical_data, - x='السنة', - y='الفرق', - title="الفرق بين نسبة المحتوى المحلي الفعلية والمستهدفة", - text_auto='.1f', - color='الفرق', - color_continuous_scale=['red', 'green'] - ) - - fig.update_traces(texttemplate='%{text}%', textposition='outside') - - st.plotly_chart(fig, use_container_width=True) - - elif report_type == "تقرير الموردين ذوي المحتوى المحلي المرتفع": - # محاكاة بيانات الموردين - suppliers_data = pd.DataFrame({ - 'المورد': ['شركة الإنشاءات السعودية', 'مؤسسة الطاقة المتقدمة', 'شركة التبريد العالمية', - 'شركة الزجاج المتطورة', 'مؤسسة التشطيبات الحديثة', 'مصنع الحديد السعودي', - 'شركة المعدات الهندسية', 'مكتب الاستشارات الهندسية'], - 'الفئة': ['مواد بناء', 'أنظمة كهربائية', 'أنظمة ميكانيكية', 'مواد بناء', - 'تشطيبات', 'مواد بناء', 'معدات وآليات', 'خدمات هندسية'], - 'نسبة المحتوى المحلي': [95, 85, 75, 80, 90, 100, 85, 100], - 'حجم التعامل': [3000000, 1500000, 1200000, 800000, 1200000, 2500000, 900000, 500000] - }) - - # عر�� جدول الموردين - st.dataframe(suppliers_data.style.format({ - 'نسبة المحتوى المحلي': '{:.0f}%', - 'حجم التعامل': '{:,.0f} ريال' - }), use_container_width=True) - - # مخطط شريطي للموردين - fig = px.bar( - suppliers_data, - x='المورد', - y='نسبة المحتوى المحلي', - title="نسبة المحتوى المحلي للموردين", - text_auto='.0f', - color='الفئة' - ) - - fig.update_traces(texttemplate='%{text}%', textposition='outside') - fig.update_layout(xaxis_tickangle=-45) - - st.plotly_chart(fig, use_container_width=True) - - # مخطط فقاعي للموردين - fig = px.scatter( - suppliers_data, - x='نسبة المحتوى المحلي', - y='حجم التعامل', - size='حجم التعامل', - color='الفئة', - text='المورد', - title="العلاقة بين نسبة المحتوى المحلي وحجم التعامل مع الموردين" - ) - - fig.update_traces(textposition='top center') - fig.update_layout(xaxis_title="نسبة المحتوى المحلي (%)", yaxis_title="حجم التعامل (ريال)") - - st.plotly_chart(fig, use_container_width=True) - - elif report_type == "تقرير الامتثال لمتطلبات هيئة المحتوى المحلي": - # محاكاة بيانات الامتثال - compliance_data = pd.DataFrame({ - 'المتطلب': [ - 'نسبة المحتوى المحلي الإجمالية', - 'نسبة السعودة في القوى العاملة', - 'نسبة المنتجات المحلية', - 'نسبة الخدمات المحلية', - 'نسبة الموردين المحليين', - 'المساهمة في تطوير المحتوى المحلي' - ], - 'المستهدف': [40, 30, 50, 60, 70, 20], - 'المحقق': [38, 35, 45, 65, 75, 25], - 'حالة الامتثال': ['قريب', 'ممتثل', 'غير ممتثل', 'ممتثل', 'ممتثل', 'ممتثل'] - }) - - # إضافة ألوان لحالة الامتثال - colors = [] - for status in compliance_data['حالة الامتثال']: - if status == 'ممتثل': - colors.append('green') - elif status == 'قريب': - colors.append('orange') - else: - colors.append('red') - - compliance_data['اللون'] = colors - - # عرض جدول الامتثال - st.dataframe(compliance_data.style.format({ - 'المستهدف': '{:.0f}%', - 'المحقق': '{:.0f}%' - }), use_container_width=True) - - # مخطط شريطي للامتثال - fig = px.bar( - compliance_data, - x='المتطلب', - y=['المستهدف', 'المحقق'], - title="مقارنة المتطلبات المستهدفة والمحققة", - barmode='group', - labels={'value': 'النسبة (%)', 'variable': ''} - ) - - st.plotly_chart(fig, use_container_width=True) - - # مخطط دائري لحالة الامتثال - status_counts = compliance_data['حالة الامتثال'].value_counts().reset_index() - status_counts.columns = ['حالة الامتثال', 'العدد'] - - fig = px.pie( - status_counts, - values='العدد', - names='حالة الامتثال', - title="توزيع حالة الامتثال للمتطلبات", - color='حالة الامتثال', - color_discrete_map={ - 'ممتثل': 'green', - 'قريب': 'orange', - 'غير ممتثل': 'red' - } - ) - - st.plotly_chart(fig, use_container_width=True) - - # أزرار التصدير - col1, col2 = st.columns(2) - - with col1: - st.download_button( - "تصدير التقرير كملف Excel", - "بيانات التقرير", - file_name=f"{report_type}.xlsx", - mime="application/vnd.ms-excel" - ) - - with col2: - st.download_button( - "تصدير التقرير كملف PDF", - "بيانات التقرير", - file_name=f"{report_type}.pdf", - mime="application/pdf" - ) - - def _render_lc_optimization_tab(self): - """عرض تبويب تحسين المحتوى المحلي""" - - st.markdown("#### تحسين المحتوى المحلي") - - st.markdown(""" - تساعدك هذه الأداة في تحسين نسبة المحتوى المحلي في المشروع من خلال تقديم توصيات وبدائل للمكونات ذات المحتوى المحلي المنخفض. - """) - - # عرض المكونات ذات المحتوى المحلي المنخفض - st.markdown("##### المكونات ذات المحتوى المحلي المنخفض") - - # محاكاة بيانات المكونات ذات المحتوى المحلي المنخفض - low_lc_components = [ - { - "id": 1, - "name": "الأثاث والتجهيزات", - "category": "أثاث", - "value": 900000, - "local_content": 30, - "supplier": "شركة الأثاث المكتبي" - }, - { - "id": 2, - "name": "أنظمة الأمن والمراقبة", - "category": "أنظمة", - "value": 600000, - "local_content": 40, - "supplier": "شركة الأنظمة الأمنية المتقدمة" - }, - { - "id": 3, - "name": "الواجهات والنوافذ", - "category": "مواد", - "value": 800000, - "local_content": 45, - "supplier": "شركة الزجاج المتطورة" - } - ] - - # عرض جدول المكونات - low_lc_df = pd.DataFrame(low_lc_components) - - st.dataframe( - low_lc_df[["name", "category", "value", "local_content", "supplier"]].rename(columns={ - "name": "المكون", - "category": "الفئة", - "value": "القيمة", - "local_content": "المحتوى المحلي", - "supplier": "المورد" - }).style.format({ - "القيمة": "{:,.0f} ريال", - "المحتوى المحلي": "{:.0f}%" - }), - use_container_width=True - ) - - # اختيار مكون للتحسين - selected_component = st.selectbox( - "اختر المكون للتحسين", - options=[comp["name"] for comp in low_lc_components], - index=0 - ) - - # الحصول على المكون المختار - selected_comp_data = next((comp for comp in low_lc_components if comp["name"] == selected_component), None) - - # عرض بدائل المكون المختار - if selected_comp_data: - st.markdown(f"##### البدائل المقترحة لـ {selected_component}") - - # محاكاة بيانات البدائل - alternatives = [] - - if selected_component == "الأثاث والتجهيزات": - alternatives = [ - { - "id": 1, - "name": "شركة الأثاث الوطني", - "description": "شركة متخصصة في تصنيع الأثاث المكتبي محلياً", - "local_content": 80, - "cost_factor": 1.05, - "quality_rating": 4.2 - }, - { - "id": 2, - "name": "مصنع التجهيزات المكتبية", - "description": "مصنع متخصص في إنتاج الأثاث المكتبي بخامات محلية", - "local_content": 90, - "cost_factor": 1.10, - "quality_rating": 4.5 - }, - { - "id": 3, - "name": "توزيع المكونات على موردين محليين", - "description": "تقسيم توريد الأثاث على عدة موردين محليين", - "local_content": 75, - "cost_factor": 1.00, - "quality_rating": 4.0 - } - ] - elif selected_component == "أنظمة الأمن والمراقبة": - alternatives = [ - { - "id": 1, - "name": "شركة التقنية الأمنية السعودية", - "description": "شركة متخصصة في تركيب وتجميع أنظمة الأمن محلياً", - "local_content": 70, - "cost_factor": 1.08, - "quality_rating": 4.0 - }, - { - "id": 2, - "name": "مؤسسة تقنيات الحماية", - "description": "توريد وتركيب أنظمة أمنية معتمدة من هيئة المحتوى المحلي", - "local_content": 65, - "cost_factor": 0.95, - "quality_rating": 3.8 - }, - { - "id": 3, - "name": "تجميع الأنظمة محلياً", - "description": "استيراد المكونات وتجميعها وبرمجتها محلياً", - "local_content": 60, - "cost_factor": 0.90, - "quality_rating": 3.7 - } - ] - elif selected_component == "الواجهات والنوافذ": - alternatives = [ - { - "id": 1, - "name": "مصنع الزجاج السعودي", - "description": "مصنع متخصص في إنتاج الزجاج والواجهات الزجاجية محلياً", - "local_content": 85, - "cost_factor": 1.15, - "quality_rating": 4.3 - }, - { - "id": 2, - "name": "شركة الألمنيوم الوطنية", - "description": "شركة متخصصة في إنتاج الواجهات والنوافذ من الألمنيوم محلياً", - "local_content": 90, - "cost_factor": 1.20, - "quality_rating": 4.5 - }, - { - "id": 3, - "name": "تعديل التصميم لاستخدام مواد محلية", - "description": "تعديل تصميم الواجهات لاستخدام نسبة أكبر من المواد المتوفرة محلياً", - "local_content": 75, - "cost_factor": 1.00, - "quality_rating": 4.0 - } - ] - - # عرض البدائل - for alt in alternatives: - with st.container(): - col1, col2, col3 = st.columns([3, 1, 1]) - - with col1: - st.markdown(f""" -
-
{alt['name']}
-

{alt['description']}

-

المحتوى المحلي: {alt['local_content']}% | معامل التكلفة: {alt['cost_factor']:.2f} | تقييم الجودة: {alt['quality_rating']}/5

-
- """, unsafe_allow_html=True) - - with col2: - st.button(f"تفاصيل #{alt['id']}", key=f"alt_details_{alt['id']}") - - with col3: - if st.button(f"اختيار #{alt['id']}", key=f"select_alt_{alt['id']}"): - st.success(f"تم اختيار {alt['name']} كبديل لـ {selected_component}.") - - # حساب تأثير البدائل على المحتوى المحلي الإجمالي - st.markdown("##### تأثير البدائل على المحتوى المحلي الإجمالي") - - # محاكاة البيانات الإجمالية - total_value = 10000000 - current_lc_value = 6000000 - current_lc_percent = current_lc_value / total_value * 100 - - # حساب التأثير لكل بديل - impact_data = [] - for alt in alternatives: - # القيمة الحالية للمحتوى المحلي في المكون - current_component_lc_value = selected_comp_data["value"] * selected_comp_data["local_content"] / 100 - - # القيمة المتوقعة للمحتوى المحلي مع البديل - new_component_value = selected_comp_data["value"] * alt["cost_factor"] - new_component_lc_value = new_component_value * alt["local_content"] / 100 - - # الفرق في قيمة المحتوى المحلي - lc_value_diff = new_component_lc_value - current_component_lc_value - - # القيمة الإجمالية الجديدة للمشروع - new_total_value = total_value - selected_comp_data["value"] + new_component_value - - # قيمة المحتوى المحلي الإجمالية الجديدة - new_total_lc_value = current_lc_value + lc_value_diff - - # نسبة المحتوى المحلي الإجمالية الجديدة - new_total_lc_percent = new_total_lc_value / new_total_value * 100 - - # إضافة البيانات - impact_data.append({ - "البديل": alt["name"], - "نسبة المحتوى المحلي الحالية": current_lc_percent, - "نسبة المحتوى المحلي المتوقعة": new_total_lc_percent, - "التغير": new_total_lc_percent - current_lc_percent, - "القيمة الإجمالية الجديدة": new_total_value, - "تقييم الجودة": alt["quality_rating"] - }) - - # عرض جدول التأثير - impact_df = pd.DataFrame(impact_data) - - st.dataframe( - impact_df.style.format({ - "نسبة المحتوى المحلي الحالية": "{:.1f}%", - "نسبة المحتوى المحلي المتوقعة": "{:.1f}%", - "التغير": "{:+.1f}%", - "القيمة الإجمالية الجديدة": "{:,.0f} ريال", - "تقييم الجودة": "{:.1f}/5" - }), - use_container_width=True - ) - - # مخطط مقارنة للبدائل - fig = px.bar( - impact_df, - x="البديل", - y=["نسبة المحتوى المحلي الحالية", "نسبة المحتوى المحلي المتوقعة"], - barmode="group", - title="مقارنة تأثير البدائل على نسبة المحتوى المحلي الإجمالية", - labels={"value": "نسبة المحتوى المحلي (%)", "variable": ""} - ) - - st.plotly_chart(fig, use_container_width=True) - - # استخدام Claude AI للتحليل المتقدم - if st.checkbox("استخدام Claude AI لتحليل البدائل", value=False, key="lc_optimization_use_claude"): - with st.spinner("جاري تحليل البدائل..."): - # محاكاة وقت المعالجة - time.sleep(2) - - try: - # إنشاء نص المدخلات للتحليل - prompt = f"""تحليل بدائل المحتوى المحلي لمكون {selected_component}: - - المكون الحالي: - - الاسم: {selected_component} - - الفئة: {selected_comp_data['category']} - - القيمة: {selected_comp_data['value']:,} ريال - - نسبة المحتوى المحلي: {selected_comp_data['local_content']}% - - المورد: {selected_comp_data['supplier']} - - البدائل المقترحة: - 1. {alternatives[0]['name']}: - - المحتوى المحلي: {alternatives[0]['local_content']}% - - معامل التكلفة: {alternatives[0]['cost_factor']:.2f} - - تقييم الجودة: {alternatives[0]['quality_rating']}/5 - - الوصف: {alternatives[0]['description']} - - 2. {alternatives[1]['name']}: - - المحتوى المحلي: {alternatives[1]['local_content']}% - - معامل التكلفة: {alternatives[1]['cost_factor']:.2f} - - تقييم الجودة: {alternatives[1]['quality_rating']}/5 - - الوصف: {alternatives[1]['description']} - - 3. {alternatives[2]['name']}: - - المحتوى المحلي: {alternatives[2]['local_content']}% - - معامل التكلفة: {alternatives[2]['cost_factor']:.2f} - - تقييم الجودة: {alternatives[2]['quality_rating']}/5 - - الوصف: {alternatives[2]['description']} - - المطلوب: - 1. تحليل مقارن شامل للبدائل من حيث المحتوى المحلي والتكلفة والجودة - 2. تحديد البديل الأفضل مع شرح أسباب اختياره - 3. تقديم توصيات إضافية لتحسين المحتوى المحلي لهذا المكون - 4. تحديد أي مخاطر محتملة في الانتقال للبديل المقترح - - يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. - """ - - # استدعاء Claude للتحليل - claude_analysis = self.claude_service.chat_completion( - [{"role": "user", "content": prompt}] - ) - - if "error" not in claude_analysis: - # عرض تحليل Claude - st.markdown("##### تحليل متقدم للبدائل") - st.info(claude_analysis["content"]) - else: - st.warning(f"تعذر إجراء التحليل المتقدم: {claude_analysis['error']}") - except Exception as e: - st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}") - - # زر تطبيق البديل المختار - if st.button("تطبيق البديل المختار على المشروع"): - st.success("تم تطبيق البديل المختار على المشروع وتحديث نسبة المحتوى المحلي.") - - def _render_faq_tab(self): - """عرض تبويب الأسئلة الشائعة""" - - st.markdown("### الأسئلة الشائعة") - - # البحث في الأسئلة الشائعة - search_query = st.text_input("البحث في الأسئلة الشائعة", key="faq_search") - - # فلترة الأسئلة حسب البحث - if search_query: - filtered_faqs = [ - faq for faq in self.faqs - if search_query.lower() in faq["question"].lower() or search_query.lower() in faq["answer"].lower() - ] - else: - filtered_faqs = self.faqs - - # عرض الأسئلة والأجوبة - for i, faq in enumerate(filtered_faqs): - with st.expander(faq["question"]): - st.markdown(faq["answer"]) - - # زر التواصل مع الدعم - st.markdown("##### لم تجد إجابة لسؤالك؟") - col1, col2 = st.columns(2) - - with col1: - if st.button("التواصل مع الدعم الفني", use_container_width=True): - st.info("سيتم التواصل معك قريباً من قبل فريق الدعم الفني.") - - with col2: - if st.button("طرح سؤال جديد", use_container_width=True): - st.text_area("اكتب سؤالك هنا") - st.button("إرسال") \ No newline at end of file + st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.")