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"""
+
+ """, 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"""
+
+ """, 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