|
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") |
|
|
|
|
|
if BIM_ANALYZER_AVAILABLE: |
|
self.bim_analyzer = BIMAnalyzer() |
|
|
|
|
|
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: قيمة السر |
|
""" |
|
|
|
env_var_name = f"HF_SECRET_{secret_name.upper()}" |
|
secret_value = os.environ.get(env_var_name) |
|
|
|
|
|
if not secret_value: |
|
secret_value = os.environ.get(secret_name) |
|
|
|
return secret_value |
|
|
|
def get_api_key(self): |
|
"""الحصول على مفتاح API من متغيرات البيئة""" |
|
|
|
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_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 |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
|
|
|
|
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_key = self.get_api_key() |
|
|
|
|
|
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 |
|
} |
|
|
|
|
|
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: |
|
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("جاري التفكير..."): |
|
|
|
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("جاري تحليل المستند..."): |
|
|
|
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] |
|
]) |
|
|
|
|
|
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)) |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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. التواصل مع فريق الدعم الفني عبر البريد الإلكتروني: [email protected] |
|
4. زيارة موقع الدعم الفني: https://support.wahbi-ai.com |
|
""" |
|
} |
|
] |
|
|
|
|
|
for i, faq in enumerate(faqs): |
|
with st.expander(faq["question"]): |
|
st.markdown(faq["answer"]) |
|
|
|
|
|
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() |
|
|