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" @@ -10,7 +10,6 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt import plotly.express as px -import plotly.graph_objects as go import requests import json import time @@ -24,20 +23,6 @@ import random from io import BytesIO from tempfile import NamedTemporaryFile from PIL import Image -import arabic_reshaper -from bidi.algorithm import get_display -import matplotlib.font_manager as fm -import seaborn as sns - -# تكوين الخطوط العربية -plt.rcParams['font.family'] = 'Arial' - -# دالة مساعدة لعرض النص العربي بشكل صحيح -def get_display_arabic(text): - """تحويل النص العربي للعرض الصحيح في الرسوم البيانية""" - reshaped_text = arabic_reshaper.reshape(text) - bidi_text = get_display(reshaped_text) - return bidi_text class ClaudeAIService: """ @@ -86,53 +71,22 @@ class ClaudeAIService: def analyze_image(self, image_path, prompt, model_name="claude-3-7-sonnet"): """ تحليل صورة باستخدام نموذج Claude AI - + المعلمات: - image_path: مسار الصورة المراد تحليلها - prompt: التوجيه للنموذج - model_name: اسم نموذج Claude المراد استخدامه - + image_path: مسار الصورة المراد تحليلها + prompt: التوجيه للنموذج + model_name: اسم نموذج Claude المراد استخدامه + العوائد: - dict: نتائج التحليل + 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 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.") + # قراءة محتوى الصورة + with open(image_path, 'rb') as f: + file_content = f.read() # تحويل المحتوى إلى Base64 file_base64 = base64.b64encode(file_content).decode('utf-8') @@ -293,118 +247,6 @@ class ClaudeAIService: stack_trace = traceback.format_exc() return {"error": f"فشل في إكمال المحادثة: {str(e)}\n{stack_trace}"} - def analyze_text(self, text, analysis_type="general", model_name="claude-3-5-haiku"): - """ - تحليل النص باستخدام الذكاء الاصطناعي - - المعلمات: - text: النص المراد تحليله - analysis_type: نوع التحليل (general, requirements, risks, costs) - model_name: اسم النموذج المستخدم - - العوائد: - dict: نتائج التحليل - """ - try: - # تحديد التوجيه المناسب حسب نوع ��لتحليل - if analysis_type == "requirements": - prompt = f""" - قم بتحليل النص التالي واستخراج المتطلبات الرئيسية للمشروع. صنف المتطلبات إلى فئات (فنية، إدارية، مالية، قانونية) وحدد أولوية كل متطلب (عالية، متوسطة، منخفضة). - - النص: - {text} - - قدم النتائج بتنسيق JSON يحتوي على مصفوفة من المتطلبات، كل متطلب يحتوي على: الوصف، الفئة، الأولوية. - """ - elif analysis_type == "risks": - prompt = f""" - قم بتحليل النص التالي وتحديد المخاطر المحتملة للمشروع. لكل خطر، حدد احتمالية حدوثه (عالية، متوسطة، منخفضة) وتأثيره (عالي، متوسط، منخفض) واقترح استراتيجية للتخفيف من حدته. - - النص: - {text} - - قدم النتائج بتنسيق JSON يحتوي على مصفوفة من المخاطر، كل خطر يحتوي على: الوصف، الاحتمالية، التأثير، استراتيجية التخفيف. - """ - elif analysis_type == "costs": - prompt = f""" - قم بتحليل النص التالي وتحديد عناصر التكلفة المحتملة للمشروع. صنف التكاليف إلى فئات (مباشرة، غير مباشرة) وحدد ما إذا كانت ثابتة أو متغيرة. - - النص: - {text} - - قدم النتائج بتنسيق JSON يحتوي على مصفوفة من عناصر التكلفة، كل عنصر يحتوي على: الوصف، الفئة، النوع (ثابت/متغير)، تقدير نسبي للتكلفة (%). - """ - elif analysis_type == "local_content": - prompt = f""" - قم بتحليل النص التالي وتحديد عناصر المحتوى المحلي المحتملة. صنف العناصر إلى فئات (منتجات، خدمات، قوى عاملة) وحدد مدى توفرها محلياً (متوفر بشكل كامل، متوفر جزئياً، غير متوفر). - - النص: - {text} - - قدم النتائج بتنسيق JSON يحتوي على مصفوفة من عناصر المحتوى المحلي، كل عنصر يحتوي على: الوصف، الفئة، مدى التوفر، تقدير نسبي للمساهمة في المحتوى المحلي (%). - """ - else: # general - prompt = f""" - قم بتحليل النص التالي وتلخيص النقاط الرئيسية. حدد الموضوعات الأساسية والأفكار المهمة والتوصيات إن وجدت. - - النص: - {text} - - قدم النتائج بتنسيق JSON يحتوي على: ملخص عام، النقاط الرئيسية (مصفوفة)، التوصيات (مصفوفة). - """ - - # إنشاء رسائل المحادثة - messages = [ - {"role": "user", "content": prompt} - ] - - # استدعاء دالة إكمال المحادثة - result = self.chat_completion(messages, model_name) - - # معالجة النتيجة - if "error" in result: - return result - - # محاولة تحويل النتيجة إلى JSON - try: - # استخراج النص من النتيجة - content = result["content"] - - # البحث عن بداية ونهاية JSON - json_start = content.find('{') - json_end = content.rfind('}') + 1 - - if json_start >= 0 and json_end > json_start: - json_str = content[json_start:json_end] - analysis_result = json.loads(json_str) - - return { - "success": True, - "result": analysis_result, - "model": result["model"] - } - else: - # إذا لم يتم العثور على JSON، إرجاع النص كاملاً - return { - "success": True, - "result": {"text": content}, - "model": result["model"] - } - except Exception as e: - # إذا فشل تحويل النتيجة إلى JSON، إرجاع النص كاملاً - return { - "success": True, - "result": {"text": content}, - "model": result["model"], - "parse_error": str(e) - } - - except Exception as e: - logging.error(f"خطأ أثناء تحليل النص: {str(e)}") - import traceback - stack_trace = traceback.format_exc() - return {"error": f"فشل في تحليل النص: {str(e)}\n{stack_trace}"} - class AIAssistantApp: """وحدة المساعد الذكي""" @@ -452,17 +294,33 @@ class AIAssistantApp: { "question": "كيف يمكنني تقييم المخاطر للمشروع؟", "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة." + } + ] + + # تهيئة قائمة الأسئلة والإجابات الشائعة + self.faqs = [ + { + "question": "كيف يمكنني إضافة مشروع جديد؟", + "answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة." }, { - "question": "ما هي طرق التسعير المتاحة في النظام؟", - "answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة." + "question": "ما هي خطوات تسعير المناقصة؟", + "answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية." }, { - "question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟", - "answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً." + "question": "كيف يتم حساب المحتوى المحلي؟", + "answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي." + }, + { + "question": "كيف يمكنني تصدير التقارير؟", + "answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV." + }, + { + "question": "كيف يمكنني تقييم المخاطر للمشروع؟", + "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة." } ] - + def _load_cost_prediction_model(self): """تحميل نموذج التنبؤ بالتكاليف""" # في البيئة الإنتاجية، سيتم تحميل نموذج حقيقي @@ -484,6 +342,38 @@ 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): """عرض واجهة وحدة المساعد الذكي""" @@ -642,840 +532,2751 @@ class AIAssistantApp: # مربع إدخال الرسالة user_input = st.text_input("اكتب رسالتك هنا", key="ai_assistant_input") - # زر إرسال مع تحليل ذكي - col1, col2 = st.columns([3, 1]) - with col1: - send_button = st.button("إرسال", key="send_message_button") - with col2: - analyze_options = st.selectbox( - "تحليل ذكي", - ["عام", "متطلبات", "مخاطر", "تكاليف", "محتوى محلي"], - key="analyze_type" - ) - - # معالجة الإدخال - if send_button and user_input: - # إضافة رسالة المستخدم إلى المحادثة - st.session_state.ai_assistant_messages.append( - {"role": "user", "content": user_input} - ) - - # تحديد نوع التحليل - analysis_type_map = { - "عام": "general", - "متطلبات": "requirements", - "مخاطر": "risks", - "تكاليف": "costs", - "محتوى محلي": "local_content" - } - - analysis_type = analysis_type_map.get(analyze_options, "general") - - # إظهار مؤشر التحميل - with st.spinner("جاري التحليل..."): - # تحليل النص باستخدام الذكاء الاصطناعي - result = self.claude_service.analyze_text(user_input, analysis_type, selected_model) - - # إعداد الرد - if "error" in result: - response = f"عذراً، حدث خطأ أثناء التحليل: {result['error']}" - else: - # ت��سيق النتيجة بشكل مقروء - if isinstance(result["result"], dict): - if "text" in result["result"]: - # إذا كانت النتيجة نصية - response = result["result"]["text"] + # التحقق من وجود مفتاح API + api_available = True + try: + 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 = 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') + # استخدام مسار الصورة بدلاً من PDF + os.remove(temp_file_path) + temp_file_path = temp_image_path + except Exception as e: + st.error(f"فشل في تحويل ملف PDF إلى صورة: {str(e)}") else: - # إذا كانت النتيجة هيكلية - response = "نتائج التحليل:\n\n" - for key, value in result["result"].items(): - if isinstance(value, list): - response += f"**{key}**:\n" - for i, item in enumerate(value): - if isinstance(item, dict): - response += f"{i+1}. " - for k, v in item.items(): - response += f"**{k}**: {v}, " - response = response[:-2] + "\n" - else: - response += f"{i+1}. {item}\n" - else: - response += f"**{key}**: {value}\n" + 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 = str(result["result"]) + response = results["content"] - # إضافة رد المساعد إلى المحادثة - st.session_state.ai_assistant_messages.append( - {"role": "assistant", "content": response} - ) + # إضافة رد المساعد إلى المحفوظات + st.session_state.ai_assistant_messages.append({"role": "assistant", "content": response}) + + # عرض رد المساعد + with chat_container: + st.markdown(f""" +
+
+ {response} +
+
+ """, unsafe_allow_html=True) - # إعادة تحميل الصفحة لعرض الرد - st.experimental_rerun() + # إعادة تعيين قيمة الإدخال + 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 - api_available = True try: self.claude_service.get_api_key() except ValueError: - api_available = False - st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.") + 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.info("هذه الوحدة تساعدك في تقدير تكاليف المشاريع بناءً على بيانات المناقصات السابقة ومعايير التسعير المعتمدة.") + # عرض نموذج إدخال بيانات المشروع + st.markdown("#### بيانات المشروع") - # إنشاء نموذج إدخال البيانات col1, col2 = st.columns(2) with col1: - st.subheader("معلومات المشروع") project_type = st.selectbox( "نوع المشروع", - ["مشروع إنشائي", "مشروع تقنية معلومات", "مشروع استشاري", "مشروع توريد", "أخرى"] + [ + "مباني سكنية", + "مباني تجارية", + "مباني حكومية", + "مراكز صحية", + "مدارس", + "بنية تحتية", + "طرق", + "جسور", + "صرف صحي", + "مياه", + "كهرباء" + ], + key="cost_project_type" ) - project_duration = st.number_input( - "مدة المشروع (بالأشهر)", - min_value=1, - max_value=60, - value=12 + location = st.selectbox( + "الموقع", + [ + "الرياض", + "جدة", + "الدمام", + "مكة", + "المدينة", + "تبوك", + "حائل", + "عسير", + "جازان", + "نجران", + "الباحة", + "الجوف", + "القصيم" + ], + key="cost_location" ) - project_location = st.selectbox( - "موقع المشروع", - ["الرياض", "جدة", "الدمام", "مكة المكرمة", "المدينة المنورة", "أخرى"] + client_type = st.selectbox( + "نوع العميل", + [ + "حكومي", + "شبه حكومي", + "شركة كبيرة", + "شركة متوسطة", + "شركة صغيرة", + "أفراد" + ], + key="cost_client_type" ) with col2: - st.subheader("معايير التكلفة") + area = st.number_input("المساحة (م²)", min_value=100, max_value=1000000, value=5000, key="cost_area") - direct_cost = st.number_input( - "التكاليف المباشرة المقدرة (ريال)", - min_value=0, - value=1000000 - ) + floors = st.number_input("عدد الطوابق", min_value=1, max_value=100, value=3, key="cost_floors") - overhead_percentage = st.slider( - "نسبة المصاريف العامة (%)", - min_value=5, - max_value=30, - value=15 - ) + duration = st.number_input("مدة التنفيذ (ش��ور)", min_value=1, max_value=60, value=12, key="cost_duration") - profit_percentage = st.slider( - "نسبة الربح المستهدفة (%)", - min_value=5, - max_value=30, - value=20 + tender_type = st.selectbox( + "نوع المناقصة", + [ + "عامة", + "خاصة", + "أمر مباشر" + ], + key="cost_tender_type" ) - # زر حساب التكلفة - col1, col2 = st.columns([3, 1]) + st.markdown("#### متغيرات إضافية") + + col1, col2, col3 = st.columns(3) + with col1: - calculate_button = st.button("حساب التكلفة التقديرية") + has_basement = st.checkbox("يتضمن بدروم", key="cost_has_basement") + has_special_finishing = st.checkbox("تشطيبات خاصة", key="cost_has_special_finishing") + with col2: - ai_analysis = st.checkbox("تحليل ذكي", value=True) + has_landscape = st.checkbox("أعمال تنسيق المواقع", key="cost_has_landscape") + has_parking = st.checkbox("مواقف متعددة الطوابق", key="cost_has_parking") - if calculate_button: - # حساب التكلفة (هذا مثال بسيط) - overhead_cost = direct_cost * (overhead_percentage / 100) - profit = direct_cost * (profit_percentage / 100) - total_cost = direct_cost + overhead_cost + profit - - # عرض النتائج - st.success("تم حساب التكلفة التقديرية بنجاح!") - - col1, col2, col3 = st.columns(3) - - with col1: - st.metric("التكاليف المباشرة", f"{direct_cost:,.2f} ريال") - - with col2: - st.metric("المصاريف العامة", f"{overhead_cost:,.2f} ريال") - - with col3: - st.metric("الربح المتوقع", f"{profit:,.2f} ريال") - - st.metric("إجمالي التكلفة التقديرية", f"{total_cost:,.2f} ريال", delta="تقدير أولي") - - # عرض رسم بياني للتكاليف - fig = go.Figure() - - # إضافة البيانات - labels = ["التكاليف المباشرة", "المصاريف العامة", "الربح المتوقع"] - values = [direct_cost, overhead_cost, profit] - colors = ['#1f77b4', '#ff7f0e', '#2ca02c'] - - fig.add_trace(go.Pie( - labels=[get_display_arabic(label) for label in labels], - values=values, - textinfo='percent+label', - insidetextorientation='radial', - marker=dict(colors=colors), - hole=0.4 - )) - - # تخصيص الرسم البياني - fig.update_layout( - title=get_display_arabic("توزيع التكاليف"), - height=400, - margin=dict(l=0, r=0, t=40, b=0), - font=dict(size=14), - legend=dict( - orientation="h", - yanchor="bottom", - y=-0.2, - xanchor="center", - x=0.5 - ) - ) - - st.plotly_chart(fig, use_container_width=True) + 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") - # تحليل ذكي للتكاليف إذا تم تحديد الخيار - if ai_analysis: - with st.spinner("جاري تحليل التكاليف باستخدام الذكاء الاصطناعي..."): - # إنشاء نص وصفي للمشروع - project_description = f""" - نوع المشروع: {project_type} - مدة المشروع: {project_duration} أشهر - موقع المشروع: {project_location} - التكاليف المباشرة: {direct_cost:,.2f} ريال - نسبة المصاريف العامة: {overhead_percentage}% - نسبة الربح المستهدفة: {profit_percentage}% - إجمالي التكلفة ��لتقديرية: {total_cost:,.2f} ريال - """ - - # تحليل النص باستخدام الذكاء الاصطناعي - result = self.claude_service.analyze_text(project_description, "costs") - - if "error" not in result: - st.subheader("تحليل التكاليف بالذكاء الاصطناعي") + 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 'لا'} - # عرض نتائج التحليل - if isinstance(result["result"], dict) and "text" not in result["result"]: - # إذا كانت النتيجة هيكلية - if "عناصر التكلفة" in result["result"]: - cost_items = result["result"]["عناصر التكلفة"] - - # إنشاء جدول لعناصر التكلفة - cost_data = [] - for item in cost_items: - if isinstance(item, dict): - cost_data.append({ - "الوصف": item.get("الوصف", ""), - "الفئة": item.get("الفئة", ""), - "النوع": item.get("النوع", ""), - "التقدير النسبي": item.get("تقدير نسبي للتكلفة", "") - }) - - if cost_data: - cost_df = pd.DataFrame(cost_data) - st.dataframe(cost_df, use_container_width=True) - else: - # إذا كانت النتيجة نصية - st.write(result["result"].get("text", "لا توجد نتائج تحليل")) + نتائج التنبؤ الأولية: + - التكلفة الإجمالية المقدرة: {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 _render_risk_analysis_tab(self): - """عرض تبويب تحليل المخاطر""" - st.markdown("### تحليل المخاطر") - - st.info("هذه الوحدة تساعدك في تحديد وتقييم المخاطر المحتملة للمشروع ووضع خطط الاستجابة المناسبة.") - - # إنشاء جدول المخاطر - st.subheader("سجل المخاطر") - - # بيانات نموذجية للمخاطر - risk_data = { - "المخاطر": [ - "تأخر توريد المواد", - "تغيير متطلبات المشروع", - "نقص العمالة الماهرة", - "مشاكل فنية غير متوقعة", - "تغيرات في الأنظمة واللوائح" - ], - "الاحتمالية": [ - "متوسطة", - "عالية", - "منخفضة", - "متوسطة", - "منخفضة" - ], - "التأثير": [ - "عالي", - "عالي", - "متوسط", - "عالي", - "عالي" - ], - "درجة الخطورة": [ - "عالية", - "عالية", - "متوسطة", - "عالية", - "متوسطة" - ], - "خطة الاستجابة": [ - "التعاقد مع موردين بدلاء", - "توثيق المتطلبات وإدارة التغيير", - "التعاقد المبكر مع فرق العمل", - "إجراء اختبارات مبكرة", - "متابعة التحديثات التنظيمية" - ] + 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 + } } - risk_df = pd.DataFrame(risk_data) - st.dataframe(risk_df, use_container_width=True) + return results + + def _display_cost_prediction_results(self, results): + """عرض نتائج التنبؤ بالتكاليف""" - # إضافة مخاطر جديدة - st.subheader("إضافة مخاطر جديدة") + st.markdown("### نتائج التنبؤ بالتكاليف") - col1, col2 = st.columns(2) + # عرض التكلفة الإجمالية وتكلفة المتر المربع + col1, col2, col3 = st.columns(3) with col1: - new_risk = st.text_input("وصف المخاطرة") - probability = st.selectbox( - "احتمالية الحدوث", - ["منخفضة", "متوسطة", "عالية"] + st.metric( + "التكلفة الإجمالية المتوقعة", + f"{results['total_cost']:,.0f} ريال", + delta=f"{(results['total_cost'] - results['comparison']['historical_projects']):,.0f} ريال" ) with col2: - impact = st.selectbox( - "التأثير", - ["منخفض", "متوسط", "عالي"] + st.metric( + "تكلفة المتر المربع", + f"{results['cost_per_sqm']:,.0f} ريال/م²" ) - response_plan = st.text_area("خطة الاستجابة") - col1, col2 = st.columns([3, 1]) - with col1: - add_button = st.button("إضافة المخاطرة") - with col2: - ai_suggestion = st.checkbox("اقتراح ذكي", value=True) + with col3: + st.metric( + "مستوى الثقة في التنبؤ", + f"{results['confidence_level'] * 100:.0f}%" + ) - if ai_suggestion and new_risk: - with st.spinner("جاري تحليل المخاطرة باستخدام الذكاء الاصطناعي..."): - # تحليل المخاطرة باستخدام الذكاء الاصطناعي - risk_description = f"المخاطرة: {new_risk}" - result = self.claude_service.analyze_text(risk_description, "risks") - - if "error" not in result and isinstance(result["result"], dict): - if "المخاطر" in result["result"] and isinstance(result["result"]["المخاطر"], list): - risk_analysis = result["result"]["المخاطر"][0] - - if isinstance(risk_analysis, dict): - st.success("تم تحليل المخاطرة بنجاح!") - - # عرض نتائج التحليل - col1, col2 = st.columns(2) - with col1: - st.write("**الاحتمالية المقترحة:**", risk_analysis.get("الاحتمالية", "غير محدد")) - st.write("**التأثير المقترح:**", risk_analysis.get("التأثير", "غير محدد")) - with col2: - st.write("**استراتيجية التخفيف المقترحة:**", risk_analysis.get("استراتيجية التخفيف", "غير محدد")) - - if add_button and new_risk: - st.success("تمت إضافة المخاطرة بنجاح!") - - # عرض مصفوفة المخاطر - st.subheader("مصفوفة المخاطر") - - # بيانات مصفوفة المخاطر - risk_matrix = np.array([ - [1, 2, 3], - [2, 4, 6], - [3, 6, 9] - ]) + # عرض تفصيل التكاليف + st.markdown("#### تفصيل التكاليف") - # إنشاء مصفوفة المخاطر باستخدام Plotly - fig = go.Figure() + # رسم مخطط دائري للتكاليف المفصلة + fig = px.pie( + values=[ + results['material_cost'], + results['labor_cost'], + results['equipment_cost'] + ], + names=["تكلفة المواد", "تكلفة العمالة", "تكلفة المعدات"], + title="توزيع التكاليف الرئيسية" + ) - # تحديد الألوان - colorscale = [ - [0, '#1a9850'], # أخضر داكن (مخاطر منخفضة) - [0.3, '#91cf60'], # أخضر فاتح - [0.5, '#ffffbf'], # أصفر - [0.7, '#fc8d59'], # برتقالي - [1, '#d73027'] # أحمر (مخاطر عالية) - ] + st.plotly_chart(fig, use_container_width=True) - # إنشاء مصفوفة المخاطر - fig.add_trace(go.Heatmap( - z=risk_matrix, - x=['منخفض', 'متوسط', 'عالي'], - y=['منخفضة', 'متوسطة', 'عالية'], - text=risk_matrix, - texttemplate="%{text}", - textfont={"size":20}, - colorscale=colorscale, - showscale=True, - colorbar=dict( - title=get_display_arabic("درجة الخطورة"), - titleside="right", - titlefont=dict(size=14), - tickfont=dict(size=12), - ) - )) - - # تخصيص الرسم البياني - fig.update_layout( - title=get_display_arabic("مصفوفة المخاطر"), - height=500, - margin=dict(l=50, r=50, t=50, b=50), - xaxis=dict( - title=get_display_arabic("التأثير"), - titlefont=dict(size=14), - tickfont=dict(size=12), - ), - yaxis=dict( - title=get_display_arabic("الاحتمالية"), - titlefont=dict(size=14), - tickfont=dict(size=12), - ), - font=dict(size=14) + # رسم مخطط شريطي لتفصيل الأعمال + 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.subheader("تحليل المخاطر بالذكاء الاصطناعي") + # عرض مقارنة مع متوسط السوق + st.markdown("#### مقارنة مع متوسط السوق") - risk_text = st.text_area("أدخل وصف المشروع لتحليل المخاطر المحتملة", height=150) + 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" + } + ) - if st.button("تحليل المخاطر"): - if risk_text: - with st.spinner("جاري تحليل المخاطر باستخدام الذكاء الاصطناعي..."): - # تحليل النص باستخدام الذكاء الاصطناعي - result = self.claude_service.analyze_text(risk_text, "risks") - - if "error" not in result: - st.success("تم تحليل المخاطر بنجاح!") - - # عرض نتائج التحليل - if isinstance(result["result"], dict) and "المخاطر" in result["result"]: - risks = result["result"]["المخاطر"] - - # إنشاء جدول للمخاطر - risk_data = [] - for risk in risks: - if isinstance(risk, dict): - risk_data.append({ - "المخاطر": risk.get("الوصف", ""), - "الاحتمالية": risk.get("الاحتمالية", ""), - "التأثير": risk.get("التأثير", ""), - "خطة الاستجابة": risk.get("استراتيجية التخفيف", "") - }) - - if risk_data: - risk_df = pd.DataFrame(risk_data) - st.dataframe(risk_df, use_container_width=True) - else: - # إذا كانت النتيجة نصية - st.write(result["result"].get("text", "لا توجد نتائج تحليل")) - else: - st.error(f"حدث خطأ أثناء التحليل: {result['error']}") - else: - st.warning("يرجى إدخال وصف المشروع لتحليل المخاطر.") - - def _render_document_analysis_tab(self): - """عرض تبويب تحليل المستندات""" - st.markdown("### تحليل المستندات") + fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside') - st.info("هذه الوحدة تساعدك في تحليل مستندات المناقصات واستخراج المعلومات المهمة منها.") + st.plotly_chart(fig, use_container_width=True) - # رفع المستندات - uploaded_file = st.file_uploader( - "ارفع مستند المناقصة (PDF, DOCX, XLSX)", - type=["pdf", "docx", "xlsx"], - key="document_analysis_upload" - ) + # عرض تحليل Claude AI إذا كان مت��فراً + if "claude_analysis" in results: + st.markdown("### تحليل Claude AI المتقدم") + st.info(results["claude_analysis"]) - if uploaded_file is not None: - # عرض معلومات الملف - file_details = { - "اسم الملف": uploaded_file.name, - "نوع الملف": uploaded_file.type, - "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" - } - - st.json(file_details) - - # خيارات التحليل - analysis_options = st.multiselect( - "اختر أنواع التحليل", + # عرض ملاحظات وتوصيات + 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( + "نوع المشروع", [ - "استخراج المتطلبات", - "تحديد المواعيد النهائية", - "تحليل الشروط والأحكام", - "استخراج جداول الكميات", - "تحديد المعايير الفنية" + "مباني سكنية", + "مباني تجارية", + "مباني حكومية", + "مراكز صحية", + "مدارس", + "بنية تحتية", + "طرق", + "جسور", + "صرف صحي", + "مياه", + "كهرباء" ], - default=["استخراج المتطلبات", "تحديد المواعيد النهائية"] + key="risk_project_type" ) - col1, col2 = st.columns([3, 1]) - with col1: - analyze_button = st.button("تحليل المستند") - with col2: - ai_analysis = st.checkbox("تحليل ذكي", value=True) + location = st.selectbox( + "الموقع", + [ + "الرياض", + "جدة", + "الدمام", + "مكة", + "المدينة", + "تبوك", + "حائل", + "عسير", + "جازان", + "نجران", + "الباحة", + "الجوف", + "القصيم" + ], + key="risk_location" + ) + + with col2: + client_type = st.selectbox( + "نوع العميل", + [ + "حكومي", + "شبه حكومي", + "شركة كبيرة", + "شركة متوسطة", + "شركة صغيرة", + "أفراد" + ], + key="risk_client_type" + ) - if analyze_button: - # عرض شريط التقدم - progress_bar = st.progress(0) - status_text = st.empty() - - # محاكاة عملية التحليل - for i in range(101): - progress_bar.progress(i) - if i < 30: - status_text.text("جاري معالجة المستند...") - elif i < 60: - status_text.text("جاري استخراج النصوص...") - elif i < 90: - status_text.text("جاري تحليل ال��حتوى...") - else: - status_text.text("جاري إعداد النتائج...") - time.sleep(0.02) - - st.success("تم تحليل المستند بنجاح!") - - # عرض نتائج التحليل - st.subheader("نتائج التحليل") - - # بيانات نموذجية للنتائج - if "استخراج المتطلبات" in analysis_options: - st.write("#### المتطلبات الرئيسية") - requirements = [ - "توفير فريق عمل مؤهل لا يقل عن 10 أفراد", - "خبرة سابقة في مشاريع مماثلة لا تقل عن 5 سنوات", - "شهادة ISO 9001 للجودة", - "تقديم ضمان بنكي بنسبة 5% من قيمة العقد", - "الالتزام بمعايير السلامة المهنية" - ] - for req in requirements: - st.markdown(f"- {req}") + 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) - if "تحديد المواعيد النهائية" in analysis_options: - st.write("#### المواعيد النهائية") - deadlines = { - "تقديم العروض": "15/05/2025", - "بدء المشروع": "01/06/2025", - "المرحلة الأولى": "01/08/2025", - "المرحلة الثانية": "01/10/2025", - "تسليم المشروع": "31/12/2025" - } - deadline_df = pd.DataFrame(list(deadlines.items()), columns=["المرحلة", "التاريخ"]) - st.dataframe(deadline_df, use_container_width=True) + # تجهيز البيانات للنموذج + 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 + } - if "تحليل الشروط والأحكام" in analysis_options: - st.write("#### الشروط والأحكام الهامة") - terms = [ - "غرامة التأخير: 1% من قيمة العقد عن كل أسبوع تأخير", - "مدة الضمان: سنتان من تاريخ الاستلام النهائي", - "شروط الدفع: 20% دفعة مقدمة، 60% على مراحل، 20% بعد الاستلام النهائي", - "التحكيم: وفقاً لأنظمة المملكة العربية السعودية", - "التأمين: يجب توفير تأمين شامل للمشروع" - ] - for term in terms: - st.markdown(f"- {term}") + # استدعاء النموذج لتحليل المخاطر + risk_analysis_results = self._analyze_risks(features) - # تحليل ذكي للمستند إذا تم تحديد الخيار - if ai_analysis: - st.subheader("تحليل المستند بالذكاء الاصطناعي") - - # محاكاة تحليل المستند - with st.spinner("جاري تحليل المستند باستخدام الذكاء الاصطناعي..."): - # محاكاة نص المستند - document_text = """ - مناقصة رقم: 2025/123 + # إضافة تحليل إضافي باستخدام 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 + """ - المتطلبات الفنية: - 1. تطوير نظام متكامل لإدارة المشاريع - 2. توفير واجهة مستخدم سهلة الاستخدام - 3. دعم اللغة العربية والإنجليزية - 4. توفير تقارير تحليلية متقدمة - 5. دعم الأجهزة المحمولة + prompt = f"""تحليل مخاطر مشروع: - المواعيد: - - آخر موعد لتقديم العروض: 15/05/2025 - - بدء المشروع: 01/06/2025 - - تسليم المرحلة الأولى: 01/08/2025 - - تسليم المرحلة الثانية: 01/10/2025 - - التسليم النهائي: 31/12/2025 + {features_text} - الشروط والأحكام: - - مدة العقد: 12 شهر - - غرامة التأخير: 1% من قيمة العقد عن كل أسبوع تأخير - - شروط الدفع: 20% دفعة مقدمة، 60% على مراحل، 20% بعد الاستلام النهائي - - الضمان: سنتان من تاريخ الاستلام النهائي + المطلوب: + 1. تحليل عوامل المخاطرة وتأثيرها على المشروع + 2. تقديم توصيات إضافية لإدارة المخاطر + 3. اقتراح استراتيجيات استجابة للمخاطر الرئيسية + 4. تقديم نصائح لتحسين شروط العقد لتقليل المخاطر + 5. تقييم مدى ملاءمة المشروع لاستراتيجية الشركة + + يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. """ - # تحليل النص باستخدام الذكاء الاصطناعي - result = self.claude_service.analyze_text(document_text, "requirements") + # استدعاء Claude للتحليل + claude_analysis = self.claude_service.chat_completion( + [{"role": "user", "content": prompt}] + ) - if "error" not in result: - # عرض نتائج التحليل - if isinstance(result["result"], dict) and "المتطلبات" in result["result"]: - requirements = result["result"]["المتطلبات"] - - # إنشاء جدول للمتطلبات - req_data = [] - for req in requirements: - if isinstance(req, dict): - req_data.append({ - "الوصف": req.get("الوصف", ""), - "الفئة": req.get("الفئة", ""), - "الأولوية": req.get("الأولوية", "") - }) - - if req_data: - req_df = pd.DataFrame(req_data) - st.dataframe(req_df, use_container_width=True) - else: - # إذا كانت النتيجة نصية - st.write(result["result"].get("text", "لا توجد نتائج تحليل")) - else: - st.error(f"حدث خطأ أثناء التحليل: {result['error']}") + 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 _render_local_content_tab(self): - """عرض تبويب المحتوى المحلي""" - st.markdown("### تحليل المحتوى المحلي") + def _analyze_risks(self, features): + """تحليل مخاطر المشروع""" - st.info("هذه الوحدة تساعدك في حساب وتحسين نسبة المحتوى المحلي في المشاريع وفقاً لمتطلبات هيئة المحتوى المحلي.") + # في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المخاطر + # محاكاة نتائج تحليل المخاطر للعرض - # إنشاء نموذج إدخال البيانات - col1, col2 = st.columns(2) + # تعريف قائمة من المخاطر المحتملة + 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.subheader("بيانات المشروع") - project_value = st.number_input( - "القيمة الإجمالية للمشروع (ريال)", - min_value=0, - value=5000000 + 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 ) - project_sector = st.selectbox( - "قطاع المشروع", - ["البناء والتشييد", "تقنية المعلومات", "الطاقة", "النقل", "الصحة", "أخرى"] + # عرض مخطط للتكاليف المقدرة + 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) - with col2: - st.subheader("بيانات المحتوى المحلي") + # عرض الشروط التعاقدية إذا وجدت + if results["contract_terms"]: + st.markdown("#### الشروط التعاقدية الهامة") - local_products = st.number_input( - "قيمة المنتجات المحلية (ريال)", - min_value=0, - value=2000000 + # عرض كل شرط في قسم منفصل + 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": "التكلفة المقدرة (ريال)"} ) - local_services = st.number_input( - "قيمة الخدمات المحلية (ريال)", - min_value=0, - value=1000000 + 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 ) - local_workforce = st.number_input( - "تكلفة القوى العاملة المحلية (ريال)", - min_value=0, - value=1500000 + # عرض قائمة تحقق للمتطلبات القانونية + 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([3, 1]) + col1, col2 = st.columns([1, 3]) + with col1: - calculate_button = st.button("حساب نسبة المحتوى المحلي") + calculate_button = st.button("حساب المحتوى المحلي", use_container_width=True) + with col2: - ai_optimization = st.checkbox("تحسين ذكي", value=True) + use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="lc_use_claude") if calculate_button: - # حساب نسبة المحتوى المحلي - total_local_content = local_products + local_services + local_workforce - local_content_percentage = (total_local_content / project_value) * 100 + 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 + } - # عرض النتائج - st.success("تم حساب نسبة المحتوى المحلي بنجاح!") + 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: - st.metric("إجمالي المحتوى المحلي", f"{total_local_content:,.2f} ريال") - st.metric("نسبة المحتوى المحلي", f"{local_content_percentage:.2f}%") + fig = px.pie( + project_data, + values='القيمة', + names='المكون', + title="توزيع قيمة المشروع" + ) - # تقييم النسبة - if local_content_percentage >= 70: - st.success("ممتاز! نسبة المحتوى المحلي تتجاوز المتطلبات.") - elif local_content_percentage >= 50: - st.info("جيد. نسبة المحتوى المحلي تلبي الحد الأدنى من المتطلبات.") - else: - st.warning("تحذير: نسبة المحتوى المحلي أقل من المتطلبات. يرجى العمل على تحسينها.") + st.plotly_chart(fig, use_container_width=True) with col2: - # عرض رسم بياني للمحتوى المحلي - fig = go.Figure() - - # إضافة البيانات - labels = ["المنتجات المحلية", "الخدمات المحلية", "القوى العاملة المحلية", "غير محلي"] - values = [local_products, local_services, local_workforce, project_value - total_local_content] - colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'] - - fig.add_trace(go.Pie( - labels=[get_display_arabic(label) for label in labels], - values=values, - textinfo='percent', - insidetextorientation='radial', - marker=dict(colors=colors), - hole=0.4 - )) - - # تخصيص الرسم البياني - fig.update_layout( - title=get_display_arabic("توزيع المحتوى المحلي"), - height=400, - margin=dict(l=0, r=0, t=40, b=0), - font=dict(size=14), - legend=dict( - orientation="h", - yanchor="bottom", - y=-0.2, - xanchor="center", - x=0.5 - ) + fig = px.pie( + project_data, + values='قيمة المحتوى المحلي', + names='المكون', + title="توزيع قيمة المحتوى المحلي" ) st.plotly_chart(fig, use_container_width=True) - # عرض تفاصيل المحتوى المحلي - st.subheader("تفاصيل المحتوى المحلي") - - # إنشاء جدول البيانات - local_content_data = { - "العنصر": ["المنتجات المحلية", "الخدمات المحلية", "القوى العاملة المحلية", "إجمالي المحتوى المحلي", "غير محلي", "إجمالي المشروع"], - "القيمة (ريال)": [local_products, local_services, local_workforce, total_local_content, project_value - total_local_content, project_value], - "النسبة (%)": [ - local_products / project_value * 100, - local_services / project_value * 100, - local_workforce / project_value * 100, - local_content_percentage, - (project_value - total_local_content) / project_value * 100, - 100 - ] - } + # مخطط شريطي للمحتوى المحلي + fig = px.bar( + project_data, + x='المكون', + y='نسبة المحتوى المحلي', + title="نسبة المحتوى المحلي لكل مكون", + text_auto='.1f', + color='نسبة من المشروع' + ) - local_content_df = pd.DataFrame(local_content_data) - local_content_df["النسبة (%)"] = local_content_df["النسبة (%)"].round(2) - local_content_df["القيمة (ريال)"] = local_content_df["القيمة (ريال)"].apply(lambda x: f"{x:,.2f}") + fig.update_traces(texttemplate='%{text}%', textposition='outside') - st.dataframe(local_content_df, use_container_width=True) + 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='سنة الإنجاز' + ) - # تحسين المحتوى المحلي باستخدام الذكاء الاصطناعي - if ai_optimization: - st.subheader("توصيات تحسين المحتوى المحلي") - - with st.spinner("جاري تحليل وتحسين المحتوى المحلي باستخدام الذكاء الاصطناعي..."): - # إنشاء وصف للمشروع - project_description = f""" - قطاع المشروع: {project_sector} - القيمة الإجمالية للمشروع: {project_value:,.2f} ريال - قيمة المنتجات المحلية: {local_products:,.2f} ريال ({local_products/project_value*100:.2f}%) - قيمة الخدمات المحلية: {local_services:,.2f} ريال ({local_services/project_value*100:.2f}%) - تكلفة القوى العاملة المحلية: {local_workforce:,.2f} ريال ({local_workforce/project_value*100:.2f}%) - إجمالي المحتوى المحلي: {total_local_content:,.2f} ريال ({local_content_percentage:.2f}%) - """ - - # تحليل النص باستخدام الذكاء الاصطناعي - result = self.claude_service.analyze_text(project_description, "local_content") - - if "error" not in result: - # عرض نتائج التحليل - if isinstance(result["result"], dict): - if "توصيات" in result["result"]: - recommendations = result["result"]["توصيات"] - - for i, rec in enumerate(recommendations): - st.markdown(f"**{i+1}. {rec}**") - elif "عناصر المحتوى المحلي" in result["result"]: - items = result["result"]["عناصر المحتوى المحلي"] - - # إنشاء جدول للعناصر - items_data = [] - for item in items: - if isinstance(item, dict): - items_data.append({ - "الوصف": item.get("الوصف", ""), - "الفئة": item.get("الفئة", ""), - "مدى التوفر": item.get("مدى التوفر", ""), - "المساهمة": item.get("تقدير نسبي للمساهمة في المحتوى المحلي", "") - }) - - if items_data: - items_df = pd.DataFrame(items_data) - st.dataframe(items_df, use_container_width=True) - else: - # إذا كانت النتيجة نصية - st.write(result["result"].get("text", "لا توجد نتائج تحليل")) - else: - st.write("لا توجد توصيات متاحة.") - else: - st.error(f"حدث خطأ أثناء التحليل: {result['error']}") + 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) - # توصيات لتحسين المحتوى المحلي - st.subheader("توصيات عامة لتحسين المحتوى المحلي") + 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) - recommendations = [ - "استخدام منتجات محلية الصنع بدلاً من المستوردة حيثما أمكن", - "التعاقد مع موردين محليين معتمدين من هيئة المحتوى المحلي", - "توظيف وتدريب كوادر سعودية للعمل في المشروع", - "الاستفادة من برامج دعم المحتوى المحلي المقدمة من الجهات الحكومية", - "تطوير شراكات مع مصنعين محليين لتوطين التقنيات المطلوبة" + 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": "شركة الزجاج المتطورة" + } ] - for rec in recommendations: - st.markdown(f"- {rec}") + # عرض جدول المكونات + 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("### الأسئلة الشائعة") - st.info("هذه الصفحة تحتوي على إجابات للأسئلة الشائعة حول استخدام نظام تسعير المناقصات.") + # البحث في الأسئلة الشائعة + search_query = st.text_input("البحث في الأسئلة الشائعة", key="faq_search") - # عرض الأسئلة الشائعة - for i, faq in enumerate(self.faqs): - with st.expander(faq["question"]): - st.write(faq["answer"]) + # فلترة الأسئلة حسب البحث + 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 - # إضافة سؤال جديد - st.subheader("هل لديك سؤال آخر؟") + # عرض الأسئلة والأجوبة + for i, faq in enumerate(filtered_faqs): + with st.expander(faq["question"]): + st.markdown(faq["answer"]) - new_question = st.text_input("اكتب سؤالك هنا") + # زر التواصل مع الدعم + st.markdown("##### لم تجد إجابة لسؤالك؟") + col1, col2 = st.columns(2) - col1, col2 = st.columns([3, 1]) with col1: - send_button = st.button("إرسال السؤال") + if st.button("التواصل مع الدعم الفني", use_container_width=True): + st.info("سيتم التواصل معك قريباً من قبل فريق الدعم الفني.") + with col2: - ai_answer = st.checkbox("إجابة ذكية", value=True) - - if send_button and new_question: - if ai_answer: - with st.spinner("جاري تحليل السؤال باستخدام الذكاء الاصطناعي..."): - # تحليل السؤال باستخدام الذكاء الاصطناعي - result = self.claude_service.chat_completion([ - {"role": "user", "content": f"السؤال: {new_question}\n\nالرجاء الإجابة على هذا السؤال المتعلق بنظام تسعير المناقصات بشكل مختصر ومفيد."} - ]) - - if "error" not in result: - st.success("تم تحليل السؤال والإجابة عليه!") - - # عرض الإجابة - st.info(f"**سؤالك:** {new_question}") - st.write(f"**الإجابة:** {result['content']}") - else: - st.error(f"حدث خطأ أثناء تحليل السؤال: {result['error']}") - st.success("تم إرسال سؤالك بنجاح! سيتم الرد عليه في أقرب وقت.") - else: - st.success("تم إرسال سؤالك بنجاح! سيتم الرد عليه في أقرب وقت.") - elif send_button: - st.error("يرجى كتابة السؤال قبل الإرسال.") + if st.button("طرح سؤال جديد", use_container_width=True): + st.text_area("اكتب سؤالك هنا") + st.button("إرسال") \ No newline at end of file