diff --git "a/modules/ai_assistant/ai_app.py" "b/modules/ai_assistant/ai_app.py"
--- "a/modules/ai_assistant/ai_app.py"
+++ "b/modules/ai_assistant/ai_app.py"
@@ -80,66 +80,24 @@ class ClaudeAIService:
العوائد:
dict: نتائج التحليل
"""
- try:
- # الحصول على مفتاح API
- api_key = self.get_api_key()
-
- # قراءة محتوى الملف مع ضغط الصور فقط
- file_size = os.path.getsize(image_path)
- _, ext = os.path.splitext(image_path)
- ext = ext.lower()
-
- if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp'] and file_size > 5 * 1024 * 1024:
- # فقط ضغط الصور العادية وليس ملفات PDF
- try:
- with Image.open(image_path) as img:
- # حفظ الصورة المضغوطة في ذاكرة مؤقتة
- compressed_img_buffer = io.BytesIO()
-
- # حساب معامل الجودة المناسب
- quality = min(95, int((5 * 1024 * 1024 / file_size) * 100))
-
- # حفظ الصورة بالجودة المحسوبة
- img.save(compressed_img_buffer, format='JPEG', quality=quality)
- compressed_img_buffer.seek(0)
-
- # استخدام البيانات المضغوطة
- file_content = compressed_img_buffer.read()
- logging.info(f"تم ضغط الصورة من {file_size/1024/1024:.2f} ميجابايت إلى {len(file_content)/1024/1024:.2f} ميجابايت")
- except Exception as e:
- logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}")
- with open(image_path, 'rb') as f:
- file_content = f.read()
- else:
- # استخدام الملف الأصلي للملفات غير الصور أو الصور الصغيرة
- with open(image_path, 'rb') as f:
- file_content = f.read()
-
- # التحقق من حجم الملف بعد القراءة
- if len(file_content) > 5 * 1024 * 1024:
- raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.")
-
- # تحويل المحتوى إلى Base64
- file_base64 = base64.b64encode(file_content).decode('utf-8')
-
-
- except Exception as e:
- logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}")
- with open(image_path, 'rb') as f:
- file_content = f.read()
- else:
- # استخدام الملف الأصلي للملفات غير الصور أو الصور الصغيرة
- with open(image_path, 'rb') as f:
- file_content = f.read()
+ try:
+ # الحصول على مفتاح API
+ api_key = self.get_api_key()
+
+ # قراءة محتوى الملف مع ضغط الصور فقط
+ file_size = os.path.getsize(image_path)
+ _, ext = os.path.splitext(image_path)
+ ext = ext.lower()
- # التحقق من حجم الملف بعد القراءة
- if len(file_content) > 5 * 1024 * 1024:
- raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.")
-
-
+ if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp'] and file_size > 5 * 1024 * 1024:
+ # فقط ضغط الصور العادية وليس ملفات PDF
+ try:
+ with Image.open(image_path) as img:
+ # حفظ الصورة المضغوطة في ذاكرة مؤقتة
+ compressed_img_buffer = io.BytesIO()
# حساب معامل الجودة المناسب
- quality = min(95, int((5 * 1024 * 1024 / file_size) * 100)
+ quality = min(95, int((5 * 1024 * 1024 / file_size) * 100))
# حفظ الصورة بالجودة المحسوبة
img.save(compressed_img_buffer, format='JPEG', quality=quality)
@@ -147,9 +105,20 @@ class ClaudeAIService:
# استخدام البيانات المضغوطة
file_content = compressed_img_buffer.read()
-
-
+ logging.info(f"تم ضغط الصورة من {file_size/1024/1024:.2f} ميجابايت إلى {len(file_content)/1024/1024:.2f} ميجابايت")
+ except Exception as e:
+ logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}")
+ with open(image_path, 'rb') as f:
+ file_content = f.read()
+ else:
+ # استخدام الملف الأصلي للملفات غير الصور أو الصور الصغيرة
+ with open(image_path, 'rb') as f:
+ file_content = f.read()
+ # التحقق من حجم الملف بعد القراءة
+ if len(file_content) > 5 * 1024 * 1024:
+ raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.")
+
# تحويل المحتوى إلى Base64
file_base64 = base64.b64encode(file_content).decode('utf-8')
@@ -356,33 +325,17 @@ class AIAssistantApp:
{
"question": "كيف يمكنني تقييم المخاطر للمشروع؟",
"answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة."
- }
- ]
-
- # تهيئة قائمة الأسئلة والإجابات الشائعة
- self.faqs = [
- {
- "question": "كيف يمكنني إضافة مشروع جديد؟",
- "answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة."
- },
- {
- "question": "ما هي خطوات تسعير المناقصة؟",
- "answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية."
- },
- {
- "question": "كيف يتم حساب المحتوى المحلي؟",
- "answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي."
},
{
- "question": "كيف يمكنني تصدير التقارير؟",
- "answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV."
+ "question": "ما هي طرق التسعير المتاحة في النظام؟",
+ "answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة."
},
{
- "question": "كيف يمكنني تقييم المخاطر للمشروع؟",
- "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة."
+ "question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟",
+ "answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً."
}
]
-
+
def _load_cost_prediction_model(self):
"""تحميل نموذج التنبؤ بالتكاليف"""
# في البيئة الإنتاجية، سيتم تحميل نموذج حقيقي
@@ -404,38 +357,6 @@ class AIAssistantApp:
def _load_entity_recognition_model(self):
"""تحميل نموذج التعرف على الكيانات"""
return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"}
-
- # تهيئة قائمة الأسئلة والإجابات الشائعة
- self.faqs = [
- {
- "question": "كيف يمكنني إضافة مشروع جديد؟",
- "answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة."
- },
- {
- "question": "ما هي خطوات تسعير المناقصة؟",
- "answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية."
- },
- {
- "question": "كيف يتم حساب المحتوى المحلي؟",
- "answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي."
- },
- {
- "question": "كيف يمكنني تصدير التقارير؟",
- "answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV."
- },
- {
- "question": "كيف يمكنني تقييم المخاطر للمشروع؟",
- "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة."
- },
- {
- "question": "ما هي طرق التسعير المتاحة في النظام؟",
- "answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة."
- },
- {
- "question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟",
- "answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً."
- }
- ]
def render(self):
"""عرض واجهة وحدة المساعد الذكي"""
@@ -600,2745 +521,4 @@ class AIAssistantApp:
self.claude_service.get_api_key()
except ValueError:
api_available = False
- st.warning("مفتاح API لـ Claude غير متوفر. يرجى التأكد من تعيين متغير البيئة 'anthropic'.")
-
- if user_input and api_available:
- # إضافة رسالة المستخدم إلى المحفوظات
- st.session_state.ai_assistant_messages.append({"role": "user", "content": user_input})
-
- # عرض محفوظات المحادثة المحدثة
- with chat_container:
- st.markdown(f"""
-
- """, unsafe_allow_html=True)
-
- # معالجة الرد
- with st.spinner("جاري التفكير..."):
- # التحقق مما إذا كان هناك ملف مرفق
- if uploaded_file:
- # حفظ الملف المرفوع مؤقتاً
- with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as temp_file:
- temp_file.write(uploaded_file.getbuffer())
- temp_file_path = temp_file.name
-
- # إذا كان الملف PDF، تحويله إلى صورة
- if uploaded_file.name.lower().endswith('.pdf'):
- if self.pdf_conversion_available:
- try:
- # تحويل الصفحة الأولى فقط
- images = self.convert_from_path(temp_file_path, first_page=1, last_page=1)
- if images:
- # حفظ الصورة بشكل مؤقت
- temp_image_path = f"{temp_file_path}_image.jpg"
- images[0].save(temp_image_path, 'JPEG', quality=85)
- # استخدام مسار الصورة بدلاً من PDF
- os.remove(temp_file_path)
- temp_file_path = temp_image_path
- except Exception as e:
- st.error(f"فشل في تحويل ملف PDF إلى صورة: {str(e)}")
- else:
- st.error("تحليل ملفات PDF يتطلب تثبيت مكتبة pdf2image.")
- response = "عذراً، لا يمكنني تحليل ملفات PDF في الوقت الحالي. يرجى تحويل الملف إلى صورة أو مشاركة المعلومات كنص."
-
- # تحليل الصورة باستخدام Claude
- prompt = f"المستخدم قام برفع هذه الصورة وسأل: {user_input}\nقم بتحليل الصورة والرد على سؤال المستخدم بشكل تفصيلي."
- results = self.claude_service.analyze_image(temp_file_path, prompt, model_name=selected_model)
-
- # حذف الملف المؤقت
- try:
- os.remove(temp_file_path)
- except:
- pass
-
- if "error" in results:
- response = f"عذراً، حدث خطأ أثناء تحليل الملف: {results['error']}"
- else:
- response = results["content"]
- else:
- # استخدام خدمة Claude للرد على الرسائل النصية
- results = self.claude_service.chat_completion(st.session_state.ai_assistant_messages, model_name=selected_model)
-
- if "error" in results:
- response = f"عذراً، حدث خطأ أثناء معالجة طلبك: {results['error']}"
- else:
- response = results["content"]
-
- # إضافة رد المساعد إلى المحفوظات
- st.session_state.ai_assistant_messages.append({"role": "assistant", "content": response})
-
- # عرض رد المساعد
- with chat_container:
- st.markdown(f"""
-
- """, unsafe_allow_html=True)
-
- # إعادة تعيين قيمة الإدخال
- st.text_input("اكتب رسالتك هنا", value="", key="ai_assistant_input_reset")
-
- def _generate_ai_response(self, user_input, model_name="claude-3-7-sonnet"):
- """توليد رد المساعد الذكي باستخدام Claude AI"""
-
- # التحقق من وجود مفتاح API
- try:
- self.claude_service.get_api_key()
- except ValueError:
- return "عذراً، لا يمكنني الاتصال بخدمة الذكاء الاصطناعي في الوقت الحالي. يرجى التحقق من إعدادات API."
-
- # البحث في الأسئلة الشائعة أولاً
- for faq in self.faqs:
- if any(keyword in user_input.lower() for keyword in faq["question"].lower().split()):
- return f"{faq['answer']}\n\nهل تحتاج إلى مساعدة أخرى؟"
-
- # إنشاء محادثة لإرسالها إلى Claude
- messages = [
- {"role": "user", "content": user_input}
- ]
-
- # استدعاء خدمة Claude
- results = self.claude_service.chat_completion(messages, model_name=model_name)
-
- if "error" in results:
- # إذا فشل الاتصال، استخدم التوليد الافتراضي
- logging.warning(f"فشل الاتصال بـ Claude AI: {results['error']}. استخدام التوليد الافتراضي.")
- return self._generate_default_response(user_input)
- else:
- return results["content"]
-
- def _generate_default_response(self, user_input):
- """توليد رد افتراضي في حالة عدم توفر Claude AI"""
-
- if "تسعير" in user_input or "سعر" in user_input or "تكلفة" in user_input:
- return "يمكنك استخدام وحدة التنبؤ بالتكاليف لتقدير تكاليف المشروع بناءً على خصائصه. انتقل إلى تبويب 'التنبؤ بالتكاليف' وأدخل بيانات المشروع لتحصل على تقدير دقيق للتكاليف."
-
- elif "مخاطر" in user_input or "مخاطرة" in user_input:
- return "يمكنك استخدام وحدة تحليل المخاطر لتقييم المخاطر المحتملة للمشروع. انتقل إلى تبويب 'تحليل المخاطر' وأدخل بيانات المشروع وعوامل المخاطرة لتحصل على تحليل شامل للمخاطر واستراتيجيات الاستجابة المقترحة."
-
- elif "مستند" in user_input or "ملف" in user_input or "وثيقة" in user_input or "مناقصة" in user_input:
- return "يمكنك استخدام وحدة تحليل المستندات لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها. انتقل إلى تبويب 'تحليل المستندات' وقم بتحميل ملفات المناقصة لتحصل على تحليل تفصيلي للمستندات."
-
- elif "محتوى محلي" in user_input or "محلي" in user_input:
- return "يمكنك استخدام وحدة المحتوى المحلي لحساب وتحسين نسبة المحتوى المحلي في مشروعك. انتقل إلى تبويب 'المحتوى المحلي' وأدخل بيانات مكونات المشروع لتحصل على تحليل شامل للمحتوى المحلي واقتراحات لتحسينه."
-
- elif "تقرير" in user_input or "إحصائيات" in user_input or "بيانات" in user_input:
- return "يمكنك استخدام وحدة التقارير والتحليلات للحصول على تقارير تفصيلية وإحصائيات عن المشاريع. يمكنك الوصول إليها من القائمة الرئيسية للنظام."
-
- else:
- return "شكراً لاستفسارك. يمكنني مساعدتك في تسعير المناقصات، وتحليل المخاطر، وتحليل المستندات، وحساب المحتوى المحلي. يرجى توضيح استفسارك أكثر أو اختيار أحد الخيارات في الأعلى للحصول على المساعدة المطلوبة."
-
- def _render_cost_prediction_tab(self):
- """عرض تبويب التنبؤ بالتكاليف"""
-
- st.markdown("### التنبؤ بالتكاليف")
-
- # عرض نموذج إدخال بيانات المشروع
- st.markdown("#### بيانات المشروع")
-
- col1, col2 = st.columns(2)
-
- with col1:
- project_type = st.selectbox(
- "نوع المشروع",
- [
- "مباني سكنية",
- "مباني تجارية",
- "مباني حكومية",
- "مراكز صحية",
- "مدارس",
- "بنية تحتية",
- "طرق",
- "جسور",
- "صرف صحي",
- "مياه",
- "كهرباء"
- ],
- key="cost_project_type"
- )
-
- location = st.selectbox(
- "الموقع",
- [
- "الرياض",
- "جدة",
- "الدمام",
- "مكة",
- "المدينة",
- "تبوك",
- "حائل",
- "عسير",
- "جازان",
- "نجران",
- "الباحة",
- "الجوف",
- "القصيم"
- ],
- key="cost_location"
- )
-
- client_type = st.selectbox(
- "نوع العميل",
- [
- "حكومي",
- "شبه حكومي",
- "شركة كبيرة",
- "شركة متوسطة",
- "شركة صغيرة",
- "أفراد"
- ],
- key="cost_client_type"
- )
-
- with col2:
- area = st.number_input("المساحة (م²)", min_value=100, max_value=1000000, value=5000, key="cost_area")
-
- floors = st.number_input("عدد الطوابق", min_value=1, max_value=100, value=3, key="cost_floors")
-
- duration = st.number_input("مدة التنفيذ (شهور)", min_value=1, max_value=60, value=12, key="cost_duration")
-
- tender_type = st.selectbox(
- "نوع المناقصة",
- [
- "عامة",
- "خاصة",
- "أمر مباشر"
- ],
- key="cost_tender_type"
- )
-
- st.markdown("#### متغيرات إضافية")
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- has_basement = st.checkbox("يتضمن بدروم", key="cost_has_basement")
- has_special_finishing = st.checkbox("تشطيبات خاصة", key="cost_has_special_finishing")
-
- with col2:
- has_landscape = st.checkbox("أعمال تنسيق المواقع", key="cost_has_landscape")
- has_parking = st.checkbox("مواقف متعددة الطوابق", key="cost_has_parking")
-
- with col3:
- has_smart_systems = st.checkbox("أنظمة ذكية", key="cost_has_smart_systems")
- has_sustainability = st.checkbox("متطلبات استدامة", key="cost_has_sustainability")
-
- # زر التنبؤ بالتكلفة مع دعم Claude AI
- col1, col2 = st.columns([1, 3])
-
- with col1:
- predict_button = st.button("التنبؤ بالتكلفة", use_container_width=True, key="cost_predict_button")
-
- with col2:
- use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="cost_use_claude")
-
- if predict_button:
- with st.spinner("جاري تحليل البيانات والتنبؤ بالتكاليف..."):
- # محاكاة وقت المعالجة
- time.sleep(2)
-
- # تجهيز البيانات للنموذج
- features = {
- 'project_type': project_type,
- 'location': location,
- 'area': area,
- 'floors': floors,
- 'duration_months': duration,
- 'tender_type': tender_type,
- 'client_type': client_type,
- 'has_basement': has_basement,
- 'has_special_finishing': has_special_finishing,
- 'has_landscape': has_landscape,
- 'has_parking': has_parking,
- 'has_smart_systems': has_smart_systems,
- 'has_sustainability': has_sustainability
- }
-
- # استدعاء النموذج للتنبؤ
- cost_prediction_results = self._predict_cost(features)
-
- # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار
- if use_claude:
- try:
- # إنشاء نص الميزات للتحليل
- features_text = f"""
- بيانات المشروع:
- - نوع المشروع: {project_type}
- - الموقع: {location}
- - المساحة: {area} م²
- - عدد الطوابق: {floors}
- - مدة التنفيذ: {duration} شهر
- - نوع المناقصة: {tender_type}
- - نوع العميل: {client_type}
- - يتضمن بدروم: {'نعم' if has_basement else 'لا'}
- - تشطيبات خاصة: {'نعم' if has_special_finishing else 'لا'}
- - أعمال تنسيق المواقع: {'نعم' if has_landscape else 'لا'}
- - مواقف متعددة الطوابق: {'نعم' if has_parking else 'لا'}
- - أنظمة ذكية: {'نعم' if has_smart_systems else 'لا'}
- - متطلبات استدامة: {'نعم' if has_sustainability else 'لا'}
-
- نتائج التنبؤ الأولية:
- - التكلفة الإجمالية المقدرة: {cost_prediction_results['total_cost']:,.0f} ريال
- - تكلفة المتر المربع: {cost_prediction_results['cost_per_sqm']:,.0f} ريال/م²
- - تكلفة المواد: {cost_prediction_results['material_cost']:,.0f} ريال
- - تكلفة العمالة: {cost_prediction_results['labor_cost']:,.0f} ريال
- - تكلفة المعدات: {cost_prediction_results['equipment_cost']:,.0f} ريال
- """
-
- prompt = f"""تحليل بيانات مشروع وتكاليف��:
-
- {features_text}
-
- المطلوب:
- 1. تحليل التكاليف المتوقعة ومعقوليتها مقارنة بمشاريع مماثلة في السوق السعودي
- 2. تقديم توصيات وملاحظات لتحسين التكلفة
- 3. تحديد أي مخاطر محتملة قد تؤثر على التكلفة
- 4. تقديم نصائح لزيادة فعالية التكلفة
- 5. تقديم رأي حول مدى تنافسية هذه التكلفة في السوق الحالي
-
- يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية.
- """
-
- # استدعاء Claude للتحليل
- claude_analysis = self.claude_service.chat_completion(
- [{"role": "user", "content": prompt}]
- )
-
- if "error" not in claude_analysis:
- # إضافة تحليل Claude إلى النتائج
- cost_prediction_results["claude_analysis"] = claude_analysis["content"]
- except Exception as e:
- st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
-
- # عرض نتائج التنبؤ
- self._display_cost_prediction_results(cost_prediction_results)
-
- def _predict_cost(self, features):
- """التنبؤ بتكاليف المشروع"""
-
- # في البيئة الحقيقية، سيتم استدعاء نموذج التنبؤ بالتكاليف
- # محاكاة نتائج التنبؤ للعرض
-
- # حساب القيمة الأساسية للمتر المربع حسب نوع المشروع
- base_cost_per_sqm = {
- "مباني سكنية": 2500,
- "مباني تجارية": 3000,
- "مباني حكومية": 3500,
- "مراكز صحية": 4000,
- "مدارس": 3200,
- "بنية تحتية": 2000,
- "طرق": 1500,
- "جسور": 5000,
- "صرف صحي": 2200,
- "مياه": 2000,
- "كهرباء": 2500
- }.get(features['project_type'], 2500)
-
- # تطبيق معاملات التعديل حسب المتغيرات
- location_factor = {
- "الرياض": 1.1,
- "جدة": 1.15,
- "الدمام": 1.05,
- "مكة": 1.2,
- "المدينة": 1.1,
- "تبوك": 0.95,
- "حائل": 0.9,
- "عسير": 0.95,
- "جازان": 0.9,
- "نجران": 0.85,
- "الباحة": 0.9,
- "الجوف": 0.85,
- "القصيم": 0.9
- }.get(features['location'], 1.0)
-
- client_factor = {
- "حكومي": 1.05,
- "شبه حكومي": 1.0,
- "شركة كبيرة": 0.95,
- "شركة متوسطة": 0.9,
- "شركة صغيرة": 0.85,
- "أفراد": 0.8
- }.get(features['client_type'], 1.0)
-
- tender_factor = {
- "عامة": 1.0,
- "خاصة": 0.95,
- "أمر مباشر": 0.9
- }.get(features['tender_type'], 1.0)
-
- # معاملات للميزات الإضافية
- basement_factor = 1.1 if features['has_basement'] else 1.0
- special_finishing_factor = 1.2 if features['has_special_finishing'] else 1.0
- landscape_factor = 1.05 if features['has_landscape'] else 1.0
- parking_factor = 1.1 if features['has_parking'] else 1.0
- smart_systems_factor = 1.15 if features['has_smart_systems'] else 1.0
- sustainability_factor = 1.1 if features['has_sustainability'] else 1.0
-
- # معامل لعدد الطوابق
- floors_factor = 1.0 + (features['floors'] - 1) * 0.05
-
- # حساب التكلفة الإجمالية
- total_sqm_cost = base_cost_per_sqm * location_factor * client_factor * tender_factor * \
- basement_factor * special_finishing_factor * landscape_factor * \
- parking_factor * smart_systems_factor * sustainability_factor * \
- floors_factor
-
- total_cost = total_sqm_cost * features['area']
-
- # حساب التكاليف المفصلة
- material_cost = total_cost * 0.6
- labor_cost = total_cost * 0.25
- equipment_cost = total_cost * 0.15
-
- # إضافة هامش خطأ عشوائي للمحاكاة
- error_margin = 0.05 # 5%
- total_cost = total_cost * (1 + np.random.uniform(-error_margin, error_margin))
-
- # إعداد النتائج
- results = {
- "total_cost": total_cost,
- "cost_per_sqm": total_cost / features['area'],
- "material_cost": material_cost,
- "labor_cost": labor_cost,
- "equipment_cost": equipment_cost,
- "breakdown": {
- "structural_works": total_cost * 0.35,
- "architectural_works": total_cost * 0.25,
- "mep_works": total_cost * 0.25,
- "site_works": total_cost * 0.1,
- "general_requirements": total_cost * 0.05
- },
- "confidence_level": 0.85, # مستوى الثقة في التنبؤ
- "comparison": {
- "market_average": total_cost * 1.1,
- "historical_projects": total_cost * 0.95
- }
- }
-
- return results
-
- def _display_cost_prediction_results(self, results):
- """عرض نتائج التنبؤ بالتكاليف"""
-
- st.markdown("### نتائج التنبؤ بالتكاليف")
-
- # عرض التكلفة الإجمالية وتكلفة المتر المربع
- col1, col2, col3 = st.columns(3)
-
- with col1:
- st.metric(
- "التكلفة الإجمالية المتوقعة",
- f"{results['total_cost']:,.0f} ريال",
- delta=f"{(results['total_cost'] - results['comparison']['historical_projects']):,.0f} ريال"
- )
-
- with col2:
- st.metric(
- "تكلفة المتر المربع",
- f"{results['cost_per_sqm']:,.0f} ريال/م²"
- )
-
- with col3:
- st.metric(
- "مستوى الثقة في التنبؤ",
- f"{results['confidence_level'] * 100:.0f}%"
- )
-
- # عرض تفصيل التكاليف
- st.markdown("#### تفصيل التكاليف")
-
- # رسم مخطط دائري للتكاليف المفصلة
- fig = px.pie(
- values=[
- results['material_cost'],
- results['labor_cost'],
- results['equipment_cost']
- ],
- names=["تكلفة المواد", "تكلفة العمالة", "تكلفة المعدات"],
- title="توزيع التكاليف الرئيسية"
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # رسم مخطط شريطي لتفصيل الأعمال
- breakdown_data = pd.DataFrame({
- 'فئة الأعمال': [
- "الأعمال الإنشائية",
- "الأعمال المعمارية",
- "الأعمال الكهروميكانيكية",
- "أعمال الموقع",
- "المتطلبات العامة"
- ],
- 'التكلفة': [
- results['breakdown']['structural_works'],
- results['breakdown']['architectural_works'],
- results['breakdown']['mep_works'],
- results['breakdown']['site_works'],
- results['breakdown']['general_requirements']
- ]
- })
-
- fig = px.bar(
- breakdown_data,
- x='فئة الأعمال',
- y='التكلفة',
- title="تفصيل التكاليف حسب فئة الأعمال",
- text_auto='.3s'
- )
-
- fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside')
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض مقارنة مع متوسط السوق
- st.markdown("#### مقارنة مع متوسط السوق")
-
- comparison_data = pd.DataFrame({
- 'المصدر': [
- "التكلفة المتوقعة",
- "متوسط السوق",
- "مشاريع مماثلة سابقة"
- ],
- 'التكلفة': [
- results['total_cost'],
- results['comparison']['market_average'],
- results['comparison']['historical_projects']
- ]
- })
-
- fig = px.bar(
- comparison_data,
- x='المصدر',
- y='التكلفة',
- title="مقارنة التكلفة المتوقعة مع السوق",
- text_auto='.3s',
- color='المصدر',
- color_discrete_map={
- "التكلفة المتوقعة": "#1f77b4",
- "متوسط السوق": "#ff7f0e",
- "مشاريع مماثلة سابقة": "#2ca02c"
- }
- )
-
- fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside')
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض تحليل Claude AI إذا كان متوفراً
- if "claude_analysis" in results:
- st.markdown("### تحليل Claude AI المتقدم")
- st.info(results["claude_analysis"])
-
- # عرض ملاحظات وتوصيات
- st.markdown("#### ملاحظات وتوصيات")
-
- st.info("""
- - تم التنبؤ بالتكاليف بناءً على البيانات المدخلة ونماذج التعلم الآلي المدربة على مشاريع مماثلة.
- - مستوى الثقة في التنبؤ جيد، ولكن يجب مراجعة التكاليف بشكل تفصيلي قبل اتخاذ القرار النهائي.
- - تكلفة المتر المربع متوافقة مع متوسط السوق لهذا النوع من المشاريع.
- - ينصح بمراجعة التصميم لتحسين التكلفة وزيادة الكفاءة.
- """)
-
- # زر تصدير التقرير
- if st.button("تصدير تقرير التكاليف"):
- st.success("تم تصدير تقرير التكاليف بنجاح!")
-
- def _render_risk_analysis_tab(self):
- """عرض تبويب تحليل المخاطر"""
-
- st.markdown("### تحليل المخاطر")
-
- # عرض نموذج إدخال بيانات المشروع للمخاطر
- st.markdown("#### بيانات المشروع")
-
- col1, col2 = st.columns(2)
-
- with col1:
- project_type = st.selectbox(
- "نوع المشروع",
- [
- "مباني سكنية",
- "مباني تجارية",
- "مباني حكومية",
- "مراكز صحية",
- "مدارس",
- "بنية تحتية",
- "طرق",
- "جسور",
- "صرف صحي",
- "مياه",
- "كهرباء"
- ],
- key="risk_project_type"
- )
-
- location = st.selectbox(
- "الموقع",
- [
- "الرياض",
- "جدة",
- "الدمام",
- "مكة",
- "المدينة",
- "تبوك",
- "حائل",
- "عسير",
- "جازان",
- "نجران",
- "الباحة",
- "الجوف",
- "القصيم"
- ],
- key="risk_location"
- )
-
- with col2:
- client_type = st.selectbox(
- "نوع العميل",
- [
- "حكومي",
- "شبه حكومي",
- "شركة كبيرة",
- "شركة متوسطة",
- "شركة صغيرة",
- "أفراد"
- ],
- key="risk_client_type"
- )
-
- tender_type = st.selectbox(
- "نوع المناقصة",
- [
- "عامة",
- "خاصة",
- "أمر مباشر"
- ],
- key="risk_tender_type"
- )
-
- st.markdown("#### عوامل المخاطرة")
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- payment_terms = st.slider("شروط الدفع (1-10)", 1, 10, 5,
- help="1: شروط دفع سيئة جداً، 10: شروط دفع ممتازة",
- key="risk_payment_terms")
- completion_deadline = st.slider("مهلة الإنجاز (1-10)", 1, 10, 5,
- help="1: مهلة قصيرة جداً، 10: مهلة مريحة",
- key="risk_completion_deadline")
-
- with col2:
- penalty_clause = st.slider("شروط الغرامات (1-10)", 1, 10, 5,
- help="1: غرامات مرتفعة جداً، 10: غرامات معقولة",
- key="risk_penalty_clause")
- technical_complexity = st.slider("التعقيد الفني (1-10)", 1, 10, 5,
- help="1: بسيط جداً، 10: معقد للغاية",
- key="risk_technical_complexity")
-
- with col3:
- company_experience = st.slider("خبرة الشركة (1-10)", 1, 10, 7,
- help="1: لا توجد خبرة، 10: خبرة عالية",
- key="risk_company_experience")
- market_volatility = st.slider("تقلبات السوق (1-10)", 1, 10, 5,
- help="1: مستقر جداً، 10: متقلب للغاية",
- key="risk_market_volatility")
-
- # زر تحليل المخاطر مع دعم Claude AI
- col1, col2 = st.columns([1, 3])
-
- with col1:
- analyze_button = st.button("تحليل المخاطر", use_container_width=True, key="risk_analyze_button")
-
- with col2:
- # Añadimos un key único para este checkbox
- use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="risk_use_claude")
-
- if analyze_button:
- with st.spinner("جاري تحليل المخاطر..."):
- # محاكاة وقت المعالجة
- time.sleep(2)
-
- # تجهيز البيانات للنموذج
- features = {
- 'project_type': project_type,
- 'location': location,
- 'client_type': client_type,
- 'tender_type': tender_type,
- 'payment_terms': payment_terms,
- 'completion_deadline': completion_deadline,
- 'penalty_clause': penalty_clause,
- 'technical_complexity': technical_complexity,
- 'company_experience': company_experience,
- 'market_volatility': market_volatility
- }
-
- # استدعاء النموذج لتحليل المخاطر
- risk_analysis_results = self._analyze_risks(features)
-
- # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار
- if use_claude:
- try:
- # إنشاء نص الميزات للتحليل
- features_text = f"""
- بيانات المشروع:
- - نوع المشروع: {project_type}
- - الموقع: {location}
- - نوع العميل: {client_type}
- - نوع المناقصة: {tender_type}
-
- عوامل المخاطرة:
- - شروط الدفع: {payment_terms}/10
- - مهلة الإنجاز: {completion_deadline}/10
- - شروط الغرامات: {penalty_clause}/10
- - التعقيد الفني: {technical_complexity}/10
- - خبرة الشركة: {company_experience}/10
- - تقلبات السوق: {market_volatility}/10
-
- ملخص التحليل الأولي:
- - متوسط درجة المخاطرة: {risk_analysis_results['avg_risk_score']:.1f}/10
- - عدد المخاطر العالية: {risk_analysis_results['high_risks']}
- - عدد المخاطر المتوسطة: {risk_analysis_results['medium_risks']}
- - عدد المخاطر المنخفضة: {risk_analysis_results['low_risks']}
-
- أعلى المخاطر:
- """
-
- # إضافة تفاصيل أعلى المخاطر
- for i, risk in enumerate(risk_analysis_results['top_risks'][:3]):
- features_text += f"""
- {i+1}. {risk['name']} ({risk['category']})
- - الاحتمالية: {risk['probability'] * 100:.0f}%
- - التأثير: {risk['impact'] * 100:.0f}%
- - درجة المخاطرة: {risk['risk_score']}/10
- """
-
- prompt = f"""تحليل مخاطر مشروع:
-
- {features_text}
-
- المطلوب:
- 1. تحليل عوامل المخاطرة وتأثيرها على المشروع
- 2. تقديم توصيات إضافية لإدارة المخاطر
- 3. اقتراح استراتيجيات استجابة للمخاطر الرئيسية
- 4. تقديم نصائح لتحسين شروط العقد لتقليل المخاطر
- 5. تقييم مدى ملاءمة المشروع لاستراتيجية الشركة
-
- يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية.
- """
-
- # استدعاء Claude للتحليل
- claude_analysis = self.claude_service.chat_completion(
- [{"role": "user", "content": prompt}]
- )
-
- if "error" not in claude_analysis:
- # إضافة تحليل Claude إلى النتائج
- risk_analysis_results["claude_analysis"] = claude_analysis["content"]
- except Exception as e:
- st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
-
- # عرض نتائج تحليل المخاطر
- self._display_risk_analysis_results(risk_analysis_results)
-
- def _analyze_risks(self, features):
- """تحليل مخاطر المشروع"""
-
- # في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المخاطر
- # محاكاة نتائج تحليل المخاطر للعرض
-
- # تعريف قائمة من المخاطر المحتملة
- potential_risks = [
- {
- "id": "R-001",
- "name": "غرامة تأخير مرتفعة",
- "category": "مخاطر مالية",
- "description": "غرامة تأخير مرتفعة تصل إلى 10% من قيمة العقد، مما قد يؤثر سلباً على ربحية المشروع في حال التأخير.",
- "probability": 0.6,
- "impact": 0.8,
- "risk_score": 7.8,
- "response_strategy": "تخطيط مفصل للمشروع مع وضع مخزون زمني مناسب وتحديد نقاط التسليم المبكر."
- },
- {
- "id": "R-002",
- "name": "تقلبات أسعار المواد",
- "category": "مخاطر السوق",
- "description": "ارتفاع محتمل في أسعار المواد الخام خلال فترة تنفيذ المشروع، مما يؤثر على التكلفة الإجمالية.",
- "probability": 0.7,
- "impact": 0.7,
- "risk_score": 7.5,
- "response_strategy": "التعاقد المبكر مع الموردين وتثبيت الأسعار، أو إضافة بند تعديل سعري في العقد."
- },
- {
- "id": "R-003",
- "name": "ضعف تدفق المدفوعات",
- "category": "مخاطر مالية",
- "description": "تأخر العميل في سداد المستخلصات مما يؤثر على التدفق النقدي للمشروع.",
- "probability": 0.5,
- "impact": 0.8,
- "risk_score": 7.2,
- "response_strategy": "التفاوض على شروط دفع واضحة ومواعيد محددة، وإمكانية طلب دفعة مقدمة."
- },
- {
- "id": "R-004",
- "name": "نقص العمالة الماهرة",
- "category": "مخاطر الموارد",
- "description": "صعوبة توفير عمالة ماهرة لتنفيذ أجزاء محددة من المشروع.",
- "probability": 0.5,
- "impact": 0.6,
- "risk_score": 6.5,
- "response_strategy": "التخطيط المبكر للموارد البشرية وتوقيع عقود مع مقاولي الباطن المتخصصين."
- },
- {
- "id": "R-005",
- "name": "تغييرات في نطاق العمل",
- "category": "مخاطر تعاقدية",
- "description": "طلبات تغيير من العميل تؤدي إلى زيادة نطاق العمل دون تعديل مناسب للتكلفة والجدول الزمني.",
- "probability": 0.6,
- "impact": 0.6,
- "risk_score": 6.0,
- "response_strategy": "تضمين آلية واضحة لإدارة التغيير في العقد وتقييم تأثير أي تغييرات على التكلفة والزمن."
- },
- {
- "id": "R-006",
- "name": "مشاكل في الموقع",
- "category": "مخاطر فنية",
- "description": "ظروف موقع غير متوقعة تؤثر على تنفيذ الأعمال، مثل مشاكل في التربة أو مرافق تحت الأرض.",
- "probability": 0.4,
- "impact": 0.7,
- "risk_score": 5.8,
- "response_strategy": "إجراء دراسات واختبارات مفصلة للموقع قبل بدء التنفيذ، وتخصيص احتياطي للطوارئ."
- },
- {
- "id": "R-007",
- "name": "تضارب في التصاميم",
- "category": "مخاطر فنية",
- "description": "تعارض بين مختلف تخصصات التصميم (معماري، إنشائي، كهروميكانيكي) يؤدي إلى تأخير وإعادة عمل.",
- "probability": 0.4,
- "impact": 0.6,
- "risk_score": 5.4,
- "response_strategy": "مراجعة شاملة للتصاميم قبل البدء في التنفيذ واستخدام نمذجة معلومات البناء (BIM) لكشف التعارضات."
- },
- {
- "id": "R-008",
- "name": "تأخر الموافقات",
- "category": "مخاطر تنظيمية",
- "description": "تأخر في الحصول على الموافقات والتصاريح اللازمة من الجهات المختصة.",
- "probability": 0.5,
- "impact": 0.5,
- "risk_score": 5.0,
- "response_strategy": "التخطيط المبكر للتصاريح المطلوبة وبناء علاقات جيدة مع الجهات التنظيمية."
- },
- {
- "id": "R-009",
- "name": "عدم توفر المعدات",
- "category": "مخاطر الموارد",
- "description": "صعوبة في توفير المعدات المتخصصة في الوقت المطلوب.",
- "probability": 0.3,
- "impact": 0.6,
- "risk_score": 4.8,
- "response_strategy": "حجز المعدات مبكراً وتوفير بدائل محتملة في حالة عدم توفر المعدات الأساسية."
- },
- {
- "id": "R-010",
- "name": "ظروف جوية قاسية",
- "category": "مخاطر خارجية",
- "description": "تأثير الظروف الجوية القاسية (حرارة شديدة، أمطار غزيرة، عواصف رملية) على سير العمل.",
- "probability": 0.3,
- "impact": 0.5,
- "risk_score": 4.5,
- "response_strategy": "تخطيط الجدول الزمني مع مراعاة المواسم وإضافة مخزون زمني للظروف الجوية غير المتوقعة."
- }
- ]
-
- # حساب درجات المخاطرة بناءً على الميزات المدخلة
- for risk in potential_risks:
- # تعديل احتمالية حدوث المخاطر بناءً على العوامل المدخلة
- if risk["id"] == "R-001": # غرامة تأخير
- risk["probability"] = risk["probability"] * (10 - features["penalty_clause"]) / 10
- risk["probability"] = risk["probability"] * (10 - features["completion_deadline"]) / 10
-
- elif risk["id"] == "R-002": # تقلبات أسعار المواد
- risk["probability"] = risk["probability"] * features["market_volatility"] / 10
-
- elif risk["id"] == "R-003": # ضعف تدفق المدفوعات
- risk["probability"] = risk["probability"] * (10 - features["payment_terms"]) / 10
-
- if features["client_type"] == "حكومي":
- risk["probability"] = risk["probability"] * 0.6 # احتمالية أقل مع العملاء الحكوميين
- elif features["client_type"] == "أفراد":
- risk["probability"] = risk["probability"] * 1.3 # احتمالية أعلى مع العملاء الأفراد
-
- elif risk["id"] == "R-004": # نقص العمالة الماهرة
- risk["probability"] = risk["probability"] * features["technical_complexity"] / 10
-
- elif risk["id"] == "R-005": # تغييرات في نطاق العمل
- risk["probability"] = risk["probability"] * features["technical_complexity"] / 10
-
- if features["client_type"] == "حكومي":
- risk["probability"] = risk["probability"] * 1.2 # احتمالية أعلى للتغييرات مع العملاء الحكوميين
-
- # تعديل تأثير المخاطر بناءً على العوامل المدخلة
- if risk["category"] == "مخاطر فنية":
- risk["impact"] = risk["impact"] * (10 - features["company_experience"]) / 10
-
- # إعادة حساب درجة المخاطرة
- risk["risk_score"] = round(risk["probability"] * risk["impact"] * 10, 1)
-
- # ترتيب المخاطر تنازلياً حسب درجة المخاطرة
- sorted_risks = sorted(potential_risks, key=lambda x: x["risk_score"], reverse=True)
-
- # حساب عدد المخاطر حسب شدتها
- high_risks = sum(1 for risk in sorted_risks if risk["risk_score"] >= 6.0)
- medium_risks = sum(1 for risk in sorted_risks if 3.0 <= risk["risk_score"] < 6.0)
- low_risks = sum(1 for risk in sorted_risks if risk["risk_score"] < 3.0)
-
- # حساب متوسط درجة المخاطرة
- avg_risk_score = sum(risk["risk_score"] for risk in sorted_risks) / len(sorted_risks)
-
- # تجهيز النتائج
- results = {
- "top_risks": sorted_risks,
- "high_risks": high_risks,
- "medium_risks": medium_risks,
- "low_risks": low_risks,
- "avg_risk_score": avg_risk_score,
- "risk_profile": {
- "financial_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر مالية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر مالية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر مالية") > 0 else 0,
- "technical_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر فنية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر فنية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر فنية") > 0 else 0,
- "market_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر السوق") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر السوق") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر السوق") > 0 else 0,
- "resource_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر الموارد") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر الموارد") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر الموارد") > 0 else 0,
- "contract_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") > 0 else 0,
- "regulatory_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") > 0 else 0
- },
- "overall_assessment": "",
- "recommendation": ""
- }
-
- # تقييم شامل للمخاطر
- if avg_risk_score >= 6.0:
- results["overall_assessment"] = "مشروع عالي المخاطر"
- results["recommendation"] = "ينصح بإعادة التفاوض على شروط العقد أو إضافة هامش ربح أعلى لتغطية المخاطر."
- elif avg_risk_score >= 4.0:
- results["overall_assessment"] = "مشروع متوسط المخاطر"
- results["recommendation"] = "متابعة دقيقة للمخاطر العالية ووضع خطط استجابة مفصلة لها."
- else:
- results["overall_assessment"] = "مشروع منخفض المخاطر"
- results["recommendation"] = "مراقبة المخاطر بشكل دوري والتركيز على تحسين الأداء."
-
- return results
-
- def _display_risk_analysis_results(self, results):
- """عرض نتائج تحليل المخاطر"""
-
- st.markdown("### نتائج تحليل المخاطر")
-
- # عرض ملخص تقييم المخاطر
- st.markdown(f"#### التقييم العام: {results['overall_assessment']}")
-
- # عرض الإحصائيات الرئيسية للمخاطر
- col1, col2, col3, col4 = st.columns(4)
-
- with col1:
- st.metric("متوسط درجة المخاطرة", f"{results['avg_risk_score']:.1f}/10")
-
- with col2:
- st.metric("المخاطر العالية", f"{results['high_risks']}")
-
- with col3:
- st.metric("المخاطر المتوسطة", f"{results['medium_risks']}")
-
- with col4:
- st.metric("المخاطر المنخفضة", f"{results['low_risks']}")
-
- # عرض ملف المخاطر حسب الفئة
- st.markdown("#### ملف المخاطر حسب الفئة")
-
- # تجهيز البيانات للرسم البياني
- risk_profile_data = pd.DataFrame({
- 'الفئة': [
- "مخاطر مالية",
- "مخاطر فنية",
- "مخاطر السوق",
- "مخاطر الموارد",
- "مخاطر تعاقدية",
- "مخاطر تنظيم��ة"
- ],
- 'درجة المخاطرة': [
- results['risk_profile']['financial_risk'],
- results['risk_profile']['technical_risk'],
- results['risk_profile']['market_risk'],
- results['risk_profile']['resource_risk'],
- results['risk_profile']['contract_risk'],
- results['risk_profile']['regulatory_risk']
- ]
- })
-
- # رسم مخطط شعاعي لملف المخاطر
- fig = px.line_polar(
- risk_profile_data,
- r='درجة المخاطرة',
- theta='الفئة',
- line_close=True,
- range_r=[0, 10],
- title="ملف المخاطر حسب الفئة"
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض المخاطر الرئيسية
- st.markdown("#### المخاطر الرئيسية")
-
- # إنشاء جدول المخاطر
- risk_table_data = []
-
- for risk in results['top_risks'][:5]: # عرض أعلى 5 مخاطر فقط
- risk_level = "عالية" if risk["risk_score"] >= 6.0 else "متوسطة" if risk["risk_score"] >= 3.0 else "منخفضة"
- risk_color = "red" if risk_level == "عالية" else "orange" if risk_level == "متوسطة" else "green"
-
- risk_table_data.append({
- "المعرف": risk["id"],
- "الوصف": risk["name"],
- "الفئة": risk["category"],
- "الاحتمالية": f"{risk['probability'] * 100:.0f}%",
- "التأثير": f"{risk['impact'] * 100:.0f}%",
- "درجة المخاطرة": risk["risk_score"],
- "المستوى": risk_level,
- "استراتيجية الاستجابة": risk["response_strategy"],
- "color": risk_color
- })
-
- # عرض جدول المخاطر
- risk_df = pd.DataFrame(risk_table_data)
-
- # استخدام تنسيق HTML مخصص لعرض المخاطر الرئيسية
- for index, row in risk_df.iterrows():
- with st.container():
- st.markdown(f"""
-
-
{row['المعرف']} - {row['الوصف']} {row['المستوى']}
-
الفئة: {row['الفئة']} | الاحتمالية: {row['الاحتمالية']} | التأثير: {row['التأثير']} | درجة المخاطرة: {row['درجة المخاطرة']}/10
-
استراتيجية الاستجابة: {row['استراتيجية الاستجابة']}
-
- """, unsafe_allow_html=True)
-
- # عرض توصيات عامة
- st.markdown("#### التوصيات العامة")
- st.info(results["recommendation"])
-
- # عرض تحليل Claude AI إذا كان متوفراً
- if "claude_analysis" in results:
- st.markdown("### تحليل Claude AI المتقدم")
- st.success(results["claude_analysis"])
-
- # زر تصدير تقرير المخاطر
- if st.button("تصدير تقرير المخاطر"):
- st.success("تم تصدير تقرير المخاطر بنجاح!")
-
- def _render_document_analysis_tab(self):
- """عرض تبويب تحليل المستندات"""
-
- st.markdown("### تحليل المستندات")
-
- # خيارات رفع الملفات
- st.markdown("#### رفع ملفات المناقصة")
-
- # رفع الملفات
- uploaded_files = st.file_uploader(
- "اختر ملفات المناقصة للتحليل",
- type=["pdf", "docx", "doc", "xls", "xlsx", "jpg", "jpeg", "png"],
- accept_multiple_files=True,
- key="document_analysis_files"
- )
-
- # اختيار نموذج التحليل
- analysis_model = st.radio(
- "اختر نموذج التحليل",
- [
- "استخراج البنود والمواصفات",
- "استخراج الشروط التعاقدية",
- "تحليل الكميات",
- "تحليل المتطلبات القانونية",
- "تحليل شامل (يستخدم Claude AI)"
- ],
- horizontal=True
- )
-
- # زر بدء التحليل
- if uploaded_files and st.button("بدء تحليل المستندات"):
- with st.spinner("جاري تحليل ��لمستندات..."):
- # محاكاة وقت المعالجة
- time.sleep(3)
-
- # معالجة الملفات المرفوعة
- analysis_results = self._analyze_documents(uploaded_files, analysis_model)
-
- # عرض نتائج التحليل
- self._display_document_analysis_results(analysis_results)
-
- def _analyze_documents(self, files, analysis_model):
- """تحليل المستندات المرفوعة"""
-
- # في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المستندات
- # محاكاة نتائج تحليل المستندات للعرض
-
- # نتائج التحليل المبدئية
- basic_results = {
- "file_count": len(files),
- "file_names": [file.name for file in files],
- "file_sizes": [f"{file.size / 1024:.1f} KB" for file in files],
- "file_types": [file.type or "غير محدد" for file in files],
- "extracted_text_samples": {},
- "entities": [],
- "tender_items": [],
- "contract_terms": [],
- "quantities": [],
- "legal_requirements": [],
- "summary": ""
- }
-
- # محاكاة استخراج نص من الملفات
- for file in files:
- # استخراج عينة نصية (في البيئة الحقيقية سيتم استخراج النص الكامل)
- sample_text = f"عينة نصية مستخرجة من الملف {file.name}. هذا النص لأغراض العرض فقط."
- basic_results["extracted_text_samples"][file.name] = sample_text
-
- # محاكاة تحليل المحتوى حسب نموذج التحليل المختار
- if analysis_model == "استخراج البنود والمواصفات" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
- basic_results["tender_items"] = [
- {
- "id": "T-001",
- "description": "أعمال الحفر والردم",
- "unit": "م³",
- "quantity": 1500,
- "estimated_price": 85,
- "specifications": "حفر في أي نوع من التربة بما في ذلك الصخور والردم باستخدام مواد معتمدة."
- },
- {
- "id": "T-002",
- "description": "أعمال الخرسانة المسلحة للأساسات",
- "unit": "م³",
- "quantity": 750,
- "estimated_price": 1200,
- "specifications": "خرسانة مسلحة بقوة 30 نيوتن/مم² بعد 28 يوم، مع حديد تسليح من الفئة 60."
- },
- {
- "id": "T-003",
- "description": "أعمال الخرسانة المسلحة للهيكل",
- "unit": "م³",
- "quantity": 1200,
- "estimated_price": 1350,
- "specifications": "خرسانة مسلحة بقوة 30 نيوتن/مم² بعد 28 يوم، مع حديد تسليح من الفئة 60."
- },
- {
- "id": "T-004",
- "description": "أعمال الطابوق",
- "unit": "م²",
- "quantity": 3500,
- "estimated_price": 120,
- "specifications": "جدران طابوق مفرغ سمك 20 سم مع مونة إسمنتية."
- },
- {
- "id": "T-005",
- "description": "أعمال التشطيبات الداخلية",
- "unit": "م²",
- "quantity": 5000,
- "estimated_price": 200,
- "specifications": "تشطيبات داخلية تشمل اللياسة والدهان والأرضيات حسب المواصفات المرفقة."
- }
- ]
-
- if analysis_model == "استخراج الشروط التعاقدية" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
- basic_results["contract_terms"] = [
- {
- "id": "C-001",
- "title": "مدة تنفيذ المشروع",
- "description": "يجب إنجاز جميع الأعمال خلال 18 شهراً من تاريخ تسليم الموقع.",
- "risk_level": "متوسط",
- "notes": "مدة تنفيذ معقولة نسبياً للحجم المتوقع من الأعمال."
- },
- {
- "id": "C-002",
- "title": "غرامة التأخير",
- "description": "تفرض غرا��ة تأخير بنسبة 0.1% من قيمة العقد عن كل يوم تأخير، بحد أقصى 10% من القيمة الإجمالية للعقد.",
- "risk_level": "عالي",
- "notes": "غرامة مرتفعة نسبياً، تتطلب جدولة دقيقة وإدارة استباقية للمخاطر."
- },
- {
- "id": "C-003",
- "title": "شروط الدفع",
- "description": "يتم صرف المستخلصات خلال 45 يوماً من تاريخ تقديمها، مع خصم نسبة 10% كضمان حسن التنفيذ تسترد بعد فترة الضمان.",
- "risk_level": "متوسط",
- "notes": "فترة 45 يوماً طويلة نسبياً وقد تؤثر على التدفق النقدي."
- },
- {
- "id": "C-004",
- "title": "التزامات المحتوى المحلي",
- "description": "يجب أن لا تقل نسبة المحتوى المحلي عن 30% من إجمالي قيمة العقد.",
- "risk_level": "منخفض",
- "notes": "يمكن تحقيق النسبة المطلوبة من خلال توريد المواد والعمالة المحلية."
- },
- {
- "id": "C-005",
- "title": "التغييرات والأعمال الإضافية",
- "description": "يحق للمالك طلب تغييرات بنسبة ±10% من قيمة العقد دون تعديل أسعار الوحدات.",
- "risk_level": "متوسط",
- "notes": "نسبة معقولة، لكن يجب مراعاة احتمالية الطلبات الإضافية عند تسعير البنود."
- }
- ]
-
- if analysis_model == "تحليل الكميات" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
- basic_results["quantities"] = [
- {
- "category": "أعمال الحفر والردم",
- "volume": 1500,
- "unit": "م³",
- "estimated_cost": 127500
- },
- {
- "category": "أعمال الخرسانة",
- "volume": 1950,
- "unit": "م³",
- "estimated_cost": 2437500
- },
- {
- "category": "أعمال الطابوق",
- "volume": 3500,
- "unit": "م²",
- "estimated_cost": 420000
- },
- {
- "category": "أعمال التشطيبات الداخلية",
- "volume": 5000,
- "unit": "م²",
- "estimated_cost": 1000000
- },
- {
- "category": "أعمال التشطيبات الخارجية",
- "volume": 2200,
- "unit": "م²",
- "estimated_cost": 660000
- },
- {
- "category": "أعمال الكهروميكانيكية",
- "volume": 1,
- "unit": "مقطوعية",
- "estimated_cost": 1750000
- }
- ]
-
- if analysis_model == "تحليل المتطلبات القانونية" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
- basic_results["legal_requirements"] = [
- {
- "id": "L-001",
- "title": "متطلبات التراخيص",
- "description": "يجب أن يكون المقاول حاصلاً على تصنيف في الفئة الأولى في مجال المباني.",
- "compliance_status": "مطلوب التحقق",
- "required_documents": "شهادة التصنيف سارية المفعول"
- },
- {
- "id": "L-002",
- "title": "متطلبات التأمين",
- "description": "يجب تقديم بوليصة تأمين شاملة تغطي جميع مخاطر المشروع بقيمة لا تقل عن 100% من قيمة العقد.",
- "compliance_status": "مطلوب التحقق",
- "required_documents": "وثائق التأمين الشاملة"
- },
- {
- "id": "L-003",
- "title": "متطلبات الضمان البنكي",
- "description": "يجب تقديم ضمان بنكي ابتدائي بنسبة 2% من قيمة العطاء، وضمان نهائي بنسبة 5% من قيمة العقد.",
- "compliance_status": "مطلوب ��لتحقق",
- "required_documents": "نماذج الضمانات البنكية"
- },
- {
- "id": "L-004",
- "title": "متطلبات السعودة",
- "description": "يجب الالتزام بنسبة السعودة المطلوبة حسب برنامج نطاقات وأن يكون المقاول في النطاق الأخضر.",
- "compliance_status": "مطلوب التحقق",
- "required_documents": "شهادة نطاقات سارية المفعول"
- },
- {
- "id": "L-005",
- "title": "متطلبات الزكاة والدخل",
- "description": "يجب تقديم شهادة سداد الزكاة والضريبة سارية المفعول.",
- "compliance_status": "مطلوب التحقق",
- "required_documents": "شهادة الزكاة والدخل"
- }
- ]
-
- # إعداد ملخص التحليل
- basic_results["summary"] = f"""
- تم تحليل {len(files)} ملفات بإجمالي حجم {sum([file.size for file in files]) / 1024 / 1024:.2f} ميجابايت.
-
- نتائج التحليل الرئيسية:
- - تم استخراج {len(basic_results.get('tender_items', []))} بنود رئيسية للمناقصة.
- - تم تحديد {len(basic_results.get('contract_terms', []))} شروط تعاقدية هامة.
- - تم تحليل الكميات لـ {len(basic_results.get('quantities', []))} فئات من الأعمال.
- - تم تحديد {len(basic_results.get('legal_requirements', []))} متطلبات قانونية.
-
- التوصيات:
- - مراجعة شروط التعاقد وخاصة البنود المتعلقة بالغرامات والدفعات.
- - تدقيق جداول الكميات والتأكد من تغطية جميع البنود اللازمة للتنفيذ.
- - التحقق من استيفاء جميع المتطلبات القانونية قبل تقديم العطاء.
- """
-
- # إضافة تحليل متقدم باستخدام Claude AI إذا تم اختياره
- if analysis_model == "تحليل شامل (يستخدم Claude AI)":
- try:
- # إنشاء مدخلات للتحليل
- analysis_input = f"""
- المناقصة: تطوير مبنى إداري متعدد الطوابق
-
- ملفات تم تحليلها:
- {', '.join(basic_results['file_names'])}
-
- بنود رئيسية:
- - أعمال الحفر والردم: 1500 م³
- - أعمال الخرسانة المسلحة للأساسات: 750 م³
- - أعمال الخرسانة المسلحة للهيكل: 1200 م³
- - أعمال الطابوق: 3500 م²
- - أعمال التشطيبات الداخلية: 5000 م²
-
- شروط تعاقدية رئيسية:
- - مدة التنفيذ: 18 شهر
- - غرامة التأخير: 0.1% يومياً بحد أقصى 10%
- - شروط الدفع: 45 يوم للمستخلصات مع خصم 10% ضمان
- - المحتوى المحلي: 30% كحد أدنى
-
- متطلبات قانونية:
- - تصنيف الفئة الأولى مباني
- - تأمين شامل بنسبة 100%
- - ضمان بنكي ابتدائي 2% ونهائي 5%
- - الالتزام بمتطلبات السعودة (النطاق الأخضر)
-
- من فضلك قم بتحليل هذه المناقصة وتقديم:
- 1. تقييم عام للمناقصة وجاذبيتها
- 2. نقاط القوة والضعف الرئيسية
- 3. المخاطر المحتملة التي يجب مراعاتها
- 4. توصيات للتسعير المناسب
- 5. استراتيجية مقترحة للتنافس على المناقصة
- """
-
- # استدعاء خدمة Claude للتحليل
- claude_response = self.claude_service.chat_completion(
- [{"role": "user", "content": analysis_input}]
- )
-
- if "error" not in claude_response:
- # إضافة تحليل Claude إلى النتائج
- basic_results["claude_analysis"] = claude_response["content"]
- except Exception as e:
- logging.error(f"فشل في تحليل المستندات باستخدام Claude AI: {str(e)}")
-
- return basic_results
-
- def _display_document_analysis_results(self, results):
- """عرض نتائج تحليل المستندات"""
-
- st.markdown("### نتائج تحليل المستندات")
-
- # عرض ملخص التحليل
- st.markdown("#### ملخص التحليل")
- st.info(results["summary"])
-
- # عرض البنود المستخرجة من المناقصة إذا وجدت
- if results["tender_items"]:
- st.markdown("#### بنود المناقصة المستخرجة")
-
- # إنشاء DataFrame للبنود
- items_df = pd.DataFrame(results["tender_items"])
-
- # عرض الجدول بشكل منسق
- st.dataframe(
- items_df[["id", "description", "unit", "quantity", "estimated_price"]],
- use_container_width=True
- )
-
- # عرض مخطط للتكاليف المقدرة
- costs = [item["quantity"] * item["estimated_price"] for item in results["tender_items"]]
- labels = [item["description"] for item in results["tender_items"]]
-
- fig = px.pie(
- names=labels,
- values=costs,
- title="توزيع التكاليف المقدرة حسب البنود"
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض الشروط التعاقدية إذا وجدت
- if results["contract_terms"]:
- st.markdown("#### الشروط التعاقدية الهامة")
-
- # عرض كل شرط في قسم منفصل
- for term in results["contract_terms"]:
- risk_color = "red" if term["risk_level"] == "عالي" else "orange" if term["risk_level"] == "متوسط" else "green"
-
- st.markdown(f"""
-
-
{term['id']} - {term['title']} مستوى الخطورة: {term['risk_level']}
-
{term['description']}
-
ملاحظات: {term['notes']}
-
- """, unsafe_allow_html=True)
-
- # عرض تحليل الكميات إذا وجد
- if results["quantities"]:
- st.markdown("#### تحليل الكميات")
-
- # إنشاء DataFrame للكميات
- quantities_df = pd.DataFrame(results["quantities"])
-
- # عرض الجدول بشكل منسق
- st.dataframe(quantities_df, use_container_width=True)
-
- # عرض مخطط شريطي للتكاليف المقدرة
- fig = px.bar(
- quantities_df,
- x="category",
- y="estimated_cost",
- title="التكاليف المقدرة حسب فئة الأعمال",
- labels={"category": "فئة الأعمال", "estimated_cost": "التكلفة المقدرة (ريال)"}
- )
-
- fig.update_traces(text=quantities_df["estimated_cost"], textposition="outside")
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض المتطلبات القانونية إذا وجدت
- if results["legal_requirements"]:
- st.markdown("#### المتطلبات القانونية")
-
- # عرض المتطلبات في جدول
- legal_df = pd.DataFrame(results["legal_requirements"])
-
- # عرض الجدول بشكل منسق
- st.dataframe(
- legal_df[["id", "title", "description", "compliance_status", "required_documents"]],
- use_container_width=True
- )
-
- # عرض قائمة تحقق للمتطلبات القانونية
- st.markdown("##### قائمة التحقق من المتطلبات القانونية")
-
- for req in results["legal_requirements"]:
- st.checkbox(f"{req['title']} - {req['description']}", key=f"req_{req['id']}")
-
- # عرض تحليل Claude AI المتقدم إذا وجد
- if "claude_analysis" in results:
- st.markdown("### تحليل Claude AI المتقدم")
- st.success(results["claude_analysis"])
-
- # أزرار إضافية
- col1, col2 = st.columns(2)
-
- with col1:
- if st.button("تصدير تقرير تحليل المستندات"):
- st.success("تم تصدير تقرير تحليل المستندات بنجاح!")
-
- with col2:
- if st.button("استخراج جدول الكميات"):
- st.success("تم استخراج جدول الكميات بنجاح!")
-
- def _render_local_content_tab(self):
- """عرض تبويب المحتوى المحلي"""
-
- st.markdown("### المحتوى المحلي")
-
- st.markdown("""
- وحدة حساب المحتوى المحلي تساعدك في تحليل وتحسين نسبة المحتوى المحلي في مشروعك طبقاً لمتطلبات هيئة المحتوى المحلي والمشتريات الحكومية.
- """)
-
- # عرض علامات تبويب فرعية
- lc_tabs = st.tabs([
- "حساب المحتوى المحلي",
- "قاعدة بيانات الموردين",
- "التقارير",
- "التحسين"
- ])
-
- with lc_tabs[0]:
- self._render_lc_calculator_tab()
-
- with lc_tabs[1]:
- self._render_lc_suppliers_tab()
-
- with lc_tabs[2]:
- self._render_lc_reports_tab()
-
- with lc_tabs[3]:
- self._render_lc_optimization_tab()
-
- def _render_lc_calculator_tab(self):
- """عرض تبويب حساب المحتوى المحلي"""
-
- st.markdown("#### حساب المحتوى المحلي")
-
- # نموذج إدخال بيانات المشروع
- st.markdown("##### بيانات المشروع")
-
- col1, col2 = st.columns(2)
-
- with col1:
- project_name = st.text_input("اسم المشروع", "مبنى إداري الرياض")
- project_value = st.number_input("القيمة الإجمالية للمشروع (ريال)", min_value=1000, value=10000000)
-
- with col2:
- target_lc = st.slider("نسبة المحتوى المحلي المستهدفة (%)", 0, 100, 40)
- calculation_method = st.selectbox(
- "طريقة الحساب",
- [
- "الطريقة القياسية (المدخلات)",
- "طريقة القيمة المضافة",
- "الطريقة المختلطة"
- ]
- )
-
- # جدول مكونات المشروع
- st.markdown("##### مكونات المشروع")
-
- # إعداد بيانات المكونات الافتراضية
- if 'lc_components' not in st.session_state:
- st.session_state.lc_components = [
- {
- "id": 1,
- "name": "الخرسانة المسلحة",
- "category": "مواد",
- "value": 3000000,
- "local_content": 85,
- "supplier": "شركة الإنشاءات السعودية"
- },
- {
- "id": 2,
- "name": "الأعمال الكهربائية",
- "category": "أنظمة",
- "value": 1500000,
- "local_content": 65,
- "supplier": "مؤسسة الطاقة المتقدمة"
- },
- {
- "id": 3,
- "name": "أعمال التكييف",
- "category": "أنظمة",
- "value": 1200000,
- "local_content": 55,
- "supplier": "شركة التبريد العالمية"
- },
- {
- "id": 4,
- "name": "الواجهات والنوافذ",
- "category": "مواد",
- "value": 800000,
- "local_content": 45,
- "supplier": "شركة الزجاج المتطورة"
- },
- {
- "id": 5,
- "name": "أعمال التشطيبات",
- "category": "مواد وعمالة",
- "value": 1200000,
- "local_content": 80,
- "supplier": "مؤسسة التشطيبات الحديثة"
- },
- {
- "id": 6,
- "name": "الأثاث والتجهيزات",
- "category": "أثاث",
- "value": 900000,
- "local_content": 30,
- "supplier": "شركة الأثاث المكتبي"
- },
- {
- "id": 7,
- "name": "أنظمة الأمن والمراقبة",
- "category": "أنظمة",
- "value": 600000,
- "local_content": 40,
- "supplier": "شركة الأنظمة الأمنية المتقدمة"
- },
- {
- "id": 8,
- "name": "العمالة المباش��ة",
- "category": "عمالة",
- "value": 800000,
- "local_content": 50,
- "supplier": "داخلي"
- }
- ]
-
- # عرض جدول المكونات للتعديل
- for i, component in enumerate(st.session_state.lc_components):
- col1, col2, col3, col4, col5, col6 = st.columns([2, 1, 1, 1, 2, 1])
-
- with col1:
- st.session_state.lc_components[i]["name"] = st.text_input(
- "المكون",
- component["name"],
- key=f"comp_name_{i}"
- )
-
- with col2:
- st.session_state.lc_components[i]["category"] = st.selectbox(
- "الفئة",
- ["مواد", "أنظمة", "عمالة", "مواد وعمالة", "أثاث", "خدمات"],
- index=["مواد", "أنظمة", "عمالة", "مواد وعمالة", "أثاث", "خدمات"].index(component["category"]),
- key=f"comp_category_{i}"
- )
-
- with col3:
- st.session_state.lc_components[i]["value"] = st.number_input(
- "القيمة (ريال)",
- min_value=0,
- value=int(component["value"]),
- key=f"comp_value_{i}"
- )
-
- with col4:
- st.session_state.lc_components[i]["local_content"] = st.slider(
- "المحتوى المحلي (%)",
- 0, 100, int(component["local_content"]),
- key=f"comp_lc_{i}"
- )
-
- with col5:
- st.session_state.lc_components[i]["supplier"] = st.text_input(
- "المورد",
- component["supplier"],
- key=f"comp_supplier_{i}"
- )
-
- with col6:
- if st.button("حذف", key=f"delete_comp_{i}"):
- st.session_state.lc_components.pop(i)
- st.rerun()
-
- # زر إضافة مكون جديد
- if st.button("إضافة مكون جديد"):
- new_id = max([c["id"] for c in st.session_state.lc_components]) + 1 if st.session_state.lc_components else 1
- st.session_state.lc_components.append({
- "id": new_id,
- "name": f"مكون جديد {new_id}",
- "category": "مواد",
- "value": 100000,
- "local_content": 50,
- "supplier": "غير محدد"
- })
- st.rerun()
-
- # زر حساب المحتوى المحلي
- col1, col2 = st.columns([1, 3])
-
- with col1:
- calculate_button = st.button("حساب المحتوى المحلي", use_container_width=True)
-
- with col2:
- use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="lc_use_claude")
-
- if calculate_button:
- with st.spinner("جاري حساب وتحليل المحتوى المحلي..."):
- # محاكاة وقت المعالجة
- time.sleep(2)
-
- # حساب المحتوى المحلي
- lc_results = self._calculate_local_content(st.session_state.lc_components, target_lc, calculation_method)
-
- # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار
- if use_claude:
- try:
- # إنشاء نص المكونات للتحليل
- components_text = ""
- for comp in st.session_state.lc_components:
- components_text += f"""
- - {comp['name']} ({comp['category']}):
- القيمة: {comp['value']:,} ريال | المحتوى المحلي: {comp['local_content']}% | المورد: {comp['supplier']}
- """
-
- prompt = f"""تحليل وتحسين المحتوى المحلي:
-
- بيانات المشروع:
- - اسم المشروع: {project_name}
- - القيمة الإجمالية: {project_value:,} ريال
- - نسبة المحتوى المحلي المستهدفة: {target_lc}%
- - النسبة المحسوبة: {lc_results['total_local_content']:.1f}%
-
- مكونات المشروع:
- {components_text}
-
- المطلوب:
- 1. تحليل نسبة المح��وى المحلي المحسوبة ومقارنتها بالمستهدف
- 2. تحديد المكونات ذات المحتوى المحلي المنخفض التي يمكن تحسينها
- 3. اقتراح بدائل محلية أو استراتيجيات لزيادة المحتوى المحلي
- 4. تقديم توصيات عملية لتحقيق النسبة المستهدفة
- 5. تحديد أي فرص إضافية لتحسين المحتوى المحلي في المشروع
-
- يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية.
- """
-
- # استدعاء Claude للتحليل
- claude_analysis = self.claude_service.chat_completion(
- [{"role": "user", "content": prompt}]
- )
-
- if "error" not in claude_analysis:
- # إضافة تحليل Claude إلى النتائج
- lc_results["claude_analysis"] = claude_analysis["content"]
- except Exception as e:
- st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
-
- # عرض نتائج حساب المحتوى المحلي
- self._display_local_content_results(lc_results, target_lc)
-
- def _calculate_local_content(self, components, target_lc, calculation_method):
- """حساب المحتوى المحلي"""
-
- # حساب إجمالي قيمة المشروع
- total_value = sum([comp["value"] for comp in components])
-
- # حساب المحتوى المحلي الإجمالي
- total_local_content_value = sum([comp["value"] * comp["local_content"] / 100 for comp in components])
-
- # حساب نسبة المحتوى المحلي الإجمالية
- total_local_content_percent = (total_local_content_value / total_value) * 100 if total_value > 0 else 0
-
- # تحليل المحتوى المحلي حسب الفئة
- categories = {}
- for comp in components:
- category = comp["category"]
- if category not in categories:
- categories[category] = {
- "total_value": 0,
- "local_content_value": 0
- }
-
- categories[category]["total_value"] += comp["value"]
- categories[category]["local_content_value"] += comp["value"] * comp["local_content"] / 100
-
- # حساب نسبة المحتوى المحلي لكل فئة
- for category in categories:
- if categories[category]["total_value"] > 0:
- categories[category]["local_content_percent"] = (categories[category]["local_content_value"] / categories[category]["total_value"]) * 100
- else:
- categories[category]["local_content_percent"] = 0
-
- # تحديد المكونات ذات المحتوى المحلي المنخفض
- low_lc_components = sorted(
- [comp for comp in components if comp["local_content"] < 50],
- key=lambda x: x["local_content"]
- )
-
- # تحديد المكونات ذات المحتوى المحلي المرتفع
- high_lc_components = sorted(
- [comp for comp in components if comp["local_content"] >= 80],
- key=lambda x: x["local_content"],
- reverse=True
- )
-
- # تقديم توصيات لتحسين المحتوى المحلي
- improvement_recommendations = []
-
- # توصيات للمكونات ذات المحتوى المحلي المنخفض
- for comp in low_lc_components[:3]: # أخذ أقل 3 مكونات
- improvement_recommendations.append({
- "component": comp["name"],
- "current_lc": comp["local_content"],
- "recommendation": f"البحث عن بدائل محلية لـ {comp['name']} التي تمثل {comp['value'] / total_value * 100:.1f}% من قيمة المشروع."
- })
-
- # حساب الفجوة بين المحتوى المحلي الفعلي والمستهدف
- lc_gap = target_lc - total_local_content_percent
-
- # إعداد النتائج
- results = {
- "total_value": total_value,
- "total_local_content_value": total_local_content_value,
- "total_local_content": total_local_content_percent,
- "target_lc": target_lc,
- "lc_gap": lc_gap,
- "categories": categories,
- "low_lc_components": low_lc_components,
- "high_lc_components": high_lc_components,
- "improvement_recommendations": improvement_recommendations,
- "calculation_method": calculation_method,
- "components": components
- }
-
- # تحديد حالة المحتوى المحلي
- if lc_gap <= 0:
- results["status"] = "تم تحقيق المستهدف"
- results["color"] = "green"
- elif lc_gap <= 5:
- results["status"] = "قريب من المستهدف"
- results["color"] = "orange"
- else:
- results["status"] = "بعيد عن المستهدف"
- results["color"] = "red"
-
- return results
-
- def _display_local_content_results(self, results, target_lc):
- """عرض نتائج حساب المحتوى المحلي"""
-
- st.markdown("### نتائج حساب المحتوى المحلي")
-
- # عرض نسبة المحتوى المحلي الإجمالية
- col1, col2, col3 = st.columns(3)
-
- with col1:
- st.metric(
- "نسبة المحتوى المحلي الحالية",
- f"{results['total_local_content']:.1f}%",
- delta=f"{results['lc_gap']:.1f}%" if results['lc_gap'] < 0 else f"-{results['lc_gap']:.1f}%",
- delta_color="normal" if results['lc_gap'] < 0 else "inverse"
- )
-
- with col2:
- st.metric(
- "النسبة المستهدفة",
- f"{target_lc}%"
- )
-
- with col3:
- # Aquí está el problema - no podemos usar 'green' como valor para delta_color
- # En lugar de eso, usamos un texto formateado para mostrar el estado
- st.markdown(f"""
-
-
{results["status"]}
-
- """, unsafe_allow_html=True)
-
- # Alternativa sin usar delta_color
- # st.metric(
- # "حالة المحتوى المحلي",
- # results["status"]
- # )
-
-
- # عرض مخطط مقارنة بين النسبة الحالية والمستهدفة
- comparison_data = pd.DataFrame({
- 'النوع': ['النسبة الحالية', 'النسبة المستهدفة'],
- 'النسبة': [results['total_local_content'], target_lc]
- })
-
- fig = px.bar(
- comparison_data,
- x='النوع',
- y='النسبة',
- title="مقارنة نسبة المحتوى المحلي الحالية مع المستهدفة",
- color='النوع',
- color_discrete_map={
- 'النسبة الحالية': results["color"],
- 'النسبة المستهدفة': 'blue'
- }
- )
-
- fig.update_layout(yaxis_range=[0, 100])
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض توزيع المحتوى المحلي حسب الفئة
- st.markdown("#### توزيع المحتوى المحلي حسب الفئة")
-
- categories_data = []
- for category, data in results["categories"].items():
- categories_data.append({
- 'الفئة': category,
- 'القيمة الإجمالية': data["total_value"],
- 'قيمة المحتوى المحلي': data["local_content_value"],
- 'نسبة المحتوى المحلي': data["local_content_percent"]
- })
-
- categories_df = pd.DataFrame(categories_data)
-
- col1, col2 = st.columns(2)
-
- with col1:
- fig = px.pie(
- categories_df,
- values='القيمة الإجمالية',
- names='الفئة',
- title="توزيع قيمة المشروع حسب الفئة"
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- with col2:
- fig = px.bar(
- categories_df,
- x='الفئة',
- y='نسبة المحتوى المحلي',
- title="نسبة المحتوى المحلي لكل فئة",
- text_auto='.1f'
- )
-
- fig.update_traces(texttemplate='%{text}%', textposition='outside')
- fig.update_layout(yaxis_range=[0, 100])
-
- st.plotly_chart(fig, use_container_width=True)
-
- # عرض المكونات ذات المحتوى المحلي المنخفض
- st.markdown("#### المكونات ذات المحتوى المحلي المنخفض")
-
- if results["low_lc_components"]:
- low_lc_df = pd.DataFrame([
- {
- 'المكون': comp["name"],
- 'الفئة': comp["category"],
- 'القيمة': comp["value"],
- 'نسبة المحتوى المحلي': comp["local_content"],
- 'المورد': comp["supplier"]
- }
- for comp in results["low_lc_components"]
- ])
-
- st.dataframe(low_lc_df, use_container_width=True)
-
- # مخطط المكونات ذات المحتوى المحلي المنخفض
- fig = px.bar(
- low_lc_df,
- x='المكون',
- y='نسبة المحتوى المحلي',
- color='القيمة',
- title="المكونات ذات المحتوى المحلي المنخفض",
- text_auto='.1f'
- )
-
- fig.update_traces(texttemplate='%{text}%', textposition='outside')
-
- st.plotly_chart(fig, use_container_width=True)
- else:
- st.info("لا توجد مكونات ذات محتوى محلي منخفض (أقل من 50%).")
-
- # عرض توصيات لتحسين المحتوى المحلي
- st.markdown("#### توصيات لتحسين المحتوى المحلي")
-
- if results["improvement_recommendations"]:
- for recommendation in results["improvement_recommendations"]:
- st.markdown(f"""
-
-
{recommendation['component']} (المحتوى المحلي الحالي: {recommendation['current_lc']}%)
-
{recommendation['recommendation']}
-
- """, unsafe_allow_html=True)
- else:
- st.success("المحتوى المحلي جيد ولا توجد توصيات للتحسين.")
-
- # عرض تحليل Claude AI المتقدم إذا كان متوفراً
- if "claude_analysis" in results:
- st.markdown("### تحليل Claude AI المتقدم")
- st.info(results["claude_analysis"])
-
- def _render_lc_suppliers_tab(self):
- """عرض تبويب قاعدة بيانات الموردين للمحتوى المحلي"""
-
- st.markdown("#### قاعدة بيانات الموردين المحليين")
-
- # قائمة الفئات
- categories = [
- "جميع الفئات",
- "مواد بناء",
- "أنظمة كهربائية",
- "أنظمة ميكانيكية",
- "تشطيبات",
- "أثاث ومفروشات",
- "خدمات هندسية",
- "أنظمة أمنية",
- "معدات وآليات"
- ]
-
- # اختيار الفئة
- selected_category = st.selectbox("فئة الموردين", categories)
-
- # البحث
- search_query = st.text_input("البحث عن مورد")
-
- # إعداد قائمة الموردين
- suppliers = [
- {
- "id": 1,
- "name": "شركة الإنشاءات السعودية",
- "category": "مواد بناء",
- "lc_rating": 95,
- "quality_rating": 4.5,
- "location": "الرياض",
- "contact": "info@saudiconstruction.com",
- "description": "شركة متخصصة في توريد جميع أنواع مواد البناء ذات المنشأ المحلي."
- },
- {
- "id": 2,
- "name": "مؤسسة الطاقة المتقدمة",
- "category": "أنظمة كهربائية",
- "lc_rating": 85,
- "quality_rating": 4.2,
- "location": "جدة",
- "contact": "sales@advancedpower.com",
- "description": "مؤسسة متخصصة في توريد وتركيب الأنظمة الكهربائية والطاقة المتجددة."
- },
- {
- "id": 3,
- "name": "شركة التبريد العالمية",
- "category": "أنظمة ميكانيكية",
- "lc_rating": 75,
- "quality_rating": 4.0,
- "location": "الدمام",
- "contact": "info@globalcooling.com",
- "description": "شركة متخصصة في أنظمة التكييف والتبريد المركزي للمشاريع الكبرى."
- },
- {
- "id": 4,
- "name": "شركة الزجاج المتطورة",
- "category": "مواد بناء",
- "lc_rating": 80,
- "quality_rating": 4.3,
- "location": "��لرياض",
- "contact": "sales@advancedglass.com",
- "description": "شركة متخصصة في إنتاج وتوريد الزجاج والواجهات الزجاجية للمباني."
- },
- {
- "id": 5,
- "name": "مؤسسة التشطيبات الحديثة",
- "category": "تشطيبات",
- "lc_rating": 90,
- "quality_rating": 4.7,
- "location": "جدة",
- "contact": "info@modernfinishing.com",
- "description": "مؤسسة متخصصة في أعمال التشطيبات الداخلية والخارجية بجودة عالية."
- },
- {
- "id": 6,
- "name": "شركة الأثاث المكتبي",
- "category": "أثاث ومفروشات",
- "lc_rating": 70,
- "quality_rating": 4.0,
- "location": "الرياض",
- "contact": "sales@officefurniture.com",
- "description": "شركة متخصصة في تصنيع وتوريد الأثاث المكتبي والتجهيزات المكتبية."
- },
- {
- "id": 7,
- "name": "شركة الأنظمة الأمنية المتقدمة",
- "category": "أنظمة أمنية",
- "lc_rating": 65,
- "quality_rating": 4.1,
- "location": "الدمام",
- "contact": "info@advancedsecurity.com",
- "description": "شركة متخصصة في أنظمة الأمن والمراقبة والإنذار للمباني والمنشآت."
- },
- {
- "id": 8,
- "name": "شركة المعدات الهندسية",
- "category": "معدات وآليات",
- "lc_rating": 85,
- "quality_rating": 4.5,
- "location": "جدة",
- "contact": "sales@engineeringequipment.com",
- "description": "شركة متخصصة في توريد وصيانة المعدات الهندسية والآليات للمشاريع."
- },
- {
- "id": 9,
- "name": "مكتب الاستشارات الهندسية",
- "category": "خدمات هندسية",
- "lc_rating": 100,
- "quality_rating": 4.8,
- "location": "الرياض",
- "contact": "info@engineeringconsultants.com",
- "description": "مكتب استشاري متخصص في تقديم الخدمات الهندسية والاستشارية للمشاريع."
- },
- {
- "id": 10,
- "name": "مصنع الحديد السعودي",
- "category": "مواد بناء",
- "lc_rating": 100,
- "quality_rating": 4.6,
- "location": "جدة",
- "contact": "sales@saudisteel.com",
- "description": "مصنع متخصص في إنتاج وتوريد منتجات الحديد والصلب للمشاريع الإنشائية."
- }
- ]
-
- # تطبيق الفلترة حسب الفئة
- if selected_category != "جميع الفئات":
- filtered_suppliers = [s for s in suppliers if s["category"] == selected_category]
- else:
- filtered_suppliers = suppliers
-
- # تطبيق فلترة البحث
- if search_query:
- filtered_suppliers = [s for s in filtered_suppliers if search_query.lower() in s["name"].lower() or search_query.lower() in s["description"].lower()]
-
- # عرض الموردين
- for supplier in filtered_suppliers:
- with st.container():
- col1, col2 = st.columns([3, 1])
-
- with col1:
- st.markdown(f"""
-
-
{supplier['name']} ({supplier['category']})
-
الموقع: {supplier['location']} | التواصل: {supplier['contact']}
-
تصنيف المحتوى المحلي: {supplier['lc_rating']}% | تقييم الجودة: {supplier['quality_rating']}/5
-
{supplier['description']}
-
- """, unsafe_allow_html=True)
-
- with col2:
- st.button(f"عرض التفاصيل #{supplier['id']}", key=f"supplier_details_{supplier['id']}")
- st.button(f"إضافة للمشروع #{supplier['id']}", key=f"add_supplier_{supplier['id']}")
-
- # زر إضافة مورد جديد
- st.button("إضافة مورد جديد")
-
- def _render_lc_reports_tab(self):
- """عرض تبويب تقارير المحتوى المحلي"""
-
- st.markdown("#### تقارير المحتوى المحلي")
-
- # اختيار نوع التقرير
- report_type = st.selectbox(
- "نوع التقرير",
- [
- "تقرير المحتوى المحلي للمشروع الحالي",
- "تقرير مقارنة المحتوى المحلي بين المشاريع",
- "تقرير التطور التاريخي للمحتوى المحلي",
- "تقرير الموردين ذوي المحتوى المحلي المرتفع",
- "تقرير الامتثال لمتطلبات هيئة المحتوى المحلي"
- ]
- )
-
- # عرض محاكاة للتقرير المختار
- st.markdown(f"##### {report_type}")
-
- if report_type == "تقرير المحتوى المحلي للمشروع الحالي":
- # محاكاة تقرير المشروع الحالي
- project_data = pd.DataFrame({
- 'المكون': ['الخرسانة المسلحة', 'الأعمال الكهربائية', 'أعمال التكييف', 'الواجهات والنوافذ',
- 'أعمال التشطيبات', 'الأثاث والتجهيزات', 'أنظمة الأمن والمراقبة', 'العمالة المباشرة'],
- 'القيمة': [3000000, 1500000, 1200000, 800000, 1200000, 900000, 600000, 800000],
- 'نسبة المحتوى المحلي': [85, 65, 55, 45, 80, 30, 40, 50]
- })
-
- # حساب قيمة المحتوى المحلي
- project_data['قيمة المحتوى المحلي'] = project_data['القيمة'] * project_data['نسبة المحتوى المحلي'] / 100
-
- # إضافة نسبة من إجمالي المشروع
- total_value = project_data['القيمة'].sum()
- project_data['نسبة من المشروع'] = project_data['القيمة'] / total_value * 100
-
- # حساب النسبة الإجمالية للمحتوى المحلي
- total_lc = project_data['قيمة المحتوى المحلي'].sum() / total_value * 100
-
- # عرض الإجمالي
- st.metric("نسبة المحتوى المحلي الإجمالية", f"{total_lc:.1f}%")
-
- # عرض تفاصيل المكونات
- st.dataframe(project_data.style.format({
- 'القيمة': '{:,.0f} ريال',
- 'قيمة المحتوى المحلي': '{:,.0f} ريال',
- 'نسبة المحتوى المحلي': '{:.1f}%',
- 'نسبة من المشروع': '{:.1f}%'
- }), use_container_width=True)
-
- # مخطط توزيع المحتوى المحلي
- col1, col2 = st.columns(2)
-
- with col1:
- fig = px.pie(
- project_data,
- values='القيمة',
- names='المكون',
- title="توزيع قيمة المشروع"
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- with col2:
- fig = px.pie(
- project_data,
- values='قيمة المحتوى المحلي',
- names='المكون',
- title="توزيع قيمة المحتوى المحلي"
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # مخطط شريطي للمحتوى المحلي
- fig = px.bar(
- project_data,
- x='المكون',
- y='نسبة المحتوى المحلي',
- title="نسبة المحتوى المحلي لكل مكون",
- text_auto='.1f',
- color='نسبة من المشروع'
- )
-
- fig.update_traces(texttemplate='%{text}%', textposition='outside')
-
- st.plotly_chart(fig, use_container_width=True)
-
- elif report_type == "تقرير مقارنة المحتوى المحلي بين المشاريع":
- # محاكاة بيانات مقارنة المشاريع
- projects_data = pd.DataFrame({
- 'المشروع': ['مبنى إداري الرياض', 'مجمع سكني جدة', 'مستشفى الدمام', 'مركز تجاري المدينة', 'فندق مكة'],
- 'القيمة': [10000000, 15000000, 20000000, 12000000, 18000000],
- 'نسبة المحتوى المحلي': [65, 55, 70, 60, 50],
- 'سنة الإنجاز': [2022, 2022, 2023, 2023, 2024]
- })
-
- # عرض جدول المقارنة
- st.dataframe(projects_data.style.format({
- 'القيمة': '{:,.0f} ريال',
- 'نسبة المحتوى المحلي': '{:.1f}%'
- }), use_container_width=True)
-
- # مخطط شريطي للمقارنة
- fig = px.bar(
- projects_data,
- x='المشروع',
- y='نسبة المحتوى المحلي',
- title="مقارنة نسبة المحتوى المحلي بين المشاريع",
- text_auto='.1f',
- color='سنة الإنجاز'
- )
-
- fig.update_traces(texttemplate='%{text}%', textposition='outside')
-
- st.plotly_chart(fig, use_container_width=True)
-
- # مخطط فقاعي للمقارنة
- fig = px.scatter(
- projects_data,
- x='القيمة',
- y='نسبة المحتوى المحلي',
- size='القيمة',
- color='سنة الإنجاز',
- text='المشروع',
- title="العلاقة بين قيمة المشروع ونسبة المحتوى المحلي"
- )
-
- fig.update_traces(textposition='top center')
- fig.update_layout(xaxis_title="قيمة المشروع (ريال)", yaxis_title="نسبة المحتوى المحلي (%)")
-
- st.plotly_chart(fig, use_container_width=True)
-
- elif report_type == "تقرير التطور التاريخي للمحتوى المحلي":
- # محاكاة بيانات التطور التاريخي
- historical_data = pd.DataFrame({
- 'السنة': [2019, 2020, 2021, 2022, 2023, 2024],
- 'نسبة المحتوى المحلي': [45, 48, 52, 58, 62, 66],
- 'المستهدف': [40, 45, 50, 55, 60, 65]
- })
-
- # عرض جدول التطور التاريخي
- st.dataframe(historical_data.style.format({
- 'نسبة المحتوى المحلي': '{:.1f}%',
- 'المستهدف': '{:.1f}%'
- }), use_container_width=True)
-
- # مخطط خطي للتطور التاريخي
- fig = px.line(
- historical_data,
- x='السنة',
- y=['نسبة المحتوى المحلي', 'المستهدف'],
- title="التطور التاريخي لنسبة المحتوى المحلي",
- markers=True,
- labels={'value': 'النسبة (%)', 'variable': ''}
- )
-
- fig.update_layout(legend_title_text='')
-
- st.plotly_chart(fig, use_container_width=True)
-
- # مخطط شريطي للمقارنة بين الفعلي والمستهدف
- historical_data['الفرق'] = historical_data['نسبة المحتوى المحلي'] - historical_data['المستهدف']
-
- fig = px.bar(
- historical_data,
- x='السنة',
- y='الفرق',
- title="الفرق بين نسبة المحتوى المحلي الفعلية والمستهدفة",
- text_auto='.1f',
- color='الفرق',
- color_continuous_scale=['red', 'green']
- )
-
- fig.update_traces(texttemplate='%{text}%', textposition='outside')
-
- st.plotly_chart(fig, use_container_width=True)
-
- elif report_type == "تقرير الموردين ذوي المحتوى المحلي المرتفع":
- # محاكاة بيانات الموردين
- suppliers_data = pd.DataFrame({
- 'المورد': ['شركة الإنشاءات السعودية', 'مؤسسة الطاقة المتقدمة', 'شركة التبريد العالمية',
- 'شركة الزجاج المتطورة', 'مؤسسة التشطيبات الحديثة', 'مصنع الحديد السعودي',
- 'شركة المعدات الهندسية', 'مكتب الاستشارات الهندسية'],
- 'الفئة': ['مواد بناء', 'أنظمة كهربائية', 'أنظمة ميكانيكية', 'مواد بناء',
- 'تشطيبات', 'مواد بناء', 'معدات وآليات', 'خدمات هندسية'],
- 'نسبة المحتوى المحلي': [95, 85, 75, 80, 90, 100, 85, 100],
- 'حجم التعامل': [3000000, 1500000, 1200000, 800000, 1200000, 2500000, 900000, 500000]
- })
-
- # عر�� جدول الموردين
- st.dataframe(suppliers_data.style.format({
- 'نسبة المحتوى المحلي': '{:.0f}%',
- 'حجم التعامل': '{:,.0f} ريال'
- }), use_container_width=True)
-
- # مخطط شريطي للموردين
- fig = px.bar(
- suppliers_data,
- x='المورد',
- y='نسبة المحتوى المحلي',
- title="نسبة المحتوى المحلي للموردين",
- text_auto='.0f',
- color='الفئة'
- )
-
- fig.update_traces(texttemplate='%{text}%', textposition='outside')
- fig.update_layout(xaxis_tickangle=-45)
-
- st.plotly_chart(fig, use_container_width=True)
-
- # مخطط فقاعي للموردين
- fig = px.scatter(
- suppliers_data,
- x='نسبة المحتوى المحلي',
- y='حجم التعامل',
- size='حجم التعامل',
- color='الفئة',
- text='المورد',
- title="العلاقة بين نسبة المحتوى المحلي وحجم التعامل مع الموردين"
- )
-
- fig.update_traces(textposition='top center')
- fig.update_layout(xaxis_title="نسبة المحتوى المحلي (%)", yaxis_title="حجم التعامل (ريال)")
-
- st.plotly_chart(fig, use_container_width=True)
-
- elif report_type == "تقرير الامتثال لمتطلبات هيئة المحتوى المحلي":
- # محاكاة بيانات الامتثال
- compliance_data = pd.DataFrame({
- 'المتطلب': [
- 'نسبة المحتوى المحلي الإجمالية',
- 'نسبة السعودة في القوى العاملة',
- 'نسبة المنتجات المحلية',
- 'نسبة الخدمات المحلية',
- 'نسبة الموردين المحليين',
- 'المساهمة في تطوير المحتوى المحلي'
- ],
- 'المستهدف': [40, 30, 50, 60, 70, 20],
- 'المحقق': [38, 35, 45, 65, 75, 25],
- 'حالة الامتثال': ['قريب', 'ممتثل', 'غير ممتثل', 'ممتثل', 'ممتثل', 'ممتثل']
- })
-
- # إضافة ألوان لحالة الامتثال
- colors = []
- for status in compliance_data['حالة الامتثال']:
- if status == 'ممتثل':
- colors.append('green')
- elif status == 'قريب':
- colors.append('orange')
- else:
- colors.append('red')
-
- compliance_data['اللون'] = colors
-
- # عرض جدول الامتثال
- st.dataframe(compliance_data.style.format({
- 'المستهدف': '{:.0f}%',
- 'المحقق': '{:.0f}%'
- }), use_container_width=True)
-
- # مخطط شريطي للامتثال
- fig = px.bar(
- compliance_data,
- x='المتطلب',
- y=['المستهدف', 'المحقق'],
- title="مقارنة المتطلبات المستهدفة والمحققة",
- barmode='group',
- labels={'value': 'النسبة (%)', 'variable': ''}
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # مخطط دائري لحالة الامتثال
- status_counts = compliance_data['حالة الامتثال'].value_counts().reset_index()
- status_counts.columns = ['حالة الامتثال', 'العدد']
-
- fig = px.pie(
- status_counts,
- values='العدد',
- names='حالة الامتثال',
- title="توزيع حالة الامتثال للمتطلبات",
- color='حالة الامتثال',
- color_discrete_map={
- 'ممتثل': 'green',
- 'قريب': 'orange',
- 'غير ممتثل': 'red'
- }
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # أزرار التصدير
- col1, col2 = st.columns(2)
-
- with col1:
- st.download_button(
- "تصدير التقرير كملف Excel",
- "بيانات التقرير",
- file_name=f"{report_type}.xlsx",
- mime="application/vnd.ms-excel"
- )
-
- with col2:
- st.download_button(
- "تصدير التقرير كملف PDF",
- "بيانات التقرير",
- file_name=f"{report_type}.pdf",
- mime="application/pdf"
- )
-
- def _render_lc_optimization_tab(self):
- """عرض تبويب تحسين المحتوى المحلي"""
-
- st.markdown("#### تحسين المحتوى المحلي")
-
- st.markdown("""
- تساعدك هذه الأداة في تحسين نسبة المحتوى المحلي في المشروع من خلال تقديم توصيات وبدائل للمكونات ذات المحتوى المحلي المنخفض.
- """)
-
- # عرض المكونات ذات المحتوى المحلي المنخفض
- st.markdown("##### المكونات ذات المحتوى المحلي المنخفض")
-
- # محاكاة بيانات المكونات ذات المحتوى المحلي المنخفض
- low_lc_components = [
- {
- "id": 1,
- "name": "الأثاث والتجهيزات",
- "category": "أثاث",
- "value": 900000,
- "local_content": 30,
- "supplier": "شركة الأثاث المكتبي"
- },
- {
- "id": 2,
- "name": "أنظمة الأمن والمراقبة",
- "category": "أنظمة",
- "value": 600000,
- "local_content": 40,
- "supplier": "شركة الأنظمة الأمنية المتقدمة"
- },
- {
- "id": 3,
- "name": "الواجهات والنوافذ",
- "category": "مواد",
- "value": 800000,
- "local_content": 45,
- "supplier": "شركة الزجاج المتطورة"
- }
- ]
-
- # عرض جدول المكونات
- low_lc_df = pd.DataFrame(low_lc_components)
-
- st.dataframe(
- low_lc_df[["name", "category", "value", "local_content", "supplier"]].rename(columns={
- "name": "المكون",
- "category": "الفئة",
- "value": "القيمة",
- "local_content": "المحتوى المحلي",
- "supplier": "المورد"
- }).style.format({
- "القيمة": "{:,.0f} ريال",
- "المحتوى المحلي": "{:.0f}%"
- }),
- use_container_width=True
- )
-
- # اختيار مكون للتحسين
- selected_component = st.selectbox(
- "اختر المكون للتحسين",
- options=[comp["name"] for comp in low_lc_components],
- index=0
- )
-
- # الحصول على المكون المختار
- selected_comp_data = next((comp for comp in low_lc_components if comp["name"] == selected_component), None)
-
- # عرض بدائل المكون المختار
- if selected_comp_data:
- st.markdown(f"##### البدائل المقترحة لـ {selected_component}")
-
- # محاكاة بيانات البدائل
- alternatives = []
-
- if selected_component == "الأثاث والتجهيزات":
- alternatives = [
- {
- "id": 1,
- "name": "شركة الأثاث الوطني",
- "description": "شركة متخصصة في تصنيع الأثاث المكتبي محلياً",
- "local_content": 80,
- "cost_factor": 1.05,
- "quality_rating": 4.2
- },
- {
- "id": 2,
- "name": "مصنع التجهيزات المكتبية",
- "description": "مصنع متخصص في إنتاج الأثاث المكتبي بخامات محلية",
- "local_content": 90,
- "cost_factor": 1.10,
- "quality_rating": 4.5
- },
- {
- "id": 3,
- "name": "توزيع المكونات على موردين محليين",
- "description": "تقسيم توريد الأثاث على عدة موردين محليين",
- "local_content": 75,
- "cost_factor": 1.00,
- "quality_rating": 4.0
- }
- ]
- elif selected_component == "أنظمة الأمن والمراقبة":
- alternatives = [
- {
- "id": 1,
- "name": "شركة التقنية الأمنية السعودية",
- "description": "شركة متخصصة في تركيب وتجميع أنظمة الأمن محلياً",
- "local_content": 70,
- "cost_factor": 1.08,
- "quality_rating": 4.0
- },
- {
- "id": 2,
- "name": "مؤسسة تقنيات الحماية",
- "description": "توريد وتركيب أنظمة أمنية معتمدة من هيئة المحتوى المحلي",
- "local_content": 65,
- "cost_factor": 0.95,
- "quality_rating": 3.8
- },
- {
- "id": 3,
- "name": "تجميع الأنظمة محلياً",
- "description": "استيراد المكونات وتجميعها وبرمجتها محلياً",
- "local_content": 60,
- "cost_factor": 0.90,
- "quality_rating": 3.7
- }
- ]
- elif selected_component == "الواجهات والنوافذ":
- alternatives = [
- {
- "id": 1,
- "name": "مصنع الزجاج السعودي",
- "description": "مصنع متخصص في إنتاج الزجاج والواجهات الزجاجية محلياً",
- "local_content": 85,
- "cost_factor": 1.15,
- "quality_rating": 4.3
- },
- {
- "id": 2,
- "name": "شركة الألمنيوم الوطنية",
- "description": "شركة متخصصة في إنتاج الواجهات والنوافذ من الألمنيوم محلياً",
- "local_content": 90,
- "cost_factor": 1.20,
- "quality_rating": 4.5
- },
- {
- "id": 3,
- "name": "تعديل التصميم لاستخدام مواد محلية",
- "description": "تعديل تصميم الواجهات لاستخدام نسبة أكبر من المواد المتوفرة محلياً",
- "local_content": 75,
- "cost_factor": 1.00,
- "quality_rating": 4.0
- }
- ]
-
- # عرض البدائل
- for alt in alternatives:
- with st.container():
- col1, col2, col3 = st.columns([3, 1, 1])
-
- with col1:
- st.markdown(f"""
-
-
{alt['name']}
-
{alt['description']}
-
المحتوى المحلي: {alt['local_content']}% | معامل التكلفة: {alt['cost_factor']:.2f} | تقييم الجودة: {alt['quality_rating']}/5
-
- """, unsafe_allow_html=True)
-
- with col2:
- st.button(f"تفاصيل #{alt['id']}", key=f"alt_details_{alt['id']}")
-
- with col3:
- if st.button(f"اختيار #{alt['id']}", key=f"select_alt_{alt['id']}"):
- st.success(f"تم اختيار {alt['name']} كبديل لـ {selected_component}.")
-
- # حساب تأثير البدائل على المحتوى المحلي الإجمالي
- st.markdown("##### تأثير البدائل على المحتوى المحلي الإجمالي")
-
- # محاكاة البيانات الإجمالية
- total_value = 10000000
- current_lc_value = 6000000
- current_lc_percent = current_lc_value / total_value * 100
-
- # حساب التأثير لكل بديل
- impact_data = []
- for alt in alternatives:
- # القيمة الحالية للمحتوى المحلي في المكون
- current_component_lc_value = selected_comp_data["value"] * selected_comp_data["local_content"] / 100
-
- # القيمة المتوقعة للمحتوى المحلي مع البديل
- new_component_value = selected_comp_data["value"] * alt["cost_factor"]
- new_component_lc_value = new_component_value * alt["local_content"] / 100
-
- # الفرق في قيمة المحتوى المحلي
- lc_value_diff = new_component_lc_value - current_component_lc_value
-
- # القيمة الإجمالية الجديدة للمشروع
- new_total_value = total_value - selected_comp_data["value"] + new_component_value
-
- # قيمة المحتوى المحلي الإجمالية الجديدة
- new_total_lc_value = current_lc_value + lc_value_diff
-
- # نسبة المحتوى المحلي الإجمالية الجديدة
- new_total_lc_percent = new_total_lc_value / new_total_value * 100
-
- # إضافة البيانات
- impact_data.append({
- "البديل": alt["name"],
- "نسبة المحتوى المحلي الحالية": current_lc_percent,
- "نسبة المحتوى المحلي المتوقعة": new_total_lc_percent,
- "التغير": new_total_lc_percent - current_lc_percent,
- "القيمة الإجمالية الجديدة": new_total_value,
- "تقييم الجودة": alt["quality_rating"]
- })
-
- # عرض جدول التأثير
- impact_df = pd.DataFrame(impact_data)
-
- st.dataframe(
- impact_df.style.format({
- "نسبة المحتوى المحلي الحالية": "{:.1f}%",
- "نسبة المحتوى المحلي المتوقعة": "{:.1f}%",
- "التغير": "{:+.1f}%",
- "القيمة الإجمالية الجديدة": "{:,.0f} ريال",
- "تقييم الجودة": "{:.1f}/5"
- }),
- use_container_width=True
- )
-
- # مخطط مقارنة للبدائل
- fig = px.bar(
- impact_df,
- x="البديل",
- y=["نسبة المحتوى المحلي الحالية", "نسبة المحتوى المحلي المتوقعة"],
- barmode="group",
- title="مقارنة تأثير البدائل على نسبة المحتوى المحلي الإجمالية",
- labels={"value": "نسبة المحتوى المحلي (%)", "variable": ""}
- )
-
- st.plotly_chart(fig, use_container_width=True)
-
- # استخدام Claude AI للتحليل المتقدم
- if st.checkbox("استخدام Claude AI لتحليل البدائل", value=False, key="lc_optimization_use_claude"):
- with st.spinner("جاري تحليل البدائل..."):
- # محاكاة وقت المعالجة
- time.sleep(2)
-
- try:
- # إنشاء نص المدخلات للتحليل
- prompt = f"""تحليل بدائل المحتوى المحلي لمكون {selected_component}:
-
- المكون الحالي:
- - الاسم: {selected_component}
- - الفئة: {selected_comp_data['category']}
- - القيمة: {selected_comp_data['value']:,} ريال
- - نسبة المحتوى المحلي: {selected_comp_data['local_content']}%
- - المورد: {selected_comp_data['supplier']}
-
- البدائل المقترحة:
- 1. {alternatives[0]['name']}:
- - المحتوى المحلي: {alternatives[0]['local_content']}%
- - معامل التكلفة: {alternatives[0]['cost_factor']:.2f}
- - تقييم الجودة: {alternatives[0]['quality_rating']}/5
- - الوصف: {alternatives[0]['description']}
-
- 2. {alternatives[1]['name']}:
- - المحتوى المحلي: {alternatives[1]['local_content']}%
- - معامل التكلفة: {alternatives[1]['cost_factor']:.2f}
- - تقييم الجودة: {alternatives[1]['quality_rating']}/5
- - الوصف: {alternatives[1]['description']}
-
- 3. {alternatives[2]['name']}:
- - المحتوى المحلي: {alternatives[2]['local_content']}%
- - معامل التكلفة: {alternatives[2]['cost_factor']:.2f}
- - تقييم الجودة: {alternatives[2]['quality_rating']}/5
- - الوصف: {alternatives[2]['description']}
-
- المطلوب:
- 1. تحليل مقارن شامل للبدائل من حيث المحتوى المحلي والتكلفة والجودة
- 2. تحديد البديل الأفضل مع شرح أسباب اختياره
- 3. تقديم توصيات إضافية لتحسين المحتوى المحلي لهذا المكون
- 4. تحديد أي مخاطر محتملة في الانتقال للبديل المقترح
-
- يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية.
- """
-
- # استدعاء Claude للتحليل
- claude_analysis = self.claude_service.chat_completion(
- [{"role": "user", "content": prompt}]
- )
-
- if "error" not in claude_analysis:
- # عرض تحليل Claude
- st.markdown("##### تحليل متقدم للبدائل")
- st.info(claude_analysis["content"])
- else:
- st.warning(f"تعذر إجراء التحليل المتقدم: {claude_analysis['error']}")
- except Exception as e:
- st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
-
- # زر تطبيق البديل المختار
- if st.button("تطبيق البديل المختار على المشروع"):
- st.success("تم تطبيق البديل المختار على المشروع وتحديث نسبة المحتوى المحلي.")
-
- def _render_faq_tab(self):
- """عرض تبويب الأسئلة الشائعة"""
-
- st.markdown("### الأسئلة الشائعة")
-
- # البحث في الأسئلة الشائعة
- search_query = st.text_input("البحث في الأسئلة الشائعة", key="faq_search")
-
- # فلترة الأسئلة حسب البحث
- if search_query:
- filtered_faqs = [
- faq for faq in self.faqs
- if search_query.lower() in faq["question"].lower() or search_query.lower() in faq["answer"].lower()
- ]
- else:
- filtered_faqs = self.faqs
-
- # عرض الأسئلة والأجوبة
- for i, faq in enumerate(filtered_faqs):
- with st.expander(faq["question"]):
- st.markdown(faq["answer"])
-
- # زر التواصل مع الدعم
- st.markdown("##### لم تجد إجابة لسؤالك؟")
- col1, col2 = st.columns(2)
-
- with col1:
- if st.button("التواصل مع الدعم الفني", use_container_width=True):
- st.info("سيتم التواصل معك قريباً من قبل فريق الدعم الفني.")
-
- with col2:
- if st.button("طرح سؤال جديد", use_container_width=True):
- st.text_area("اكتب سؤالك هنا")
- st.button("إرسال")
\ No newline at end of file
+ st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.")