Spaces:
Sleeping
Sleeping
# -*- coding: utf-8 -*- | |
""" | |
وحدة المساعد الذكي | |
هذا الملف يحتوي على الفئة الرئيسية لتطبيق المساعد الذكي مع دعم نموذج Claude AI. | |
""" | |
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 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 - أسرع نموذج للمهام اليومية" | |
} | |
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: | |
# الحصول على مفتاح 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: | |
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: | |
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": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً." | |
} | |
] | |
def render(self): | |
"""عرض واجهة وحدة المساعد الذكي""" | |
st.markdown("<h1 class='module-title'>وحدة المساعد الذكي</h1>", 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(""" | |
<div class="chat-container"> | |
<div class="chat-header"> | |
<h4>المساعد الذكي</h4> | |
<p>تحدث مع المساعد الذكي للحصول على المساعدة في تسعير المناقصات وتحليل البيانات</p> | |
</div> | |
</div> | |
""", 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""" | |
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px;"> | |
<div style="background-color: #e0f7fa; padding: 10px; border-radius: 10px; max-width: 80%;"> | |
{message["content"]} | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
else: | |
st.markdown(f""" | |
<div style="display: flex; justify-content: flex-start; margin-bottom: 10px;"> | |
<div style="background-color: #f0f0f0; padding: 10px; border-radius: 10px; max-width: 80%;"> | |
{message["content"]} | |
</div> | |
</div> | |
""", 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""" | |
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px;"> | |
<div style="background-color: #e0f7fa; padding: 10px; border-radius: 10px; max-width: 80%;"> | |
{user_input} | |
</div> | |
</div> | |
""", 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""" | |
<div style="display: flex; justify-content: flex-start; margin-bottom: 10px;"> | |
<div style="background-color: #f0f0f0; padding: 10px; border-radius: 10px; max-width: 80%;"> | |
{response} | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# إعادة تعيين قيمة الإدخال | |
st.text_input("اكتب رسالتك هنا", value="", key="ai_assistant_input_reset") | |
def _generate_ai_response(self, user_input, model_name="claude-3-7-sonnet"): | |
"""توليد رد المساعد الذكي باستخدام Claude AI""" | |
# التحقق من وجود مفتاح API | |
try: | |
self.claude_service.get_api_key() | |
except ValueError: | |
return "عذراً، لا يمكنني الاتصال بخدمة الذكاء الاصطناعي في الوقت الحالي. يرجى التحقق من إعدادات API." | |
# البحث في الأسئلة الشائعة أولاً | |
for faq in self.faqs: | |
if any(keyword in user_input.lower() for keyword in faq["question"].lower().split()): | |
return f"{faq['answer']}\n\nهل تحتاج إلى مساعدة أخرى؟" | |
# إنشاء محادثة لإرسالها إلى Claude | |
messages = [ | |
{"role": "user", "content": user_input} | |
] | |
# استدعاء خدمة Claude | |
results = self.claude_service.chat_completion(messages, model_name=model_name) | |
if "error" in results: | |
# إذا فشل الاتصال، استخدم التوليد الافتراضي | |
logging.warning(f"فشل الاتصال بـ Claude AI: {results['error']}. استخدام التوليد الافتراضي.") | |
return self._generate_default_response(user_input) | |
else: | |
return results["content"] | |
def _generate_default_response(self, user_input): | |
"""توليد رد افتراضي في حالة عدم توفر Claude AI""" | |
if "تسعير" in user_input or "سعر" in user_input or "تكلفة" in user_input: | |
return "يمكنك استخدام وحدة التنبؤ بالتكاليف لتقدير تكاليف المشروع بناءً على خصائصه. انتقل إلى تبويب 'التنبؤ بالتكاليف' وأدخل بيانات المشروع لتحصل على تقدير دقيق للتكاليف." | |
elif "مخاطر" in user_input or "مخاطرة" in user_input: | |
return "يمكنك استخدام وحدة تحليل المخاطر لتقييم المخاطر المحتملة للمشروع. انتقل إلى تبويب 'تحليل المخاطر' وأدخل بيانات المشروع وعوامل المخاطرة لتحصل على تحليل شامل للمخاطر واستراتيجيات الاستجابة المقترحة." | |
elif "مستند" in user_input or "ملف" in user_input or "وثيقة" in user_input or "مناقصة" in user_input: | |
return "يمكنك استخدام وحدة تحليل المستندات لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها. انتقل إلى تبويب 'تحليل المستندات' وقم بتحميل ملفات المناقصة لتحصل على تحليل تفصيلي للمستندات." | |
elif "محتوى محلي" in user_input or "محلي" in user_input: | |
return "يمكنك استخدام وحدة المحتوى المحلي لحساب وتحسين نسبة المحتوى المحلي في مشروعك. انتقل إلى تبويب 'المحتوى المحلي' وأدخل بيانات مكونات المشروع لتحصل على تحليل شامل للمحتوى المحلي واقتراحات لتحسينه." | |
elif "تقرير" in user_input or "إحصائيات" in user_input or "بيانات" in user_input: | |
return "يمكنك استخدام وحدة التقارير والتحليلات للحصول على تقارير تفصيلية وإحصائيات عن المشاريع. يمكنك الوصول إليها من القائمة الرئيسية للنظام." | |
else: | |
return "شكراً لاستفسارك. يمكنني مساعدتك في تسعير المناقصات، وتحليل المخاطر، وتحليل المستندات، وحساب المحتوى المحلي. يرجى توضيح استفسارك أكثر أو اختيار أحد الخيارات في الأعلى للحصول على المساعدة المطلوبة." | |
def _render_cost_prediction_tab(self): | |
"""عرض تبويب التنبؤ بالتكاليف""" | |
st.markdown("### التنبؤ بالتكاليف") | |
# عرض نموذج إدخال بيانات المشروع | |
st.markdown("#### بيانات المشروع") | |
col1, col2 = st.columns(2) | |
with col1: | |
project_type = st.selectbox( | |
"نوع المشروع", | |
[ | |
"مباني سكنية", | |
"مباني تجارية", | |
"مباني حكومية", | |
"مراكز صحية", | |
"مدارس", | |
"بنية تحتية", | |
"طرق", | |
"جسور", | |
"صرف صحي", | |
"مياه", | |
"كهرباء" | |
], | |
key="cost_project_type" | |
) | |
location = st.selectbox( | |
"الموقع", | |
[ | |
"الرياض", | |
"جدة", | |
"الدمام", | |
"مكة", | |
"المدينة", | |
"تبوك", | |
"حائل", | |
"عسير", | |
"جازان", | |
"نجران", | |
"الباحة", | |
"الجوف", | |
"القصيم" | |
], | |
key="cost_location" | |
) | |
client_type = st.selectbox( | |
"نوع العميل", | |
[ | |
"حكومي", | |
"شبه حكومي", | |
"شركة كبيرة", | |
"شركة متوسطة", | |
"شركة صغيرة", | |
"أفراد" | |
], | |
key="cost_client_type" | |
) | |
with col2: | |
area = st.number_input("المساحة (م²)", min_value=100, max_value=1000000, value=5000, key="cost_area") | |
floors = st.number_input("عدد الطوابق", min_value=1, max_value=100, value=3, key="cost_floors") | |
duration = st.number_input("مدة التنفيذ (شهور)", min_value=1, max_value=60, value=12, key="cost_duration") | |
tender_type = st.selectbox( | |
"نوع المناقصة", | |
[ | |
"عامة", | |
"خاصة", | |
"أمر مباشر" | |
], | |
key="cost_tender_type" | |
) | |
st.markdown("#### متغيرات إضافية") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
has_basement = st.checkbox("يتضمن بدروم", key="cost_has_basement") | |
has_special_finishing = st.checkbox("تشطيبات خاصة", key="cost_has_special_finishing") | |
with col2: | |
has_landscape = st.checkbox("أعمال تنسيق المواقع", key="cost_has_landscape") | |
has_parking = st.checkbox("مواقف متعددة الطوابق", key="cost_has_parking") | |
with col3: | |
has_smart_systems = st.checkbox("أنظمة ذكية", key="cost_has_smart_systems") | |
has_sustainability = st.checkbox("متطلبات استدامة", key="cost_has_sustainability") | |
# زر التنبؤ بالتكلفة مع دعم Claude AI | |
col1, col2 = st.columns([1, 3]) | |
with col1: | |
predict_button = st.button("التنبؤ بالتكلفة", use_container_width=True, key="cost_predict_button") | |
with col2: | |
use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="cost_use_claude") | |
if predict_button: | |
with st.spinner("جاري تحليل البيانات والتنبؤ بالتكاليف..."): | |
# محاكاة وقت المعالجة | |
time.sleep(2) | |
# تجهيز البيانات للنموذج | |
features = { | |
'project_type': project_type, | |
'location': location, | |
'area': area, | |
'floors': floors, | |
'duration_months': duration, | |
'tender_type': tender_type, | |
'client_type': client_type, | |
'has_basement': has_basement, | |
'has_special_finishing': has_special_finishing, | |
'has_landscape': has_landscape, | |
'has_parking': has_parking, | |
'has_smart_systems': has_smart_systems, | |
'has_sustainability': has_sustainability | |
} | |
# استدعاء النموذج للتنبؤ | |
cost_prediction_results = self._predict_cost(features) | |
# إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار | |
if use_claude: | |
try: | |
# إنشاء نص الميزات للتحليل | |
features_text = f""" | |
بيانات المشروع: | |
- نوع المشروع: {project_type} | |
- الموقع: {location} | |
- المساحة: {area} م² | |
- عدد الطوابق: {floors} | |
- مدة التنفيذ: {duration} شهر | |
- نوع المناقصة: {tender_type} | |
- نوع العميل: {client_type} | |
- يتضمن بدروم: {'نعم' if has_basement else 'لا'} | |
- تشطيبات خاصة: {'نعم' if has_special_finishing else 'لا'} | |
- أعمال تنسيق المواقع: {'نعم' if has_landscape else 'لا'} | |
- مواقف متعددة الطوابق: {'نعم' if has_parking else 'لا'} | |
- أنظمة ذكية: {'نعم' if has_smart_systems else 'لا'} | |
- متطلبات استدامة: {'نعم' if has_sustainability else 'لا'} | |
نتائج التنبؤ الأولية: | |
- التكلفة الإجمالية المقدرة: {cost_prediction_results['total_cost']:,.0f} ريال | |
- تكلفة المتر المربع: {cost_prediction_results['cost_per_sqm']:,.0f} ريال/م² | |
- تكلفة المواد: {cost_prediction_results['material_cost']:,.0f} ريال | |
- تكلفة العمالة: {cost_prediction_results['labor_cost']:,.0f} ريال | |
- تكلفة المعدات: {cost_prediction_results['equipment_cost']:,.0f} ريال | |
""" | |
prompt = f"""تحليل بيانات مشروع وتكاليفه: | |
{features_text} | |
المطلوب: | |
1. تحليل التكاليف المتوقعة ومعقوليتها مقارنة بمشاريع مماثلة في السوق السعودي | |
2. تقديم توصيات وملاحظات لتحسين التكلفة | |
3. تحديد أي مخاطر محتملة قد تؤثر على التكلفة | |
4. تقديم نصائح لزيادة فعالية التكلفة | |
5. تقديم رأي حول مدى تنافسية هذه التكلفة في السوق الحالي | |
يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. | |
""" | |
# استدعاء Claude للتحليل | |
claude_analysis = self.claude_service.chat_completion( | |
[{"role": "user", "content": prompt}] | |
) | |
if "error" not in claude_analysis: | |
# إضافة تحليل Claude إلى النتائج | |
cost_prediction_results["claude_analysis"] = claude_analysis["content"] | |
except Exception as e: | |
st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}") | |
# عرض نتائج التنبؤ | |
self._display_cost_prediction_results(cost_prediction_results) | |
def _predict_cost(self, features): | |
"""التنبؤ بتكاليف المشروع""" | |
# في البيئة الحقيقية، سيتم استدعاء نموذج التنبؤ بالتكاليف | |
# محاكاة نتائج التنبؤ للعرض | |
# حساب القيمة الأساسية للمتر المربع حسب نوع المشروع | |
base_cost_per_sqm = { | |
"مباني سكنية": 2500, | |
"مباني تجارية": 3000, | |
"مباني حكومية": 3500, | |
"مراكز صحية": 4000, | |
"مدارس": 3200, | |
"بنية تحتية": 2000, | |
"طرق": 1500, | |
"جسور": 5000, | |
"صرف صحي": 2200, | |
"مياه": 2000, | |
"كهرباء": 2500 | |
}.get(features['project_type'], 2500) | |
# تطبيق معاملات التعديل حسب المتغيرات | |
location_factor = { | |
"الرياض": 1.1, | |
"جدة": 1.15, | |
"الدمام": 1.05, | |
"مكة": 1.2, | |
"المدينة": 1.1, | |
"تبوك": 0.95, | |
"حائل": 0.9, | |
"عسير": 0.95, | |
"جازان": 0.9, | |
"نجران": 0.85, | |
"الباحة": 0.9, | |
"الجوف": 0.85, | |
"القصيم": 0.9 | |
}.get(features['location'], 1.0) | |
client_factor = { | |
"حكومي": 1.05, | |
"شبه حكومي": 1.0, | |
"شركة كبيرة": 0.95, | |
"شركة متوسطة": 0.9, | |
"شركة صغيرة": 0.85, | |
"أفراد": 0.8 | |
}.get(features['client_type'], 1.0) | |
tender_factor = { | |
"عامة": 1.0, | |
"خاصة": 0.95, | |
"أمر مباشر": 0.9 | |
}.get(features['tender_type'], 1.0) | |
# معاملات للميزات الإضافية | |
basement_factor = 1.1 if features['has_basement'] else 1.0 | |
special_finishing_factor = 1.2 if features['has_special_finishing'] else 1.0 | |
landscape_factor = 1.05 if features['has_landscape'] else 1.0 | |
parking_factor = 1.1 if features['has_parking'] else 1.0 | |
smart_systems_factor = 1.15 if features['has_smart_systems'] else 1.0 | |
sustainability_factor = 1.1 if features['has_sustainability'] else 1.0 | |
# معامل لعدد الطوابق | |
floors_factor = 1.0 + (features['floors'] - 1) * 0.05 | |
# حساب التكلفة الإجمالية | |
total_sqm_cost = base_cost_per_sqm * location_factor * client_factor * tender_factor * \ | |
basement_factor * special_finishing_factor * landscape_factor * \ | |
parking_factor * smart_systems_factor * sustainability_factor * \ | |
floors_factor | |
total_cost = total_sqm_cost * features['area'] | |
# حساب التكاليف المفصلة | |
material_cost = total_cost * 0.6 | |
labor_cost = total_cost * 0.25 | |
equipment_cost = total_cost * 0.15 | |
# إضافة هامش خطأ عشوائي للمحاكاة | |
error_margin = 0.05 # 5% | |
total_cost = total_cost * (1 + np.random.uniform(-error_margin, error_margin)) | |
# إعداد النتائج | |
results = { | |
"total_cost": total_cost, | |
"cost_per_sqm": total_cost / features['area'], | |
"material_cost": material_cost, | |
"labor_cost": labor_cost, | |
"equipment_cost": equipment_cost, | |
"breakdown": { | |
"structural_works": total_cost * 0.35, | |
"architectural_works": total_cost * 0.25, | |
"mep_works": total_cost * 0.25, | |
"site_works": total_cost * 0.1, | |
"general_requirements": total_cost * 0.05 | |
}, | |
"confidence_level": 0.85, # مستوى الثقة في التنبؤ | |
"comparison": { | |
"market_average": total_cost * 1.1, | |
"historical_projects": total_cost * 0.95 | |
} | |
} | |
return results | |
def _display_cost_prediction_results(self, results): | |
"""عرض نتائج التنبؤ بالتكاليف""" | |
st.markdown("### نتائج التنبؤ بالتكاليف") | |
# عرض التكلفة الإجمالية وتكلفة المتر المربع | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric( | |
"التكلفة الإجمالية المتوقعة", | |
f"{results['total_cost']:,.0f} ريال", | |
delta=f"{(results['total_cost'] - results['comparison']['historical_projects']):,.0f} ريال" | |
) | |
with col2: | |
st.metric( | |
"تكلفة المتر المربع", | |
f"{results['cost_per_sqm']:,.0f} ريال/م²" | |
) | |
with col3: | |
st.metric( | |
"مستوى الثقة في التنبؤ", | |
f"{results['confidence_level'] * 100:.0f}%" | |
) | |
# عرض تفصيل التكاليف | |
st.markdown("#### تفصيل التكاليف") | |
# رسم مخطط دائري للتكاليف المفصلة | |
fig = px.pie( | |
values=[ | |
results['material_cost'], | |
results['labor_cost'], | |
results['equipment_cost'] | |
], | |
names=["تكلفة المواد", "تكلفة العمالة", "تكلفة المعدات"], | |
title="توزيع التكاليف الرئيسية" | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# رسم مخطط شريطي لتفصيل الأعمال | |
breakdown_data = pd.DataFrame({ | |
'فئة الأعمال': [ | |
"الأعمال الإنشائية", | |
"الأعمال المعمارية", | |
"الأعمال الكهروميكانيكية", | |
"أعمال الموقع", | |
"المتطلبات العامة" | |
], | |
'التكلفة': [ | |
results['breakdown']['structural_works'], | |
results['breakdown']['architectural_works'], | |
results['breakdown']['mep_works'], | |
results['breakdown']['site_works'], | |
results['breakdown']['general_requirements'] | |
] | |
}) | |
fig = px.bar( | |
breakdown_data, | |
x='فئة الأعمال', | |
y='التكلفة', | |
title="تفصيل التكاليف حسب فئة الأعمال", | |
text_auto='.3s' | |
) | |
fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside') | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض مقارنة مع متوسط السوق | |
st.markdown("#### مقارنة مع متوسط السوق") | |
comparison_data = pd.DataFrame({ | |
'المصدر': [ | |
"التكلفة المتوقعة", | |
"متوسط السوق", | |
"مشاريع مماثلة سابقة" | |
], | |
'التكلفة': [ | |
results['total_cost'], | |
results['comparison']['market_average'], | |
results['comparison']['historical_projects'] | |
] | |
}) | |
fig = px.bar( | |
comparison_data, | |
x='المصدر', | |
y='التكلفة', | |
title="مقارنة التكلفة المتوقعة مع السوق", | |
text_auto='.3s', | |
color='المصدر', | |
color_discrete_map={ | |
"التكلفة المتوقعة": "#1f77b4", | |
"متوسط السوق": "#ff7f0e", | |
"مشاريع مماثلة سابقة": "#2ca02c" | |
} | |
) | |
fig.update_traces(texttemplate='%{text:,.0f} ريال', textposition='outside') | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض تحليل Claude AI إذا كان متوفراً | |
if "claude_analysis" in results: | |
st.markdown("### تحليل Claude AI المتقدم") | |
st.info(results["claude_analysis"]) | |
# عرض ملاحظات وتوصيات | |
st.markdown("#### ملاحظات وتوصيات") | |
st.info(""" | |
- تم التنبؤ بالتكاليف بناءً على البيانات المدخلة ونماذج التعلم الآلي المدربة على مشاريع مماثلة. | |
- مستوى الثقة في التنبؤ جيد، ولكن يجب مراجعة التكاليف بشكل تفصيلي قبل اتخاذ القرار النهائي. | |
- تكلفة المتر المربع متوافقة مع متوسط السوق لهذا النوع من المشاريع. | |
- ينصح بمراجعة التصميم لتحسين التكلفة وزيادة الكفاءة. | |
""") | |
# زر تصدير التقرير | |
if st.button("تصدير تقرير التكاليف"): | |
st.success("تم تصدير تقرير التكاليف بنجاح!") | |
def _render_risk_analysis_tab(self): | |
"""عرض تبويب تحليل المخاطر""" | |
st.markdown("### تحليل المخاطر") | |
# عرض نموذج إدخال بيانات المشروع للمخاطر | |
st.markdown("#### بيانات المشروع") | |
col1, col2 = st.columns(2) | |
with col1: | |
project_type = st.selectbox( | |
"نوع المشروع", | |
[ | |
"مباني سكنية", | |
"مباني تجارية", | |
"مباني حكومية", | |
"مراكز صحية", | |
"مدارس", | |
"بنية تحتية", | |
"طرق", | |
"جسور", | |
"صرف صحي", | |
"مياه", | |
"كهرباء" | |
], | |
key="risk_project_type" | |
) | |
location = st.selectbox( | |
"الموقع", | |
[ | |
"الرياض", | |
"جدة", | |
"الدمام", | |
"مكة", | |
"المدينة", | |
"تبوك", | |
"حائل", | |
"عسير", | |
"جازان", | |
"نجران", | |
"الباحة", | |
"الجوف", | |
"القصيم" | |
], | |
key="risk_location" | |
) | |
with col2: | |
client_type = st.selectbox( | |
"نوع العميل", | |
[ | |
"حكومي", | |
"شبه حكومي", | |
"شركة كبيرة", | |
"شركة متوسطة", | |
"شركة صغيرة", | |
"أفراد" | |
], | |
key="risk_client_type" | |
) | |
tender_type = st.selectbox( | |
"نوع المناقصة", | |
[ | |
"عامة", | |
"خاصة", | |
"أمر مباشر" | |
], | |
key="risk_tender_type" | |
) | |
st.markdown("#### عوامل المخاطرة") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
payment_terms = st.slider("شروط الدفع (1-10)", 1, 10, 5, | |
help="1: شروط دفع سيئة جداً، 10: شروط دفع ممتازة", | |
key="risk_payment_terms") | |
completion_deadline = st.slider("مهلة الإنجاز (1-10)", 1, 10, 5, | |
help="1: مهلة قصيرة جداً، 10: مهلة مريحة", | |
key="risk_completion_deadline") | |
with col2: | |
penalty_clause = st.slider("شروط الغرامات (1-10)", 1, 10, 5, | |
help="1: غرامات مرتفعة جداً، 10: غرامات معقولة", | |
key="risk_penalty_clause") | |
technical_complexity = st.slider("التعقيد الفني (1-10)", 1, 10, 5, | |
help="1: بسيط جداً، 10: معقد للغاية", | |
key="risk_technical_complexity") | |
with col3: | |
company_experience = st.slider("خبرة الشركة (1-10)", 1, 10, 7, | |
help="1: لا توجد خبرة، 10: خبرة عالية", | |
key="risk_company_experience") | |
market_volatility = st.slider("تقلبات السوق (1-10)", 1, 10, 5, | |
help="1: مستقر جداً، 10: متقلب للغاية", | |
key="risk_market_volatility") | |
# زر تحليل المخاطر مع دعم Claude AI | |
col1, col2 = st.columns([1, 3]) | |
with col1: | |
analyze_button = st.button("تحليل المخاطر", use_container_width=True, key="risk_analyze_button") | |
with col2: | |
# Añadimos un key único para este checkbox | |
use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="risk_use_claude") | |
if analyze_button: | |
with st.spinner("جاري تحليل المخاطر..."): | |
# محاكاة وقت المعالجة | |
time.sleep(2) | |
# تجهيز البيانات للنموذج | |
features = { | |
'project_type': project_type, | |
'location': location, | |
'client_type': client_type, | |
'tender_type': tender_type, | |
'payment_terms': payment_terms, | |
'completion_deadline': completion_deadline, | |
'penalty_clause': penalty_clause, | |
'technical_complexity': technical_complexity, | |
'company_experience': company_experience, | |
'market_volatility': market_volatility | |
} | |
# استدعاء النموذج لتحليل المخاطر | |
risk_analysis_results = self._analyze_risks(features) | |
# إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار | |
if use_claude: | |
try: | |
# إنشاء نص الميزات للتحليل | |
features_text = f""" | |
بيانات المشروع: | |
- نوع المشروع: {project_type} | |
- الموقع: {location} | |
- نوع العميل: {client_type} | |
- نوع المناقصة: {tender_type} | |
عوامل المخاطرة: | |
- شروط الدفع: {payment_terms}/10 | |
- مهلة الإنجاز: {completion_deadline}/10 | |
- شروط الغرامات: {penalty_clause}/10 | |
- التعقيد الفني: {technical_complexity}/10 | |
- خبرة الشركة: {company_experience}/10 | |
- تقلبات السوق: {market_volatility}/10 | |
ملخص التحليل الأولي: | |
- متوسط درجة المخاطرة: {risk_analysis_results['avg_risk_score']:.1f}/10 | |
- عدد المخاطر العالية: {risk_analysis_results['high_risks']} | |
- عدد المخاطر المتوسطة: {risk_analysis_results['medium_risks']} | |
- عدد المخاطر المنخفضة: {risk_analysis_results['low_risks']} | |
أعلى المخاطر: | |
""" | |
# إضافة تفاصيل أعلى المخاطر | |
for i, risk in enumerate(risk_analysis_results['top_risks'][:3]): | |
features_text += f""" | |
{i+1}. {risk['name']} ({risk['category']}) | |
- الاحتمالية: {risk['probability'] * 100:.0f}% | |
- التأثير: {risk['impact'] * 100:.0f}% | |
- درجة المخاطرة: {risk['risk_score']}/10 | |
""" | |
prompt = f"""تحليل مخاطر مشروع: | |
{features_text} | |
المطلوب: | |
1. تحليل عوامل المخاطرة وتأثيرها على المشروع | |
2. تقديم توصيات إضافية لإدارة المخاطر | |
3. اقتراح استراتيجيات استجابة للمخاطر الرئيسية | |
4. تقديم نصائح لتحسين شروط العقد لتقليل المخاطر | |
5. تقييم مدى ملاءمة المشروع لاستراتيجية الشركة | |
يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية. | |
""" | |
# استدعاء Claude للتحليل | |
claude_analysis = self.claude_service.chat_completion( | |
[{"role": "user", "content": prompt}] | |
) | |
if "error" not in claude_analysis: | |
# إضافة تحليل Claude إلى النتائج | |
risk_analysis_results["claude_analysis"] = claude_analysis["content"] | |
except Exception as e: | |
st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}") | |
# عرض نتائج تحليل المخاطر | |
self._display_risk_analysis_results(risk_analysis_results) | |
def _analyze_risks(self, features): | |
"""تحليل مخاطر المشروع""" | |
# في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المخاطر | |
# محاكاة نتائج تحليل المخاطر للعرض | |
# تعريف قائمة من المخاطر المحتملة | |
potential_risks = [ | |
{ | |
"id": "R-001", | |
"name": "غرامة تأخير مرتفعة", | |
"category": "مخاطر مالية", | |
"description": "غرامة تأخير مرتفعة تصل إلى 10% من قيمة العقد، مما قد يؤثر سلباً على ربحية المشروع في حال التأخير.", | |
"probability": 0.6, | |
"impact": 0.8, | |
"risk_score": 7.8, | |
"response_strategy": "تخطيط مفصل للمشروع مع وضع مخزون زمني مناسب وتحديد نقاط التسليم المبكر." | |
}, | |
{ | |
"id": "R-002", | |
"name": "تقلبات أسعار المواد", | |
"category": "مخاطر السوق", | |
"description": "ارتفاع محتمل في أسعار المواد الخام خلال فترة تنفيذ المشروع، مما يؤثر على التكلفة الإجمالية.", | |
"probability": 0.7, | |
"impact": 0.7, | |
"risk_score": 7.5, | |
"response_strategy": "التعاقد المبكر مع الموردين وتثبيت الأسعار، أو إضافة بند تعديل سعري في العقد." | |
}, | |
{ | |
"id": "R-003", | |
"name": "ضعف تدفق المدفوعات", | |
"category": "مخاطر مالية", | |
"description": "تأخر العميل في سداد المستخلصات مما يؤثر على التدفق النقدي للمشروع.", | |
"probability": 0.5, | |
"impact": 0.8, | |
"risk_score": 7.2, | |
"response_strategy": "التفاوض على شروط دفع واضحة ومواعيد محددة، وإمكانية طلب دفعة مقدمة." | |
}, | |
{ | |
"id": "R-004", | |
"name": "نقص العمالة الماهرة", | |
"category": "مخاطر الموارد", | |
"description": "صعوبة توفير عمالة ماهرة لتنفيذ أجزاء محددة من المشروع.", | |
"probability": 0.5, | |
"impact": 0.6, | |
"risk_score": 6.5, | |
"response_strategy": "التخطيط المبكر للموارد البشرية وتوقيع عقود مع مقاولي الباطن المتخصصين." | |
}, | |
{ | |
"id": "R-005", | |
"name": "تغييرات في نطاق العمل", | |
"category": "مخاطر تعاقدية", | |
"description": "طلبات تغيير من العميل تؤدي إلى زيادة نطاق العمل دون تعديل مناسب للتكلفة والجدول الزمني.", | |
"probability": 0.6, | |
"impact": 0.6, | |
"risk_score": 6.0, | |
"response_strategy": "تضمين آلية واضحة لإدارة التغيير في العقد وتقييم تأثير أي تغييرات على التكلفة والزمن." | |
}, | |
{ | |
"id": "R-006", | |
"name": "مشاكل في الموقع", | |
"category": "مخاطر فنية", | |
"description": "ظروف موقع غير متوقعة تؤثر على تنفيذ الأعمال، مثل مشاكل في التربة أو مرافق تحت الأرض.", | |
"probability": 0.4, | |
"impact": 0.7, | |
"risk_score": 5.8, | |
"response_strategy": "إجراء دراسات واختبارات مفصلة للموقع قبل بدء التنفيذ، وتخصيص احتياطي للطوارئ." | |
}, | |
{ | |
"id": "R-007", | |
"name": "تضارب في التصاميم", | |
"category": "مخاطر فنية", | |
"description": "تعارض بين مختلف تخصصات التصميم (معماري، إنشائي، كهروميكانيكي) يؤدي إلى تأخير وإعادة عمل.", | |
"probability": 0.4, | |
"impact": 0.6, | |
"risk_score": 5.4, | |
"response_strategy": "مراجعة شاملة للتصاميم قبل البدء في التنفيذ واستخدام نمذجة معلومات البناء (BIM) لكشف التعارضات." | |
}, | |
{ | |
"id": "R-008", | |
"name": "تأخر الموافقات", | |
"category": "مخاطر تنظيمية", | |
"description": "تأخر في الحصول على الموافقات والتصاريح اللازمة من الجهات المختصة.", | |
"probability": 0.5, | |
"impact": 0.5, | |
"risk_score": 5.0, | |
"response_strategy": "التخطيط المبكر للتصاريح المطلوبة وبناء علاقات جيدة مع الجهات التنظيمية." | |
}, | |
{ | |
"id": "R-009", | |
"name": "عدم توفر المعدات", | |
"category": "مخاطر الموارد", | |
"description": "صعوبة في توفير المعدات المتخصصة في الوقت المطلوب.", | |
"probability": 0.3, | |
"impact": 0.6, | |
"risk_score": 4.8, | |
"response_strategy": "حجز المعدات مبكراً وتوفير بدائل محتملة في حالة عدم توفر المعدات الأساسية." | |
}, | |
{ | |
"id": "R-010", | |
"name": "ظروف جوية قاسية", | |
"category": "مخاطر خارجية", | |
"description": "تأثير الظروف الجوية القاسية (حرارة شديدة، أمطار غزيرة، عواصف رملية) على سير العمل.", | |
"probability": 0.3, | |
"impact": 0.5, | |
"risk_score": 4.5, | |
"response_strategy": "تخطيط الجدول الزمني مع مراعاة المواسم وإضافة مخزون زمني للظروف الجوية غير المتوقعة." | |
} | |
] | |
# حساب درجات المخاطرة بناءً على الميزات المدخلة | |
for risk in potential_risks: | |
# تعديل احتمالية حدوث المخاطر بناءً على العوامل المدخلة | |
if risk["id"] == "R-001": # غرامة تأخير | |
risk["probability"] = risk["probability"] * (10 - features["penalty_clause"]) / 10 | |
risk["probability"] = risk["probability"] * (10 - features["completion_deadline"]) / 10 | |
elif risk["id"] == "R-002": # تقلبات أسعار المواد | |
risk["probability"] = risk["probability"] * features["market_volatility"] / 10 | |
elif risk["id"] == "R-003": # ضعف تدفق المدفوعات | |
risk["probability"] = risk["probability"] * (10 - features["payment_terms"]) / 10 | |
if features["client_type"] == "حكومي": | |
risk["probability"] = risk["probability"] * 0.6 # احتمالية أقل مع العملاء الحكوميين | |
elif features["client_type"] == "أفراد": | |
risk["probability"] = risk["probability"] * 1.3 # احتمالية أعلى مع العملاء الأفراد | |
elif risk["id"] == "R-004": # نقص العمالة الماهرة | |
risk["probability"] = risk["probability"] * features["technical_complexity"] / 10 | |
elif risk["id"] == "R-005": # تغييرات في نطاق العمل | |
risk["probability"] = risk["probability"] * features["technical_complexity"] / 10 | |
if features["client_type"] == "حكومي": | |
risk["probability"] = risk["probability"] * 1.2 # احتمالية أعلى للتغييرات مع العملاء الحكوميين | |
# تعديل تأثير المخاطر بناءً على العوامل المدخلة | |
if risk["category"] == "مخاطر فنية": | |
risk["impact"] = risk["impact"] * (10 - features["company_experience"]) / 10 | |
# إعادة حساب درجة المخاطرة | |
risk["risk_score"] = round(risk["probability"] * risk["impact"] * 10, 1) | |
# ترتيب المخاطر تنازلياً حسب درجة المخاطرة | |
sorted_risks = sorted(potential_risks, key=lambda x: x["risk_score"], reverse=True) | |
# حساب عدد المخاطر حسب شدتها | |
high_risks = sum(1 for risk in sorted_risks if risk["risk_score"] >= 6.0) | |
medium_risks = sum(1 for risk in sorted_risks if 3.0 <= risk["risk_score"] < 6.0) | |
low_risks = sum(1 for risk in sorted_risks if risk["risk_score"] < 3.0) | |
# حساب متوسط درجة المخاطرة | |
avg_risk_score = sum(risk["risk_score"] for risk in sorted_risks) / len(sorted_risks) | |
# تجهيز النتائج | |
results = { | |
"top_risks": sorted_risks, | |
"high_risks": high_risks, | |
"medium_risks": medium_risks, | |
"low_risks": low_risks, | |
"avg_risk_score": avg_risk_score, | |
"risk_profile": { | |
"financial_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر مالية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر مالية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر مالية") > 0 else 0, | |
"technical_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر فنية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر فنية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر فنية") > 0 else 0, | |
"market_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر السوق") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر السوق") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر السوق") > 0 else 0, | |
"resource_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر الموارد") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر الموارد") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر الموارد") > 0 else 0, | |
"contract_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تعاقدية") > 0 else 0, | |
"regulatory_risk": sum(risk["risk_score"] for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") / sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") if sum(1 for risk in sorted_risks if risk["category"] == "مخاطر تنظيمية") > 0 else 0 | |
}, | |
"overall_assessment": "", | |
"recommendation": "" | |
} | |
# تقييم شامل للمخاطر | |
if avg_risk_score >= 6.0: | |
results["overall_assessment"] = "مشروع عالي المخاطر" | |
results["recommendation"] = "ينصح بإعادة التفاوض على شروط العقد أو إضافة هامش ربح أعلى لتغطية المخاطر." | |
elif avg_risk_score >= 4.0: | |
results["overall_assessment"] = "مشروع متوسط المخاطر" | |
results["recommendation"] = "متابعة دقيقة للمخاطر العالية ووضع خطط استجابة مفصلة لها." | |
else: | |
results["overall_assessment"] = "مشروع منخفض المخاطر" | |
results["recommendation"] = "مراقبة المخاطر بشكل دوري والتركيز على تحسين الأداء." | |
return results | |
def _display_risk_analysis_results(self, results): | |
"""عرض نتائج تحليل المخاطر""" | |
st.markdown("### نتائج تحليل المخاطر") | |
# عرض ملخص تقييم المخاطر | |
st.markdown(f"#### التقييم العام: {results['overall_assessment']}") | |
# عرض الإحصائيات الرئيسية للمخاطر | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric("متوسط درجة المخاطرة", f"{results['avg_risk_score']:.1f}/10") | |
with col2: | |
st.metric("المخاطر العالية", f"{results['high_risks']}") | |
with col3: | |
st.metric("المخاطر المتوسطة", f"{results['medium_risks']}") | |
with col4: | |
st.metric("المخاطر المنخفضة", f"{results['low_risks']}") | |
# عرض ملف المخاطر حسب الفئة | |
st.markdown("#### ملف المخاطر حسب الفئة") | |
# تجهيز البيانات للرسم البياني | |
risk_profile_data = pd.DataFrame({ | |
'الفئة': [ | |
"مخاطر مالية", | |
"مخاطر فنية", | |
"مخاطر السوق", | |
"مخاطر الموارد", | |
"مخاطر تعاقدية", | |
"مخاطر تنظيمية" | |
], | |
'درجة المخاطرة': [ | |
results['risk_profile']['financial_risk'], | |
results['risk_profile']['technical_risk'], | |
results['risk_profile']['market_risk'], | |
results['risk_profile']['resource_risk'], | |
results['risk_profile']['contract_risk'], | |
results['risk_profile']['regulatory_risk'] | |
] | |
}) | |
# رسم مخطط شعاعي لملف المخاطر | |
fig = px.line_polar( | |
risk_profile_data, | |
r='درجة المخاطرة', | |
theta='الفئة', | |
line_close=True, | |
range_r=[0, 10], | |
title="ملف المخاطر حسب الفئة" | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# عرض المخاطر الرئيسية | |
st.markdown("#### المخاطر الرئيسية") | |
# إنشاء جدول المخاطر | |
risk_table_data = [] | |
for risk in results['top_risks'][:5]: # عرض أعلى 5 مخاطر فقط | |
risk_level = "عالية" if risk["risk_score"] >= 6.0 else "متوسطة" if risk["risk_score"] >= 3.0 else "منخفضة" | |
risk_color = "red" if risk_level == "عالية" else "orange" if risk_level == "متوسطة" else "green" | |
risk_table_data.append({ | |
"المعرف": risk["id"], | |
"الوصف": risk["name"], | |
"الفئة": risk["category"], | |
"الاحتمالية": f"{risk['probability'] * 100:.0f}%", | |
"التأثير": f"{risk['impact'] * 100:.0f}%", | |
"درجة المخاطرة": risk["risk_score"], | |
"المستوى": risk_level, | |
"استراتيجية الاستجابة": risk["response_strategy"], | |
"color": risk_color | |
}) | |
# عرض جدول المخاطر | |
risk_df = pd.DataFrame(risk_table_data) | |
# استخدام تنسيق HTML مخصص لعرض المخاطر الرئيسية | |
for index, row in risk_df.iterrows(): | |
with st.container(): | |
st.markdown(f""" | |
<div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px; margin-bottom: 10px;"> | |
<h5 style="margin-top: 0;">{row['المعرف']} - {row['الوصف']} <span style="float: right; background-color: {row['color']}; color: white; padding: 2px 8px; border-radius: 10px;">{row['المستوى']}</span></h5> | |
<p><strong>الفئة:</strong> {row['الفئة']} | <strong>الاحتمالية:</strong> {row['الاحتمالية']} | <strong>التأثير:</strong> {row['التأثير']} | <strong>درجة المخاطرة:</strong> {row['درجة المخاطرة']}/10</p> | |
<p><strong>استراتيجية الاستجابة:</strong> {row['استراتيجية الاستجابة']}</p> | |
</div> | |
""", 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""" | |
<div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px; margin-bottom: 10px;"> | |
<h5 style="margin-top: 0;">{term['id']} - {term['title']} <span style="float: right; background-color: {risk_color}; color: white; padding: 2px 8px; border-radius: 10px;">مستوى الخطورة: {term['risk_level']}</span></h5> | |
<p>{term['description']}</p> | |
<p><strong>ملاحظات:</strong> {term['notes']}</p> | |
</div> | |
""", 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""" | |
<div style="padding: 10px; border-radius: 5px; background-color: {"green" if results['status'] == 'تم تحقيق المستهدف' else "orange" if results['status'] == 'قريب من المستهدف' else "red"}; color: white; text-align: center;"> | |
<h4 style="margin: 0;">{results["status"]}</h4> | |
</div> | |
""", 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""" | |
<div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px; margin-bottom: 10px;"> | |
<h5 style="margin-top: 0;">{recommendation['component']} (المحتوى المحلي الحالي: {recommendation['current_lc']}%)</h5> | |
<p>{recommendation['recommendation']}</p> | |
</div> | |
""", 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": "[email protected]", | |
"description": "شركة متخصصة في توريد جميع أنواع مواد البناء ذات المنشأ المحلي." | |
}, | |
{ | |
"id": 2, | |
"name": "مؤسسة الطاقة المتقدمة", | |
"category": "أنظمة كهربائية", | |
"lc_rating": 85, | |
"quality_rating": 4.2, | |
"location": "جدة", | |
"contact": "[email protected]", | |
"description": "مؤسسة متخصصة في توريد وتركيب الأنظمة الكهربائية والطاقة المتجددة." | |
}, | |
{ | |
"id": 3, | |
"name": "شركة التبريد العالمية", | |
"category": "أنظمة ميكانيكية", | |
"lc_rating": 75, | |
"quality_rating": 4.0, | |
"location": "الدمام", | |
"contact": "[email protected]", | |
"description": "شركة متخصصة في أنظمة التكييف والتبريد المركزي للمشاريع الكبرى." | |
}, | |
{ | |
"id": 4, | |
"name": "شركة الزجاج المتطورة", | |
"category": "مواد بناء", | |
"lc_rating": 80, | |
"quality_rating": 4.3, | |
"location": "الرياض", | |
"contact": "[email protected]", | |
"description": "شركة متخصصة في إنتاج وتوريد الزجاج والواجهات الزجاجية للمباني." | |
}, | |
{ | |
"id": 5, | |
"name": "مؤسسة التشطيبات الحديثة", | |
"category": "تشطيبات", | |
"lc_rating": 90, | |
"quality_rating": 4.7, | |
"location": "جدة", | |
"contact": "[email protected]", | |
"description": "مؤسسة متخصصة في أعمال التشطيبات الداخلية والخارجية بجودة عالية." | |
}, | |
{ | |
"id": 6, | |
"name": "شركة الأثاث المكتبي", | |
"category": "أثاث ومفروشات", | |
"lc_rating": 70, | |
"quality_rating": 4.0, | |
"location": "الرياض", | |
"contact": "[email protected]", | |
"description": "شركة متخصصة في تصنيع وتوريد الأثاث المكتبي والتجهيزات المكتبية." | |
}, | |
{ | |
"id": 7, | |
"name": "شركة الأنظمة الأمنية المتقدمة", | |
"category": "أنظمة أمنية", | |
"lc_rating": 65, | |
"quality_rating": 4.1, | |
"location": "الدمام", | |
"contact": "[email protected]", | |
"description": "شركة متخصصة في أنظمة الأمن والمراقبة والإنذار للمباني والمنشآت." | |
}, | |
{ | |
"id": 8, | |
"name": "شركة المعدات الهندسية", | |
"category": "معدات وآليات", | |
"lc_rating": 85, | |
"quality_rating": 4.5, | |
"location": "جدة", | |
"contact": "[email protected]", | |
"description": "شركة متخصصة في توريد وصيانة المعدات الهندسية والآليات للمشاريع." | |
}, | |
{ | |
"id": 9, | |
"name": "مكتب الاستشارات الهندسية", | |
"category": "خدمات هندسية", | |
"lc_rating": 100, | |
"quality_rating": 4.8, | |
"location": "الرياض", | |
"contact": "[email protected]", | |
"description": "مكتب استشاري متخصص في تقديم الخدمات الهندسية والاستشارية للمشاريع." | |
}, | |
{ | |
"id": 10, | |
"name": "مصنع الحديد السعودي", | |
"category": "مواد بناء", | |
"lc_rating": 100, | |
"quality_rating": 4.6, | |
"location": "جدة", | |
"contact": "[email protected]", | |
"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""" | |
<div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px; margin-bottom: 10px;"> | |
<h5 style="margin-top: 0;">{supplier['name']} <span style="color: #888; font-size: 0.8em;">({supplier['category']})</span></h5> | |
<p style="margin-bottom: 5px;"><strong>الموقع:</strong> {supplier['location']} | <strong>التواصل:</strong> {supplier['contact']}</p> | |
<p style="margin-bottom: 5px;"><strong>تصنيف المحتوى المحلي:</strong> {supplier['lc_rating']}% | <strong>تقييم الجودة:</strong> {supplier['quality_rating']}/5</p> | |
<p>{supplier['description']}</p> | |
</div> | |
""", 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""" | |
<div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px; margin-bottom: 10px;"> | |
<h6 style="margin-top: 0;">{alt['name']}</h6> | |
<p style="margin-bottom: 5px;">{alt['description']}</p> | |
<p style="margin-bottom: 0;"><strong>المحتوى المحلي:</strong> {alt['local_content']}% | <strong>معامل التكلفة:</strong> {alt['cost_factor']:.2f} | <strong>تقييم الجودة:</strong> {alt['quality_rating']}/5</p> | |
</div> | |
""", 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("إرسال") |