diff --git "a/modules/ai_assistant/assistant.py" "b/modules/ai_assistant/assistant.py" --- "a/modules/ai_assistant/assistant.py" +++ "b/modules/ai_assistant/assistant.py" @@ -1,444 +1,3175 @@ +# -*- coding: utf-8 -*- """ -وحدة المساعد الذكي لنظام إدارة المناقصات - Hybrid Face +وحدة المساعد الذكي + +هذا الملف يحتوي على الفئة الرئيسية لتطبيق المساعد الذكي مع دعم نموذج Claude AI. """ -import os -import logging -import threading -import datetime +import streamlit as st +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import plotly.express as px +import requests import json -import re -from pathlib import Path - -# تهيئة السجل -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('ai_assistant') - -class AIAssistant: - """المساعد الذكي""" - - def __init__(self, config=None, db=None): - """تهيئة المساعد الذكي""" - self.config = config - self.db = db - self.processing_in_progress = False - self.current_query = None - self.processing_results = {} - self.conversation_history = [] - - # إعدادات المساعد الذكي - self.ai_model = config.AI_MODEL if config and hasattr(config, 'AI_MODEL') else "gpt-4" - self.ai_temperature = config.AI_TEMPERATURE if config and hasattr(config, 'AI_TEMPERATURE') else 0.7 - self.ai_max_tokens = config.AI_MAX_TOKENS if config and hasattr(config, 'AI_MAX_TOKENS') else 2000 - - # إنشاء مجلد المساعد الذكي إذا لم يكن موجوداً - if config and hasattr(config, 'EXPORTS_PATH'): - self.exports_path = Path(config.EXPORTS_PATH) - else: - self.exports_path = Path('data/exports') - - if not self.exports_path.exists(): - self.exports_path.mkdir(parents=True, exist_ok=True) - - def process_query(self, query, context=None, callback=None): - """معالجة استعلام المستخدم""" - if self.processing_in_progress: - logger.warning("هناك عملية معالجة جارية بالفعل") - return False - - self.processing_in_progress = True - self.current_query = query - self.processing_results = { - "query": query, - "context": context, - "processing_start_time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - "status": "جاري المعالجة", - "response": "", - "suggestions": [], - "references": [] +import time +import base64 +import logging +import os +from datetime import datetime, timedelta +import io +import tempfile +import random +from io import BytesIO +from tempfile import NamedTemporaryFile +from PIL import Image + +# استيراد النماذج المطلوبة +try: + from models.inference import ( + load_cost_prediction_model, + load_document_classifier_model, + load_risk_assessment_model, + load_local_content_model, + load_entity_recognition_model + ) +except ImportError: + # إنشاء دوال وهمية في حال عدم توفر النماذج + def load_cost_prediction_model(): + return None + + def load_document_classifier_model(): + return None + + def load_risk_assessment_model(): + return None + + def load_local_content_model(): + return None + + def load_entity_recognition_model(): + return None + +try: + # استيراد مكتبة pdf2image للتعامل مع ملفات PDF + from pdf2image import convert_from_path + pdf_conversion_available = True +except ImportError: + pdf_conversion_available = False + logging.warning("لم يتم العثور على مكتبة pdf2image. لن يمكن تحويل ملفات PDF إلى صور.") + + +class ClaudeAIService: + """ + فئة خدمة Claude AI للتحليل الذكي + """ + def __init__(self): + """تهيئة خدمة Claude AI""" + self.api_url = "https://api.anthropic.com/v1/messages" + + def get_api_key(self): + """الحصول على مفتاح API من متغيرات البيئة""" + api_key = os.environ.get("anthropic") + if not api_key: + raise ValueError("مفتاح API لـ Claude غير موجود في متغيرات البيئة") + return api_key + + def get_available_models(self): + """ + الحصول على قائمة بالنماذج المتاحة + + العوائد: + dict: قائمة بالنماذج مع وصفها + """ + return { + "claude-3-7-sonnet": "Claude 3.7 Sonnet - نموذج ذكي للمهام المتقدمة", + "claude-3-5-haiku": "Claude 3.5 Haiku - أسرع نموذج للمهام اليومية" } - - # إضافة الاستعلام إلى سجل المحادثة - self.conversation_history.append({ - "role": "user", - "content": query, - "timestamp": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - # بدء المعالجة في خيط منفصل - thread = threading.Thread( - target=self._process_query_thread, - args=(query, context, callback) - ) - thread.daemon = True - thread.start() - - return True - - def _process_query_thread(self, query, context, callback): - """خيط معالجة الاستعلام""" + + def get_model_full_name(self, short_name): + """ + تحويل الاسم المختصر للنموذج إلى الاسم الكامل + + المعلمات: + short_name: الاسم المختصر للنموذج + + العوائد: + str: الاسم الكامل للنموذج + """ + valid_models = { + "claude-3-7-sonnet": "claude-3-7-sonnet-20250219", + "claude-3-5-haiku": "claude-3-5-haiku-20240307" + } + + return valid_models.get(short_name, short_name) + + def analyze_image(self, image_path, prompt, model_name="claude-3-7-sonnet"): + """ + تحليل صورة باستخدام نموذج Claude AI + + المعلمات: + image_path: مسار الصورة المراد تحليلها + prompt: التوجيه للنموذج + model_name: اسم نموذج Claude المراد استخدامه + + العوائد: + dict: نتائج التحليل + """ try: - # تحليل الاستعلام - query_type = self._analyze_query(query) - - # معالجة الاستعلام بناءً على نوعه - if query_type == "document_analysis": - response = self._handle_document_analysis_query(query, context) - elif query_type == "pricing": - response = self._handle_pricing_query(query, context) - elif query_type == "risk_analysis": - response = self._handle_risk_analysis_query(query, context) - elif query_type == "project_management": - response = self._handle_project_management_query(query, context) - elif query_type == "reporting": - response = self._handle_reporting_query(query, context) + # الحصول على مفتاح API + api_key = self.get_api_key() + + # قراءة محتوى الصورة + with open(image_path, 'rb') as f: + file_content = f.read() + + # تحويل المحتوى إلى Base64 + file_base64 = base64.b64encode(file_content).decode('utf-8') + + # تحديد نوع الملف من امتداده + _, ext = os.path.splitext(image_path) + ext = ext.lower() + + if ext in ('.jpg', '.jpeg'): + file_type = "image/jpeg" + elif ext == '.png': + file_type = "image/png" + elif ext == '.gif': + file_type = "image/gif" + elif ext == '.webp': + file_type = "image/webp" else: - response = self._handle_general_query(query, context) - - # توليد اقتراحات - suggestions = self._generate_suggestions(query_type, query, response) - - # تحديث نتائج المعالجة - self.processing_results["response"] = response - self.processing_results["query_type"] = query_type - self.processing_results["suggestions"] = suggestions - self.processing_results["status"] = "اكتملت المعالجة" - self.processing_results["processing_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - # إضافة الاستجابة إلى سجل المحادثة - self.conversation_history.append({ - "role": "assistant", - "content": response, - "timestamp": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - logger.info(f"اكتملت معالجة الاستعلام: {query[:50]}...") - + file_type = "image/jpeg" # افتراضي + + # التحقق من اسم النموذج وتصحيحه إذا لزم الأمر + model_name = self.get_model_full_name(model_name) + + # إعداد البيانات للطلب + headers = { + "Content-Type": "application/json", + "x-api-key": api_key, + "anthropic-version": "2023-06-01" + } + + payload = { + "model": model_name, + "max_tokens": 4096, + "messages": [ + { + "role": "user", + "content": [ + {"type": "text", "text": prompt}, + { + "type": "image", + "source": { + "type": "base64", + "media_type": file_type, + "data": file_base64 + } + } + ] + } + ] + } + + # إرسال الطلب إلى API + response = requests.post( + self.api_url, + headers=headers, + json=payload, + timeout=60 + ) + + # التحقق من نجاح الطلب + if response.status_code != 200: + error_message = f"فشل طلب API: {response.status_code}" + try: + error_details = response.json() + error_message += f"\nتفاصيل: {error_details}" + except: + error_message += f"\nتفاصيل: {response.text}" + + return {"error": error_message} + + # معالجة الاستجابة + result = response.json() + + return { + "success": True, + "content": result["content"][0]["text"], + "model": result["model"], + "usage": result.get("usage", {}) + } + except Exception as e: - logger.error(f"خطأ في معالجة الاستعلام: {str(e)}") - self.processing_results["status"] = "فشلت المعالجة" - self.processing_results["error"] = str(e) - - # إضافة رسالة الخطأ إلى سجل المحادثة - self.conversation_history.append({ - "role": "system", - "content": f"حدث خطأ أثناء معالجة الاستعلام: {str(e)}", - "timestamp": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - finally: - self.processing_in_progress = False - - # استدعاء دالة الاستجابة إذا تم توفيرها - if callback and callable(callback): - callback(self.processing_results) - - def _analyze_query(self, query): - """تحليل نوع الاستعلام""" - query = query.lower() - - # تحديد نوع الاستعلام بناءً على الكلمات المفتاحية - if any(keyword in query for keyword in ["تحليل المستند", "تحليل وثيقة", "استخراج بيانات", "قراءة مستند"]): - return "document_analysis" - elif any(keyword in query for keyword in ["تسعير", "سعر", "تكلفة", "ميزانية", "تقدير"]): - return "pricing" - elif any(keyword in query for keyword in ["مخاطر", "تحليل المخاطر", "تقييم المخاطر"]): - return "risk_analysis" - elif any(keyword in query for keyword in ["مشروع", "إدارة المشروع", "جدول زمني", "خطة"]): - return "project_management" - elif any(keyword in query for keyword in ["تقرير", "إحصائيات", "تحليل البيانات", "رسم بياني"]): - return "reporting" - else: - return "general" - - def _handle_document_analysis_query(self, query, context): - """معالجة استعلام تحليل المستندات""" - # محاكاة استجابة المساعد الذكي لاستعلام تحليل المستندات - response = """ -يمكنني مساعدتك في تحليل المستندات واستخراج المعلومات المهمة منها. لتحليل مستند، يرجى اتباع الخطوات التالية: - -1. انتقل إلى وحدة "تحليل المستندات" من القائمة الجانبية. -2. انقر على زر "تحميل مستند" واختر المستند المراد تحليله. -3. حدد نوع المستند (مناقصة، عقد، مواصفات فنية، إلخ). -4. انقر على زر "تحليل" لبدء عملية التحليل. - -سيقوم النظام باستخراج المعلومات التالية من المستند: -- البنود والكميات -- الكيانات (العميل، الموقع، المقاول، إلخ) -- التواريخ المهمة -- المبالغ والتكاليف -- المخاطر المحتملة - -بعد اكتمال التحليل، يمكنك مراجعة النتائج وتعديلها إذا لزم الأمر، ثم استخدامها في وحدات النظام الأخرى مثل التسعير وتحليل المخاطر. -""" - - # إضافة مراجع ذات صلة - self.processing_results["references"] = [ - {"title": "دليل استخدام وحدة تحليل المستندات", "type": "manual"}, - {"title": "أنواع المستندات المدعومة", "type": "documentation"}, - {"title": "تقنيات استخراج البيانات من المستندات", "type": "article"} - ] - - return response - - def _handle_pricing_query(self, query, context): - """معالجة استعلام التسعير""" - # محاكاة استجابة المساعد الذكي لاستعلام التسعير - response = """ -يمكنني مساعدتك في تسعير المشاريع وتقدير التكاليف. لإنشاء تسعير لمشروع، يرجى اتباع الخطوات التالية: - -1. انتقل إلى وحدة "التسعير المتكامل" من القائمة الجانبية. -2. اختر المشروع المراد تسعيره أو أنشئ مشروعاً جديداً. -3. أدخل بنود المشروع والكميات التقديرية (يمكن استيرادها من نتائج تحليل المستندات). -4. حدد الموارد المطلوبة (مواد، معدات، عمالة). -5. اختر استراتيجية التسعير المناسبة: - - شاملة: تغطية كاملة للتكاليف والمخاطر مع هامش ربح مناسب. - - تنافسية: تخفيض الهوامش لتقديم سعر تنافسي. - - متوازنة: توازن بين الربحية والتنافسية. -6. انقر على زر "حساب التسعير" لإنشاء التسعير. - -سيقوم النظام بحساب: -- التكاليف المباشرة (بنود المشروع) -- التكاليف غير المباشرة (نفقات عامة، إدارية، ربح) -- تكاليف المخاطر -- ضريبة القيمة المضافة -- السعر النهائي - -يمكنك تعديل المعلمات وإعادة حساب التسعير، ثم تصدير النتائج إلى تقرير مفصل. -""" - - # إضافة مراجع ذات صلة - self.processing_results["references"] = [ - {"title": "دليل استخدام وحدة التسعير المتكامل", "type": "manual"}, - {"title": "استراتيجيات التسعير", "type": "documentation"}, - {"title": "حساب التكاليف غير المباشرة", "type": "article"} - ] - - return response - - def _handle_risk_analysis_query(self, query, context): - """معالجة استعلام تحليل المخاطر""" - # محاكاة استجابة المساعد الذكي لاستعلام تحليل المخاطر - response = """ -يمكنني مساعدتك في تحليل وإدارة مخاطر المشروع. لإجراء تحليل للمخاطر، يرجى اتباع الخطوات التالية: - -1. انتقل إلى وحدة "تحليل المخاطر" من القائمة الجانبية. -2. اختر المشروع المراد تحليل مخاطره. -3. اختر طريقة التحليل: - - شاملة: تحليل مفصل يغطي جميع جوانب المشروع. - - أساسية: تحليل سريع للمخاطر الرئيسية. -4. انقر على زر "تحليل المخاطر" لبدء التحليل. - -سيقوم النظام بما يلي: -- تحديد المخاطر المحتملة بناءً على بيانات المشروع -- تصنيف المخاطر إلى فئات (فني، مالي، إداري، إلخ) -- إنشاء مصفوفة المخاطر (الاحتمالية × التأثير) -- تطوير استراتيجيات التخفيف لكل مخاطرة -- إنشاء ملخص للمخاطر وتوصيات - -يمكنك مراجعة نتائج التحليل وتعديلها، ثم تصدير التقرير النهائي واستخدامه في خطة إدارة المشروع. -""" - - # إضافة مراجع ذات صلة - self.processing_results["references"] = [ - {"title": "دليل استخدام وحدة تحليل المخاطر", "type": "manual"}, - {"title": "منهجيات تحليل المخاطر", "type": "documentation"}, - {"title": "استراتيجيات التخفيف من المخاطر", "type": "article"} - ] - - return response - - def _handle_project_management_query(self, query, context): - """معالجة استعلام إدارة المشاريع""" - # محاكاة استجابة المساعد الذكي لاستعلام إدارة المشاريع - response = """ -يمكنني مساعدتك في إدارة المشاريع وتتبع تقدمها. لإدارة مشروع، يرجى اتباع الخطوات التالية: - -1. انتقل إلى وحدة "إدارة المشاريع" من القائمة الجانبية. -2. أنشئ مشروعاً جديداً أو اختر مشروعاً موجوداً. -3. أدخل معلومات المشروع الأساسية (الاسم، العميل، الوصف، التواريخ). -4. أضف بنود المشروع (يمكن استيرادها من نتائج تحليل المستندات). -5. أنشئ الجدول الزمني للمشروع وحدد المراحل والمهام. -6. عين الموارد للمهام وحدد التبعيات بينها. - -يمكنك استخدام وحدة إدارة المشاريع لـ: -- تتبع تقدم المشروع ومقارنته بالخطة -- إدارة الموارد وتوزيعها -- متابعة المشكلات والمخاطر -- إدارة التغييرات في نطاق العمل -- إنشاء تقارير حالة المشروع - -كما يمكنك دمج نتائج التسعير وتحليل المخاطر في خطة المشروع لإدارة شاملة. -""" - - # إضافة مراجع ذات صلة - self.processing_results["references"] = [ - {"title": "دليل استخدام وحدة إدارة المشاريع", "type": "manual"}, - {"title": "أفضل ممارسات إدارة المشاريع", "type": "documentation"}, - {"title": "إنشاء جداول زمنية فعالة", "type": "article"} - ] - - return response - - def _handle_reporting_query(self, query, context): - """معالجة استعلام التقارير""" - # محاكاة استجابة المساعد الذكي لاستعلام التقارير - response = """ -يمكنني مساعدتك في إنشاء تقارير وتحليلات للمشاريع والمناقصات. لإنشاء تقرير، يرجى اتباع الخطوات التالية: - -1. انتقل إلى وحدة "التقارير والتحليلات" من القائمة الجانبية. -2. اختر نوع التقرير: - - تقرير المشروع: معلومات شاملة عن مشروع محدد - - تقرير التسعير: تفاصيل تسعير مشروع أو مناقصة - - تقرير المخاطر: تحليل مخاطر المشروع واستراتيجيات التخفيف - - تقرير الأداء: مقارنة الأداء الفعلي بالمخطط - - تقرير مالي: تحليل مالي للمشاريع والمناقصات -3. حدد معلمات التقرير (المشروع، الفترة الزمنية، إلخ). -4. انقر على زر "إنشاء التقرير". - -يمكنك تخصيص التقارير بإضافة أو إزالة أقسام، وتغيير طريقة عرض البيانات (جداول، رسوم بيانية، إلخ). - -التقارير المنشأة يمكن: -- تصديرها بتنسيقات مختلفة (PDF، Excel، Word) -- مشاركتها مع أعضاء الفريق أو العملاء -- جدولتها للإنشاء التلقائي بشكل دوري -- حفظها كقوالب لاستخدامها في المستقبل -""" - - # إضافة مراجع ذات صلة - self.processing_results["references"] = [ - {"title": "دليل استخدام وحدة التقارير والتحليلات", "type": "manual"}, - {"title": "أنواع التقارير المتاحة", "type": "documentation"}, - {"title": "إنشاء رسوم بيانية فعالة", "type": "article"} - ] - - return response - - def _handle_general_query(self, query, context): - """معالجة استعلام عام""" - # محاكاة استجابة المساعد الذكي لاستعلام عام - response = """ -مرحباً بك في المساعد الذكي لنظام إدارة المناقصات Hybrid Face. يمكنني مساعدتك في مجموعة متنوعة من المهام المتعلقة بإدارة المناقصات والمشاريع. - -يمكنني مساعدتك في: -- تحليل مستندات المناقصات واستخراج المعلومات المهمة منها -- تسعير المشاريع وتقدير التكاليف -- تحليل وإدارة مخاطر المشاريع -- إدارة المشاريع وتتبع تقدمها -- إنشاء تقارير وتحليلات - -للحصول على مساعدة محددة، يرجى طرح سؤال يتعلق بإحدى هذه المجالات. على سبيل المثال: -- "كيف يمكنني تحليل مستند مناقصة؟" -- "ساعدني في تسعير مشروع جديد" -- "كيف أقوم بتحليل مخاطر المشروع؟" -- "أريد إنشاء تقرير عن حالة المشروع" - -يمكنك أيضاً استخدام الوحدات المختلفة في النظام مباشرة من القائمة الجانبية. -""" - - # إضافة مراجع ذات صلة - self.processing_results["references"] = [ - {"title": "دليل المستخدم الشامل", "type": "manual"}, - {"title": "نظرة عامة على النظام", "type": "documentation"}, - {"title": "الأسئلة الشائعة", "type": "faq"} + logging.error(f"خطأ أثناء تحليل الصورة: {str(e)}") + import traceback + stack_trace = traceback.format_exc() + return {"error": f"فشل في تحليل الصورة: {str(e)}\n{stack_trace}"} + + def chat_completion(self, messages, model_name="claude-3-7-sonnet"): + """ + إكمال محادثة باستخدام نموذج Claude AI + + المعلمات: + messages: سجل المحادثة + model_name: اسم نموذج Claude المراد استخدامه + + العوائد: + dict: نتائج الإكمال + """ + try: + # الحصول على مفتاح API + api_key = self.get_api_key() + + # تحويل رسائل streamlit إلى تنسيق Claude API + claude_messages = [] + for msg in messages: + claude_messages.append({ + "role": msg["role"], + "content": msg["content"] + }) + + # التحقق من اسم النموذج وتصحيحه إذا لزم الأمر + model_name = self.get_model_full_name(model_name) + + # إعداد البيانات للطلب + headers = { + "Content-Type": "application/json", + "x-api-key": api_key, + "anthropic-version": "2023-06-01" + } + + payload = { + "model": model_name, + "max_tokens": 2048, + "messages": claude_messages, + "temperature": 0.7 + } + + # إرسال الطلب إلى API + response = requests.post( + self.api_url, + headers=headers, + json=payload, + timeout=30 + ) + + # التحقق من نجاح الطلب + if response.status_code != 200: + error_message = f"فشل طلب API: {response.status_code}" + try: + error_details = response.json() + error_message += f"\nتفاصيل: {error_details}" + except: + error_message += f"\nتفاصيل: {response.text}" + + return {"error": error_message} + + # معالجة الاستجابة + result = response.json() + + return { + "success": True, + "content": result["content"][0]["text"], + "model": result["model"], + "usage": result.get("usage", {}) + } + + 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: + """وحدة المساعد الذكي""" + + def __init__(self): + """تهيئة وحدة المساعد الذكي""" + # تحميل النماذج عند بدء التشغيل + self.cost_model = load_cost_prediction_model() + self.document_model = load_document_classifier_model() + self.risk_model = load_risk_assessment_model() + self.local_content_model = load_local_content_model() + self.entity_model = load_entity_recognition_model() + + # إنشاء خدمة Claude AI + self.claude_service = ClaudeAIService() + + # تهيئة قائمة الأسئلة والإجابات الشائعة + 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": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً." + } ] - - return response - - def _generate_suggestions(self, query_type, query, response): - """توليد اقتراحات للمستخدم بناءً على الاستعلام والاستجابة""" - suggestions = [] - - if query_type == "document_analysis": - suggestions = [ - "كيف يمكنني استيراد نتائج تحليل المستندات إلى وحدة التسعير؟", - "ما هي أنواع المستندات المدعومة للتحليل؟", - "كيف يمكنني تحسين دقة تحليل المستندات؟" - ] - elif query_type == "pricing": - suggestions = [ - "ما هي استراتيجية التسعير المناسبة لمشروعي؟", - "كيف يمكنني حساب التكاليف غير المباشرة؟", - "كيف أضيف تكاليف المخاطر إلى التسعير؟" - ] - elif query_type == "risk_analysis": - suggestions = [ - "ما هي أفضل استراتيجيات التخفيف من المخاطر؟", - "كيف يمكنني تحديد المخاطر الحرجة في المشروع؟", - "كيف أدمج تحليل المخاطر في خطة المشروع؟" - ] - elif query_type == "project_management": - suggestions = [ - "كيف أنشئ جدولاً زمنياً فعالاً للمشروع؟", - "كيف أتتبع تقدم المشروع مقارنة بالخطة؟", - "كيف أدير التغييرات في نطاق المشروع؟" - ] - elif query_type == "reporting": - suggestions = [ - "ما هي أنواع التقارير المتاحة في النظام؟", - "كيف يمكنني تخصيص تقرير المشروع؟", - "كيف أقوم بجدولة إنشاء تقارير دورية؟" + + def render(self): + """عرض واجهة وحدة المساعد الذكي""" + + st.markdown("

وحدة المساعد الذكي

", unsafe_allow_html=True) + + tabs = st.tabs([ + "المساعد الذكي", + "التنبؤ بالتكاليف", + "تحليل المخاطر", + "تحليل المستندات", + "المحتوى المحلي", + "الأسئلة الشائعة" + ]) + + with tabs[0]: + self._render_ai_assistant_tab() + + with tabs[1]: + self._render_cost_prediction_tab() + + with tabs[2]: + self._render_risk_analysis_tab() + + with tabs[3]: + self._render_document_analysis_tab() + + with tabs[4]: + self._render_local_content_tab() + + with tabs[5]: + self._render_faq_tab() + + def _render_ai_assistant_tab(self): + """عرض تبويب المساعد الذكي مع دعم Claude AI""" + + st.markdown("### المساعد الذكي لتسعير المناقصات") + + # اختيار نموذج Claude + claude_models = self.claude_service.get_available_models() + + selected_model = st.radio( + "اختر نموذج الذكاء الاصطناعي", + options=list(claude_models.keys()), + format_func=lambda x: claude_models[x], + horizontal=True, + key="assistant_ai_model" + ) + + # عرض واجهة المحادثة + st.markdown(""" +
+
+

المساعد الذكي

+

تحدث مع المساعد الذكي للحصول على المساعدة في تسعير المناقصات وتحليل البيانات

+
+
+ """, unsafe_allow_html=True) + + # تهيئة محفوظات المحادثة في حالة الجلسة إذا لم تكن موجودة + if 'ai_assistant_messages' not in st.session_state: + st.session_state.ai_assistant_messages = [ + {"role": "assistant", "content": "مرحباً! أنا المساعد الذكي لنظام تسعير المناقصات. كيف يمكنني مساعدتك اليوم؟"} ] + + # عرض محفوظات المحادثة بتنسيق محسن + chat_container = st.container() + with chat_container: + for message in st.session_state.ai_assistant_messages: + if message["role"] == "user": + st.markdown(f""" +
+
+ {message["content"]} +
+
+ """, unsafe_allow_html=True) + else: + st.markdown(f""" +
+
+ {message["content"]} +
+
+ """, unsafe_allow_html=True) + + # إضافة خيار رفع الملفات + uploaded_file = st.file_uploader( + "اختياري: ارفع ملفًا للمساعدة (صورة، PDF)", + type=["jpg", "jpeg", "png", "pdf"], + key="assistant_file_upload" + ) + + # مربع إدخال الرسالة + user_input = st.text_input("اكتب رسالتك هنا", key="ai_assistant_input") + + # التحقق من وجود مفتاح 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 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: + st.error("تحليل ملفات PDF يتطلب تثبيت مكتبة pdf2image.") + response = "عذراً، لا يمكنني تحليل ملفات PDF في الوقت الحالي. يرجى تحويل الملف إلى صورة أو مشاركة المعلومات كنص." + + # تحليل الصورة باستخدام Claude + prompt = f"المستخدم قام برفع هذه الصورة وسأل: {user_input}\nقم بتحليل الصورة والرد على سؤال المستخدم بشكل تفصيلي." + results = self.claude_service.analyze_image(temp_file_path, prompt, model_name=selected_model) + + # حذف الملف المؤقت + try: + os.remove(temp_file_path) + except: + pass + + if "error" in results: + response = f"عذراً، حدث خطأ أثناء تحليل الملف: {results['error']}" + else: + response = results["content"] + else: + # استخدام خدمة Claude للرد على الرسائل النصية + results = self.claude_service.chat_completion(st.session_state.ai_assistant_messages, model_name=selected_model) + + if "error" in results: + response = f"عذراً، حدث خطأ أثناء معالجة طلبك: {results['error']}" + else: + response = results["content"] + + # إضافة رد المساعد إلى المحفوظات + st.session_state.ai_assistant_messages.append({"role": "assistant", "content": response}) + + # عرض رد المساعد + with chat_container: + st.markdown(f""" +
+
+ {response} +
+
+ """, unsafe_allow_html=True) + + # إعادة تعيين قيمة الإدخال + st.text_input("اكتب رسالتك هنا", value="", key="ai_assistant_input_reset") + + def _generate_ai_response(self, user_input, model_name="claude-3-7-sonnet"): + """توليد رد المساعد الذكي باستخدام Claude AI""" + + # التحقق من وجود مفتاح API + try: + self.claude_service.get_api_key() + except ValueError: + return "عذراً، لا يمكنني الاتصال بخدمة الذكاء الاصطناعي في الوقت الحالي. يرجى التحقق من إعدادات API." + + # البحث في الأسئلة الشائعة أولاً + for faq in self.faqs: + if any(keyword in user_input.lower() for keyword in faq["question"].lower().split()): + return f"{faq['answer']}\n\nهل تحتاج إلى مساعدة أخرى؟" + + # إنشاء محادثة لإرسالها إلى Claude + messages = [ + {"role": "user", "content": user_input} + ] + + # استدعاء خدمة Claude + results = self.claude_service.chat_completion(messages, model_name=model_name) + + if "error" in results: + # إذا فشل الاتصال، استخدم التوليد الافتراضي + logging.warning(f"فشل الاتصال بـ Claude AI: {results['error']}. استخدام التوليد الافتراضي.") + return self._generate_default_response(user_input) + else: + return results["content"] + + def _generate_default_response(self, user_input): + """توليد رد افتراضي في حالة عدم توفر Claude AI""" + + if "تسعير" in user_input or "سعر" in user_input or "تكلفة" in user_input: + return "يمكنك استخدام وحدة التنبؤ بالتكاليف لتقدير تكاليف المشروع بناءً على خصائصه. انتقل إلى تبويب 'التنبؤ بالتكاليف' وأدخل بيانات المشروع لتحصل على تقدير دقيق للتكاليف." + + elif "مخاطر" in user_input or "مخاطرة" in user_input: + return "يمكنك استخدام وحدة تحليل المخاطر لتقييم المخاطر المحتملة للمشروع. انتقل إلى تبويب 'تحليل المخاطر' وأدخل بيانات المشروع وعوامل المخاطرة لتحصل ��لى تحليل شامل للمخاطر واستراتيجيات الاستجابة المقترحة." + + elif "مستند" in user_input or "ملف" in user_input or "وثيقة" in user_input or "مناقصة" in user_input: + return "يمكنك استخدام وحدة تحليل المستندات لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها. انتقل إلى تبويب 'تحليل المستندات' وقم بتحميل ملفات المناقصة لتحصل على تحليل تفصيلي للمستندات." + + elif "محتوى محلي" in user_input or "محلي" in user_input: + return "يمكنك استخدام وحدة المحتوى المحلي لحساب وتحسين نسبة المحتوى المحلي في مشروعك. انتقل إلى تبويب 'المحتوى المحلي' وأدخل بيانات مكونات المشروع لتحصل على تحليل شامل للمحتوى المحلي واقتراحات لتحسينه." + + elif "تقرير" in user_input or "إحصائيات" in user_input or "بيانات" in user_input: + return "يمكنك استخدام وحدة التقارير والتحليلات للحصول على تقارير تفصيلية وإحصائيات عن المشاريع. يمكنك الوصول إليها من القائمة الرئيسية للنظام." + else: - suggestions = [ - "كيف يمكنني البدء باستخدام النظام؟", - "ما هي الوحدات المتاحة في النظام؟", - "كيف يمكنني إنشاء مشروع جديد؟" + 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'] ] - - return suggestions - - def get_processing_status(self): - """الحصول على حالة المعالجة الحالية""" - if not self.processing_in_progress: - if not self.processing_results: - return {"status": "لا توجد معالجة جارية"} - else: - return {"status": self.processing_results.get("status", "غير معروف")} - - return { - "status": "جاري المعالجة", - "query": self.current_query, - "start_time": self.processing_results.get("processing_start_time") + }) + + 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": "" } - - def get_processing_results(self): - """الحصول على نتائج المعالجة""" - return self.processing_results - - def get_conversation_history(self, limit=10): - """الحصول على سجل المحادثة""" - if limit and limit > 0: - return self.conversation_history[-limit:] - return self.conversation_history - - def clear_conversation_history(self): - """مسح سجل المحادثة""" - self.conversation_history = [] - return True - - def export_conversation_history(self, output_path=None): - """تصدير سجل المحادثة إلى ملف JSON""" - if not self.conversation_history: - logger.warning("لا يوجد سجل محادثة للتصدير") - return None - - if not output_path: - # إنشاء اسم ملف افتراضي - timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') - filename = f"conversation_history_{timestamp}.json" - output_path = os.path.join(self.exports_path, filename) - - try: - with open(output_path, 'w', encoding='utf-8') as f: - json.dump(self.conversation_history, f, ensure_ascii=False, indent=4) - - logger.info(f"تم تصدير سجل المحادثة إلى: {output_path}") - return output_path - - except Exception as e: - logger.error(f"خطأ في تصدير سجل المحادثة: {str(e)}") - return None + + # تقييم شامل للمخاطر + 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