|
|
|
""" |
|
وحدة المساعد الذكي |
|
|
|
هذا الملف يحتوي على الفئة الرئيسية لتطبيق المساعد الذكي مع دعم نموذج 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 plotly.graph_objects as go |
|
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 |
|
import arabic_reshaper |
|
from bidi.algorithm import get_display |
|
import matplotlib.font_manager as fm |
|
import seaborn as sns |
|
|
|
|
|
plt.rcParams['font.family'] = 'Arial' |
|
|
|
|
|
def get_display_arabic(text): |
|
"""تحويل النص العربي للعرض الصحيح في الرسوم البيانية""" |
|
reshaped_text = arabic_reshaper.reshape(text) |
|
bidi_text = get_display(reshaped_text) |
|
return bidi_text |
|
|
|
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_key = self.get_api_key() |
|
|
|
|
|
file_size = os.path.getsize(image_path) |
|
_, ext = os.path.splitext(image_path) |
|
ext = ext.lower() |
|
|
|
if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp'] and file_size > 5 * 1024 * 1024: |
|
|
|
try: |
|
with Image.open(image_path) as img: |
|
|
|
compressed_img_buffer = io.BytesIO() |
|
|
|
|
|
quality = min(95, int((5 * 1024 * 1024 / file_size) * 100)) |
|
|
|
|
|
img.save(compressed_img_buffer, format='JPEG', quality=quality) |
|
compressed_img_buffer.seek(0) |
|
|
|
|
|
file_content = compressed_img_buffer.read() |
|
logging.info(f"تم ضغط الصورة من {file_size/1024/1024:.2f} ميجابايت إلى {len(file_content)/1024/1024:.2f} ميجابايت") |
|
except Exception as e: |
|
logging.error(f"خطأ أثناء ضغط الصورة: {str(e)}") |
|
with open(image_path, 'rb') as f: |
|
file_content = f.read() |
|
else: |
|
|
|
with open(image_path, 'rb') as f: |
|
file_content = f.read() |
|
|
|
|
|
if len(file_content) > 5 * 1024 * 1024: |
|
raise ValueError(f"حجم الملف ({len(file_content)/1024/1024:.2f} ميجابايت) يتجاوز الحد الأقصى (5 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.") |
|
|
|
|
|
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 |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
|
|
|
|
response = requests.post( |
|
self.api_url, |
|
headers=headers, |
|
json=payload, |
|
timeout=60 |
|
) |
|
|
|
|
|
if response.status_code != 200: |
|
error_message = f"فشل طلب API: {response.status_code}" |
|
try: |
|
error_details = response.json() |
|
error_message += f"\nتفاصيل: {error_details}" |
|
except: |
|
error_message += f"\nتفاصيل: {response.text}" |
|
|
|
return {"error": error_message} |
|
|
|
|
|
result = response.json() |
|
|
|
return { |
|
"success": True, |
|
"content": result["content"][0]["text"], |
|
"model": result["model"], |
|
"usage": result.get("usage", {}) |
|
} |
|
|
|
except Exception as e: |
|
logging.error(f"خطأ أثناء تحليل الصورة: {str(e)}") |
|
import traceback |
|
stack_trace = traceback.format_exc() |
|
return {"error": f"فشل في تحليل الصورة: {str(e)}\n{stack_trace}"} |
|
|
|
def chat_completion(self, messages, model_name="claude-3-7-sonnet"): |
|
""" |
|
إكمال محادثة باستخدام نموذج Claude AI |
|
|
|
المعلمات: |
|
messages: سجل المحادثة |
|
model_name: اسم نموذج Claude المراد استخدامه |
|
|
|
العوائد: |
|
dict: نتائج الإكمال |
|
""" |
|
try: |
|
|
|
api_key = self.get_api_key() |
|
|
|
|
|
claude_messages = [] |
|
for msg in messages: |
|
claude_messages.append({ |
|
"role": msg["role"], |
|
"content": msg["content"] |
|
}) |
|
|
|
|
|
model_name = self.get_model_full_name(model_name) |
|
|
|
|
|
headers = { |
|
"Content-Type": "application/json", |
|
"x-api-key": api_key, |
|
"anthropic-version": "2023-06-01" |
|
} |
|
|
|
payload = { |
|
"model": model_name, |
|
"max_tokens": 2048, |
|
"messages": claude_messages, |
|
"temperature": 0.7 |
|
} |
|
|
|
|
|
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}"} |
|
|
|
def analyze_text(self, text, analysis_type="general", model_name="claude-3-5-haiku"): |
|
""" |
|
تحليل النص باستخدام الذكاء الاصطناعي |
|
|
|
المعلمات: |
|
text: النص المراد تحليله |
|
analysis_type: نوع التحليل (general, requirements, risks, costs) |
|
model_name: اسم النموذج المستخدم |
|
|
|
العوائد: |
|
dict: نتائج التحليل |
|
""" |
|
try: |
|
|
|
if analysis_type == "requirements": |
|
prompt = f""" |
|
قم بتحليل النص التالي واستخراج المتطلبات الرئيسية للمشروع. صنف المتطلبات إلى فئات (فنية، إدارية، مالية، قانونية) وحدد أولوية كل متطلب (عالية، متوسطة، منخفضة). |
|
|
|
النص: |
|
{text} |
|
|
|
قدم النتائج بتنسيق JSON يحتوي على مصفوفة من المتطلبات، كل متطلب يحتوي على: الوصف، الفئة، الأولوية. |
|
""" |
|
elif analysis_type == "risks": |
|
prompt = f""" |
|
قم بتحليل النص التالي وتحديد المخاطر المحتملة للمشروع. لكل خطر، حدد احتمالية حدوثه (عالية، متوسطة، منخفضة) وتأثيره (عالي، متوسط، منخفض) واقترح استراتيجية للتخفيف من حدته. |
|
|
|
النص: |
|
{text} |
|
|
|
قدم النتائج بتنسيق JSON يحتوي على مصفوفة من المخاطر، كل خطر يحتوي على: الوصف، الاحتمالية، التأثير، استراتيجية التخفيف. |
|
""" |
|
elif analysis_type == "costs": |
|
prompt = f""" |
|
قم بتحليل النص التالي وتحديد عناصر التكلفة المحتملة للمشروع. صنف التكاليف إلى فئات (مباشرة، غير مباشرة) وحدد ما إذا كانت ثابتة أو متغيرة. |
|
|
|
النص: |
|
{text} |
|
|
|
قدم النتائج بتنسيق JSON يحتوي على مصفوفة من عناصر التكلفة، كل عنصر يحتوي على: الوصف، الفئة، النوع (ثابت/متغير)، تقدير نسبي للتكلفة (%). |
|
""" |
|
elif analysis_type == "local_content": |
|
prompt = f""" |
|
قم بتحليل النص التالي وتحديد عناصر المحتوى المحلي المحتملة. صنف العناصر إلى فئات (منتجات، خدمات، قوى عاملة) وحدد مدى توفرها محلياً (متوفر بشكل كامل، متوفر جزئياً، غير متوفر). |
|
|
|
النص: |
|
{text} |
|
|
|
قدم النتائج بتنسيق JSON يحتوي على مصفوفة من عناصر المحتوى المحلي، كل عنصر يحتوي على: الوصف، الفئة، مدى التوفر، تقدير نسبي للمساهمة في المحتوى المحلي (%). |
|
""" |
|
else: |
|
prompt = f""" |
|
قم بتحليل النص التالي وتلخيص النقاط الرئيسية. حدد الموضوعات الأساسية والأفكار المهمة والتوصيات إن وجدت. |
|
|
|
النص: |
|
{text} |
|
|
|
قدم النتائج بتنسيق JSON يحتوي على: ملخص عام، النقاط الرئيسية (مصفوفة)، التوصيات (مصفوفة). |
|
""" |
|
|
|
|
|
messages = [ |
|
{"role": "user", "content": prompt} |
|
] |
|
|
|
|
|
result = self.chat_completion(messages, model_name) |
|
|
|
|
|
if "error" in result: |
|
return result |
|
|
|
|
|
try: |
|
|
|
content = result["content"] |
|
|
|
|
|
json_start = content.find('{') |
|
json_end = content.rfind('}') + 1 |
|
|
|
if json_start >= 0 and json_end > json_start: |
|
json_str = content[json_start:json_end] |
|
analysis_result = json.loads(json_str) |
|
|
|
return { |
|
"success": True, |
|
"result": analysis_result, |
|
"model": result["model"] |
|
} |
|
else: |
|
|
|
return { |
|
"success": True, |
|
"result": {"text": content}, |
|
"model": result["model"] |
|
} |
|
except Exception as e: |
|
|
|
return { |
|
"success": True, |
|
"result": {"text": content}, |
|
"model": result["model"], |
|
"parse_error": str(e) |
|
} |
|
|
|
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): |
|
"""تهيئة وحدة المساعد الذكي""" |
|
|
|
try: |
|
from pdf2image import convert_from_path |
|
pdf_conversion_available = True |
|
self.pdf_conversion_available = True |
|
self.convert_from_path = convert_from_path |
|
except ImportError: |
|
pdf_conversion_available = False |
|
self.pdf_conversion_available = False |
|
|
|
|
|
self.cost_model = self._load_cost_prediction_model() |
|
self.document_model = self._load_document_classifier_model() |
|
self.risk_model = self._load_risk_assessment_model() |
|
self.local_content_model = self._load_local_content_model() |
|
self.entity_model = self._load_entity_recognition_model() |
|
|
|
|
|
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 _load_cost_prediction_model(self): |
|
"""تحميل نموذج التنبؤ بالتكاليف""" |
|
|
|
|
|
return {"name": "cost_prediction_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_document_classifier_model(self): |
|
"""تحميل نموذج تصنيف المستندات""" |
|
return {"name": "document_classifier_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_risk_assessment_model(self): |
|
"""تحميل نموذج تقييم المخاطر""" |
|
return {"name": "risk_assessment_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_local_content_model(self): |
|
"""تحميل نموذج تحليل المحتوى المحلي""" |
|
return {"name": "local_content_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
def _load_entity_recognition_model(self): |
|
"""تحميل نموذج التعرف على الكيانات""" |
|
return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"} |
|
|
|
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_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: |
|
st.markdown(""" |
|
<style> |
|
.chat-container { |
|
max-width: 800px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
} |
|
.message { |
|
display: flex; |
|
margin-bottom: 20px; |
|
align-items: flex-start; |
|
} |
|
.user-message { |
|
justify-content: flex-end; |
|
} |
|
.assistant-message { |
|
justify-content: flex-start; |
|
} |
|
.avatar { |
|
width: 40px; |
|
height: 40px; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-weight: bold; |
|
color: white; |
|
margin: 0 10px; |
|
} |
|
.user-avatar { |
|
background-color: #2196F3; |
|
} |
|
.assistant-avatar { |
|
background-color: #4CAF50; |
|
} |
|
.message-content { |
|
padding: 15px; |
|
border-radius: 15px; |
|
max-width: 70%; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
} |
|
.user-content { |
|
background-color: #E3F2FD; |
|
border-top-right-radius: 5px; |
|
} |
|
.assistant-content { |
|
background-color: #F5F5F5; |
|
border-top-left-radius: 5px; |
|
} |
|
.message-time { |
|
font-size: 0.8em; |
|
color: #757575; |
|
margin-top: 5px; |
|
text-align: right; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
for message in st.session_state.ai_assistant_messages: |
|
if message["role"] == "user": |
|
st.markdown(f""" |
|
<div class="message user-message"> |
|
<div class="message-content user-content"> |
|
{message["content"]} |
|
<div class="message-time">أنت • الآن</div> |
|
</div> |
|
<div class="avatar user-avatar">أ</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.markdown(f""" |
|
<div class="message assistant-message"> |
|
<div class="avatar assistant-avatar">م</div> |
|
<div class="message-content assistant-content"> |
|
{message["content"]} |
|
<div class="message-time">المساعد • الآن</div> |
|
</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") |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
send_button = st.button("إرسال", key="send_message_button") |
|
with col2: |
|
analyze_options = st.selectbox( |
|
"تحليل ذكي", |
|
["عام", "متطلبات", "مخاطر", "تكاليف", "محتوى محلي"], |
|
key="analyze_type" |
|
) |
|
|
|
|
|
if send_button and user_input: |
|
|
|
st.session_state.ai_assistant_messages.append( |
|
{"role": "user", "content": user_input} |
|
) |
|
|
|
|
|
analysis_type_map = { |
|
"عام": "general", |
|
"متطلبات": "requirements", |
|
"مخاطر": "risks", |
|
"تكاليف": "costs", |
|
"محتوى محلي": "local_content" |
|
} |
|
|
|
analysis_type = analysis_type_map.get(analyze_options, "general") |
|
|
|
|
|
with st.spinner("جاري التحليل..."): |
|
|
|
result = self.claude_service.analyze_text(user_input, analysis_type, selected_model) |
|
|
|
|
|
if "error" in result: |
|
response = f"عذراً، حدث خطأ أثناء التحليل: {result['error']}" |
|
else: |
|
|
|
if isinstance(result["result"], dict): |
|
if "text" in result["result"]: |
|
|
|
response = result["result"]["text"] |
|
else: |
|
|
|
response = "نتائج التحليل:\n\n" |
|
for key, value in result["result"].items(): |
|
if isinstance(value, list): |
|
response += f"**{key}**:\n" |
|
for i, item in enumerate(value): |
|
if isinstance(item, dict): |
|
response += f"{i+1}. " |
|
for k, v in item.items(): |
|
response += f"**{k}**: {v}, " |
|
response = response[:-2] + "\n" |
|
else: |
|
response += f"{i+1}. {item}\n" |
|
else: |
|
response += f"**{key}**: {value}\n" |
|
else: |
|
response = str(result["result"]) |
|
|
|
|
|
st.session_state.ai_assistant_messages.append( |
|
{"role": "assistant", "content": response} |
|
) |
|
|
|
|
|
st.experimental_rerun() |
|
|
|
|
|
api_available = True |
|
try: |
|
self.claude_service.get_api_key() |
|
except ValueError: |
|
api_available = False |
|
st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.") |
|
|
|
def _render_cost_prediction_tab(self): |
|
"""عرض تبويب التنبؤ بالتكاليف""" |
|
st.markdown("### التنبؤ بالتكاليف") |
|
|
|
st.info("هذه الوحدة تساعدك في تقدير تكاليف المشاريع بناءً على بيانات المناقصات السابقة ومعايير التسعير المعتمدة.") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.subheader("معلومات المشروع") |
|
project_type = st.selectbox( |
|
"نوع المشروع", |
|
["مشروع إنشائي", "مشروع تقنية معلومات", "مشروع استشاري", "مشروع توريد", "أخرى"] |
|
) |
|
|
|
project_duration = st.number_input( |
|
"مدة المشروع (بالأشهر)", |
|
min_value=1, |
|
max_value=60, |
|
value=12 |
|
) |
|
|
|
project_location = st.selectbox( |
|
"موقع المشروع", |
|
["الرياض", "جدة", "الدمام", "مكة المكرمة", "المدينة المنورة", "أخرى"] |
|
) |
|
|
|
with col2: |
|
st.subheader("معايير التكلفة") |
|
|
|
direct_cost = st.number_input( |
|
"التكاليف المباشرة المقدرة (ريال)", |
|
min_value=0, |
|
value=1000000 |
|
) |
|
|
|
overhead_percentage = st.slider( |
|
"نسبة المصاريف العامة (%)", |
|
min_value=5, |
|
max_value=30, |
|
value=15 |
|
) |
|
|
|
profit_percentage = st.slider( |
|
"نسبة الربح المستهدفة (%)", |
|
min_value=5, |
|
max_value=30, |
|
value=20 |
|
) |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
calculate_button = st.button("حساب التكلفة التقديرية") |
|
with col2: |
|
ai_analysis = st.checkbox("تحليل ذكي", value=True) |
|
|
|
if calculate_button: |
|
|
|
overhead_cost = direct_cost * (overhead_percentage / 100) |
|
profit = direct_cost * (profit_percentage / 100) |
|
total_cost = direct_cost + overhead_cost + profit |
|
|
|
|
|
st.success("تم حساب التكلفة التقديرية بنجاح!") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.metric("التكاليف المباشرة", f"{direct_cost:,.2f} ريال") |
|
|
|
with col2: |
|
st.metric("المصاريف العامة", f"{overhead_cost:,.2f} ريال") |
|
|
|
with col3: |
|
st.metric("الربح المتوقع", f"{profit:,.2f} ريال") |
|
|
|
st.metric("إجمالي التكلفة التقديرية", f"{total_cost:,.2f} ريال", delta="تقدير أولي") |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
labels = ["التكاليف المباشرة", "المصاريف العامة", "الربح المتوقع"] |
|
values = [direct_cost, overhead_cost, profit] |
|
colors = ['#1f77b4', '#ff7f0e', '#2ca02c'] |
|
|
|
fig.add_trace(go.Pie( |
|
labels=[get_display_arabic(label) for label in labels], |
|
values=values, |
|
textinfo='percent+label', |
|
insidetextorientation='radial', |
|
marker=dict(colors=colors), |
|
hole=0.4 |
|
)) |
|
|
|
|
|
fig.update_layout( |
|
title=get_display_arabic("توزيع التكاليف"), |
|
height=400, |
|
margin=dict(l=0, r=0, t=40, b=0), |
|
font=dict(size=14), |
|
legend=dict( |
|
orientation="h", |
|
yanchor="bottom", |
|
y=-0.2, |
|
xanchor="center", |
|
x=0.5 |
|
) |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
if ai_analysis: |
|
with st.spinner("جاري تحليل التكاليف باستخدام الذكاء الاصطناعي..."): |
|
|
|
project_description = f""" |
|
نوع المشروع: {project_type} |
|
مدة المشروع: {project_duration} أشهر |
|
موقع المشروع: {project_location} |
|
التكاليف المباشرة: {direct_cost:,.2f} ريال |
|
نسبة المصاريف العامة: {overhead_percentage}% |
|
نسبة الربح المستهدفة: {profit_percentage}% |
|
إجمالي التكلفة التقديرية: {total_cost:,.2f} ريال |
|
""" |
|
|
|
|
|
result = self.claude_service.analyze_text(project_description, "costs") |
|
|
|
if "error" not in result: |
|
st.subheader("تحليل التكاليف بالذكاء الاصطناعي") |
|
|
|
|
|
if isinstance(result["result"], dict) and "text" not in result["result"]: |
|
|
|
if "عناصر التكلفة" in result["result"]: |
|
cost_items = result["result"]["عناصر التكلفة"] |
|
|
|
|
|
cost_data = [] |
|
for item in cost_items: |
|
if isinstance(item, dict): |
|
cost_data.append({ |
|
"الوصف": item.get("الوصف", ""), |
|
"الفئة": item.get("الفئة", ""), |
|
"النوع": item.get("النوع", ""), |
|
"التقدير النسبي": item.get("تقدير نسبي للتكلفة", "") |
|
}) |
|
|
|
if cost_data: |
|
cost_df = pd.DataFrame(cost_data) |
|
st.dataframe(cost_df, use_container_width=True) |
|
else: |
|
|
|
st.write(result["result"].get("text", "لا توجد نتائج تحليل")) |
|
|
|
def _render_risk_analysis_tab(self): |
|
"""عرض تبويب تحليل المخاطر""" |
|
st.markdown("### تحليل المخاطر") |
|
|
|
st.info("هذه الوحدة تساعدك في تحديد وتقييم المخاطر المحتملة للمشروع ووضع خطط الاستجابة المناسبة.") |
|
|
|
|
|
st.subheader("سجل المخاطر") |
|
|
|
|
|
risk_data = { |
|
"المخاطر": [ |
|
"تأخر توريد المواد", |
|
"تغيير متطلبات المشروع", |
|
"نقص العمالة الماهرة", |
|
"مشاكل فنية غير متوقعة", |
|
"تغيرات في الأنظمة واللوائح" |
|
], |
|
"الاحتمالية": [ |
|
"متوسطة", |
|
"عالية", |
|
"منخفضة", |
|
"متوسطة", |
|
"منخفضة" |
|
], |
|
"التأثير": [ |
|
"عالي", |
|
"عالي", |
|
"متوسط", |
|
"عالي", |
|
"عالي" |
|
], |
|
"درجة الخطورة": [ |
|
"عالية", |
|
"عالية", |
|
"متوسطة", |
|
"عالية", |
|
"متوسطة" |
|
], |
|
"خطة الاستجابة": [ |
|
"التعاقد مع موردين بدلاء", |
|
"توثيق المتطلبات وإدارة التغيير", |
|
"التعاقد المبكر مع فرق العمل", |
|
"إجراء اختبارات مبكرة", |
|
"متابعة التحديثات التنظيمية" |
|
] |
|
} |
|
|
|
risk_df = pd.DataFrame(risk_data) |
|
st.dataframe(risk_df, use_container_width=True) |
|
|
|
|
|
st.subheader("إضافة مخاطر جديدة") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
new_risk = st.text_input("وصف المخاطرة") |
|
probability = st.selectbox( |
|
"احتمالية الحدوث", |
|
["منخفضة", "متوسطة", "عالية"] |
|
) |
|
|
|
with col2: |
|
impact = st.selectbox( |
|
"التأثير", |
|
["منخفض", "متوسط", "عالي"] |
|
) |
|
response_plan = st.text_area("خطة الاستجابة") |
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
add_button = st.button("إضافة المخاطرة") |
|
with col2: |
|
ai_suggestion = st.checkbox("اقتراح ذكي", value=True) |
|
|
|
if ai_suggestion and new_risk: |
|
with st.spinner("جاري تحليل المخاطرة باستخدام الذكاء الاصطناعي..."): |
|
|
|
risk_description = f"المخاطرة: {new_risk}" |
|
result = self.claude_service.analyze_text(risk_description, "risks") |
|
|
|
if "error" not in result and isinstance(result["result"], dict): |
|
if "المخاطر" in result["result"] and isinstance(result["result"]["المخاطر"], list): |
|
risk_analysis = result["result"]["المخاطر"][0] |
|
|
|
if isinstance(risk_analysis, dict): |
|
st.success("تم تحليل المخاطرة بنجاح!") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
with col1: |
|
st.write("**الاحتمالية المقترحة:**", risk_analysis.get("الاحتمالية", "غير محدد")) |
|
st.write("**التأثير المقترح:**", risk_analysis.get("التأثير", "غير محدد")) |
|
with col2: |
|
st.write("**استراتيجية التخفيف المقترحة:**", risk_analysis.get("استراتيجية التخفيف", "غير محدد")) |
|
|
|
if add_button and new_risk: |
|
st.success("تمت إضافة المخاطرة بنجاح!") |
|
|
|
|
|
st.subheader("مصفوفة المخاطر") |
|
|
|
|
|
risk_matrix = np.array([ |
|
[1, 2, 3], |
|
[2, 4, 6], |
|
[3, 6, 9] |
|
]) |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
colorscale = [ |
|
[0, '#1a9850'], |
|
[0.3, '#91cf60'], |
|
[0.5, '#ffffbf'], |
|
[0.7, '#fc8d59'], |
|
[1, '#d73027'] |
|
] |
|
|
|
|
|
fig.add_trace(go.Heatmap( |
|
z=risk_matrix, |
|
x=['منخفض', 'متوسط', 'عالي'], |
|
y=['منخفضة', 'متوسطة', 'عالية'], |
|
text=risk_matrix, |
|
texttemplate="%{text}", |
|
textfont={"size":20}, |
|
colorscale=colorscale, |
|
showscale=True, |
|
colorbar=dict( |
|
title=get_display_arabic("درجة الخطورة"), |
|
titleside="right", |
|
titlefont=dict(size=14), |
|
tickfont=dict(size=12), |
|
) |
|
)) |
|
|
|
|
|
fig.update_layout( |
|
title=get_display_arabic("مصفوفة المخاطر"), |
|
height=500, |
|
margin=dict(l=50, r=50, t=50, b=50), |
|
xaxis=dict( |
|
title=get_display_arabic("التأثير"), |
|
titlefont=dict(size=14), |
|
tickfont=dict(size=12), |
|
), |
|
yaxis=dict( |
|
title=get_display_arabic("الاحتمالية"), |
|
titlefont=dict(size=14), |
|
tickfont=dict(size=12), |
|
), |
|
font=dict(size=14) |
|
) |
|
|
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.subheader("تحليل المخاطر بالذكاء الاصطناعي") |
|
|
|
risk_text = st.text_area("أدخل وصف المشروع لتحليل المخاطر المحتملة", height=150) |
|
|
|
if st.button("تحليل المخاطر"): |
|
if risk_text: |
|
with st.spinner("جاري تحليل المخاطر باستخدام الذكاء الاصطناعي..."): |
|
|
|
result = self.claude_service.analyze_text(risk_text, "risks") |
|
|
|
if "error" not in result: |
|
st.success("تم تحليل المخاطر بنجاح!") |
|
|
|
|
|
if isinstance(result["result"], dict) and "المخاطر" in result["result"]: |
|
risks = result["result"]["المخاطر"] |
|
|
|
|
|
risk_data = [] |
|
for risk in risks: |
|
if isinstance(risk, dict): |
|
risk_data.append({ |
|
"المخاطر": risk.get("الوصف", ""), |
|
"الاحتمالية": risk.get("الاحتمالية", ""), |
|
"التأثير": risk.get("التأثير", ""), |
|
"خطة الاستجابة": risk.get("استراتيجية التخفيف", "") |
|
}) |
|
|
|
if risk_data: |
|
risk_df = pd.DataFrame(risk_data) |
|
st.dataframe(risk_df, use_container_width=True) |
|
else: |
|
|
|
st.write(result["result"].get("text", "لا توجد نتائج تحليل")) |
|
else: |
|
st.error(f"حدث خطأ أثناء التحليل: {result['error']}") |
|
else: |
|
st.warning("يرجى إدخال وصف المشروع لتحليل المخاطر.") |
|
|
|
def _render_document_analysis_tab(self): |
|
"""عرض تبويب تحليل المستندات""" |
|
st.markdown("### تحليل المستندات") |
|
|
|
st.info("هذه الوحدة تساعدك في تحليل مستندات المناقصات واستخراج المعلومات المهمة منها.") |
|
|
|
|
|
uploaded_file = st.file_uploader( |
|
"ارفع مستند المناقصة (PDF, DOCX, XLSX)", |
|
type=["pdf", "docx", "xlsx"], |
|
key="document_analysis_upload" |
|
) |
|
|
|
if uploaded_file is not None: |
|
|
|
file_details = { |
|
"اسم الملف": uploaded_file.name, |
|
"نوع الملف": uploaded_file.type, |
|
"حجم الملف": f"{uploaded_file.size / 1024:.2f} كيلوبايت" |
|
} |
|
|
|
st.json(file_details) |
|
|
|
|
|
analysis_options = st.multiselect( |
|
"اختر أنواع التحليل", |
|
[ |
|
"استخراج المتطلبات", |
|
"تحديد المواعيد النهائية", |
|
"تحليل الشروط والأحكام", |
|
"استخراج جداول الكميات", |
|
"تحديد المعايير الفنية" |
|
], |
|
default=["استخراج المتطلبات", "تحديد المواعيد النهائية"] |
|
) |
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
analyze_button = st.button("تحليل المستند") |
|
with col2: |
|
ai_analysis = st.checkbox("تحليل ذكي", value=True) |
|
|
|
if analyze_button: |
|
|
|
progress_bar = st.progress(0) |
|
status_text = st.empty() |
|
|
|
|
|
for i in range(101): |
|
progress_bar.progress(i) |
|
if i < 30: |
|
status_text.text("جاري معالجة المستند...") |
|
elif i < 60: |
|
status_text.text("جاري استخراج النصوص...") |
|
elif i < 90: |
|
status_text.text("جاري تحليل المحتوى...") |
|
else: |
|
status_text.text("جاري إعداد النتائج...") |
|
time.sleep(0.02) |
|
|
|
st.success("تم تحليل المستند بنجاح!") |
|
|
|
|
|
st.subheader("نتائج التحليل") |
|
|
|
|
|
if "استخراج المتطلبات" in analysis_options: |
|
st.write("#### المتطلبات الرئيسية") |
|
requirements = [ |
|
"توفير فريق عمل مؤهل لا يقل عن 10 أفراد", |
|
"خبرة سابقة في مشاريع مماثلة لا تقل عن 5 سنوات", |
|
"شهادة ISO 9001 للجودة", |
|
"تقديم ضمان بنكي بنسبة 5% من قيمة العقد", |
|
"الالتزام بمعايير السلامة المهنية" |
|
] |
|
for req in requirements: |
|
st.markdown(f"- {req}") |
|
|
|
if "تحديد المواعيد النهائية" in analysis_options: |
|
st.write("#### المواعيد النهائية") |
|
deadlines = { |
|
"تقديم العروض": "15/05/2025", |
|
"بدء المشروع": "01/06/2025", |
|
"المرحلة الأولى": "01/08/2025", |
|
"المرحلة الثانية": "01/10/2025", |
|
"تسليم المشروع": "31/12/2025" |
|
} |
|
deadline_df = pd.DataFrame(list(deadlines.items()), columns=["المرحلة", "التاريخ"]) |
|
st.dataframe(deadline_df, use_container_width=True) |
|
|
|
if "تحليل الشروط والأحكام" in analysis_options: |
|
st.write("#### الشروط والأحكام الهامة") |
|
terms = [ |
|
"غرامة التأخير: 1% من قيمة العقد عن كل أسبوع تأخير", |
|
"مدة الضمان: سنتان من تاريخ الاستلام النهائي", |
|
"شروط الدفع: 20% دفعة مقدمة، 60% على مراحل، 20% بعد الاستلام النهائي", |
|
"التحكيم: وفقاً لأنظمة المملكة العربية السعودية", |
|
"التأمين: يجب توفير تأمين شامل للمشروع" |
|
] |
|
for term in terms: |
|
st.markdown(f"- {term}") |
|
|
|
|
|
if ai_analysis: |
|
st.subheader("تحليل المستند بالذكاء الاصطناعي") |
|
|
|
|
|
with st.spinner("جاري تحليل المستند باستخدام الذكاء الاصطناعي..."): |
|
|
|
document_text = """ |
|
مناقصة رقم: 2025/123 |
|
|
|
المشروع: تطوير نظام إدارة المشاريع |
|
|
|
المتطلبات الفنية: |
|
1. تطوير نظام متكامل لإدارة المشاريع |
|
2. توفير واجهة مستخدم سهلة الاستخدام |
|
3. دعم اللغة العربية والإنجليزية |
|
4. توفير تقارير تحليلية متقدمة |
|
5. دعم الأجهزة المحمولة |
|
|
|
المواعيد: |
|
- آخر موعد لتقديم العروض: 15/05/2025 |
|
- بدء المشروع: 01/06/2025 |
|
- تسليم المرحلة الأولى: 01/08/2025 |
|
- تسليم المرحلة الثانية: 01/10/2025 |
|
- التسليم النهائي: 31/12/2025 |
|
|
|
الشروط والأحكام: |
|
- مدة العقد: 12 شهر |
|
- غرامة التأخير: 1% من قيمة العقد عن كل أسبوع تأخير |
|
- شروط الدفع: 20% دفعة مقدمة، 60% على مراحل، 20% بعد الاستلام النهائي |
|
- الضمان: سنتان من تاريخ الاستلام النهائي |
|
""" |
|
|
|
|
|
result = self.claude_service.analyze_text(document_text, "requirements") |
|
|
|
if "error" not in result: |
|
|
|
if isinstance(result["result"], dict) and "المتطلبات" in result["result"]: |
|
requirements = result["result"]["المتطلبات"] |
|
|
|
|
|
req_data = [] |
|
for req in requirements: |
|
if isinstance(req, dict): |
|
req_data.append({ |
|
"الوصف": req.get("الوصف", ""), |
|
"الفئة": req.get("الفئة", ""), |
|
"الأولوية": req.get("الأولوية", "") |
|
}) |
|
|
|
if req_data: |
|
req_df = pd.DataFrame(req_data) |
|
st.dataframe(req_df, use_container_width=True) |
|
else: |
|
|
|
st.write(result["result"].get("text", "لا توجد نتائج تحليل")) |
|
else: |
|
st.error(f"حدث خطأ أثناء التحليل: {result['error']}") |
|
|
|
def _render_local_content_tab(self): |
|
"""عرض تبويب المحتوى المحلي""" |
|
st.markdown("### تحليل المحتوى المحلي") |
|
|
|
st.info("هذه الوحدة تساعدك في حساب وتحسين نسبة المحتوى المحلي في المشاريع وفقاً لمتطلبات هيئة المحتوى المحلي.") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.subheader("بيانات المشروع") |
|
project_value = st.number_input( |
|
"القيمة الإجمالية للمشروع (ريال)", |
|
min_value=0, |
|
value=5000000 |
|
) |
|
|
|
project_sector = st.selectbox( |
|
"قطاع المشروع", |
|
["البناء والتشييد", "تقنية المعلومات", "الطاقة", "النقل", "الصحة", "أخرى"] |
|
) |
|
|
|
with col2: |
|
st.subheader("بيانات المحتوى المحلي") |
|
|
|
local_products = st.number_input( |
|
"قيمة المنتجات المحلية (ريال)", |
|
min_value=0, |
|
value=2000000 |
|
) |
|
|
|
local_services = st.number_input( |
|
"قيمة الخدمات المحلية (ريال)", |
|
min_value=0, |
|
value=1000000 |
|
) |
|
|
|
local_workforce = st.number_input( |
|
"تكلفة القوى العاملة المحلية (ريال)", |
|
min_value=0, |
|
value=1500000 |
|
) |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
calculate_button = st.button("حساب نسبة المحتوى المحلي") |
|
with col2: |
|
ai_optimization = st.checkbox("تحسين ذكي", value=True) |
|
|
|
if calculate_button: |
|
|
|
total_local_content = local_products + local_services + local_workforce |
|
local_content_percentage = (total_local_content / project_value) * 100 |
|
|
|
|
|
st.success("تم حساب نسبة المحتوى المحلي بنجاح!") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.metric("إجمالي المحتوى المحلي", f"{total_local_content:,.2f} ريال") |
|
st.metric("نسبة المحتوى المحلي", f"{local_content_percentage:.2f}%") |
|
|
|
|
|
if local_content_percentage >= 70: |
|
st.success("ممتاز! نسبة المحتوى المحلي تتجاوز المتطلبات.") |
|
elif local_content_percentage >= 50: |
|
st.info("جيد. نسبة المحتوى المحلي تلبي الحد الأدنى من المتطلبات.") |
|
else: |
|
st.warning("تحذير: نسبة المحتوى المحلي أقل من المتطلبات. يرجى العمل على تحسينها.") |
|
|
|
with col2: |
|
|
|
fig = go.Figure() |
|
|
|
|
|
labels = ["المنتجات المحلية", "الخدمات المحلية", "القوى العاملة المحلية", "غير محلي"] |
|
values = [local_products, local_services, local_workforce, project_value - total_local_content] |
|
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'] |
|
|
|
fig.add_trace(go.Pie( |
|
labels=[get_display_arabic(label) for label in labels], |
|
values=values, |
|
textinfo='percent', |
|
insidetextorientation='radial', |
|
marker=dict(colors=colors), |
|
hole=0.4 |
|
)) |
|
|
|
|
|
fig.update_layout( |
|
title=get_display_arabic("توزيع المحتوى المحلي"), |
|
height=400, |
|
margin=dict(l=0, r=0, t=40, b=0), |
|
font=dict(size=14), |
|
legend=dict( |
|
orientation="h", |
|
yanchor="bottom", |
|
y=-0.2, |
|
xanchor="center", |
|
x=0.5 |
|
) |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.subheader("تفاصيل المحتوى المحلي") |
|
|
|
|
|
local_content_data = { |
|
"العنصر": ["المنتجات المحلية", "الخدمات المحلية", "القوى العاملة المحلية", "إجمالي المحتوى المحلي", "غير محلي", "إجمالي المشروع"], |
|
"القيمة (ريال)": [local_products, local_services, local_workforce, total_local_content, project_value - total_local_content, project_value], |
|
"النسبة (%)": [ |
|
local_products / project_value * 100, |
|
local_services / project_value * 100, |
|
local_workforce / project_value * 100, |
|
local_content_percentage, |
|
(project_value - total_local_content) / project_value * 100, |
|
100 |
|
] |
|
} |
|
|
|
local_content_df = pd.DataFrame(local_content_data) |
|
local_content_df["النسبة (%)"] = local_content_df["النسبة (%)"].round(2) |
|
local_content_df["القيمة (ريال)"] = local_content_df["القيمة (ريال)"].apply(lambda x: f"{x:,.2f}") |
|
|
|
st.dataframe(local_content_df, use_container_width=True) |
|
|
|
|
|
if ai_optimization: |
|
st.subheader("توصيات تحسين المحتوى المحلي") |
|
|
|
with st.spinner("جاري تحليل وتحسين المحتوى المحلي باستخدام الذكاء الاصطناعي..."): |
|
|
|
project_description = f""" |
|
قطاع المشروع: {project_sector} |
|
القيمة الإجمالية للمشروع: {project_value:,.2f} ريال |
|
قيمة المنتجات المحلية: {local_products:,.2f} ريال ({local_products/project_value*100:.2f}%) |
|
قيمة الخدمات المحلية: {local_services:,.2f} ريال ({local_services/project_value*100:.2f}%) |
|
تكلفة القوى العاملة المحلية: {local_workforce:,.2f} ريال ({local_workforce/project_value*100:.2f}%) |
|
إجمالي المحتوى المحلي: {total_local_content:,.2f} ريال ({local_content_percentage:.2f}%) |
|
""" |
|
|
|
|
|
result = self.claude_service.analyze_text(project_description, "local_content") |
|
|
|
if "error" not in result: |
|
|
|
if isinstance(result["result"], dict): |
|
if "توصيات" in result["result"]: |
|
recommendations = result["result"]["توصيات"] |
|
|
|
for i, rec in enumerate(recommendations): |
|
st.markdown(f"**{i+1}. {rec}**") |
|
elif "عناصر المحتوى المحلي" in result["result"]: |
|
items = result["result"]["عناصر المحتوى المحلي"] |
|
|
|
|
|
items_data = [] |
|
for item in items: |
|
if isinstance(item, dict): |
|
items_data.append({ |
|
"الوصف": item.get("الوصف", ""), |
|
"الفئة": item.get("الفئة", ""), |
|
"مدى التوفر": item.get("مدى التوفر", ""), |
|
"المساهمة": item.get("تقدير نسبي للمساهمة في المحتوى المحلي", "") |
|
}) |
|
|
|
if items_data: |
|
items_df = pd.DataFrame(items_data) |
|
st.dataframe(items_df, use_container_width=True) |
|
else: |
|
|
|
st.write(result["result"].get("text", "لا توجد نتائج تحليل")) |
|
else: |
|
st.write("لا توجد توصيات متاحة.") |
|
else: |
|
st.error(f"حدث خطأ أثناء التحليل: {result['error']}") |
|
|
|
|
|
st.subheader("توصيات عامة لتحسين المحتوى المحلي") |
|
|
|
recommendations = [ |
|
"استخدام منتجات محلية الصنع بدلاً من المستوردة حيثما أمكن", |
|
"التعاقد مع موردين محليين معتمدين من هيئة المحتوى المحلي", |
|
"توظيف وتدريب كوادر سعودية للعمل في المشروع", |
|
"الاستفادة من برامج دعم المحتوى المحلي المقدمة من الجهات الحكومية", |
|
"تطوير شراكات مع مصنعين محليين لتوطين التقنيات المطلوبة" |
|
] |
|
|
|
for rec in recommendations: |
|
st.markdown(f"- {rec}") |
|
|
|
def _render_faq_tab(self): |
|
"""عرض تبويب الأسئلة الشائعة""" |
|
st.markdown("### الأسئلة الشائعة") |
|
|
|
st.info("هذه الصفحة تحتوي على إجابات للأسئلة الشائعة حول استخدام نظام تسعير المناقصات.") |
|
|
|
|
|
for i, faq in enumerate(self.faqs): |
|
with st.expander(faq["question"]): |
|
st.write(faq["answer"]) |
|
|
|
|
|
st.subheader("هل لديك سؤال آخر؟") |
|
|
|
new_question = st.text_input("اكتب سؤالك هنا") |
|
|
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
send_button = st.button("إرسال السؤال") |
|
with col2: |
|
ai_answer = st.checkbox("إجابة ذكية", value=True) |
|
|
|
if send_button and new_question: |
|
if ai_answer: |
|
with st.spinner("جاري تحليل السؤال باستخدام الذكاء الاصطناعي..."): |
|
|
|
result = self.claude_service.chat_completion([ |
|
{"role": "user", "content": f"السؤال: {new_question}\n\nالرجاء الإجابة على هذا السؤال المتعلق بنظام تسعير المناقصات بشكل مختصر ومفيد."} |
|
]) |
|
|
|
if "error" not in result: |
|
st.success("تم تحليل السؤال والإجابة عليه!") |
|
|
|
|
|
st.info(f"**سؤالك:** {new_question}") |
|
st.write(f"**الإجابة:** {result['content']}") |
|
else: |
|
st.error(f"حدث خطأ أثناء تحليل السؤال: {result['error']}") |
|
st.success("تم إرسال سؤالك بنجاح! سيتم الرد عليه في أقرب وقت.") |
|
else: |
|
st.success("تم إرسال سؤالك بنجاح! سيتم الرد عليه في أقرب وقت.") |
|
elif send_button: |
|
st.error("يرجى كتابة السؤال قبل الإرسال.") |
|
|