import os import sys import logging import base64 import json import time from datetime import datetime import io import tempfile from pathlib import Path import streamlit as st import requests from PIL import Image import pandas as pd import numpy as np # استيراد وحدة التحقق من الصور from image_validator import validate_image # محاولة استيراد المحللات المختلفة try: from document_analyzer import TextExtractor, ItemExtractor DOCUMENT_ANALYZER_AVAILABLE = True except ImportError: DOCUMENT_ANALYZER_AVAILABLE = False try: from contract_analyzer import ContractAnalyzer CONTRACT_ANALYZER_AVAILABLE = True except ImportError: CONTRACT_ANALYZER_AVAILABLE = False try: from bim_analyzer import BIMAnalyzer BIM_ANALYZER_AVAILABLE = True except ImportError: BIM_ANALYZER_AVAILABLE = False try: from cad_bim_analyzer import CADBIMAnalyzer CAD_BIM_ANALYZER_AVAILABLE = True except ImportError: CAD_BIM_ANALYZER_AVAILABLE = False try: from engineering_drawing_analyzer import EngineeringDrawingAnalyzer ENGINEERING_DRAWING_ANALYZER_AVAILABLE = True except ImportError: ENGINEERING_DRAWING_ANALYZER_AVAILABLE = False try: from data_integration import DataAIIntegration DATA_INTEGRATION_AVAILABLE = True except ImportError: DATA_INTEGRATION_AVAILABLE = False # تكوين التسجيل logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class AIAssistantApp: """ تطبيق المساعد الذكي المتكامل """ def __init__(self): """تهيئة تطبيق المساعد الذكي""" self.claude_service = ClaudeAIService() self.initialize_analyzers() self.setup_session_state() def initialize_analyzers(self): """تهيئة المحللات المختلفة""" # تهيئة محلل المستندات if DOCUMENT_ANALYZER_AVAILABLE: self.text_extractor = TextExtractor() self.item_extractor = ItemExtractor() # تهيئة محلل العقود if CONTRACT_ANALYZER_AVAILABLE: self.contract_analyzer = ContractAnalyzer(api_key_source="security_section") # تهيئة محلل BIM if BIM_ANALYZER_AVAILABLE: self.bim_analyzer = BIMAnalyzer() # تهيئة محلل CAD/BIM if CAD_BIM_ANALYZER_AVAILABLE: self.cad_bim_analyzer = CADBIMAnalyzer(claude_client=self.claude_service) # تهيئة محلل الرسومات الهندسية if ENGINEERING_DRAWING_ANALYZER_AVAILABLE: self.engineering_drawing_analyzer = EngineeringDrawingAnalyzer(claude_client=self.claude_service) # تهيئة وحدة تكامل البيانات if DATA_INTEGRATION_AVAILABLE: self.data_integration = DataAIIntegration() def setup_session_state(self): """إعداد حالة الجلسة""" if 'messages' not in st.session_state: st.session_state.messages = [] if 'selected_model' not in st.session_state: st.session_state.selected_model = "claude-3-7-sonnet" if 'analysis_results' not in st.session_state: st.session_state.analysis_results = {} if 'uploaded_files' not in st.session_state: st.session_state.uploaded_files = {} def render(self): """عرض واجهة المستخدم الرئيسية""" st.title("وحبي - المساعد الذكي للمناقصات والعقود") # عرض التبويبات tabs = st.tabs([ "المساعد الذكي", "تحليل المستندات", "تحليل العقود", "تحليل المخاطر", "تحليل التكاليف", "تحليل المحتوى المحلي", "الأسئلة الشائعة" ]) # تبويب المساعد الذكي with tabs[0]: self._render_ai_assistant_tab() # تبويب تحليل المستندات with tabs[1]: self._render_document_analysis_tab() # تبويب تحليل العقود with tabs[2]: self._render_contract_analysis_tab() # تبويب تحليل المخاطر with tabs[3]: self._render_risk_analysis_tab() # تبويب تحليل التكاليف with tabs[4]: self._render_cost_prediction_tab() # تبويب تحليل المحتوى المحلي with tabs[5]: self._render_local_content_tab() # تبويب الأسئلة الشائعة with tabs[6]: self._render_faq_tab() class ClaudeAIService: """ فئة خدمة Claude AI للتحليل الذكي """ def __init__(self): """تهيئة خدمة Claude AI""" self.api_url = "https://api.anthropic.com/v1/messages" def _get_huggingface_secret(self, secret_name): """ الحصول على قيمة السر من متغيرات البيئة في Hugging Face المعلمات: secret_name: اسم السر العوائد: str: قيمة السر """ # في بيئة Hugging Face، الأسرار متاحة كمتغيرات بيئة بتنسيق HF_SECRET_ env_var_name = f"HF_SECRET_{secret_name.upper()}" secret_value = os.environ.get(env_var_name) # إذا لم يتم العثور على السر في تنسيق Hugging Face، نحاول البحث عنه مباشرة if not secret_value: secret_value = os.environ.get(secret_name) return secret_value def get_api_key(self): """الحصول على مفتاح API من متغيرات البيئة""" # محاولة الحصول على المفتاح من أسرار Hugging Face أولاً api_key = self._get_huggingface_secret("anthropic") if not api_key: # محاولة الحصول على المفتاح من متغيرات البيئة العادية 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 - أسرع نموذج للمهام اليومية", "claude-3-opus": "Claude 3 Opus - أقوى نموذج للمهام المعقدة" } 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", "claude-3-opus": "claude-3-opus-20240229" } 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: # الحصول على مفتاح API api_key = self.get_api_key() # التحقق من صحة الصورة وتحسينها file_base64, file_type, is_valid, error_message = validate_image(image_path) if not is_valid: return {"error": f"عذراً، حدث خطأ أثناء تحليل الملف: {error_message}"} # التحقق من اسم النموذج وتصحيحه إذا لزم الأمر 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: 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": 4096, "messages": claude_messages } # إرسال الطلب إلى 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: logging.error(f"خطأ أثناء إكمال المحادثة: {str(e)}") return {"error": f"فشل في إكمال المحادثة: {str(e)}"} def analyze_document(self, file_path, analysis_type="comprehensive", model_name="claude-3-7-sonnet"): """ تحليل مستند باستخدام نموذج Claude AI المعلمات: file_path: مسار المستند المراد تحليله analysis_type: نوع التحليل المطلوب model_name: اسم نموذج Claude المراد استخدامه العوائد: dict: نتائج التحليل """ try: # التحقق من نوع الملف _, ext = os.path.splitext(file_path) ext = ext.lower() # تحديد نوع التحليل المناسب if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: # تحليل الصورة prompt = self._get_analysis_prompt(analysis_type, ext) return self.analyze_image(file_path, prompt, model_name) elif ext in ['.pdf', '.doc', '.docx', '.txt']: # تحليل المستند النصي (محاكاة) return { "success": True, "content": f"تم تحليل المستند {file_path} بنجاح. نوع التحليل: {analysis_type}", "model": model_name } else: return {"error": f"نوع الملف غير مدعوم: {ext}"} except Exception as e: logging.error(f"خطأ أثناء تحليل المستند: {str(e)}") return {"error": f"فشل في تحليل المستند: {str(e)}"} def _get_analysis_prompt(self, analysis_type, file_ext): """ الحصول على التوجيه المناسب لنوع التحليل المعلمات: analysis_type: نوع التحليل المطلوب file_ext: امتداد الملف العوائد: str: التوجيه المناسب """ if analysis_type == "items_extraction": return """ قم بتحليل هذه الصورة من مستند المناقصة واستخراج جميع البنود والمواصفات الفنية. قدم النتائج بتنسيق منظم مع ترقيم البنود وتصنيفها حسب الأقسام. """ elif analysis_type == "contract_terms": return """ قم بتحليل هذه الصورة من العقد واستخراج جميع الشروط التعاقدية المهمة. حدد الالتزامات والحقوق لكل طرف، والمواعيد النهائية، وشروط الدفع، والضمانات. """ elif analysis_type == "quantities": return """ قم بتحليل هذه الصورة من جدول الكميات واستخراج جميع البنود والكميات والأسعار. قدم ملخصاً للتكاليف الإجمالية وتحليلاً للبنود الرئيسية. """ elif analysis_type == "legal_requirements": return """ قم بتحليل هذه الصورة واستخراج جميع المتطلبات القانونية والتنظيمية. حدد الشروط القانونية الهامة، والمتطلبات التنظيمية، والالتزامات القانونية. """ else: # comprehensive return """ قم بتحليل هذه الصورة من مستند المناقصة وتقديم تحليل شامل للمحتوى. استخرج المعلومات الرئيسية، والبنود المهمة، والمواصفات الفنية، والشروط التعاقدية. """ # إضافة دوال تبويبات واجهة المستخدم def _render_ai_assistant_tab(self): """عرض تبويب المساعد الذكي""" st.header("المساعد الذكي") # اختيار نموذج الذكاء الاصطناعي models = self.claude_service.get_available_models() model_options = list(models.keys()) model_descriptions = list(models.values()) col1, col2 = st.columns([1, 3]) with col1: selected_index = st.selectbox( "اختر نموذج التحليل", range(len(model_options)), format_func=lambda i: model_descriptions[i] ) st.session_state.selected_model = model_options[selected_index] # عرض المحادثة السابقة for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # إدخال المستخدم if prompt := st.chat_input("اكتب سؤالك هنا..."): # إضافة رسالة المستخدم إلى المحادثة st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) # معالجة الرسالة وإضافة رد المساعد with st.chat_message("assistant"): with st.spinner("جاري التفكير..."): # إرسال المحادثة إلى Claude response = self.claude_service.chat_completion( st.session_state.messages, model_name=st.session_state.selected_model ) if "error" in response: st.error(response["error"]) content = "عذراً، حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى." else: content = response["content"] st.markdown(content) # إضافة رد المساعد إلى المحادثة st.session_state.messages.append({"role": "assistant", "content": content}) # إعادة تحميل الصفحة لتحديث المحادثة st.rerun() def _render_document_analysis_tab(self): """عرض تبويب تحليل المستندات""" st.header("تحليل المستندات") # اختيار نوع التحليل analysis_type = st.selectbox( "اختر نوع التحليل", [ "تحليل شامل", "استخراج البنود والمواصفات", "استخراج الشروط التعاقدية", "تحليل الكميات", "تحليل المتطلبات القانونية" ] ) # تحويل اختيار المستخدم إلى قيمة برمجية analysis_type_map = { "تحليل شامل": "comprehensive", "استخراج البنود والمواصفات": "items_extraction", "استخراج الشروط التعاقدية": "contract_terms", "تحليل الكميات": "quantities", "تحليل المتطلبات القانونية": "legal_requirements" } selected_analysis = analysis_type_map.get(analysis_type, "comprehensive") # رفع الملف uploaded_file = st.file_uploader( "ارفع ملف المستند (PDF, Word, صورة)", type=["pdf", "docx", "doc", "jpg", "jpeg", "png"] ) if uploaded_file is not None: # حفظ الملف مؤقتاً with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file: tmp_file.write(uploaded_file.getvalue()) tmp_path = tmp_file.name # عرض معلومات الملف file_details = { "اسم الملف": uploaded_file.name, "نوع الملف": uploaded_file.type, "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" } st.json(file_details) # زر التحليل if st.button("تحليل المستند"): with st.spinner("جاري تحليل المستند..."): # تحليل المستند باستخدام Claude result = self.claude_service.analyze_document( tmp_path, analysis_type=selected_analysis, model_name=st.session_state.selected_model ) if "error" in result: st.error(result["error"]) else: st.success("تم تحليل المستند بنجاح!") st.subheader("نتائج التحليل") st.markdown(result["content"]) # حفظ النتائج في حالة الجلسة if "document_analysis" not in st.session_state.analysis_results: st.session_state.analysis_results["document_analysis"] = [] st.session_state.analysis_results["document_analysis"].append({ "file_name": uploaded_file.name, "analysis_type": analysis_type, "result": result, "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # حذف الملف المؤقت بعد الانتهاء try: os.unlink(tmp_path) except: pass def _render_contract_analysis_tab(self): """عرض تبويب تحليل العقود""" st.header("تحليل العقود") if not CONTRACT_ANALYZER_AVAILABLE: st.warning("وحدة تحليل العقود غير متاحة حالياً.") return # اختيار نوع التحليل analysis_type = st.selectbox( "اختر نوع التحليل", [ "تحليل شامل للعقد", "تحليل الشروط المالية", "تحليل الشروط القانونية", "تحليل المخاطر التعاقدية", "تحليل الجدول الزمني" ], key="contract_analysis_type" ) # تحويل اختيار المستخدم إلى قيمة برمجية analysis_type_map = { "تحليل شامل للعقد": "comprehensive", "تحليل الشروط المالية": "financial", "تحليل الشروط القانونية": "legal", "تحليل المخاطر التعاقدية": "risk", "تحليل الجدول الزمني": "timeline" } selected_analysis = analysis_type_map.get(analysis_type, "comprehensive") # رفع الملف uploaded_file = st.file_uploader( "ارفع ملف العقد (PDF, Word)", type=["pdf", "docx", "doc"], key="contract_file_uploader" ) if uploaded_file is not None: # حفظ الملف مؤقتاً with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file: tmp_file.write(uploaded_file.getvalue()) tmp_path = tmp_file.name # عرض معلومات الملف file_details = { "اسم الملف": uploaded_file.name, "نوع الملف": uploaded_file.type, "حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" } st.json(file_details) # زر التحليل if st.button("تحليل العقد"): with st.spinner("جاري تحليل العقد..."): # تحليل العقد باستخدام محلل العقود result = self.contract_analyzer.analyze_contract( tmp_path, analysis_type=selected_analysis ) if isinstance(result, dict) and "error" in result: st.error(result["error"]) else: st.success("تم تحليل العقد بنجاح!") # عرض نتائج التحليل st.subheader("ملخص التحليل") st.markdown(result.get("summary", "لا يوجد ملخص متاح")) st.subheader("البنود الرئيسية") for i, clause in enumerate(result.get("key_clauses", [])): st.markdown(f"**{i+1}. {clause['title']}**") st.markdown(clause["description"]) st.subheader("المخاطر المحتملة") risk_data = [] for risk in result.get("risks", []): risk_data.append({ "المخاطرة": risk["description"], "المستوى": risk["level"], "التأثير": risk["impact"] }) if risk_data: st.dataframe(pd.DataFrame(risk_data)) # حفظ النتائج في حالة الجلسة if "contract_analysis" not in st.session_state.analysis_results: st.session_state.analysis_results["contract_analysis"] = [] st.session_state.analysis_results["contract_analysis"].append({ "file_name": uploaded_file.name, "analysis_type": analysis_type, "result": result, "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # حذف الملف المؤقت بعد الانتهاء try: os.unlink(tmp_path) except: pass def _render_risk_analysis_tab(self): """عرض تبويب تحليل المخاطر""" st.header("تحليل المخاطر") # إدخال بيانات المشروع st.subheader("بيانات المشروع") col1, col2 = st.columns(2) with col1: project_name = st.text_input("اسم المشروع") project_type = st.selectbox( "نوع المشروع", ["إنشائي", "طرق", "جسور", "مباني", "بنية تحتية", "أخرى"] ) with col2: project_value = st.number_input("قيمة المشروع (ريال)", min_value=0, value=1000000) project_duration = st.number_input("مدة المشروع (شهر)", min_value=1, value=12) # تحليل المخاطر if st.button("تحليل المخاطر"): with st.spinner("جاري تحليل المخاطر..."): # محاكاة تحليل المخاطر باستخدام الذكاء الاصطناعي time.sleep(1) # محاكاة وقت المعالجة # إنشاء مصفوفة المخاطر st.subheader("مصفوفة المخاطر") # إنشاء بيانات مصفوفة المخاطر risk_matrix = np.array([ [1, 2, 3], [2, 4, 6], [3, 6, 9] ]) # إنشاء مصفوفة المخاطر باستخدام plotly fig = px.imshow( risk_matrix, labels=dict(x="احتمال الحدوث", y="التأثير", color="درجة الخطورة"), x=["منخفض", "متوسط", "مرتفع"], y=["منخفض", "متوسط", "مرتفع"], color_continuous_scale=["green", "yellow", "red"], text_auto=True ) # تحسين مظهر المصفوفة fig.update_layout( width=600, height=500, title="مصفوفة المخاطر", font=dict(size=14), coloraxis_colorbar=dict( title=dict( text="درجة الخطورة", side="right" ) ) ) st.plotly_chart(fig) # عرض جدول المخاطر st.subheader("تحليل المخاطر المحتملة") # إنشاء بيانات المخاطر risks_data = [ {"المخاطرة": "تأخر توريد المواد", "الاحتمال": "مرتفع", "التأثير": "متوسط", "الدرجة": 6, "الإجراء": "التعاقد مع موردين بدلاء"}, {"المخاطرة": "تغير أسعار المواد", "الاحتمال": "متوسط", "التأثير": "مرتفع", "الدرجة": 6, "الإجراء": "تضمين بند تعديل الأسعار في العقد"}, {"المخاطرة": "ظروف جوية قاسية", "الاحتمال": "منخفض", "التأثير": "مرتفع", "الدرجة": 3, "الإجراء": "وضع خطة طوارئ للعمل"}, {"المخاطرة": "نقص العمالة", "الاحتمال": "متوسط", "التأثير": "متوسط", "الدرجة": 4, "الإجراء": "التعاقد مع شركات توريد عمالة"}, {"المخاطرة": "مشاكل فنية", "الاحتمال": "متوسط", "التأثير": "مرتفع", "الدرجة": 6, "الإجراء": "توفير استشاريين فنيين"} ] # عرض جدول المخاطر st.dataframe(pd.DataFrame(risks_data)) # تحليل ذكي للمخاطر باستخدام Claude st.subheader("تحليل ذكي للمخاطر") # إنشاء توجيه للنموذج prompt = f""" قم بتحليل المخاطر المحتملة لمشروع بالمواصفات التالية: - اسم المشروع: {project_name} - نوع المشروع: {project_type} - قيمة المشروع: {project_value} ريال - مدة المشروع: {project_duration} شهر قدم تحليلاً شاملاً للمخاطر المحتملة وتوصيات للتخفيف منها. """ # استدعاء النموذج response = self.claude_service.chat_completion( [{"role": "user", "content": prompt}], model_name=st.session_state.selected_model ) if "error" in response: st.error(response["error"]) else: st.markdown(response["content"]) # حفظ النتائج في حالة الجلسة if "risk_analysis" not in st.session_state.analysis_results: st.session_state.analysis_results["risk_analysis"] = [] st.session_state.analysis_results["risk_analysis"].append({ "project_name": project_name, "project_type": project_type, "project_value": project_value, "project_duration": project_duration, "result": response["content"], "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) def _render_cost_prediction_tab(self): """عرض تبويب التنبؤ بالتكاليف""" st.header("تحليل وتنبؤ التكاليف") # إدخال بيانات المشروع st.subheader("بيانات المشروع") col1, col2 = st.columns(2) with col1: project_name = st.text_input("اسم المشروع", key="cost_project_name") project_type = st.selectbox( "نوع المشروع", ["إنشائي", "طرق", "جسور", "مباني", "بنية تحتية", "أخرى"], key="cost_project_type" ) project_area = st.number_input("المساحة (متر مربع)", min_value=0, value=1000) with col2: project_location = st.selectbox( "الموقع", ["الرياض", "جدة", "الدمام", "مكة", "المدينة", "أخرى"] ) project_quality = st.selectbox( "مستوى الجودة", ["اقتصادي", "متوسط", "فاخر", "ممتاز"] ) has_basement = st.checkbox("يتضمن بدروم") # أقسام التكاليف st.subheader("أقسام التكاليف") col1, col2, col3 = st.columns(3) with col1: structural_percent = st.slider("الهيكل الإنشائي (%)", 0, 100, 35) with col2: finishing_percent = st.slider("التشطيبات (%)", 0, 100, 40) with col3: mep_percent = st.slider("الكهروميكانيك (%)", 0, 100, 25) # التنبؤ بالتكاليف if st.button("تحليل التكاليف"): with st.spinner("جاري تحليل التكاليف..."): # محاكاة تحليل التكاليف باستخدام الذكاء الاصطناعي time.sleep(1) # محاكاة وقت المعالجة # حساب التكلفة التقديرية بناءً على المدخلات base_cost = 0 if project_type == "إنشائي": base_cost = 2000 elif project_type == "طرق": base_cost = 1500 elif project_type == "جسور": base_cost = 3000 elif project_type == "مباني": base_cost = 2500 elif project_type == "بنية تحتية": base_cost = 1800 else: base_cost = 2200 # تعديل التكلفة بناءً على الموقع location_factor = 1.0 if project_location == "الرياض": location_factor = 1.1 elif project_location == "جدة": location_factor = 1.15 elif project_location == "الدمام": location_factor = 1.05 elif project_location == "مكة": location_factor = 1.2 elif project_location == "المدينة": location_factor = 1.1 # تعديل التكلفة بناءً على مستوى الجودة quality_factor = 1.0 if project_quality == "اقتصادي": quality_factor = 0.8 elif project_quality == "متوسط": quality_factor = 1.0 elif project_quality == "فاخر": quality_factor = 1.3 elif project_quality == "ممتاز": quality_factor = 1.5 # تعديل التكلفة إذا كان يتضمن بدروم basement_factor = 1.2 if has_basement else 1.0 # حساب التكلفة الإجمالية total_cost = base_cost * project_area * location_factor * quality_factor * basement_factor # حساب تكلفة كل قسم structural_cost = total_cost * (structural_percent / 100) finishing_cost = total_cost * (finishing_percent / 100) mep_cost = total_cost * (mep_percent / 100) # عرض النتائج st.subheader("نتائج تحليل التكاليف") col1, col2 = st.columns(2) with col1: st.metric("التكلفة الإجمالية التقديرية", f"{total_cost:,.2f} ريال") st.metric("تكلفة المتر المربع", f"{total_cost/project_area:,.2f} ريال/م²") with col2: st.metric("تكلفة الهيكل الإنشائي", f"{structural_cost:,.2f} ريال") st.metric("تكلفة التشطيبات", f"{finishing_cost:,.2f} ريال") st.metric("تكلفة الكهروميكانيك", f"{mep_cost:,.2f} ريال") # عرض الرسم البياني للتكاليف cost_data = pd.DataFrame({ "القسم": ["الهيكل الإنشائي", "التشطيبات", "الكهروميكانيك"], "التكلفة": [structural_cost, finishing_cost, mep_cost] }) fig = px.pie( cost_data, values="التكلفة", names="القسم", title="توزيع التكاليف", color_discrete_sequence=px.colors.qualitative.Set2 ) fig.update_layout( font=dict(size=14), legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) ) st.plotly_chart(fig) # تحليل ذكي للتكاليف باستخدام Claude st.subheader("تحليل ذكي للتكاليف") # إنشاء توجيه للنموذج prompt = f""" قم بتحليل تكاليف مشروع بالمواصفات التالية: - اسم المشروع: {project_name} - نوع المشروع: {project_type} - المساحة: {project_area} متر مربع - الموقع: {project_location} - مستوى الجودة: {project_quality} - يتضمن بدروم: {"نعم" if has_basement else "لا"} التكلفة الإجمالية التقديرية: {total_cost:,.2f} ريال تكلفة المتر المربع: {total_cost/project_area:,.2f} ريال/م² توزيع التكاليف: - الهيكل الإنشائي: {structural_cost:,.2f} ريال ({structural_percent}%) - التشطيبات: {finishing_cost:,.2f} ريال ({finishing_percent}%) - الكهروميكانيك: {mep_cost:,.2f} ريال ({mep_percent}%) قدم تحليلاً شاملاً للتكاليف وتوصيات لتحسين الكفاءة وتقليل التكاليف. """ # استدعاء النموذج response = self.claude_service.chat_completion( [{"role": "user", "content": prompt}], model_name=st.session_state.selected_model ) if "error" in response: st.error(response["error"]) else: st.markdown(response["content"]) # حفظ النتائج في حالة الجلسة if "cost_analysis" not in st.session_state.analysis_results: st.session_state.analysis_results["cost_analysis"] = [] st.session_state.analysis_results["cost_analysis"].append({ "project_name": project_name, "project_type": project_type, "project_area": project_area, "project_location": project_location, "project_quality": project_quality, "has_basement": has_basement, "total_cost": total_cost, "result": response["content"], "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) def _render_local_content_tab(self): """عرض تبويب تحليل المحتوى المحلي""" st.header("تحليل المحتوى المحلي") # إدخال بيانات المشروع st.subheader("بيانات المشروع") col1, col2 = st.columns(2) with col1: project_name = st.text_input("اسم المشروع", key="local_project_name") project_type = st.selectbox( "نوع المشروع", ["إنشائي", "طرق", "جسور", "مباني", "بنية تحتية", "أخرى"], key="local_project_type" ) with col2: project_value = st.number_input("قيمة المشروع (ريال)", min_value=0, value=1000000, key="local_project_value") project_duration = st.number_input("مدة المشروع (شهر)", min_value=1, value=12, key="local_project_duration") # إدخال بيانات المحتوى المحلي st.subheader("بيانات المحتوى المحلي") # إضافة مكونات المشروع components = [] st.write("أضف مكونات المشروع:") # نموذج إضافة مكون جديد with st.form("add_component_form"): col1, col2, col3 = st.columns(3) with col1: component_name = st.text_input("اسم المكون") with col2: component_value = st.number_input("القيمة (ريال)", min_value=0) with col3: local_percentage = st.slider("نسبة المحتوى المحلي (%)", 0, 100, 50) submitted = st.form_submit_button("إضافة المكون") if submitted and component_name and component_value > 0: # إضافة المكون إلى القائمة if "local_content_components" not in st.session_state: st.session_state.local_content_components = [] st.session_state.local_content_components.append({ "name": component_name, "value": component_value, "local_percentage": local_percentage }) # عرض المكونات المضافة if "local_content_components" in st.session_state and st.session_state.local_content_components: st.subheader("المكونات المضافة") components_data = [] for i, component in enumerate(st.session_state.local_content_components): components_data.append({ "#": i + 1, "المكون": component["name"], "القيمة (ريال)": component["value"], "نسبة المحتوى المحلي (%)": component["local_percentage"], "قيمة المحتوى المحلي (ريال)": component["value"] * (component["local_percentage"] / 100) }) st.dataframe(pd.DataFrame(components_data)) # زر حذف جميع المكونات if st.button("حذف جميع المكونات"): st.session_state.local_content_components = [] st.rerun() # تحليل المحتوى المحلي if st.button("تحليل المحتوى المحلي"): if "local_content_components" not in st.session_state or not st.session_state.local_content_components: st.warning("يرجى إضافة مكونات المشروع أولاً") else: with st.spinner("جاري تحليل المحتوى المحلي..."): # حساب إجمالي قيمة المشروع من المكونات total_value = sum(component["value"] for component in st.session_state.local_content_components) # حساب قيمة المحتوى المحلي local_content_value = sum( component["value"] * (component["local_percentage"] / 100) for component in st.session_state.local_content_components ) # حساب نسبة المحتوى المحلي الإجمالية overall_local_percentage = (local_content_value / total_value) * 100 if total_value > 0 else 0 # عرض النتائج st.subheader("نتائج تحليل المحتوى المحلي") col1, col2 = st.columns(2) with col1: st.metric("إجمالي قيمة المشروع", f"{total_value:,.2f} ريال") st.metric("قيمة المحتوى المحلي", f"{local_content_value:,.2f} ريال") with col2: st.metric("نسبة المحتوى المحلي", f"{overall_local_percentage:.2f}%") # تقييم مستوى المحتوى المحلي if overall_local_percentage >= 70: st.success("مستوى ممتاز من المحتوى المحلي") elif overall_local_percentage >= 50: st.info("مستوى جيد من المحتوى المحلي") elif overall_local_percentage >= 30: st.warning("مستوى متوسط من المحتوى المحلي") else: st.error("مستوى منخفض من المحتوى المحلي") # عرض الرسم البياني للمحتوى المحلي st.subheader("توزيع المحتوى المحلي") # إعداد بيانات الرسم البياني chart_data = [] for component in st.session_state.local_content_components: local_value = component["value"] * (component["local_percentage"] / 100) non_local_value = component["value"] - local_value chart_data.append({ "المكون": component["name"], "محتوى محلي": local_value, "محتوى غير محلي": non_local_value }) chart_df = pd.DataFrame(chart_data) # رسم بياني شريطي مكدس fig = px.bar( chart_df, x="المكون", y=["محتوى محلي", "محتوى غير محلي"], title="توزيع المحتوى المحلي حسب المكونات", color_discrete_sequence=["#2ca02c", "#d62728"] ) fig.update_layout( font=dict(size=14), legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) ) st.plotly_chart(fig) # تحليل ذكي للمحتوى المحلي باستخدام Claude st.subheader("تحليل ذكي للمحتوى المحلي") # إنشاء توجيه للنموذج components_text = "\n".join([ f"- {component['name']}: {component['value']:,.2f} ريال (نسبة المحتوى المحلي: {component['local_percentage']}%)" for component in st.session_state.local_content_components ]) prompt = f""" قم بتحليل المحتوى المحلي لمشروع بالمواصفات التالية: - اسم المشروع: {project_name} - نوع المشروع: {project_type} - قيمة المشروع: {total_value:,.2f} ريال - مدة المشروع: {project_duration} شهر مكونات المشروع: {components_text} نتائج التحليل: - إجمالي قيمة المشروع: {total_value:,.2f} ريال - قيمة المحتوى المحلي: {local_content_value:,.2f} ريال - نسبة المحتوى المحلي: {overall_local_percentage:.2f}% قدم تحليلاً شاملاً للمحتوى المحلي وتوصيات لتحسين نسبة المحتوى المحلي في المشروع. اشرح كيف يمكن زيادة المحتوى المحلي مع الحفاظ على جودة المشروع وتكلفته التنافسية. قدم أمثلة على موردين محليين يمكن الاستعانة بهم لزيادة المحتوى المحلي. """ # استدعاء النموذج response = self.claude_service.chat_completion( [{"role": "user", "content": prompt}], model_name=st.session_state.selected_model ) if "error" in response: st.error(response["error"]) else: st.markdown(response["content"]) # حفظ النتائج في حالة الجلسة if "local_content_analysis" not in st.session_state.analysis_results: st.session_state.analysis_results["local_content_analysis"] = [] st.session_state.analysis_results["local_content_analysis"].append({ "project_name": project_name, "project_type": project_type, "components": st.session_state.local_content_components.copy(), "total_value": total_value, "local_content_value": local_content_value, "overall_local_percentage": overall_local_percentage, "result": response["content"], "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) def _render_faq_tab(self): """عرض تبويب الأسئلة الشائعة""" st.header("الأسئلة الشائعة") # قائمة الأسئلة الشائعة faqs = [ { "question": "ما هو وحبي - المساعد الذكي للمناقصات والعقود؟", "answer": """ وحبي هو نظام ذكاء اصطناعي متكامل مصمم خصيصاً لتحليل المناقصات والعقود في قطاع البناء والإنشاءات. يوفر النظام مجموعة من الأدوات المتقدمة لتحليل المستندات، وتقييم المخاطر، وتحليل التكاليف، وتحليل المحتوى المحلي، بالإضافة إلى مساعد ذكي يمكنه الإجابة على الاستفسارات المتعلقة بالمناقصات والعقود. """ }, { "question": "ما هي أنواع الملفات المدعومة للتحليل؟", "answer": """ يدعم النظام مجموعة واسعة من أنواع الملفات، بما في ذلك: - ملفات PDF - مستندات Word (DOCX, DOC) - ملفات Excel (XLSX, XLS) - الصور (JPG, PNG) - رسومات CAD (DWG, DXF) - نماذج BIM (IFC, RVT) """ }, { "question": "كيف يمكنني تحليل مستند مناقصة؟", "answer": """ لتحليل مستند مناقصة، اتبع الخطوات التالية: 1. انتقل إلى تبويب "تحليل المستندات" 2. اختر نوع التحليل المناسب (تحليل شامل، استخراج البنود، إلخ) 3. ارفع ملف المستند باستخدام زر رفع الملفات 4. انقر على زر "تحليل المستند" 5. انتظر حتى يكتمل التحليل وعرض النتائج """ }, { "question": "كيف يمكنني تحليل المخاطر المحتملة في مشروع؟", "answer": """ لتحليل المخاطر المحتملة في مشروع، اتبع الخطوات التالية: 1. انتقل إلى تبويب "تحليل المخاطر" 2. أدخل بيانات المشروع (الاسم، النوع، القيمة، المدة) 3. انقر على زر "تحليل المخاطر" 4. سيعرض النظام مصفوفة المخاطر وجدول المخاطر المحتملة 5. سيقدم النظام أيضاً تحليلاً ذكياً للمخاطر وتوصيات للتخفيف منها """ }, { "question": "كيف يمكنني حساب نسبة المحتوى المحلي في مشروع؟", "answer": """ لحساب نسبة المحتوى المحلي في مشروع، اتبع الخطوات التالية: 1. انتقل إلى تبويب "تحليل المحتوى المحلي" 2. أدخل بيانات المشروع (الاسم، النوع، القيمة، المدة) 3. أضف مكونات المشروع مع تحديد قيمة كل مكون ونسبة المحتوى المحلي فيه 4. انقر على زر "تحليل المحتوى المحلي" 5. سيعرض النظام نتائج التحليل ورسوماً بيانية توضح توزيع المحتوى المحلي 6. سيقدم النظام أيضاً تحليلاً ذكياً وتوصيات لتحسين نسبة المحتوى المحلي """ }, { "question": "هل يمكنني استخدام المساعد الذكي للإجابة على أسئلة محددة؟", "answer": """ نعم، يمكنك استخدام المساعد الذكي للإجابة على أسئلة محددة حول المناقصات والعقود. انتقل إلى تبويب "المساعد الذكي" واكتب سؤالك في مربع الدردشة. يمكنك اختيار نموذج الذكاء الاصطناعي المناسب لاحتياجاتك من القائمة المنسدلة. """ }, { "question": "هل يمكنني تحليل رسومات CAD وملفات BIM؟", "answer": """ نعم، يدعم النظام تحليل رسومات CAD وملفات BIM. يمكنك رفع ملفات DWG أو DXF أو IFC أو RVT، وسيقوم النظام بتحليلها واستخراج المعلومات المهمة منها، مثل العناصر والطبقات والأبعاد. يمكن أيضاً تحليل التكاليف والمخاطر المرتبطة بالرسومات والنماذج. """ }, { "question": "كيف يمكنني الحصول على المساعدة؟", "answer": """ للحصول على المساعدة، يمكنك: 1. استخدام المساعد الذكي في تبويب "المساعد الذكي" لطرح أسئلتك 2. الاطلاع على الأسئلة الشائعة في هذا التبويب 3. التواصل مع فريق الدعم الفني عبر البريد الإلكتروني: support@wahbi-ai.com 4. زيارة موقع الدعم الفني: https://support.wahbi-ai.com """ } ] # عرض الأسئلة الشائعة for i, faq in enumerate(faqs): with st.expander(faq["question"]): st.markdown(faq["answer"]) # إضافة الدوال إلى فئة AIAssistantApp AIAssistantApp._render_ai_assistant_tab = _render_ai_assistant_tab AIAssistantApp._render_document_analysis_tab = _render_document_analysis_tab AIAssistantApp._render_contract_analysis_tab = _render_contract_analysis_tab AIAssistantApp._render_risk_analysis_tab = _render_risk_analysis_tab AIAssistantApp._render_cost_prediction_tab = _render_cost_prediction_tab AIAssistantApp._render_local_content_tab = _render_local_content_tab AIAssistantApp._render_faq_tab = _render_faq_tab # نقطة الدخول الرئيسية للتطبيق def main(): # تهيئة تطبيق المساعد الذكي ai_app = AIAssistantApp() # عرض واجهة المستخدم ai_app.render() if __name__ == "__main__": main()