diff --git "a/modules/ai_assistant/ai_app.py" "b/modules/ai_assistant/ai_app.py"
--- "a/modules/ai_assistant/ai_app.py"
+++ "b/modules/ai_assistant/ai_app.py"
@@ -34,7 +34,7 @@ class ClaudeAIService:
def get_api_key(self):
"""الحصول على مفتاح API من متغيرات البيئة"""
- api_key = os.environ.get("ANTHROPIC_API_KEY") # تصحيح اسم متغير البيئة
+ api_key = os.environ.get("anthropic")
if not api_key:
raise ValueError("مفتاح API لـ Claude غير موجود في متغيرات البيئة")
return api_key
@@ -71,53 +71,22 @@ class ClaudeAIService:
def analyze_image(self, image_path, prompt, model_name="claude-3-7-sonnet"):
"""
تحليل صورة باستخدام نموذج Claude AI
-
+
المعلمات:
- image_path: مسار الصورة المراد تحليلها
- prompt: التوجيه للنموذج
- model_name: اسم نموذج Claude المراد استخدامه
-
+ image_path: مسار الصورة المراد تحليلها
+ prompt: التوجيه للنموذج
+ model_name: اسم نموذج Claude المراد استخدامه
+
العوائد:
- dict: نتائج التحليل
+ dict: نتائج التحليل
"""
try:
# الحصول على مفتاح API
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:
- # فقط ضغط الصور العادية وليس ملفات PDF
- 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 ميجابايت). يرجى تقليل حجم الملف قبل الرفع.")
+ # قراءة محتوى الصورة
+ with open(image_path, 'rb') as f:
+ file_content = f.read()
# تحويل المحتوى إلى Base64
file_base64 = base64.b64encode(file_content).decode('utf-8')
@@ -162,558 +131,6 @@ class ClaudeAIService:
"media_type": file_type,
"data": file_base64
}
-
- # تحويل البيانات إلى DataFrame
- quantities_df = pd.DataFrame(quantities_data)
- quantities_df['النسبة المئوية'] = quantities_df['السعر التقديري'] / quantities_df['السعر التقديري'].sum() * 100
- quantities_df['النسبة المئوية'] = quantities_df['النسبة المئوية'].round(1)
-
- # عرض جدول الكميات
- st.dataframe(quantities_df, use_container_width=True)
-
- # عرض رسم بياني للتكاليف
- fig = px.bar(
- quantities_df,
- x='البند',
- y='السعر التقديري',
- title='التكاليف التقديرية حسب البند',
- labels={'السعر التقديري': 'التكلفة التقديرية (ريال)', 'البند': 'بنود الأعمال'}
- )
- st.plotly_chart(fig, use_container_width=True)
-
- # استخراج الكلمات المفتاحية من المستند
- st.markdown("### الكلمات المفتاحية المستخرجة")
-
- keywords = [
- {"word": "غرامة التأخير", "count": 3, "relevance": 0.85},
- {"word": "المحتوى المحلي", "count": 5, "relevance": 0.92},
- {"word": "الدفعة المقدمة", "count": 2, "relevance": 0.78},
- {"word": "مدة التنفيذ", "count": 4, "relevance": 0.88},
- {"word": "الضمان", "count": 3, "relevance": 0.75},
- {"word": "مواقف سيارات", "count": 2, "relevance": 0.70},
- {"word": "تكييف", "count": 3, "relevance": 0.65},
- {"word": "سلامة", "count": 2, "relevance": 0.60}
- ]
-
- keywords_df = pd.DataFrame(keywords)
-
- # عرض الكلمات المفتاحية كسحابة كلمات
- from matplotlib.pyplot import figure
-
- fig, ax = plt.subplots(figsize=(10, 5))
-
- # رسم الكلمات بأحجام مختلفة حسب الأهمية
- for i, kw in enumerate(keywords):
- size = 10 + (kw["relevance"] * 30)
- color = plt.cm.viridis(kw["relevance"])
- x = random.uniform(0.1, 0.9)
- y = random.uniform(0.1, 0.9)
- ax.text(x, y, kw["word"], fontsize=size, color=color,
- ha='center', va='center', rotation=random.randint(-30, 30))
-
- ax.set_xlim(0, 1)
- ax.set_ylim(0, 1)
- ax.axis('off')
-
- st.pyplot(fig)
-
- # عرض توصيات
- st.markdown("### التوصيات")
-
- st.info("""
- بناءً على تحليل مستند المناقصة، نوصي بالآتي:
-
- 1. **التركيز على المحتوى المحلي**: تقديم خطة تفصيلية لتحقيق نسبة 60% من المحتوى المحلي.
-
- 2. **مراجعة شروط الدفعات**: التأكد من توفير السيولة المالية اللازمة نظرًا لنظام الدفعات الشهرية.
-
- 3. **الانتباه لغرامات التأخير**: وضع جدول زمني واقعي مع هوامش كافية لتجنب غرامات التأخير.
-
- 4. **الاستعانة بالمتخصصين**: تكوين فريق فني متكامل يلبي متطلبات الخبرة المطلوبة.
-
- 5. **مراجعة تفصيلية لجدول الكميات**: التأكد من شمولية الأسعار لجميع متطلبات المشروع.
- """)
-
- def _render_local_content_tab(self):
- """عرض تبويب المحتوى المحلي"""
- st.markdown("### حاسبة المحتوى المحلي")
-
- st.markdown("""
- استخدم هذه الأداة لحساب نسبة المحتوى المحلي للمشروع حسب متطلبات هيئة المحتوى المحلي.
- """)
-
- # مدخلات القسم الأول: العمالة
- st.markdown("#### القسم الأول: العمالة")
-
- col1, col2 = st.columns(2)
-
- with col1:
- local_labor_cost = st.number_input(
- "تكلفة العمالة المحلية",
- min_value=0,
- value=500000,
- step=10000,
- format="%d",
- key="local_labor_cost"
- )
-
- foreign_labor_cost = st.number_input(
- "تكلفة العمالة الأجنبية",
- min_value=0,
- value=300000,
- step=10000,
- format="%d",
- key="foreign_labor_cost"
- )
-
- with col2:
- local_labor_weight = st.slider(
- "وزن العمالة المحلية",
- min_value=0.0,
- max_value=1.0,
- value=0.4,
- step=0.1,
- format="%.1f",
- key="local_labor_weight"
- )
-
- # مدخلات القسم الثاني: المواد
- st.markdown("#### القسم الثاني: المواد")
-
- col1, col2 = st.columns(2)
-
- with col1:
- local_materials_cost = st.number_input(
- "تكلفة المواد المحلية",
- min_value=0,
- value=800000,
- step=10000,
- format="%d",
- key="local_materials_cost"
- )
-
- imported_materials_cost = st.number_input(
- "تكلفة المواد المستوردة",
- min_value=0,
- value=1200000,
- step=10000,
- format="%d",
- key="imported_materials_cost"
- )
-
- with col2:
- materials_weight = st.slider(
- "وزن المواد",
- min_value=0.0,
- max_value=1.0,
- value=0.3,
- step=0.1,
- format="%.1f",
- key="materials_weight"
- )
-
- # مدخلات القسم الثالث: المعدات
- st.markdown("#### القسم الثالث: المعدات")
-
- col1, col2 = st.columns(2)
-
- with col1:
- local_equipment_cost = st.number_input(
- "تكلفة المعدات المحلية",
- min_value=0,
- value=300000,
- step=10000,
- format="%d",
- key="local_equipment_cost"
- )
-
- imported_equipment_cost = st.number_input(
- "تكلفة المعدات المستوردة",
- min_value=0,
- value=700000,
- step=10000,
- format="%d",
- key="imported_equipment_cost"
- )
-
- with col2:
- equipment_weight = st.slider(
- "وزن المعدات",
- min_value=0.0,
- max_value=1.0,
- value=0.2,
- step=0.1,
- format="%.1f",
- key="equipment_weight"
- )
-
- # مدخلات القسم الرابع: الخدمات
- st.markdown("#### القسم الرابع: الخدمات")
-
- col1, col2 = st.columns(2)
-
- with col1:
- local_services_cost = st.number_input(
- "تكلفة الخدمات المحلية",
- min_value=0,
- value=400000,
- step=10000,
- format="%d",
- key="local_services_cost"
- )
-
- foreign_services_cost = st.number_input(
- "تكلفة الخدمات الأجنبية",
- min_value=0,
- value=200000,
- step=10000,
- format="%d",
- key="foreign_services_cost"
- )
-
- with col2:
- services_weight = st.slider(
- "وزن الخدمات",
- min_value=0.0,
- max_value=1.0,
- value=0.1,
- step=0.1,
- format="%.1f",
- key="services_weight"
- )
-
- # التحقق من إجمالي الأوزان
- total_weight = local_labor_weight + materials_weight + equipment_weight + services_weight
-
- if total_weight != 1.0:
- st.warning(f"إجمالي الأوزان يجب أن يساوي 1.0. الإجمالي الحالي هو {total_weight}.")
-
- # زر حساب المحتوى المحلي
- if st.button("حساب المحتوى المحلي", key="calculate_local_content_button"):
- # عرض مؤشر التقدم
- with st.spinner("جاري حساب نسبة المحتوى المحلي..."):
- # محاكاة وقت المعالجة
- time.sleep(1)
-
- # حساب نسبة المحتوى المحلي لكل قسم
- labor_total = local_labor_cost + foreign_labor_cost
- labor_local_percentage = (local_labor_cost / labor_total) * 100 if labor_total > 0 else 0
-
- materials_total = local_materials_cost + imported_materials_cost
- materials_local_percentage = (local_materials_cost / materials_total) * 100 if materials_total > 0 else 0
-
- equipment_total = local_equipment_cost + imported_equipment_cost
- equipment_local_percentage = (local_equipment_cost / equipment_total) * 100 if equipment_total > 0 else 0
-
- services_total = local_services_cost + foreign_services_cost
- services_local_percentage = (local_services_cost / services_total) * 100 if services_total > 0 else 0
-
- # حساب النسبة الإجمالية المرجحة
- weighted_local_content = (
- (labor_local_percentage * local_labor_weight) +
- (materials_local_percentage * materials_weight) +
- (equipment_local_percentage * equipment_weight) +
- (services_local_percentage * services_weight)
- )
-
- # تقييم النتيجة
- target_percentage = 60 # النسبة المستهدفة
-
- if weighted_local_content >= target_percentage:
- status = "مطابق"
- status_color = "green"
- elif weighted_local_content >= target_percentage * 0.9: # ضمن 90% من الهدف
- status = "قريب"
- status_color = "orange"
- else:
- status = "غير مطابق"
- status_color = "red"
-
- # عرض النتائج
- st.success("تم حساب نسبة المحتوى المحلي بنجاح!")
-
- # عرض الملخص
- st.markdown("#### نتائج المحتوى المحلي")
-
- col1, col2, col3 = st.columns([2, 1, 1])
-
- with col1:
- st.metric(
- "نسبة المحتوى المحلي الإجمالية",
- f"{weighted_local_content:.1f}%",
- f"{weighted_local_content - target_percentage:.1f}%" if weighted_local_content != target_percentage else "0%"
- )
-
- with col2:
- st.metric("النسبة المستهدفة", f"{target_percentage}%")
-
- with col3:
- st.markdown(f"""
-
- {status}
-
- """, unsafe_allow_html=True)
-
- # عرض التفاصيل
- st.markdown("#### تفاصيل حساب المحتوى المحلي")
-
- # إنشاء بيانات التفاصيل
- details_data = {
- 'القسم': ['العمالة', 'المواد', 'المعدات', 'الخدمات', 'الإجمالي'],
- 'التكلفة المحلية': [
- local_labor_cost,
- local_materials_cost,
- local_equipment_cost,
- local_services_cost,
- local_labor_cost + local_materials_cost + local_equipment_cost + local_services_cost
- ],
- 'التكلفة الإجمالية': [
- labor_total,
- materials_total,
- equipment_total,
- services_total,
- labor_total + materials_total + equipment_total + services_total
- ],
- 'النسبة المحلية': [
- f"{labor_local_percentage:.1f}%",
- f"{materials_local_percentage:.1f}%",
- f"{equipment_local_percentage:.1f}%",
- f"{services_local_percentage:.1f}%",
- f"{weighted_local_content:.1f}%"
- ],
- 'الوزن': [
- f"{local_labor_weight:.1f}",
- f"{materials_weight:.1f}",
- f"{equipment_weight:.1f}",
- f"{services_weight:.1f}",
- "1.0"
- ],
- 'المساهمة المرجحة': [
- f"{labor_local_percentage * local_labor_weight:.1f}%",
- f"{materials_local_percentage * materials_weight:.1f}%",
- f"{equipment_local_percentage * equipment_weight:.1f}%",
- f"{services_local_percentage * services_weight:.1f}%",
- f"{weighted_local_content:.1f}%"
- ]
- }
-
- details_df = pd.DataFrame(details_data)
-
- # عرض الجدول
- st.dataframe(details_df, use_container_width=True)
-
- # عرض رسم بياني للمساهمات
- st.markdown("#### مساهمة كل قسم في المحتوى المحلي")
-
- chart_data = details_df.iloc[:-1].copy() # استبعاد صف الإجمالي
-
- fig = px.bar(
- chart_data,
- x='القسم',
- y=[local_labor_weight * labor_local_percentage,
- materials_weight * materials_local_percentage,
- equipment_weight * equipment_local_percentage,
- services_weight * services_local_percentage],
- labels={'value': 'المساهمة المرجحة (%)', 'variable': 'القسم'},
- title='مساهمة كل قسم في إجمالي المحتوى المحلي',
- color_discrete_sequence=px.colors.qualitative.Set3
- )
-
- # إضافة خط للنسبة المستهدفة
- fig.add_hline(y=target_percentage, line_dash="dash", line_color="red",
- annotation_text=f"النسبة المستهدفة ({target_percentage}%)",
- annotation_position="top right")
-
- st.plotly_chart(fig, use_container_width=True)
-
- # توصيات لتحسين المحتوى المحلي
- st.markdown("#### توصيات لتحسين المحتوى المحلي")
-
- if weighted_local_content < target_percentage:
- st.warning("""
- للوصول إلى النسبة المستهدفة للمحتوى المحلي (60%)، نوصي بما يلي:
- """)
-
- recommendations = []
-
- if labor_local_percentage < 70:
- recommendations.append("زيادة نسبة توظيف العمالة المحلية والاستفادة من برامج دعم التوطين.")
-
- if materials_local_percentage < 50:
- recommendations.append("البحث عن موردين محليين للمواد وإعطاء الأولوية للمنتجات المحلية.")
-
- if equipment_local_percentage < 40:
- recommendations.append("استئجار المعدات من شركات محلية بدلاً من الاستيراد المباشر.")
-
- if services_local_percentage < 60:
- recommendations.append("التعاقد مع مقدمي خدمات محليين والاستعانة بالشركات المحلية للخدمات المساندة.")
-
- for i, rec in enumerate(recommendations):
- st.markdown(f"{i+1}. {rec}")
-
- # نموذج محاكاة لتحسين المحتوى المحلي
- st.markdown("#### محاكاة تحسين المحتوى المحلي")
-
- st.info("""
- استخدم المحاكاة أدناه لتجربة تعديل النسب والوصول إلى المحتوى المحلي المستهدف.
- """)
-
- # أعمدة لإدخال نسب التحسين
- col1, col2, col3, col4 = st.columns(4)
-
- with col1:
- labor_improvement = st.slider(
- "تحسين العمالة المحلية",
- min_value=0,
- max_value=100,
- value=10,
- step=5,
- format="%d%%",
- key="labor_improvement"
- ) / 100
-
- with col2:
- materials_improvement = st.slider(
- "تحسين المواد المحلية",
- min_value=0,
- max_value=100,
- value=15,
- step=5,
- format="%d%%",
- key="materials_improvement"
- ) / 100
-
- with col3:
- equipment_improvement = st.slider(
- "تحسين المعدات المحلية",
- min_value=0,
- max_value=100,
- value=10,
- step=5,
- format="%d%%",
- key="equipment_improvement"
- ) / 100
-
- with col4:
- services_improvement = st.slider(
- "تحسين الخدمات المحلية",
- min_value=0,
- max_value=100,
- value=5,
- step=5,
- format="%d%%",
- key="services_improvement"
- ) / 100
-
- # حساب المحتوى المحلي بعد التحسين
- improved_labor_percentage = min(100, labor_local_percentage + (labor_improvement * 100))
- improved_materials_percentage = min(100, materials_local_percentage + (materials_improvement * 100))
- improved_equipment_percentage = min(100, equipment_local_percentage + (equipment_improvement * 100))
- improved_services_percentage = min(100, services_local_percentage + (services_improvement * 100))
-
- improved_local_content = (
- (improved_labor_percentage * local_labor_weight) +
- (improved_materials_percentage * materials_weight) +
- (improved_equipment_percentage * equipment_weight) +
- (improved_services_percentage * services_weight)
- )
-
- # عرض نتائج التحسين
- col1, col2 = st.columns(2)
-
- with col1:
- st.metric(
- "المحتوى المحلي الحالي",
- f"{weighted_local_content:.1f}%"
- )
-
- with col2:
- st.metric(
- "المحتوى المحلي بعد التحسين",
- f"{improved_local_content:.1f}%",
- f"{improved_local_content - weighted_local_content:.1f}%"
- )
-
- # تقييم التكلفة الإضافية للتحسين
- additional_cost = (
- (labor_improvement * foreign_labor_cost) +
- (materials_improvement * imported_materials_cost) +
- (equipment_improvement * imported_equipment_cost) +
- (services_improvement * foreign_services_cost)
- ) * 0.15 # تقدير تكلفة التحويل بنسبة 15%
-
- st.metric(
- "التكلفة الإضافية المقدرة للتحسين",
- f"{additional_cost:,.2f} ريال"
- )
-
- # عرض حالة التحسين
- if improved_local_content >= target_percentage:
- st.success(f"بعد التحسين، سيصبح المحتوى المحلي {improved_local_content:.1f}% وهو أعلى من النسبة المستهدفة ({target_percentage}%).")
- else:
- st.warning(f"التحسين المقترح غير كافٍ. المحتوى المحلي سيصبح {improved_local_content:.1f}% وهو أقل من النسبة المستهدفة ({target_percentage}%).")
- else:
- st.success(f"""
- تهانينا! المشروع يحقق نسبة محتوى محلي {weighted_local_content:.1f}% وهي أعلى من النسبة المستهدفة ({target_percentage}%).
-
- للحفاظ على هذا المستوى أو تحسينه، نوصي بما يلي:
-
- 1. توثيق جميع المشتريات المحلية والتأكد من حصولها على شهادات المحتوى المحلي.
- 2. متابعة تحديثات هيئة المحتوى المحلي للاستفادة من الحوافز المتاحة.
- 3. بناء علاقات استراتيجية مع الموردين المحليين لضمان استدامة سلسلة التوريد.
- 4. المشاركة في برامج تطوير الموردين المحليين لتعزيز القدرات المحلية.
- """)
-
- def _render_faq_tab(self):
- """عرض تبويب الأسئلة الشائعة"""
- st.markdown("### الأسئلة الشائعة")
-
- st.markdown("""
- فيما يلي قائمة بالأسئلة الشائعة حول نظام تسعير المناقصات والإجابات عليها.
- """)
-
- # فلترة الأسئلة
- search_query = st.text_input("ابحث في الأسئلة الشائعة", key="faq_search")
-
- filtered_faqs = self.faqs
- if search_query:
- filtered_faqs = [faq for faq in self.faqs if search_query.lower() in faq["question"].lower()]
-
- # عرض الأسئلة المصفاة
- if not filtered_faqs:
- st.info("لا توجد نتائج مطابقة لبحثك. يرجى تعديل كلمات البحث.")
- else:
- for i, faq in enumerate(filtered_faqs):
- with st.expander(faq["question"]):
- st.markdown(faq["answer"])
-
- # قسم لإضافة سؤال جديد
- st.markdown("### لم تجد إجابة لسؤالك؟")
-
- new_question = st.text_area("اكتب سؤالك هنا", key="new_question")
-
- if st.button("إرسال السؤال", key="submit_question_button"):
- if new_question:
- st.success("تم استلام سؤالك بنجاح! سيتم الرد عليه في أقرب وقت ممكن.")
-
- # في التطبيق الحقيقي، سيتم حفظ السؤال في قاعدة البيانات
- # ويمكن استخدام Claude AI للإجابة عليه تلقائيًا
- else:
- st.warning("يرجى كتابة سؤالك قبل الإرسال.")
-
-
-# إنشاء نسخة من التطبيق واستدعاء الدالة الرئيسية للعرض
-if __name__ == "__main__":
- app = AIAssistantApp()
- app.render()
}
]
}
@@ -790,7 +207,7 @@ if __name__ == "__main__":
payload = {
"model": model_name,
- "max_tokens": 4096, # زيادة الحد الأقصى للإجابة
+ "max_tokens": 2048,
"messages": claude_messages,
"temperature": 0.7
}
@@ -800,7 +217,7 @@ if __name__ == "__main__":
self.api_url,
headers=headers,
json=payload,
- timeout=60 # زيادة مهلة الانتظار
+ timeout=30
)
# التحقق من نجاح الطلب
@@ -836,17 +253,15 @@ class AIAssistantApp:
def __init__(self):
"""تهيئة وحدة المساعد الذكي"""
- # إعداد التسجيل
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-
# التحقق من توفر مكتبة pdf2image
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
- logging.warning("مكتبة pdf2image غير متوفرة. لن يتم دعم تحويل ملفات PDF.")
# تحميل النماذج عند بدء التشغيل
self.cost_model = self._load_cost_prediction_model()
@@ -879,43 +294,86 @@ class AIAssistantApp:
{
"question": "كيف يمكنني تقييم المخاطر للمشروع؟",
"answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة."
+ }
+ ]
+
+ # تهيئة قائمة الأسئلة والإجابات الشائعة
+ self.faqs = [
+ {
+ "question": "كيف يمكنني إضافة مشروع جديد؟",
+ "answer": "يمكنك إضافة مشروع جديد من خلال الانتقال إلى وحدة إدارة المشاريع، ثم النقر على زر 'إضافة مشروع جديد'، وملء النموذج بالبيانات المطلوبة."
},
{
- "question": "ما هي طرق التسعير المتاحة في النظام؟",
- "answer": "يوفر النظام أربع طرق للتسعير: 1) التسعير القياسي، 2) التسعير غير المتزن، 3) التسعير التنافسي، 4) التسعير الموجه بالربحية. يمكنك اختيار الطريقة المناسبة حسب طبيعة المشروع واستراتيجية الشركة."
+ "question": "ما هي خطوات تسعير المناقصة؟",
+ "answer": "تتضمن خطوات تسعير المناقصة: 1) تحليل مستندات المناقصة، 2) تحديد بنود العمل، 3) تقدير التكاليف المباشرة، 4) إضافة المصاريف العامة والأرباح، 5) احتساب المحتوى المحلي، 6) مراجعة النتائج النهائية."
},
{
- "question": "كيف يمكنني معالجة مستندات المناقصة ضخمة الحجم؟",
- "answer": "يمكنك استخدام وحدة تحليل المستندات لمعالجة مستندات المناقصة ضخمة الحجم، حيث تقوم الوحدة بتحليل المستندات واستخراج المعلومات المهمة مثل مواصفات المشروع ومتطلباته وشروطه تلقائياً."
+ "question": "كيف يتم حساب المحتوى المحلي؟",
+ "answer": "يتم حساب المحتوى المحلي بتحديد نسبة المنتجات والخدمات والقوى العاملة المحلية من إجمالي التكاليف. يتم استخدام قاعدة بيانات الموردين المعتمدين وتطبيق معادلات خاصة حسب متطلبات هيئة المحتوى المحلي."
+ },
+ {
+ "question": "كيف يمكنني تصدير التقارير؟",
+ "answer": "يمكنك تصدير التقارير من وحدة التقارير والتحليلات، حيث يوجد زر 'تصدير' في كل تقرير. يمكن تصدير التقارير بتنسيقات مختلفة مثل Excel و PDF و CSV."
+ },
+ {
+ "question": "كيف يمكنني تقييم المخاطر للمشروع؟",
+ "answer": "يمكنك تقييم المخاطر للمشروع من خلال وحدة المخاطر، حيث يمكنك إضافة المخاطر المحتملة وتقييم تأثيرها واحتماليتها، ثم وضع خطة الاستجابة المناسبة."
}
]
-
+
def _load_cost_prediction_model(self):
"""تحميل نموذج التنبؤ بالتكاليف"""
# في البيئة الإنتاجية، سيتم تحميل نموذج حقيقي
# هنا نقوم بإنشاء كائن محاكاة للنموذج
- logging.info("جاري تحميل نموذج التنبؤ بالتكاليف...")
return {"name": "cost_prediction_model", "version": "1.0.0", "status": "loaded"}
def _load_document_classifier_model(self):
"""تحميل نموذج تصنيف المستندات"""
- logging.info("جاري تحميل نموذج تصنيف المستندات...")
return {"name": "document_classifier_model", "version": "1.0.0", "status": "loaded"}
def _load_risk_assessment_model(self):
"""تحميل نموذج تقييم المخاطر"""
- logging.info("جاري تحميل نموذج تقييم المخاطر...")
return {"name": "risk_assessment_model", "version": "1.0.0", "status": "loaded"}
def _load_local_content_model(self):
"""تحميل نموذج تحليل المحتوى المحلي"""
- logging.info("جاري تحميل نموذج تحليل المحتوى المحلي...")
return {"name": "local_content_model", "version": "1.0.0", "status": "loaded"}
def _load_entity_recognition_model(self):
"""تحميل نموذج التعرف على الكيانات"""
- logging.info("جاري تحميل نموذج التعرف على الكيانات...")
return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"}
+
+ # تهيئة قائمة الأسئلة والإجابات الشائعة
+ 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):
"""عرض واجهة وحدة المساعد الذكي"""
@@ -1074,650 +532,2751 @@ class AIAssistantApp:
# مربع إدخال الرسالة
user_input = st.text_input("اكتب رسالتك هنا", key="ai_assistant_input")
- # زر الإرسال
- if st.button("إرسال", key="send_message_button"):
- # التحقق من وجود مفتاح API
- api_available = True
- try:
- self.claude_service.get_api_key()
- except ValueError:
- api_available = False
- st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.")
-
- # معالجة الرسالة إذا تم توفير إدخال
- if user_input and api_available:
- # إضافة رسالة المستخدم إلى المحادثة
- st.session_state.ai_assistant_messages.append({"role": "user", "content": user_input})
-
- # معالجة الملف المرفوع إذا وجد
- if uploaded_file is not None:
- # حفظ الملف المرفوع مؤقتًا
- with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file:
- tmp_file.write(uploaded_file.getvalue())
- temp_file_path = tmp_file.name
+ # التحقق من وجود مفتاح API
+ api_available = True
+ try:
+ self.claude_service.get_api_key()
+ except ValueError:
+ api_available = False
+ st.warning("مفتاح API لـ Claude غير متوفر. يرجى التأكد من تعيين متغير البيئة 'anthropic'.")
+
+ if user_input and api_available:
+ # إضافة رسالة المستخدم إلى المحفوظات
+ st.session_state.ai_assistant_messages.append({"role": "user", "content": user_input})
+
+ # عرض محفوظات المحادثة المحدثة
+ with chat_container:
+ st.markdown(f"""
+
+ """, unsafe_allow_html=True)
+
+ # معالجة الرد
+ with st.spinner("جاري التفكير..."):
+ # التحقق مما إذا كان هناك ملف مرفق
+ if uploaded_file:
+ # حفظ الملف المرفوع مؤقتاً
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as temp_file:
+ temp_file.write(uploaded_file.getbuffer())
+ temp_file_path = temp_file.name
- try:
- # إذا كان الملف PDF وكان تحويل PDF متاحًا
- if uploaded_file.name.lower().endswith('.pdf') and self.pdf_conversion_available:
- # تحويل PDF إلى صورة
- images = self.convert_from_path(temp_file_path, first_page=1, last_page=1)
- if images:
- # حفظ الصورة الأولى مؤقتًا
- image_path = f"{temp_file_path}.jpg"
- images[0].save(image_path, 'JPEG')
-
- # تحليل الصورة باستخدام Claude
- result = self.claude_service.analyze_image(
- image_path=image_path,
- prompt=f"المستخدم رفع ملف PDF وسأل: {user_input}. يرجى تحليل هذه الصفحة من الملف وتقديم إجابة مناسبة.",
- model_name=selected_model
- )
-
- # تنظيف الملفات المؤقتة
- os.remove(image_path)
- else:
- result = {"error": "فشل في تحويل ملف PDF إلى صورة."}
+ # إذا كان الملف PDF، تحويله إلى صورة
+ if uploaded_file.name.lower().endswith('.pdf'):
+ if self.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:
- # تحليل الصورة مباشرة باستخدام Claude
- result = self.claude_service.analyze_image(
- image_path=temp_file_path,
- prompt=f"المستخدم رفع صورة وسأل: {user_input}. يرجى تحليل الصورة وتقديم إجابة مناسبة.",
- model_name=selected_model
- )
- except Exception as e:
- result = {"error": f"حدث خطأ أثناء معالجة الملف: {str(e)}"}
+ st.error("تحليل ملفات PDF يتطلب تثبيت مكتبة pdf2image.")
+ response = "عذراً، لا يمكنني تحليل ملفات PDF في الوقت الحالي. يرجى تحويل الملف إلى صورة أو مشاركة المعلومات كنص."
- # تنظيف الملف المؤقت
- os.remove(temp_file_path)
-
- # إضافة استجابة المساعد
- if "error" in result:
- assistant_response = f"عذراً، حدث خطأ أثناء معالجة الملف: {result['error']}"
+ # تحليل الصورة باستخدام 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:
- assistant_response = result["content"]
+ response = results["content"]
else:
- # إرسال المحادثة إلى Claude للحصول على استجابة
- result = self.claude_service.chat_completion(
- messages=st.session_state.ai_assistant_messages,
- model_name=selected_model
- )
+ # استخدام خدمة Claude للرد على الرسائل النصية
+ results = self.claude_service.chat_completion(st.session_state.ai_assistant_messages, model_name=selected_model)
- # إضافة استجابة المساعد
- if "error" in result:
- assistant_response = f"عذراً، حدث خطأ أثناء الاتصال بالمساعد الذكي: {result['error']}"
+ if "error" in results:
+ response = f"عذراً، حدث خطأ أثناء معالجة طلبك: {results['error']}"
else:
- assistant_response = result["content"]
-
- # إضافة استجابة المساعد إلى المحادثة
- st.session_state.ai_assistant_messages.append({"role": "assistant", "content": assistant_response})
-
- # تحديث واجهة المستخدم
- st.experimental_rerun()
+ response = results["content"]
+
+ # إضافة رد المساعد إلى المحفوظات
+ st.session_state.ai_assistant_messages.append({"role": "assistant", "content": response})
+
+ # عرض رد المساعد
+ with chat_container:
+ st.markdown(f"""
+
+ """, unsafe_allow_html=True)
+
+ # إعادة تعيين قيمة الإدخال
+ st.text_input("اكتب رسالتك هنا", value="", key="ai_assistant_input_reset")
+
+ def _generate_ai_response(self, user_input, model_name="claude-3-7-sonnet"):
+ """توليد رد المساعد الذكي باستخدام Claude AI"""
+
+ # التحقق من وجود مفتاح API
+ try:
+ self.claude_service.get_api_key()
+ except ValueError:
+ return "عذراً، لا يمكنني الاتصال بخدمة الذكاء الاصطناعي في الوقت الحالي. يرجى التحقق من إعدادات API."
+
+ # البحث في الأسئلة الشائعة أولاً
+ for faq in self.faqs:
+ if any(keyword in user_input.lower() for keyword in faq["question"].lower().split()):
+ return f"{faq['answer']}\n\nهل تحتاج إلى مساعدة أخرى؟"
+
+ # إنشاء محادثة لإرسالها إلى Claude
+ messages = [
+ {"role": "user", "content": user_input}
+ ]
+
+ # استدعاء خدمة Claude
+ results = self.claude_service.chat_completion(messages, model_name=model_name)
+
+ if "error" in results:
+ # إذا فشل الاتصال، استخدم التوليد الافتراضي
+ logging.warning(f"فشل الاتصال بـ Claude AI: {results['error']}. استخدام التوليد الافتراضي.")
+ return self._generate_default_response(user_input)
+ else:
+ return results["content"]
+
+ def _generate_default_response(self, user_input):
+ """توليد رد افتراضي في حالة عدم توفر Claude AI"""
+
+ if "تسعير" in user_input or "سعر" in user_input or "تكلفة" in user_input:
+ return "يمكنك استخدام وحدة التنبؤ بالتكاليف لتقدير تكاليف المشروع بناءً على خصائصه. انتقل إلى تبويب 'التنبؤ بالتكاليف' وأدخل بيانات المشروع لتحصل على تقدير دقيق للتكاليف."
+
+ elif "مخاطر" in user_input or "مخاطرة" in user_input:
+ return "يمكنك استخدام وحدة تحليل المخاطر لتقييم المخاطر المحتملة للمشروع. انتقل إلى تبويب 'تحليل المخاطر' وأدخل بيانات المشروع وعوامل المخاطرة لتحصل على تحليل شامل للمخاطر واستراتيجيات الاستجابة المقترحة."
+
+ elif "مستند" in user_input or "ملف" in user_input or "وثيقة" in user_input or "مناقصة" in user_input:
+ return "يمكنك استخدام وحدة تحليل المستندات لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها. انتقل إلى تبويب 'تحليل المستندات' وقم بتحميل ملفات المناقصة لتحصل على تحليل تفصيلي للمستندات."
+
+ elif "محتوى محلي" in user_input or "محلي" in user_input:
+ return "يمكنك استخدام وحدة المحتوى المحلي لحساب وتحسين نسبة المحتوى المحلي في مشروعك. انتقل إلى تبويب 'المحتوى المحلي' وأدخل بيانات مكونات المشروع لتحصل على تحليل شامل للمحتوى المحلي واقتراحات لتحسينه."
+
+ elif "تقرير" in user_input or "إحصائيات" in user_input or "بيانات" in user_input:
+ return "يمكنك استخدام وحدة التقارير والتحليلات للحصول على تقارير تفصيلية وإحصائيات عن المشاريع. يمكنك الوصول إليها من القائمة الرئيسية للنظام."
+
+ else:
+ return "شكراً لاستفسارك. يمكنني مساعدتك في تسعير المناقصات، وتحليل المخاطر، وتحليل المستندات، وحساب المحتوى المحلي. يرجى توضيح استفسارك أكثر أو اختيار أحد الخيارات في الأعلى للحصول على المساعدة المطلوبة."
def _render_cost_prediction_tab(self):
"""عرض تبويب التنبؤ بالتكاليف"""
+
st.markdown("### التنبؤ بالتكاليف")
- st.markdown("""
- استخدم هذه الأداة للتنبؤ بتكاليف المشروع بناءً على البيانات التاريخية والمعايير المشابهة.
- """)
+ # عرض نموذج إدخال بيانات المشروع
+ st.markdown("#### بيانات المشروع")
col1, col2 = st.columns(2)
with col1:
project_type = st.selectbox(
"نوع المشروع",
- ["بناء", "صيانة", "تصميم", "استشارات", "توريد"],
+ [
+ "مباني سكنية",
+ "مباني تجارية",
+ "مباني حكومية",
+ "مراكز صحية",
+ "مدارس",
+ "بنية تحتية",
+ "طرق",
+ "جسور",
+ "صرف صحي",
+ "مياه",
+ "كهرباء"
+ ],
key="cost_project_type"
)
- project_size = st.select_slider(
- "حجم المشروع",
- options=["صغير", "متوسط", "كبير", "ضخم"],
- key="cost_project_size"
- )
-
- duration = st.number_input(
- "المدة المتوقعة (بالأشهر)",
- min_value=1,
- max_value=60,
- value=12,
- key="cost_duration"
- )
-
- with col2:
location = st.selectbox(
"الموقع",
- ["المنطقة الشرقية", "المنطقة الغربية", "المنطقة الوسطى", "المنطقة الشمالية", "المنطقة الجنوبية"],
+ [
+ "الرياض",
+ "جدة",
+ "الدمام",
+ "مكة",
+ "المدينة",
+ "تبوك",
+ "حائل",
+ "عسير",
+ "جازان",
+ "نجران",
+ "الباحة",
+ "الجوف",
+ "القصيم"
+ ],
key="cost_location"
)
- complexity = st.select_slider(
- "درجة التعقيد",
- options=["منخفضة", "متوسطة", "عالية", "معقدة جداً"],
- key="cost_complexity"
+ 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")
- resources = st.multiselect(
- "الموارد المطلوبة",
- ["عمالة محلية", "عمالة أجنبية", "معدات ثقيلة", "تكنولوجيا متقدمة", "مواد أولية مستوردة"],
- default=["عمالة محلية"],
- key="cost_resources"
+ duration = st.number_input("مدة التنفيذ (شهور)", min_value=1, max_value=60, value=12, key="cost_duration")
+
+ tender_type = st.selectbox(
+ "نوع المناقصة",
+ [
+ "عامة",
+ "خاصة",
+ "أمر مباشر"
+ ],
+ key="cost_tender_type"
)
- # زر التنبؤ بالتكاليف
- if st.button("تنبؤ بالتكاليف", key="predict_cost_button"):
- # عرض مؤشر التقدم
- with st.spinner("جاري حساب التكاليف المتوقعة..."):
+ 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(1.5)
-
- # توليد بيانات محاكاة للعرض
- base_cost = 0
- if project_size == "صغير":
- base_cost = random.uniform(100000, 500000)
- elif project_size == "متوسط":
- base_cost = random.uniform(500000, 2000000)
- elif project_size == "كبير":
- base_cost = random.uniform(2000000, 10000000)
- else: # ضخم
- base_cost = random.uniform(10000000, 50000000)
-
- # تعديل التكلفة بناءً على التعقيد
- complexity_factor = 1.0
- if complexity == "منخفضة":
- complexity_factor = 0.8
- elif complexity == "متوسطة":
- complexity_factor = 1.0
- elif complexity == "عالية":
- complexity_factor = 1.3
- else: # معقدة جداً
- complexity_factor = 1.6
-
- # تعديل التكلفة بناءً على المدة
- duration_factor = 1.0 + (duration / 60)
+ time.sleep(2)
- # حساب التكلفة الإجمالية
- total_cost = base_cost * complexity_factor * duration_factor
+ # تجهيز البيانات للنموذج
+ 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)}")
- # توليد بيانات تفصيلية للتكاليف
- labor_cost = total_cost * random.uniform(0.3, 0.5)
- materials_cost = total_cost * random.uniform(0.2, 0.4)
- equipment_cost = total_cost * random.uniform(0.1, 0.2)
- overhead_cost = total_cost - labor_cost - materials_cost - equipment_cost
-
- # عرض النتائج
- st.success("تم حساب التكاليف المتوقعة بنجاح!")
-
- # عرض ملخص التكاليف
- st.markdown("#### ملخص التكاليف المتوقعة")
-
- col1, col2 = st.columns(2)
-
- with col1:
- st.metric("التكلفة الإجمالية المتوقعة", f"{total_cost:,.2f} ريال")
- st.metric("تكلفة العمالة", f"{labor_cost:,.2f} ريال")
- st.metric("تكلفة المواد", f"{materials_cost:,.2f} ريال")
-
- with col2:
- st.metric("تكلفة المعدات", f"{equipment_cost:,.2f} ريال")
- st.metric("المصاريف العامة", f"{overhead_cost:,.2f} ريال")
- st.metric("مؤشر الثقة", f"{random.uniform(70, 95):.1f}%")
-
- # عرض التكاليف بشكل رسم بياني
- cost_data = {
- 'البند': ['عمالة', 'مواد', 'معدات', 'مصاريف عامة'],
- 'التكلفة': [labor_cost, materials_cost, equipment_cost, overhead_cost]
+ # عرض نتائج التنبؤ
+ 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"
}
- cost_df = pd.DataFrame(cost_data)
-
- fig = px.pie(cost_df, values='التكلفة', names='البند', title='توزيع التكاليف المتوقعة')
- st.plotly_chart(fig)
-
- # عرض نصائح لتحسين التكلفة
- st.markdown("#### نصائح لتحسين التكلفة")
- st.info("""
- * استخدام عمالة محلية لتقليل تكاليف العمالة والاستفادة من برامج دعم التوطين.
- * شراء المواد مباشرة من المورد الرئيسي لتقليل تكاليف الوسطاء.
- * استئجار المعدات بدلاً من شرائها إذا كانت مدة استخدامها قصيرة.
- * الاستفادة من برامج المحتوى المحلي للحصول على حوافز السعر.
- """)
+ )
+
+ 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("""
- استخدم هذه الأداة لتحديد وتقييم المخاطر المحتملة للمشروع، ووضع خطط الاستجابة المناسبة.
- """)
+ st.markdown("### تحليل المخاطر")
+
+ # عرض نموذج إدخال بيانات المشروع للمخاطر
+ st.markdown("#### بيانات المشروع")
col1, col2 = st.columns(2)
with col1:
project_type = st.selectbox(
"نوع المشروع",
- ["بناء", "صيانة", "تصميم", "استشارات", "توريد"],
+ [
+ "مباني سكنية",
+ "مباني تجارية",
+ "مباني حكومية",
+ "مراكز صحية",
+ "مدارس",
+ "بنية تحتية",
+ "طرق",
+ "جسور",
+ "صرف صحي",
+ "مياه",
+ "كهرباء"
+ ],
key="risk_project_type"
)
- duration = st.number_input(
- "المدة المتوقعة (بالأشهر)",
- min_value=1,
- max_value=60,
- value=12,
- key="risk_duration"
+ location = st.selectbox(
+ "الموقع",
+ [
+ "الرياض",
+ "جدة",
+ "الدمام",
+ "مكة",
+ "المدينة",
+ "تبوك",
+ "حائل",
+ "عسير",
+ "جازان",
+ "نجران",
+ "الباحة",
+ "الجوف",
+ "القصيم"
+ ],
+ key="risk_location"
)
with col2:
- complexity = st.select_slider(
- "درجة التعقيد",
- options=["منخفضة", "متوسطة", "عالية", "معقدة جداً"],
- key="risk_complexity"
+ client_type = st.selectbox(
+ "نوع العميل",
+ [
+ "حكومي",
+ "شبه حكومي",
+ "شركة كبيرة",
+ "شركة متوسطة",
+ "شركة صغيرة",
+ "أفراد"
+ ],
+ key="risk_client_type"
)
- value = st.number_input(
- "قيمة المشروع (بالريال)",
- min_value=100000,
- max_value=100000000,
- value=1000000,
- step=100000,
- format="%d",
- key="risk_value"
+ tender_type = st.selectbox(
+ "نوع المناقصة",
+ [
+ "عامة",
+ "خاصة",
+ "أمر مباشر"
+ ],
+ key="risk_tender_type"
)
- # زر تحليل المخاطر
- if st.button("تحليل المخاطر", key="analyze_risk_button"):
- # عرض مؤشر التقدم
- with st.spinner("جاري تحليل المخاطر المحتملة..."):
- # محاكاة وقت المعالجة
- time.sleep(1.5)
-
- # توليد بيانات محاكاة للمخاطر
- risk_data = [
- {
- "category": "مالية",
- "risk": "تغيرات في أسعار المواد",
- "impact": random.uniform(3, 5),
- "probability": random.uniform(2, 4),
- "severity": 0, # سيتم حسابها لاحقًا
- "mitigation": "إضافة بند تعديل الأسعار في العقد، وتأمين المواد الرئيسية مبكرًا"
- },
- {
- "category": "فنية",
- "risk": "صعوبات في التنفيذ",
- "impact": random.uniform(2, 5),
- "probability": random.uniform(1, 4),
- "severity": 0,
- "mitigation": "إجراء دراسة فنية مفصلة قبل البدء، والاستعانة بخبراء متخصصين"
- },
- {
- "category": "إدارية",
- "risk": "تأخر في الجدول الزمني",
- "impact": random.uniform(3, 5),
- "probability": random.uniform(2, 5),
- "severity": 0,
- "mitigation": "وضع خطة زمنية واقعية مع هوامش احتياطية، ومتابعة التنفيذ أسبوعيًا"
- },
- {
- "category": "تنظيمية",
- "risk": "تغييرات في اللوائح والأنظمة",
- "impact": random.uniform(2, 4),
- "probability": random.uniform(1, 3),
- "severity": 0,
- "mitigation": "متابعة التحديثات التنظيمية، والتنسيق المستمر مع الجهات المعنية"
- },
- {
- "category": "بيئية",
- "risk": "ظروف ��ناخية غير متوقعة",
- "impact": random.uniform(1, 4),
- "probability": random.uniform(1, 3),
- "severity": 0,
- "mitigation": "جدولة الأنشطة الحساسة في المواسم المناسبة، وتوفير بدائل للظروف الطارئة"
- }
- ]
-
- # حساب شدة المخاطر (Impact × Probability)
- for risk in risk_data:
- risk["severity"] = risk["impact"] * risk["probability"]
-
- # ترتيب المخاطر حسب الشدة
- risk_data = sorted(risk_data, key=lambda x: x["severity"], reverse=True)
-
- # تحويل البيانات إلى DataFrame
- risk_df = pd.DataFrame(risk_data)
-
- # عرض النتائج
- st.success("تم تحليل المخاطر المحتملة بنجاح!")
-
- # عرض جدول المخاطر
- st.markdown("#### سجل المخاطر المحتملة")
-
- # تنسيق العرض
- risk_display = risk_df.copy()
- risk_display.columns = ["الفئة", "المخاطرة", "التأثير", "الاحتمالية", "الشدة", "إجراءات التخفيف"]
- risk_display["التأثير"] = risk_display["التأثير"].round(1)
- risk_display["الاحتمالية"] = risk_display["الاحتمالية"].round(1)
- risk_display["الشدة"] = risk_display["الشدة"].round(1)
-
- st.dataframe(risk_display, use_container_width=True)
-
- # عرض مصفوفة المخاطر
- st.markdown("#### مصفوفة المخاطر")
-
- # إنشاء مصفوفة المخاطر باستخدام matplotlib
- fig, ax = plt.subplots(figsize=(10, 8))
-
- # تعيين حدود المصفوفة
- ax.set_xlim(0.5, 5.5)
- ax.set_ylim(0.5, 5.5)
-
- # إنشاء تدرج الألوان للخلفية
- risk_levels = np.zeros((5, 5))
- for i in range(5):
- for j in range(5):
- risk_levels[i, j] = (i + 1) * (j + 1)
-
- # تلوين الخلفية
- cmap = plt.cm.RdYlGn_r
- risk_levels_normalized = risk_levels / 25.0
- plt.imshow(risk_levels_normalized, cmap=cmap, extent=[0.5, 5.5, 0.5, 5.5], alpha=0.3, origin='lower')
-
- # إضافة خطوط الشبكة
- for i in range(6):
- plt.axhline(y=i + 0.5, color='gray', linestyle='-', alpha=0.3)
- plt.axvline(x=i + 0.5, color='gray', linestyle='-', alpha=0.3)
-
- # وضع المخاطر على المصفوفة
- for i, risk in enumerate(risk_data):
- ax.scatter(risk["probability"], risk["impact"], s=300, alpha=0.8,
- color=plt.cm.cool(i/len(risk_data)),
- edgecolor='black', linewidth=1.5)
- ax.text(risk["probability"], risk["impact"], str(i+1),
- ha='center', va='center', fontweight='bold')
-
- # إضافة التسميات
- ax.set_xlabel('الاحتمالية', fontsize=14)
- ax.set_ylabel('التأثير', fontsize=14)
- ax.set_title('مصفوفة المخاطر', fontsize=16)
-
- # إضافة شرح للأرقام
- legend_text = "\n".join([f"{i+1}. {risk['risk']}" for i, risk in enumerate(risk_data)])
- props = dict(boxstyle='round', facecolor='white', alpha=0.5)
- ax.text(1.05, 0.95, legend_text, transform=ax.transAxes, fontsize=10,
- verticalalignment='top', bbox=props)
-
- st.pyplot(fig)
-
- # عرض توصيات إدارة المخاطر
- st.markdown("#### توصيات إدارة المخاطر")
-
- high_risks = [risk for risk in risk_data if risk["severity"] > 12]
- medium_risks = [risk for risk in risk_data if 6 < risk["severity"] <= 12]
- low_risks = [risk for risk in risk_data if risk["severity"] <= 6]
-
- st.warning(f"""
- **المخاطر العالية ({len(high_risks)}):**
- يجب وضع خطة استجابة تفصيلية لكل مخاطرة عالية وتخصيص مسؤول متابعة لها.
- """)
-
- st.info(f"""
- **المخاطر المتوسطة ({len(medium_risks)}):**
- مراقبة هذه المخاطر بشكل دوري والتأكد من تنفيذ إجراءات التخفيف.
- """)
-
- st.success(f"""
- **المخاطر المنخفضة ({len(low_risks)}):**
- يمكن قبول هذه المخاطر مع المراقبة الدورية.
- """)
-
- # توصيات عامة
- st.markdown("""
- #### إجراءات موصى بها:
-
- 1. تخصيص احتياطي للمخاطر بقيمة تتراوح بين 5-10% من قيمة المشروع.
- 2. تعيين مسؤول إدارة مخاطر للمشروع.
- 3. مراجعة سجل المخاطر بشكل أسبوعي.
- 4. توثيق الدروس المستفادة من المخاطر التي تحققت.
- """)
-
- def _render_document_analysis_tab(self):
- """عرض تبويب تحليل المستندات"""
- st.markdown("### تحليل مستندات المناقصة")
+ st.markdown("#### عوامل المخاطرة")
- st.markdown("""
- استخدم هذه الأداة لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها.
- """)
+ col1, col2, col3 = st.columns(3)
- # خيارات تحميل المستندات
- upload_method = st.radio(
- "طريقة تحميل المستندات",
- ["تحميل ملف", "نسخ ولصق النص"],
- horizontal=True,
- key="doc_upload_method"
- )
+ 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")
- document_text = ""
+ 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])
- if upload_method == "تحميل ملف":
- uploaded_file = st.file_uploader(
- "ارفع مستند المناقصة (PDF، DOCX، TXT)",
- type=["pdf", "docx", "txt"],
- key="document_file_upload"
- )
+ with col1:
+ analyze_button = st.button("تحليل المخاطر", use_container_width=True, key="risk_analyze_button")
- if uploaded_file is not None:
- # حفظ الملف مؤقتًا
- with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file:
- tmp_file.write(uploaded_file.getvalue())
- temp_file_path = tmp_file.name
-
- # محاكاة استخراج النص من الملف
- try:
- st.info("تم استلام الملف، جاري معالجته...")
- # محاكاة وقت المعالجة
- time.sleep(1)
-
- # في التطبيق الحقيقي، سيتم استخراج النص من الملف حسب نوعه
- document_text = "هذا نموذج لنص مستند المناقصة تم استخراجه من الملف المرفوع.\n\n"
- document_text += "مناقصة رقم: 2025/123\n"
- document_text += "اسم المشروع: إنشاء مباني إدارية\n\n"
- document_text += "متطلبات المشروع:\n"
- document_text += "1. تصميم وتنفيذ مبنى إداري مكون من 5 طوابق\n"
- document_text += "2. إنشاء مواقف سيارات بسعة 200 سيارة\n"
- document_text += "3. توفير أنظمة تكييف وتهوية متطورة\n\n"
- document_text += "الشروط العامة:\n"
- document_text += "- مدة التنفيذ: 18 شهرًا من تاريخ استلام الموقع\n"
- document_text += "- غرامة التأخير: 0.5% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%\n"
- document_text += "- الدفعة المقدمة: 10% من قيمة العقد مقابل ضمان بنكي\n"
- document_text += "- محتوى محلي: لا يقل عن 60% من إجمالي العقد\n"
-
- st.success("تم معالجة الملف بنجاح")
-
- except Exception as e:
- st.error(f"حدث خطأ أثناء معالجة الملف: {str(e)}")
-
- # تنظيف الملف المؤقت
- try:
- os.remove(temp_file_path)
- except:
- pass
- else:
- document_text = st.text_area(
- "الصق نص المناقصة هنا",
- height=300,
- key="document_text_input"
- )
-
- # خيارات التحليل
- st.markdown("### خيارات التحليل")
-
- analysis_options = st.multiselect(
- "اختر عناصر التحليل",
- [
- "المعلومات الأساسية",
- "الشروط العامة",
- "المتطلبات الفنية",
- "المحتوى المحلي",
- "مواصفات المشروع",
- "جدول الكميات"
- ],
- default=["المعلومات الأساسية", "الشروط العامة"],
- key="doc_analysis_options"
- )
+ with col2:
+ # Añadimos un key único para este checkbox
+ use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="risk_use_claude")
- # زر تحليل المستند
- if st.button("تحليل المستند", key="analyze_document_button") and document_text:
- # عرض مؤشر التقدم
- with st.spinner("جاري تحليل المستند..."):
+ if analyze_button:
+ with st.spinner("جاري تحليل المخاطر..."):
# محاكاة وقت المعالجة
time.sleep(2)
- # عرض نتائج التحليل
- st.success("تم تحليل المستند بنجاح!")
-
- # تقسيم النتائج إلى أقسام
- col1, col2 = st.columns(2)
-
- with col1:
- # المعلومات الأساسية
- if "المعلومات الأساسية" in analysis_options:
- st.markdown("#### المعلومات الأساسية")
- st.info("""
- **رقم المناقصة:** 2025/123
-
- **اسم المشروع:** إنشاء مباني إدارية
-
- **الجهة المالكة:** وزارة الأشغال العامة
+ # تجهيز البيانات للنموذج
+ 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}
- **تاريخ الطرح:** 15/03/2025
+ عوامل المخاطرة:
+ - شروط الدفع: {payment_terms}/10
+ - مهلة الإنجاز: {completion_deadline}/10
+ - شروط الغرامات: {penalty_clause}/10
+ - التعقيد الفني: {technical_complexity}/10
+ - خبرة الشركة: {company_experience}/10
+ - تقلبات السوق: {market_volatility}/10
- **تاريخ الإقفال:** 15/04/2025
+ ملخص التحليل الأولي:
+ - متوسط درجة المخاطرة: {risk_analysis_results['avg_risk_score']:.1f}/10
+ - عدد المخاطر العالية: {risk_analysis_results['high_risks']}
+ - عدد المخاطر المتوسطة: {risk_analysis_results['medium_risks']}
+ - عدد المخاطر المنخفضة: {risk_analysis_results['low_risks']}
- **مدة التنفيذ:** 18 شهرًا
- """)
-
- # الشروط العامة
- if "الشروط العامة" in analysis_options:
- st.markdown("#### الشروط العامة")
- st.info("""
- **الضمان الابتدائي:** 2% من قيمة العطاء
+ أعلى المخاطر:
+ """
- **الضمان النهائي:** 5% من قيمة العقد
+ # إضافة تفاصيل أعلى المخاطر
+ 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
+ """
- **غرامة التأخير:** 0.5% أسبوعيًا بحد أقصى 10%
+ prompt = f"""تحليل مخاطر مشروع:
- **نظام الدفعات:** شهرية حسب الإنجاز
+ {features_text}
- **الدفعة المقدمة:** 10% مقابل ضمان بنكي
+ المطلوب:
+ 1. تحليل عوامل المخاطرة وتأثيرها على المشروع
+ 2. تقديم توصيات إضافية لإدارة المخاطر
+ 3. اقتراح استراتيجيات استجابة للمخاطر الرئيسية
+ 4. تقديم نصائح لتحسين شروط العقد لتقليل المخاطر
+ 5. تقييم مدى ملاءمة المشروع لاستراتيجية الشركة
- **متطلبات التأمين:** تأمين شامل ضد الأخطار + تأمين مسؤولية مدنية
- """)
-
- # المتطلبات الفنية
- if "المتطلبات الفنية" in analysis_options:
- st.markdown("#### المتطلبات الفنية")
- st.info("""
- **الشهادات المطلوبة:**
- - ISO 9001 لنظام إدارة الجودة
- - ISO 14001 لنظام الإدارة البيئية
- - شهادة تصنيف المقاولين (درجة أولى)
+ يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية.
+ """
- **الخبرات المطلوبة:**
- - 3 مشاريع مماثلة في آخر 5 سنوات
- - خبرة لا تقل عن 10 سنوات في المجال
+ # استدعاء Claude للتحليل
+ claude_analysis = self.claude_service.chat_completion(
+ [{"role": "user", "content": prompt}]
+ )
- **الكادر الفني:**
- - مدير مشروع (خبرة 15 سنة)
- - مهندس مدني (خبرة 10 سنوات)
- - مهندس معماري (خبرة 8 سنوات)
- - مهندس كهرباء (خبرة 8 سنوات)
- - مهندس ميكانيكا (خبرة 8 سنوات)
- """)
+ if "error" not in claude_analysis:
+ # إضافة تحليل Claude إلى النتائج
+ risk_analysis_results["claude_analysis"] = claude_analysis["content"]
+ except Exception as e:
+ st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
- with col2:
- # المحتوى المحلي
- if "المحتوى المحلي" in analysis_options:
- st.markdown("#### متطلبات المحتوى المحلي")
- st.info("""
- **نسبة المحتوى المحلي المطلوبة:** 60%
-
- **العناصر المحددة:**
- - توظيف 70% من القوى العاملة محلية
- - استخدام 50% من المواد من الصناعة المحلية
- - التعاقد مع موردين محليين بنسبة 40%
-
- **المستندات المطلوبة:**
- - شهادة المحتوى المحلي للمنتجات
- - خطة تفصيلية لتحقيق نسبة المحتوى المحلي
- - التزام بتقديم تقارير دورية
- """)
-
- # مواصفات المشروع
- if "مواصفات المشروع" in analysis_options:
- st.markdown("#### مواصفات المشروع")
- st.info("""
- **المباني:**
- - مبنى إداري رئيسي (5 طوابق، 15,000 م²)
- - مبنى خدمات (طابقين، 2,000 م²)
- - مواقف سيارات (200 سيارة)
-
- **التشطيبات:**
- - واجهات زجاجية عاكسة
- - أرضيات رخام في المداخل والردهات
- - أنظمة تكييف مركزية موفرة للطاقة
- - أنظمة الأمن والسلامة متطورة
-
- **المعايير:**
- - تصميم موفر للطاقة حسب متطلبات LEED
- - مراعاة متطلبات الوصول الشامل
- - الالتزام بكود البناء السعودي
- """)
-
- # جدول الكميات
- if "جدول الكميات" in analysis_options:
- st.markdown("#### تحليل جدول الكميات")
-
- # إنشاء بيانات جدول الكميات
- quantities_data = {
- 'البند': [
- 'أعمال الحفر والردم',
- 'أعمال الخرسانة',
- 'أعمال البناء',
- 'أعمال التشطيبات',
- 'أعمال الكهرباء',
- 'أعمال السباكة',
- 'أعمال التكييف',
- 'أعمال السلامة'
- ],
- 'الكمية': [
- 15000,
- 7500,
- 8000,
- 17000,
- 1,
- 1,
- 1,
- 1
- ],
- 'الوحدة': [
- 'م³',
- 'م³',
- 'م²',
- 'م²',
- 'مقطوعية',
- 'مقطوعية',
- 'مقطوعية',
- 'مقطوعية'
- ],
- 'السعر التقديري': [
- 800000,
- 3000000,
- 2500000,
- 4000000,
- 1500000,
- 1200000,
- 2000000,
- 1000000
- ]
- }
-
\ No newline at end of file
+ # عرض نتائج تحليل المخاطر
+ 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"""
+
+
{row['المعرف']} - {row['الوصف']} {row['المستوى']}
+
الفئة: {row['الفئة']} | الاحتمالية: {row['الاحتمالية']} | التأثير: {row['التأثير']} | درجة المخاطرة: {row['درجة المخاطرة']}/10
+
استراتيجية الاستجابة: {row['استراتيجية الاستجابة']}
+
+ """, unsafe_allow_html=True)
+
+ # عرض توصيات عامة
+ st.markdown("#### التوصيات العامة")
+ st.info(results["recommendation"])
+
+ # عرض تحليل Claude AI إذا كان متوفراً
+ if "claude_analysis" in results:
+ st.markdown("### تحليل Claude AI المتقدم")
+ st.success(results["claude_analysis"])
+
+ # زر تصدير تقرير المخاطر
+ if st.button("تصدير تقرير المخاطر"):
+ st.success("تم تصدير تقرير المخاطر بنجاح!")
+
+ def _render_document_analysis_tab(self):
+ """عرض تبويب تحليل المستندات"""
+
+ st.markdown("### تحليل المستندات")
+
+ # خيارات رفع الملفات
+ st.markdown("#### رفع ملفات المناقصة")
+
+ # رفع الملفات
+ uploaded_files = st.file_uploader(
+ "اختر ملفات المناقصة للتحليل",
+ type=["pdf", "docx", "doc", "xls", "xlsx", "jpg", "jpeg", "png"],
+ accept_multiple_files=True,
+ key="document_analysis_files"
+ )
+
+ # اختيار نموذج التحليل
+ analysis_model = st.radio(
+ "اختر نموذج التحليل",
+ [
+ "استخراج البنود والمواصفات",
+ "استخراج الشروط التعاقدية",
+ "تحليل الكميات",
+ "تحليل المتطلبات القانونية",
+ "تحليل شامل (يستخدم Claude AI)"
+ ],
+ horizontal=True
+ )
+
+ # زر بدء التحليل
+ if uploaded_files and st.button("بدء تحليل المستندات"):
+ with st.spinner("جاري تحليل المستندات..."):
+ # محاكاة وقت المعالجة
+ time.sleep(3)
+
+ # معالجة الملفات المرفوعة
+ analysis_results = self._analyze_documents(uploaded_files, analysis_model)
+
+ # عرض نتائج التحليل
+ self._display_document_analysis_results(analysis_results)
+
+ def _analyze_documents(self, files, analysis_model):
+ """تحليل المستندات المرفوعة"""
+
+ # في البيئة الحقيقية، سيتم استدعاء نموذج تحليل المستندات
+ # محاكاة نتائج تحليل المستندات للعرض
+
+ # نتائج التحليل المبدئية
+ basic_results = {
+ "file_count": len(files),
+ "file_names": [file.name for file in files],
+ "file_sizes": [f"{file.size / 1024:.1f} KB" for file in files],
+ "file_types": [file.type or "غير محدد" for file in files],
+ "extracted_text_samples": {},
+ "entities": [],
+ "tender_items": [],
+ "contract_terms": [],
+ "quantities": [],
+ "legal_requirements": [],
+ "summary": ""
+ }
+
+ # محاكاة استخراج نص من الملفات
+ for file in files:
+ # استخراج عينة نصية (في البيئة الحقيقية سيتم استخراج النص الكامل)
+ sample_text = f"عينة نصية مستخرجة من الملف {file.name}. هذا النص لأغراض العرض فقط."
+ basic_results["extracted_text_samples"][file.name] = sample_text
+
+ # محاكاة تحليل المحتوى حسب نموذج التحليل المختار
+ if analysis_model == "استخراج البنود والمواصفات" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
+ basic_results["tender_items"] = [
+ {
+ "id": "T-001",
+ "description": "أعمال الحفر والردم",
+ "unit": "م³",
+ "quantity": 1500,
+ "estimated_price": 85,
+ "specifications": "حفر في أي نوع من التربة بما في ذلك الصخور والردم باستخدام مواد معتمدة."
+ },
+ {
+ "id": "T-002",
+ "description": "أعمال الخرسانة المسلحة للأساسات",
+ "unit": "م³",
+ "quantity": 750,
+ "estimated_price": 1200,
+ "specifications": "خرسانة مسلحة بقوة 30 نيوتن/مم² بعد 28 يوم، مع حديد تسليح من الفئة 60."
+ },
+ {
+ "id": "T-003",
+ "description": "أعمال الخرسانة المسلحة للهيكل",
+ "unit": "م³",
+ "quantity": 1200,
+ "estimated_price": 1350,
+ "specifications": "خرسانة مسلحة بقوة 30 نيوتن/مم² بعد 28 يوم، مع حديد تسليح من الفئة 60."
+ },
+ {
+ "id": "T-004",
+ "description": "أعمال الطابوق",
+ "unit": "م²",
+ "quantity": 3500,
+ "estimated_price": 120,
+ "specifications": "جدران طابوق مفرغ سمك 20 سم مع مونة إسمنتية."
+ },
+ {
+ "id": "T-005",
+ "description": "أعمال التشطيبات الداخلية",
+ "unit": "م²",
+ "quantity": 5000,
+ "estimated_price": 200,
+ "specifications": "تشطيبات داخلية تشمل اللياسة والدهان والأرضيات حسب المواصفات المرفقة."
+ }
+ ]
+
+ if analysis_model == "استخراج الشروط التعاقدية" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
+ basic_results["contract_terms"] = [
+ {
+ "id": "C-001",
+ "title": "مدة تنفيذ المشروع",
+ "description": "يجب إنجاز جميع الأعمال خلال 18 شهراً من تاريخ تسليم الموقع.",
+ "risk_level": "متوسط",
+ "notes": "مدة تنفيذ معقولة نسبياً للحجم المتوقع من الأعمال."
+ },
+ {
+ "id": "C-002",
+ "title": "غرامة التأخير",
+ "description": "تفرض غرامة تأخير بنسبة 0.1% من قيمة العقد عن كل يوم تأخير، بحد أقصى 10% من القيمة الإجمالية للعقد.",
+ "risk_level": "عالي",
+ "notes": "غرامة مرتفعة نسبياً، تتطلب جدولة دقيقة وإدارة استباقية للمخاطر."
+ },
+ {
+ "id": "C-003",
+ "title": "شروط الدفع",
+ "description": "يتم صرف المستخلصات خلال 45 يوماً من تاريخ تقديمها، مع خصم نسبة 10% كضمان حسن التنفيذ تسترد بعد فترة الضمان.",
+ "risk_level": "متوسط",
+ "notes": "فترة 45 يوماً طويلة نسبياً وقد تؤثر على التدفق النقدي."
+ },
+ {
+ "id": "C-004",
+ "title": "التزامات المحتوى المحلي",
+ "description": "يجب أن لا تقل نسبة المحتوى المحلي عن 30% من إجمالي قيمة العقد.",
+ "risk_level": "منخفض",
+ "notes": "يمكن تحقيق النسبة المطلوبة من خلال توريد المواد والعمالة المحلية."
+ },
+ {
+ "id": "C-005",
+ "title": "التغييرات والأعمال الإضافية",
+ "description": "يحق للمالك طلب تغييرات بنسبة ±10% من قيمة العقد دون تعديل أسعار الوحدات.",
+ "risk_level": "متوسط",
+ "notes": "نسبة معقولة، لكن يجب مراعاة احتمالية الطلبات الإضافية عند تسعير البنود."
+ }
+ ]
+
+ if analysis_model == "تحليل الكميات" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
+ basic_results["quantities"] = [
+ {
+ "category": "أعمال الحفر والردم",
+ "volume": 1500,
+ "unit": "م³",
+ "estimated_cost": 127500
+ },
+ {
+ "category": "أعمال الخرسانة",
+ "volume": 1950,
+ "unit": "م³",
+ "estimated_cost": 2437500
+ },
+ {
+ "category": "أعمال الطابوق",
+ "volume": 3500,
+ "unit": "م²",
+ "estimated_cost": 420000
+ },
+ {
+ "category": "أعمال التشطيبات الداخلية",
+ "volume": 5000,
+ "unit": "م²",
+ "estimated_cost": 1000000
+ },
+ {
+ "category": "أعمال التشطيبات الخارجية",
+ "volume": 2200,
+ "unit": "م²",
+ "estimated_cost": 660000
+ },
+ {
+ "category": "أعمال الكهروميكانيكية",
+ "volume": 1,
+ "unit": "مقطوعية",
+ "estimated_cost": 1750000
+ }
+ ]
+
+ if analysis_model == "تحليل المتطلبات القانونية" or analysis_model == "تحليل شامل (يستخدم Claude AI)":
+ basic_results["legal_requirements"] = [
+ {
+ "id": "L-001",
+ "title": "متطلبات التراخيص",
+ "description": "يجب أن يكون المقاول حاصلاً على تصنيف في الفئة الأولى في مجال المباني.",
+ "compliance_status": "مطلوب التحقق",
+ "required_documents": "شهادة التصنيف سارية المفعول"
+ },
+ {
+ "id": "L-002",
+ "title": "متطلبات التأمين",
+ "description": "يجب تقديم بوليصة تأمين شاملة تغطي جميع مخاطر المشروع بقيمة لا تقل عن 100% من قيمة العقد.",
+ "compliance_status": "مطلوب التحقق",
+ "required_documents": "وثائق التأمين الشاملة"
+ },
+ {
+ "id": "L-003",
+ "title": "متطلبات الضمان البنكي",
+ "description": "يجب تقديم ضمان بنكي ابتدائي بنسبة 2% من قيمة العطاء، وضمان نهائي بنسبة 5% من قيمة العقد.",
+ "compliance_status": "مطلوب التحقق",
+ "required_documents": "نماذج الضمانات البنكية"
+ },
+ {
+ "id": "L-004",
+ "title": "متطلبات السعودة",
+ "description": "يجب الالتزام بنسبة السعودة المطلوبة حسب برنامج نطاقات وأن يكون المقاول في النطاق الأخضر.",
+ "compliance_status": "مطلوب التحقق",
+ "required_documents": "شهادة نطاقات سارية المفعول"
+ },
+ {
+ "id": "L-005",
+ "title": "متطلبات الزكاة والدخل",
+ "description": "يجب تقديم شهادة سداد الزكاة والضريبة سارية المفعول.",
+ "compliance_status": "مطلوب التحقق",
+ "required_documents": "شهادة الزكاة والدخل"
+ }
+ ]
+
+ # إعداد ملخص التحليل
+ basic_results["summary"] = f"""
+ تم تحليل {len(files)} ملفات بإجمالي حجم {sum([file.size for file in files]) / 1024 / 1024:.2f} ميجابايت.
+
+ نتائج التحليل الرئيسية:
+ - تم استخراج {len(basic_results.get('tender_items', []))} بنود رئيسية للمناقصة.
+ - تم تحديد {len(basic_results.get('contract_terms', []))} شروط تعاقدية هامة.
+ - تم تحليل الكميات لـ {len(basic_results.get('quantities', []))} فئات من الأعمال.
+ - تم تحديد {len(basic_results.get('legal_requirements', []))} متطلبات قانونية.
+
+ التوصيات:
+ - مراجعة شروط التعاقد وخاصة البنود المتعلقة بالغرامات والدفعات.
+ - تدقيق جداول الكميات والتأكد من تغطية جميع البنود اللازمة للتنفيذ.
+ - التحقق من استيفاء جميع المتطلبات القانونية قبل تقديم العطاء.
+ """
+
+ # إضافة تحليل متقدم باستخدام Claude AI إذا تم اختياره
+ if analysis_model == "تحليل شامل (يستخدم Claude AI)":
+ try:
+ # إنشاء مدخلات للتحليل
+ analysis_input = f"""
+ المناقصة: تطوير مبنى إداري متعدد الطوابق
+
+ ملفات تم تحليلها:
+ {', '.join(basic_results['file_names'])}
+
+ بنود رئيسية:
+ - أعمال الحفر والردم: 1500 م³
+ - أعمال الخرسانة المسلحة للأساسات: 750 م³
+ - أعمال الخرسانة المسلحة للهيكل: 1200 م³
+ - أعمال الطابوق: 3500 م²
+ - أعمال التشطيبات الداخلية: 5000 م²
+
+ شروط تعاقدية رئيسية:
+ - مدة التنفيذ: 18 شهر
+ - غرامة التأخير: 0.1% يومياً بحد أقصى 10%
+ - شروط الدفع: 45 يوم للمستخلصات مع خصم 10% ضمان
+ - المحتوى المحلي: 30% كحد أدنى
+
+ متطلبات قانونية:
+ - تصنيف الفئة الأولى مباني
+ - تأمين شامل بنسبة 100%
+ - ضمان بنكي ابتدائي 2% ونهائي 5%
+ - الالتزام بمتطلبات السعودة (النطاق الأخضر)
+
+ من فضلك قم بتحليل هذه المناقصة وتقديم:
+ 1. تقييم عام للمناقصة وجاذبيتها
+ 2. نقاط القوة والضعف الرئيسية
+ 3. المخاطر المحتملة التي يجب مراعاتها
+ 4. توصيات للتسعير المناسب
+ 5. استراتيجية مقترحة للتنافس على المناقصة
+ """
+
+ # استدعاء خدمة Claude للتحليل
+ claude_response = self.claude_service.chat_completion(
+ [{"role": "user", "content": analysis_input}]
+ )
+
+ if "error" not in claude_response:
+ # إضافة تحليل Claude إلى النتائج
+ basic_results["claude_analysis"] = claude_response["content"]
+ except Exception as e:
+ logging.error(f"فشل في تحليل المستندات باستخدام Claude AI: {str(e)}")
+
+ return basic_results
+
+ def _display_document_analysis_results(self, results):
+ """عرض نتائج تحليل المستندات"""
+
+ st.markdown("### نتائج تحليل المستندات")
+
+ # عرض ملخص التحليل
+ st.markdown("#### ملخص التحليل")
+ st.info(results["summary"])
+
+ # عرض البنود المستخرجة من المناقصة إذا وجدت
+ if results["tender_items"]:
+ st.markdown("#### بنود المناقصة المستخرجة")
+
+ # إنشاء DataFrame للبنود
+ items_df = pd.DataFrame(results["tender_items"])
+
+ # عرض الجدول بشكل منسق
+ st.dataframe(
+ items_df[["id", "description", "unit", "quantity", "estimated_price"]],
+ use_container_width=True
+ )
+
+ # عرض مخطط للتكاليف المقدرة
+ costs = [item["quantity"] * item["estimated_price"] for item in results["tender_items"]]
+ labels = [item["description"] for item in results["tender_items"]]
+
+ fig = px.pie(
+ names=labels,
+ values=costs,
+ title="توزيع التكاليف المقدرة حسب البنود"
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # عرض الشروط التعاقدية إذا وجدت
+ if results["contract_terms"]:
+ st.markdown("#### الشروط التعاقدية الهامة")
+
+ # عرض كل شرط في قسم منفصل
+ for term in results["contract_terms"]:
+ risk_color = "red" if term["risk_level"] == "عالي" else "orange" if term["risk_level"] == "متوسط" else "green"
+
+ st.markdown(f"""
+
+
{term['id']} - {term['title']} مستوى الخطورة: {term['risk_level']}
+
{term['description']}
+
ملاحظات: {term['notes']}
+
+ """, unsafe_allow_html=True)
+
+ # عرض تحليل الكميات إذا وجد
+ if results["quantities"]:
+ st.markdown("#### تحليل الكميات")
+
+ # إنشاء DataFrame للكميات
+ quantities_df = pd.DataFrame(results["quantities"])
+
+ # عرض الجدول بشكل منسق
+ st.dataframe(quantities_df, use_container_width=True)
+
+ # عرض مخطط شريطي للتكاليف المقدرة
+ fig = px.bar(
+ quantities_df,
+ x="category",
+ y="estimated_cost",
+ title="التكاليف المقدرة حسب فئة الأعمال",
+ labels={"category": "فئة الأعمال", "estimated_cost": "التكلفة المقدرة (ريال)"}
+ )
+
+ fig.update_traces(text=quantities_df["estimated_cost"], textposition="outside")
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # عرض المتطلبات القانونية إذا وجدت
+ if results["legal_requirements"]:
+ st.markdown("#### المتطلبات القانونية")
+
+ # عرض المتطلبات في جدول
+ legal_df = pd.DataFrame(results["legal_requirements"])
+
+ # عرض الجدول بشكل منسق
+ st.dataframe(
+ legal_df[["id", "title", "description", "compliance_status", "required_documents"]],
+ use_container_width=True
+ )
+
+ # عرض قائمة تحقق للمتطلبات القانونية
+ st.markdown("##### قائمة التحقق من المتطلبات القانونية")
+
+ for req in results["legal_requirements"]:
+ st.checkbox(f"{req['title']} - {req['description']}", key=f"req_{req['id']}")
+
+ # عرض تحليل Claude AI المتقدم إذا وجد
+ if "claude_analysis" in results:
+ st.markdown("### تحليل Claude AI المتقدم")
+ st.success(results["claude_analysis"])
+
+ # أزرار إضافية
+ col1, col2 = st.columns(2)
+
+ with col1:
+ if st.button("تصدير تقرير تحليل المستندات"):
+ st.success("تم تصدير تقرير تحليل المستندات بنجاح!")
+
+ with col2:
+ if st.button("استخراج جدول الكميات"):
+ st.success("تم استخراج جدول الكميات بنجاح!")
+
+ def _render_local_content_tab(self):
+ """عرض تبويب المحتوى المحلي"""
+
+ st.markdown("### المحتوى المحلي")
+
+ st.markdown("""
+ وحدة حساب المحتوى المحلي تساعدك في تحليل وتحسين نسبة المحتوى المحلي في مشروعك طبقاً لمتطلبات هيئة المحتوى المحلي والمشتريات الحكومية.
+ """)
+
+ # عرض علامات تبويب فرعية
+ lc_tabs = st.tabs([
+ "حساب المحتوى المحلي",
+ "قاعدة بيانات الموردين",
+ "التقارير",
+ "التحسين"
+ ])
+
+ with lc_tabs[0]:
+ self._render_lc_calculator_tab()
+
+ with lc_tabs[1]:
+ self._render_lc_suppliers_tab()
+
+ with lc_tabs[2]:
+ self._render_lc_reports_tab()
+
+ with lc_tabs[3]:
+ self._render_lc_optimization_tab()
+
+ def _render_lc_calculator_tab(self):
+ """عرض تبويب حساب المحتوى المحلي"""
+
+ st.markdown("#### حساب المحتوى المحلي")
+
+ # نموذج إدخال بيانات المشروع
+ st.markdown("##### بيانات المشروع")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ project_name = st.text_input("اسم المشروع", "مبنى إداري الرياض")
+ project_value = st.number_input("القيمة الإجمالية للمشروع (ريال)", min_value=1000, value=10000000)
+
+ with col2:
+ target_lc = st.slider("نسبة المحتوى المحلي المستهدفة (%)", 0, 100, 40)
+ calculation_method = st.selectbox(
+ "طريقة الحساب",
+ [
+ "الطريقة القياسية (المدخلات)",
+ "طريقة القيمة المضافة",
+ "الطريقة المختلطة"
+ ]
+ )
+
+ # جدول مكونات المشروع
+ st.markdown("##### مكونات المشروع")
+
+ # إعداد بيانات المكونات الافتراضية
+ if 'lc_components' not in st.session_state:
+ st.session_state.lc_components = [
+ {
+ "id": 1,
+ "name": "الخرسانة المسلحة",
+ "category": "مواد",
+ "value": 3000000,
+ "local_content": 85,
+ "supplier": "شركة الإنشاءات السعودية"
+ },
+ {
+ "id": 2,
+ "name": "الأعمال الكهربائية",
+ "category": "أنظمة",
+ "value": 1500000,
+ "local_content": 65,
+ "supplier": "مؤسسة الطاقة المتقدمة"
+ },
+ {
+ "id": 3,
+ "name": "أعمال التكييف",
+ "category": "أنظمة",
+ "value": 1200000,
+ "local_content": 55,
+ "supplier": "شركة التبريد العالمية"
+ },
+ {
+ "id": 4,
+ "name": "الواجهات والنوافذ",
+ "category": "مواد",
+ "value": 800000,
+ "local_content": 45,
+ "supplier": "شركة الزجاج المتطورة"
+ },
+ {
+ "id": 5,
+ "name": "أعمال التشطيبات",
+ "category": "مواد وعمالة",
+ "value": 1200000,
+ "local_content": 80,
+ "supplier": "مؤسسة التشطيبات الحديثة"
+ },
+ {
+ "id": 6,
+ "name": "الأثاث والتجهيزات",
+ "category": "أثاث",
+ "value": 900000,
+ "local_content": 30,
+ "supplier": "شركة الأثاث المكتبي"
+ },
+ {
+ "id": 7,
+ "name": "أنظمة الأمن والمراقبة",
+ "category": "أنظمة",
+ "value": 600000,
+ "local_content": 40,
+ "supplier": "شركة الأنظمة الأمنية المتقدمة"
+ },
+ {
+ "id": 8,
+ "name": "العمالة المباشرة",
+ "category": "عمالة",
+ "value": 800000,
+ "local_content": 50,
+ "supplier": "داخلي"
+ }
+ ]
+
+ # عرض جدول المكونات للتعديل
+ for i, component in enumerate(st.session_state.lc_components):
+ col1, col2, col3, col4, col5, col6 = st.columns([2, 1, 1, 1, 2, 1])
+
+ with col1:
+ st.session_state.lc_components[i]["name"] = st.text_input(
+ "المكون",
+ component["name"],
+ key=f"comp_name_{i}"
+ )
+
+ with col2:
+ st.session_state.lc_components[i]["category"] = st.selectbox(
+ "الفئة",
+ ["مواد", "أنظمة", "عمالة", "مواد وعمالة", "أثاث", "خدمات"],
+ index=["مواد", "أنظمة", "عمالة", "مواد وعمالة", "أثاث", "خدمات"].index(component["category"]),
+ key=f"comp_category_{i}"
+ )
+
+ with col3:
+ st.session_state.lc_components[i]["value"] = st.number_input(
+ "القيمة (ريال)",
+ min_value=0,
+ value=int(component["value"]),
+ key=f"comp_value_{i}"
+ )
+
+ with col4:
+ st.session_state.lc_components[i]["local_content"] = st.slider(
+ "المحتوى المحلي (%)",
+ 0, 100, int(component["local_content"]),
+ key=f"comp_lc_{i}"
+ )
+
+ with col5:
+ st.session_state.lc_components[i]["supplier"] = st.text_input(
+ "المورد",
+ component["supplier"],
+ key=f"comp_supplier_{i}"
+ )
+
+ with col6:
+ if st.button("حذف", key=f"delete_comp_{i}"):
+ st.session_state.lc_components.pop(i)
+ st.rerun()
+
+ # زر إضافة مكون جديد
+ if st.button("إضافة مكون جديد"):
+ new_id = max([c["id"] for c in st.session_state.lc_components]) + 1 if st.session_state.lc_components else 1
+ st.session_state.lc_components.append({
+ "id": new_id,
+ "name": f"مكون جديد {new_id}",
+ "category": "مواد",
+ "value": 100000,
+ "local_content": 50,
+ "supplier": "غير محدد"
+ })
+ st.rerun()
+
+ # زر حساب المحتوى المحلي
+ col1, col2 = st.columns([1, 3])
+
+ with col1:
+ calculate_button = st.button("حساب المحتوى المحلي", use_container_width=True)
+
+ with col2:
+ use_claude = st.checkbox("استخدام Claude AI للتحليل المتقدم", value=True, key="lc_use_claude")
+
+ if calculate_button:
+ with st.spinner("جاري حساب وتحليل المحتوى المحلي..."):
+ # محاكاة وقت المعالجة
+ time.sleep(2)
+
+ # حساب المحتوى المحلي
+ lc_results = self._calculate_local_content(st.session_state.lc_components, target_lc, calculation_method)
+
+ # إضافة تحليل إضافي باستخدام Claude AI إذا تم تفعيل الخيار
+ if use_claude:
+ try:
+ # إنشاء نص المكونات للتحليل
+ components_text = ""
+ for comp in st.session_state.lc_components:
+ components_text += f"""
+ - {comp['name']} ({comp['category']}):
+ القيمة: {comp['value']:,} ريال | المحتوى المحلي: {comp['local_content']}% | المورد: {comp['supplier']}
+ """
+
+ prompt = f"""تحليل وتحسين المحتوى المحلي:
+
+ بيانات المشروع:
+ - اسم المشروع: {project_name}
+ - القيمة الإجمالية: {project_value:,} ريال
+ - نسبة المحتوى المحلي المستهدفة: {target_lc}%
+ - النسبة المحسوبة: {lc_results['total_local_content']:.1f}%
+
+ مكونات المشروع:
+ {components_text}
+
+ المطلوب:
+ 1. تحليل نسبة المحتوى المحلي المحسوبة ومقارنتها بالمستهدف
+ 2. تحديد المكونات ذات المحتوى المحلي المنخفض التي يمكن تحسينها
+ 3. اقتراح بدائل محلية أو استراتيجيات لزيادة المحتوى المحلي
+ 4. تقديم توصيات عملية لتحقيق النسبة المستهدفة
+ 5. تحديد أي فرص إضافية لتحسين المحتوى المحلي في المشروع
+
+ يرجى تقديم تحليل مهني ومختصر يركز على الجوانب الأكثر أهمية.
+ """
+
+ # استدعاء Claude للتحليل
+ claude_analysis = self.claude_service.chat_completion(
+ [{"role": "user", "content": prompt}]
+ )
+
+ if "error" not in claude_analysis:
+ # إضافة تحليل Claude إلى النتائج
+ lc_results["claude_analysis"] = claude_analysis["content"]
+ except Exception as e:
+ st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
+
+ # عرض نتائج حساب المحتوى المحلي
+ self._display_local_content_results(lc_results, target_lc)
+
+ def _calculate_local_content(self, components, target_lc, calculation_method):
+ """حساب المحتوى المحلي"""
+
+ # حساب إجمالي قيمة المشروع
+ total_value = sum([comp["value"] for comp in components])
+
+ # حساب المحتوى المحلي الإجمالي
+ total_local_content_value = sum([comp["value"] * comp["local_content"] / 100 for comp in components])
+
+ # حساب نسبة المحتوى المحلي الإجمالية
+ total_local_content_percent = (total_local_content_value / total_value) * 100 if total_value > 0 else 0
+
+ # تحليل المحتوى المحلي حسب الفئة
+ categories = {}
+ for comp in components:
+ category = comp["category"]
+ if category not in categories:
+ categories[category] = {
+ "total_value": 0,
+ "local_content_value": 0
+ }
+
+ categories[category]["total_value"] += comp["value"]
+ categories[category]["local_content_value"] += comp["value"] * comp["local_content"] / 100
+
+ # حساب نسبة المحتوى المحلي لكل فئة
+ for category in categories:
+ if categories[category]["total_value"] > 0:
+ categories[category]["local_content_percent"] = (categories[category]["local_content_value"] / categories[category]["total_value"]) * 100
+ else:
+ categories[category]["local_content_percent"] = 0
+
+ # تحديد المكونات ذات المحتوى المحلي المنخفض
+ low_lc_components = sorted(
+ [comp for comp in components if comp["local_content"] < 50],
+ key=lambda x: x["local_content"]
+ )
+
+ # تحديد المكونات ذات المحتوى المحلي المرتفع
+ high_lc_components = sorted(
+ [comp for comp in components if comp["local_content"] >= 80],
+ key=lambda x: x["local_content"],
+ reverse=True
+ )
+
+ # تقديم توصيات لتحسين المحتوى المحلي
+ improvement_recommendations = []
+
+ # توصيات للمكونات ذات المحتوى المحلي المنخفض
+ for comp in low_lc_components[:3]: # أخذ أقل 3 مكونات
+ improvement_recommendations.append({
+ "component": comp["name"],
+ "current_lc": comp["local_content"],
+ "recommendation": f"البحث عن بدائل محلية لـ {comp['name']} التي تمثل {comp['value'] / total_value * 100:.1f}% من قيمة المشروع."
+ })
+
+ # حساب الفجوة بين المحتوى المحلي الفعلي والمستهدف
+ lc_gap = target_lc - total_local_content_percent
+
+ # إعداد النتائج
+ results = {
+ "total_value": total_value,
+ "total_local_content_value": total_local_content_value,
+ "total_local_content": total_local_content_percent,
+ "target_lc": target_lc,
+ "lc_gap": lc_gap,
+ "categories": categories,
+ "low_lc_components": low_lc_components,
+ "high_lc_components": high_lc_components,
+ "improvement_recommendations": improvement_recommendations,
+ "calculation_method": calculation_method,
+ "components": components
+ }
+
+ # تحديد حالة المحتوى المحلي
+ if lc_gap <= 0:
+ results["status"] = "تم تحقيق المستهدف"
+ results["color"] = "green"
+ elif lc_gap <= 5:
+ results["status"] = "قريب من المستهدف"
+ results["color"] = "orange"
+ else:
+ results["status"] = "بعيد عن المستهدف"
+ results["color"] = "red"
+
+ return results
+
+ def _display_local_content_results(self, results, target_lc):
+ """عرض نتائج حساب المحتوى المحلي"""
+
+ st.markdown("### نتائج حساب المحتوى المحلي")
+
+ # عرض نسبة المحتوى المحلي الإجمالية
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ st.metric(
+ "نسبة المحتوى المحلي الحالية",
+ f"{results['total_local_content']:.1f}%",
+ delta=f"{results['lc_gap']:.1f}%" if results['lc_gap'] < 0 else f"-{results['lc_gap']:.1f}%",
+ delta_color="normal" if results['lc_gap'] < 0 else "inverse"
+ )
+
+ with col2:
+ st.metric(
+ "النسبة المستهدفة",
+ f"{target_lc}%"
+ )
+
+ with col3:
+ # Aquí está el problema - no podemos usar 'green' como valor para delta_color
+ # En lugar de eso, usamos un texto formateado para mostrar el estado
+ st.markdown(f"""
+
+
{results["status"]}
+
+ """, unsafe_allow_html=True)
+
+ # Alternativa sin usar delta_color
+ # st.metric(
+ # "حالة المحتوى المحلي",
+ # results["status"]
+ # )
+
+
+ # عرض مخطط مقارنة بين النسبة الحالية والمستهدفة
+ comparison_data = pd.DataFrame({
+ 'النوع': ['النسبة الحالية', 'النسبة المستهدفة'],
+ 'النسبة': [results['total_local_content'], target_lc]
+ })
+
+ fig = px.bar(
+ comparison_data,
+ x='النوع',
+ y='النسبة',
+ title="مقارنة نسبة المحتوى المحلي الحالية مع المستهدفة",
+ color='النوع',
+ color_discrete_map={
+ 'النسبة الحالية': results["color"],
+ 'النسبة المستهدفة': 'blue'
+ }
+ )
+
+ fig.update_layout(yaxis_range=[0, 100])
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # عرض توزيع المحتوى المحلي حسب الفئة
+ st.markdown("#### توزيع المحتوى المحلي حسب الفئة")
+
+ categories_data = []
+ for category, data in results["categories"].items():
+ categories_data.append({
+ 'الفئة': category,
+ 'القيمة الإجمالية': data["total_value"],
+ 'قيمة المحتوى المحلي': data["local_content_value"],
+ 'نسبة المحتوى المحلي': data["local_content_percent"]
+ })
+
+ categories_df = pd.DataFrame(categories_data)
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ fig = px.pie(
+ categories_df,
+ values='القيمة الإجمالية',
+ names='الفئة',
+ title="توزيع قيمة المشروع حسب الفئة"
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ with col2:
+ fig = px.bar(
+ categories_df,
+ x='الفئة',
+ y='نسبة المحتوى المحلي',
+ title="نسبة المحتوى المحلي لكل فئة",
+ text_auto='.1f'
+ )
+
+ fig.update_traces(texttemplate='%{text}%', textposition='outside')
+ fig.update_layout(yaxis_range=[0, 100])
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # عرض المكونات ذات المحتوى المحلي المنخفض
+ st.markdown("#### المكونات ذات المحتوى المحلي المنخفض")
+
+ if results["low_lc_components"]:
+ low_lc_df = pd.DataFrame([
+ {
+ 'المكون': comp["name"],
+ 'الفئة': comp["category"],
+ 'القيمة': comp["value"],
+ 'نسبة المحتوى المحلي': comp["local_content"],
+ 'المورد': comp["supplier"]
+ }
+ for comp in results["low_lc_components"]
+ ])
+
+ st.dataframe(low_lc_df, use_container_width=True)
+
+ # مخطط المكونات ذات المحتوى المحلي المنخفض
+ fig = px.bar(
+ low_lc_df,
+ x='المكون',
+ y='نسبة المحتوى المحلي',
+ color='القيمة',
+ title="المكونات ذات المحتوى المحلي المنخفض",
+ text_auto='.1f'
+ )
+
+ fig.update_traces(texttemplate='%{text}%', textposition='outside')
+
+ st.plotly_chart(fig, use_container_width=True)
+ else:
+ st.info("لا توجد مكونات ذات محتوى محلي منخفض (أقل من 50%).")
+
+ # عرض توصيات لتحسين المحتوى المحلي
+ st.markdown("#### توصيات لتحسين المحتوى المحلي")
+
+ if results["improvement_recommendations"]:
+ for recommendation in results["improvement_recommendations"]:
+ st.markdown(f"""
+
+
{recommendation['component']} (المحتوى المحلي الحالي: {recommendation['current_lc']}%)
+
{recommendation['recommendation']}
+
+ """, unsafe_allow_html=True)
+ else:
+ st.success("المحتوى المحلي جيد ولا توجد توصيات للتحسين.")
+
+ # عرض تحليل Claude AI المتقدم إذا كان متوفراً
+ if "claude_analysis" in results:
+ st.markdown("### تحليل Claude AI المتقدم")
+ st.info(results["claude_analysis"])
+
+ def _render_lc_suppliers_tab(self):
+ """عرض تبويب قاعدة بيانات الموردين للمحتوى المحلي"""
+
+ st.markdown("#### قاعدة بيانات الموردين المحليين")
+
+ # قائمة الفئات
+ categories = [
+ "جميع الفئات",
+ "مواد بناء",
+ "أنظمة كهربائية",
+ "أنظمة ميكانيكية",
+ "تشطيبات",
+ "أثاث ومفروشات",
+ "خدمات هندسية",
+ "أنظمة أمنية",
+ "معدات وآليات"
+ ]
+
+ # اختيار الفئة
+ selected_category = st.selectbox("فئة الموردين", categories)
+
+ # البحث
+ search_query = st.text_input("البحث عن مورد")
+
+ # إعداد قائمة الموردين
+ suppliers = [
+ {
+ "id": 1,
+ "name": "شركة الإنشاءات السعودية",
+ "category": "مواد بناء",
+ "lc_rating": 95,
+ "quality_rating": 4.5,
+ "location": "الرياض",
+ "contact": "info@saudiconstruction.com",
+ "description": "شركة متخصصة في توريد جميع أنواع مواد البناء ذات المنشأ المحلي."
+ },
+ {
+ "id": 2,
+ "name": "مؤسسة الطاقة المتقدمة",
+ "category": "أنظمة كهربائية",
+ "lc_rating": 85,
+ "quality_rating": 4.2,
+ "location": "جدة",
+ "contact": "sales@advancedpower.com",
+ "description": "مؤسسة متخصصة في توريد وتركيب الأنظمة الكهربائية والطاقة المتجددة."
+ },
+ {
+ "id": 3,
+ "name": "شركة التبريد العالمية",
+ "category": "أنظمة ميكانيكية",
+ "lc_rating": 75,
+ "quality_rating": 4.0,
+ "location": "الدمام",
+ "contact": "info@globalcooling.com",
+ "description": "شركة متخصصة في أنظمة التكييف والتبريد المركزي للمشاريع الكبرى."
+ },
+ {
+ "id": 4,
+ "name": "شركة الزجاج المتطورة",
+ "category": "مواد بناء",
+ "lc_rating": 80,
+ "quality_rating": 4.3,
+ "location": "الرياض",
+ "contact": "sales@advancedglass.com",
+ "description": "شركة متخصصة في إنتاج وتوريد الزجاج والواجهات الزجاجية للمباني."
+ },
+ {
+ "id": 5,
+ "name": "مؤسسة التشطيبات الحديثة",
+ "category": "تشطيبات",
+ "lc_rating": 90,
+ "quality_rating": 4.7,
+ "location": "جدة",
+ "contact": "info@modernfinishing.com",
+ "description": "مؤسسة متخصصة في أعمال التشطيبات الداخلية والخارجية بجودة عالية."
+ },
+ {
+ "id": 6,
+ "name": "شركة الأثاث المكتبي",
+ "category": "أثاث ومفروشات",
+ "lc_rating": 70,
+ "quality_rating": 4.0,
+ "location": "الرياض",
+ "contact": "sales@officefurniture.com",
+ "description": "شركة متخصصة في تصنيع وتوريد الأثاث المكتبي والتجهيزات المكتبية."
+ },
+ {
+ "id": 7,
+ "name": "شركة الأنظمة الأمنية المتقدمة",
+ "category": "أنظمة أمنية",
+ "lc_rating": 65,
+ "quality_rating": 4.1,
+ "location": "الدمام",
+ "contact": "info@advancedsecurity.com",
+ "description": "شركة متخصصة في أنظمة الأمن والمراقبة والإنذار للمباني والمنشآت."
+ },
+ {
+ "id": 8,
+ "name": "شركة المعدات الهندسية",
+ "category": "معدات وآليات",
+ "lc_rating": 85,
+ "quality_rating": 4.5,
+ "location": "جدة",
+ "contact": "sales@engineeringequipment.com",
+ "description": "شركة متخصصة في توريد وصيانة المعدات الهندسية والآليات للمشاريع."
+ },
+ {
+ "id": 9,
+ "name": "مكتب الاستشارات الهندسية",
+ "category": "خدمات هندسية",
+ "lc_rating": 100,
+ "quality_rating": 4.8,
+ "location": "الرياض",
+ "contact": "info@engineeringconsultants.com",
+ "description": "مكتب استشاري متخصص في تقديم الخدمات الهندسية والاستشارية للمشاريع."
+ },
+ {
+ "id": 10,
+ "name": "مصنع الحديد السعودي",
+ "category": "مواد بناء",
+ "lc_rating": 100,
+ "quality_rating": 4.6,
+ "location": "جدة",
+ "contact": "sales@saudisteel.com",
+ "description": "مصنع متخصص في إنتاج وتوريد منتجات الحديد والصلب للمشاريع الإنشائية."
+ }
+ ]
+
+ # تطبيق الفلترة حسب الفئة
+ if selected_category != "جميع الفئات":
+ filtered_suppliers = [s for s in suppliers if s["category"] == selected_category]
+ else:
+ filtered_suppliers = suppliers
+
+ # تطبيق فلترة البحث
+ if search_query:
+ filtered_suppliers = [s for s in filtered_suppliers if search_query.lower() in s["name"].lower() or search_query.lower() in s["description"].lower()]
+
+ # عرض الموردين
+ for supplier in filtered_suppliers:
+ with st.container():
+ col1, col2 = st.columns([3, 1])
+
+ with col1:
+ st.markdown(f"""
+
+
{supplier['name']} ({supplier['category']})
+
الموقع: {supplier['location']} | التواصل: {supplier['contact']}
+
تصنيف المحتوى المحلي: {supplier['lc_rating']}% | تقييم الجودة: {supplier['quality_rating']}/5
+
{supplier['description']}
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ st.button(f"عرض التفاصيل #{supplier['id']}", key=f"supplier_details_{supplier['id']}")
+ st.button(f"إضافة للمشروع #{supplier['id']}", key=f"add_supplier_{supplier['id']}")
+
+ # زر إضافة مورد جديد
+ st.button("إضافة مورد جديد")
+
+ def _render_lc_reports_tab(self):
+ """عرض تبويب تقارير المحتوى المحلي"""
+
+ st.markdown("#### تقارير المحتوى المحلي")
+
+ # اختيار نوع التقرير
+ report_type = st.selectbox(
+ "نوع التقرير",
+ [
+ "تقرير المحتوى المحلي للمشروع الحالي",
+ "تقرير مقارنة المحتوى المحلي بين ��لمشاريع",
+ "تقرير التطور التاريخي للمحتوى المحلي",
+ "تقرير الموردين ذوي المحتوى المحلي المرتفع",
+ "تقرير الامتثال لمتطلبات هيئة المحتوى المحلي"
+ ]
+ )
+
+ # عرض محاكاة للتقرير المختار
+ st.markdown(f"##### {report_type}")
+
+ if report_type == "تقرير المحتوى المحلي للمشروع الحالي":
+ # محاكاة تقرير المشروع الحالي
+ project_data = pd.DataFrame({
+ 'المكون': ['الخرسانة المسلحة', 'الأعمال الكهربائية', 'أعمال التكييف', 'الواجهات والنوافذ',
+ 'أعمال التشطيبات', 'الأثاث والتجهيزات', 'أنظمة الأمن والمراقبة', 'العمالة المباشرة'],
+ 'القيمة': [3000000, 1500000, 1200000, 800000, 1200000, 900000, 600000, 800000],
+ 'نسبة المحتوى المحلي': [85, 65, 55, 45, 80, 30, 40, 50]
+ })
+
+ # حساب قيمة المحتوى المحلي
+ project_data['قيمة المحتوى المحلي'] = project_data['القيمة'] * project_data['نسبة المحتوى المحلي'] / 100
+
+ # إضافة نسبة من إجمالي المشروع
+ total_value = project_data['القيمة'].sum()
+ project_data['نسبة من المشروع'] = project_data['القيمة'] / total_value * 100
+
+ # حساب النسبة الإجمالية للمحتوى المحلي
+ total_lc = project_data['قيمة المحتوى المحلي'].sum() / total_value * 100
+
+ # عرض الإجمالي
+ st.metric("نسبة المحتوى المحلي الإجمالية", f"{total_lc:.1f}%")
+
+ # عرض تفاصيل المكونات
+ st.dataframe(project_data.style.format({
+ 'القيمة': '{:,.0f} ريال',
+ 'قيمة المحتوى المحلي': '{:,.0f} ريال',
+ 'نسبة المحتوى المحلي': '{:.1f}%',
+ 'نسبة من المشروع': '{:.1f}%'
+ }), use_container_width=True)
+
+ # مخطط توزيع المحتوى المحلي
+ col1, col2 = st.columns(2)
+
+ with col1:
+ fig = px.pie(
+ project_data,
+ values='القيمة',
+ names='المكون',
+ title="توزيع قيمة المشروع"
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ with col2:
+ fig = px.pie(
+ project_data,
+ values='قيمة المحتوى المحلي',
+ names='المكون',
+ title="توزيع قيمة المحتوى المحلي"
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # مخطط شريطي للمحتوى المحلي
+ fig = px.bar(
+ project_data,
+ x='المكون',
+ y='نسبة المحتوى المحلي',
+ title="نسبة المحتوى المحلي لكل مكون",
+ text_auto='.1f',
+ color='نسبة من المشروع'
+ )
+
+ fig.update_traces(texttemplate='%{text}%', textposition='outside')
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ elif report_type == "تقرير مقارنة المحتوى المحلي بين المشاريع":
+ # محاكاة بيانات مقارنة المشاريع
+ projects_data = pd.DataFrame({
+ 'المشروع': ['مبنى إداري الرياض', 'مجمع سكني جدة', 'مستشفى الدمام', 'مركز تجاري المدينة', 'فندق مكة'],
+ 'القيمة': [10000000, 15000000, 20000000, 12000000, 18000000],
+ 'نسبة المحتوى المحلي': [65, 55, 70, 60, 50],
+ 'سنة الإنجاز': [2022, 2022, 2023, 2023, 2024]
+ })
+
+ # عرض جدول المقارنة
+ st.dataframe(projects_data.style.format({
+ 'القيمة': '{:,.0f} ريال',
+ 'نسبة المحتوى المحلي': '{:.1f}%'
+ }), use_container_width=True)
+
+ # مخطط شريطي للمقارنة
+ fig = px.bar(
+ projects_data,
+ x='المشروع',
+ y='نسبة المحتوى المحلي',
+ title="مقارنة نسبة المحتوى المحلي بين المشاريع",
+ text_auto='.1f',
+ color='سنة الإنجاز'
+ )
+
+ fig.update_traces(texttemplate='%{text}%', textposition='outside')
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # مخطط فقاعي للمقارنة
+ fig = px.scatter(
+ projects_data,
+ x='القيمة',
+ y='نسبة المحتوى المحلي',
+ size='القيمة',
+ color='سنة الإنجاز',
+ text='المشروع',
+ title="العلاقة بين قيمة المشروع ونسبة المحتوى المحلي"
+ )
+
+ fig.update_traces(textposition='top center')
+ fig.update_layout(xaxis_title="قيمة المشروع (ريال)", yaxis_title="نسبة المحتوى المحلي (%)")
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ elif report_type == "تقرير التطور التاريخي للمحتوى المحلي":
+ # محاكاة بيانات التطور التاريخي
+ historical_data = pd.DataFrame({
+ 'السنة': [2019, 2020, 2021, 2022, 2023, 2024],
+ 'نسبة المحتوى المحلي': [45, 48, 52, 58, 62, 66],
+ 'المستهدف': [40, 45, 50, 55, 60, 65]
+ })
+
+ # عرض جدول التطور التاريخي
+ st.dataframe(historical_data.style.format({
+ 'نسبة المحتوى المحلي': '{:.1f}%',
+ 'المستهدف': '{:.1f}%'
+ }), use_container_width=True)
+
+ # مخطط خطي للتطور التاريخي
+ fig = px.line(
+ historical_data,
+ x='السنة',
+ y=['نسبة المحتوى المحلي', 'المستهدف'],
+ title="التطور التاريخي لنسبة المحتوى المحلي",
+ markers=True,
+ labels={'value': 'النسبة (%)', 'variable': ''}
+ )
+
+ fig.update_layout(legend_title_text='')
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # مخطط شريطي للمقارنة بين الفعلي والمستهدف
+ historical_data['الفرق'] = historical_data['نسبة المحتوى المحلي'] - historical_data['المستهدف']
+
+ fig = px.bar(
+ historical_data,
+ x='السنة',
+ y='الفرق',
+ title="الفرق بين نسبة المحتوى المحلي الفعلية والمستهدفة",
+ text_auto='.1f',
+ color='الفرق',
+ color_continuous_scale=['red', 'green']
+ )
+
+ fig.update_traces(texttemplate='%{text}%', textposition='outside')
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ elif report_type == "تقرير الموردين ذوي المحتوى المحلي المرتفع":
+ # محاكاة بيانات الموردين
+ suppliers_data = pd.DataFrame({
+ 'المورد': ['شركة الإنشاءات السعودية', 'مؤسسة الطاقة المتقدمة', 'شركة التبريد العالمية',
+ 'شركة الزجاج المتطورة', 'مؤسسة التشطيبات الحديثة', 'مصنع الحديد السعودي',
+ 'شركة المعدات الهندسية', 'مكتب الاستشارات الهندسية'],
+ 'الفئة': ['مواد بناء', 'أنظمة كهربائية', 'أنظمة ميكانيكية', 'مواد بناء',
+ 'تشطيبات', 'مواد بناء', 'معدات وآليات', 'خدمات هندسية'],
+ 'نسبة المحتوى المحلي': [95, 85, 75, 80, 90, 100, 85, 100],
+ 'حجم التعامل': [3000000, 1500000, 1200000, 800000, 1200000, 2500000, 900000, 500000]
+ })
+
+ # عرض جدول الموردين
+ st.dataframe(suppliers_data.style.format({
+ 'نسبة المحتوى المحلي': '{:.0f}%',
+ 'حجم التعامل': '{:,.0f} ريال'
+ }), use_container_width=True)
+
+ # مخطط شريطي للموردين
+ fig = px.bar(
+ suppliers_data,
+ x='المورد',
+ y='نسبة المحتوى المحلي',
+ title="نسبة المحتوى المحلي للموردين",
+ text_auto='.0f',
+ color='الفئة'
+ )
+
+ fig.update_traces(texttemplate='%{text}%', textposition='outside')
+ fig.update_layout(xaxis_tickangle=-45)
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # مخطط فقاعي للموردين
+ fig = px.scatter(
+ suppliers_data,
+ x='نسبة المحتوى المحلي',
+ y='حجم التعامل',
+ size='حجم التعامل',
+ color='الفئة',
+ text='المورد',
+ title="العلاقة بين نسبة المحتوى المحلي وحجم التعامل مع الموردين"
+ )
+
+ fig.update_traces(textposition='top center')
+ fig.update_layout(xaxis_title="نسبة المحتوى المحلي (%)", yaxis_title="حجم التعامل (ريال)")
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ elif report_type == "تقرير الامتثال لمتطلبات هيئة المحتوى المحلي":
+ # محاكاة بيانات الامتثال
+ compliance_data = pd.DataFrame({
+ 'المتطلب': [
+ 'نسبة المحتوى المحلي الإجمالية',
+ 'نسبة السعودة في القوى العاملة',
+ 'نسبة المنتجات المحلية',
+ 'نسبة الخدمات المحلية',
+ 'نسبة الموردين المحليين',
+ 'المساهمة في تطوير المحتوى المحلي'
+ ],
+ 'المستهدف': [40, 30, 50, 60, 70, 20],
+ 'المحقق': [38, 35, 45, 65, 75, 25],
+ 'حالة الامتثال': ['قريب', 'ممتثل', 'غير ممتثل', 'ممتثل', 'ممتثل', 'ممتثل']
+ })
+
+ # إضافة ألوان لحالة الامتثال
+ colors = []
+ for status in compliance_data['حالة الامتثال']:
+ if status == 'ممتثل':
+ colors.append('green')
+ elif status == 'قريب':
+ colors.append('orange')
+ else:
+ colors.append('red')
+
+ compliance_data['اللون'] = colors
+
+ # عرض جدول الامتثال
+ st.dataframe(compliance_data.style.format({
+ 'المستهدف': '{:.0f}%',
+ 'المحقق': '{:.0f}%'
+ }), use_container_width=True)
+
+ # مخطط شريطي للامتثال
+ fig = px.bar(
+ compliance_data,
+ x='المتطلب',
+ y=['المستهدف', 'المحقق'],
+ title="مقارنة المتطلبات المستهدفة والمحققة",
+ barmode='group',
+ labels={'value': 'النسبة (%)', 'variable': ''}
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # مخطط دائري لحالة الامتثال
+ status_counts = compliance_data['حالة الامتثال'].value_counts().reset_index()
+ status_counts.columns = ['حالة الامتثال', 'العدد']
+
+ fig = px.pie(
+ status_counts,
+ values='العدد',
+ names='حالة الامتثال',
+ title="توزيع حالة الامتثال للمتطلبات",
+ color='حالة الامتثال',
+ color_discrete_map={
+ 'ممتثل': 'green',
+ 'قريب': 'orange',
+ 'غير ممتثل': 'red'
+ }
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # أزرار التصدير
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.download_button(
+ "تصدير التقرير كملف Excel",
+ "بيانات التقرير",
+ file_name=f"{report_type}.xlsx",
+ mime="application/vnd.ms-excel"
+ )
+
+ with col2:
+ st.download_button(
+ "تصدير التقرير كملف PDF",
+ "بيانات التقرير",
+ file_name=f"{report_type}.pdf",
+ mime="application/pdf"
+ )
+
+ def _render_lc_optimization_tab(self):
+ """عرض تبويب تحسين المحتوى المحلي"""
+
+ st.markdown("#### تحسين المحتوى المحلي")
+
+ st.markdown("""
+ تساعدك هذه الأداة في تحسين نسبة المحتوى المحلي في المشروع من خلال تقديم توصيات وبدائل للمك��نات ذات المحتوى المحلي المنخفض.
+ """)
+
+ # عرض المكونات ذات المحتوى المحلي المنخفض
+ st.markdown("##### المكونات ذات المحتوى المحلي المنخفض")
+
+ # محاكاة بيانات المكونات ذات المحتوى المحلي المنخفض
+ low_lc_components = [
+ {
+ "id": 1,
+ "name": "الأثاث والتجهيزات",
+ "category": "أثاث",
+ "value": 900000,
+ "local_content": 30,
+ "supplier": "شركة الأثاث المكتبي"
+ },
+ {
+ "id": 2,
+ "name": "أنظمة الأمن والمراقبة",
+ "category": "أنظمة",
+ "value": 600000,
+ "local_content": 40,
+ "supplier": "شركة الأنظمة الأمنية المتقدمة"
+ },
+ {
+ "id": 3,
+ "name": "الواجهات والنوافذ",
+ "category": "مواد",
+ "value": 800000,
+ "local_content": 45,
+ "supplier": "شركة الزجاج المتطورة"
+ }
+ ]
+
+ # عرض جدول المكونات
+ low_lc_df = pd.DataFrame(low_lc_components)
+
+ st.dataframe(
+ low_lc_df[["name", "category", "value", "local_content", "supplier"]].rename(columns={
+ "name": "المكون",
+ "category": "الفئة",
+ "value": "القيمة",
+ "local_content": "المحتوى المحلي",
+ "supplier": "المورد"
+ }).style.format({
+ "القيمة": "{:,.0f} ريال",
+ "المحتوى المحلي": "{:.0f}%"
+ }),
+ use_container_width=True
+ )
+
+ # اختيار مكون للتحسين
+ selected_component = st.selectbox(
+ "اختر المكون للتحسين",
+ options=[comp["name"] for comp in low_lc_components],
+ index=0
+ )
+
+ # الحصول على المكون المختار
+ selected_comp_data = next((comp for comp in low_lc_components if comp["name"] == selected_component), None)
+
+ # عرض بدائل المكون المختار
+ if selected_comp_data:
+ st.markdown(f"##### البدائل المقترحة لـ {selected_component}")
+
+ # محاكاة بيانات البدائل
+ alternatives = []
+
+ if selected_component == "الأثاث والتجهيزات":
+ alternatives = [
+ {
+ "id": 1,
+ "name": "شركة الأثاث الوطني",
+ "description": "شركة متخصصة في تصنيع الأثاث المكتبي محلياً",
+ "local_content": 80,
+ "cost_factor": 1.05,
+ "quality_rating": 4.2
+ },
+ {
+ "id": 2,
+ "name": "مصنع التجهيزات المكتبية",
+ "description": "مصنع متخصص في إنتاج الأثاث المكتبي بخامات محلية",
+ "local_content": 90,
+ "cost_factor": 1.10,
+ "quality_rating": 4.5
+ },
+ {
+ "id": 3,
+ "name": "توزيع المكونات على موردين محليين",
+ "description": "تقسيم توريد الأثاث على عدة موردين محليين",
+ "local_content": 75,
+ "cost_factor": 1.00,
+ "quality_rating": 4.0
+ }
+ ]
+ elif selected_component == "أنظمة الأمن والمراقبة":
+ alternatives = [
+ {
+ "id": 1,
+ "name": "شركة التقنية الأمنية السعودية",
+ "description": "شركة متخصصة في تركيب وتجميع أنظمة الأمن محلياً",
+ "local_content": 70,
+ "cost_factor": 1.08,
+ "quality_rating": 4.0
+ },
+ {
+ "id": 2,
+ "name": "مؤسسة تقنيات الحماية",
+ "description": "توريد وتركيب أنظمة أمنية معتمدة من هيئة المحتوى المحلي",
+ "local_content": 65,
+ "cost_factor": 0.95,
+ "quality_rating": 3.8
+ },
+ {
+ "id": 3,
+ "name": "تجميع الأنظمة محلياً",
+ "description": "استيراد المكونات وتجميعها وبرمجتها محلياً",
+ "local_content": 60,
+ "cost_factor": 0.90,
+ "quality_rating": 3.7
+ }
+ ]
+ elif selected_component == "الواجهات والنوافذ":
+ alternatives = [
+ {
+ "id": 1,
+ "name": "مصنع الزجاج السعودي",
+ "description": "مصنع متخصص في إنتاج الزجاج والواجهات الزجاجية محلياً",
+ "local_content": 85,
+ "cost_factor": 1.15,
+ "quality_rating": 4.3
+ },
+ {
+ "id": 2,
+ "name": "شركة الألمنيوم الوطنية",
+ "description": "شركة متخصصة في إنتاج الواجهات والنوافذ من الألمنيوم محلياً",
+ "local_content": 90,
+ "cost_factor": 1.20,
+ "quality_rating": 4.5
+ },
+ {
+ "id": 3,
+ "name": "تعديل التصميم لاستخدام مواد محلية",
+ "description": "تعديل تصميم الواجهات لاستخدام نسبة أكبر من المواد المتوفرة محلياً",
+ "local_content": 75,
+ "cost_factor": 1.00,
+ "quality_rating": 4.0
+ }
+ ]
+
+ # عرض البدائل
+ for alt in alternatives:
+ with st.container():
+ col1, col2, col3 = st.columns([3, 1, 1])
+
+ with col1:
+ st.markdown(f"""
+
+
{alt['name']}
+
{alt['description']}
+
المحتوى المحلي: {alt['local_content']}% | معامل التكلفة: {alt['cost_factor']:.2f} | تقييم الجودة: {alt['quality_rating']}/5
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ st.button(f"تفاصيل #{alt['id']}", key=f"alt_details_{alt['id']}")
+
+ with col3:
+ if st.button(f"اختيار #{alt['id']}", key=f"select_alt_{alt['id']}"):
+ st.success(f"تم اختيار {alt['name']} كبديل لـ {selected_component}.")
+
+ # حساب تأثير البدائل على المحتوى المحلي الإجمالي
+ st.markdown("##### تأثير البدائل على المحتوى المحلي الإجمالي")
+
+ # محاكاة البيانات الإجمالية
+ total_value = 10000000
+ current_lc_value = 6000000
+ current_lc_percent = current_lc_value / total_value * 100
+
+ # حساب التأثير لكل بديل
+ impact_data = []
+ for alt in alternatives:
+ # القيمة الحالية للمحتوى المحلي في المكون
+ current_component_lc_value = selected_comp_data["value"] * selected_comp_data["local_content"] / 100
+
+ # القيمة المتوقعة للمحتوى المحلي مع البديل
+ new_component_value = selected_comp_data["value"] * alt["cost_factor"]
+ new_component_lc_value = new_component_value * alt["local_content"] / 100
+
+ # الفرق في قيمة المحتوى المحلي
+ lc_value_diff = new_component_lc_value - current_component_lc_value
+
+ # القيمة الإجمالية الجديدة للمشروع
+ new_total_value = total_value - selected_comp_data["value"] + new_component_value
+
+ # قيمة المحتوى المحلي الإجمالية الجديدة
+ new_total_lc_value = current_lc_value + lc_value_diff
+
+ # نسبة المحتوى المحلي الإجمالية الجديدة
+ new_total_lc_percent = new_total_lc_value / new_total_value * 100
+
+ # إضافة البيانات
+ impact_data.append({
+ "البديل": alt["name"],
+ "نسبة المحتوى المحلي الحالية": current_lc_percent,
+ "نسبة المحتوى المحلي المتوقعة": new_total_lc_percent,
+ "التغير": new_total_lc_percent - current_lc_percent,
+ "القيمة الإجمالية الجديدة": new_total_value,
+ "تقييم الجودة": alt["quality_rating"]
+ })
+
+ # عرض جدول التأثير
+ impact_df = pd.DataFrame(impact_data)
+
+ st.dataframe(
+ impact_df.style.format({
+ "نسبة المحتوى المحلي الحالية": "{:.1f}%",
+ "نسبة المحتوى المحلي المتوقعة": "{:.1f}%",
+ "التغير": "{:+.1f}%",
+ "القيمة الإجمالية الجديدة": "{:,.0f} ريال",
+ "تقييم الجودة": "{:.1f}/5"
+ }),
+ use_container_width=True
+ )
+
+ # مخطط مقارنة للبدائل
+ fig = px.bar(
+ impact_df,
+ x="البديل",
+ y=["نسبة المحتوى المحلي الحالية", "نسبة المحتوى المحلي المتوقعة"],
+ barmode="group",
+ title="مقارنة تأثير البدائل على نسبة المحتوى المحلي الإجمالية",
+ labels={"value": "نسبة المحتوى المحلي (%)", "variable": ""}
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
+
+ # استخدام Claude AI للتحليل المتقدم
+ if st.checkbox("استخدام Claude AI لتحليل البدائل", value=False, key="lc_optimization_use_claude"):
+ with st.spinner("جاري تحليل البدائل..."):
+ # محاكاة وقت المعالجة
+ time.sleep(2)
+
+ try:
+ # إنشاء نص المدخلات للتحليل
+ prompt = f"""تحليل بدائل المحتوى المحلي لمكون {selected_component}:
+
+ المكون الحالي:
+ - الاسم: {selected_component}
+ - الفئة: {selected_comp_data['category']}
+ - القيمة: {selected_comp_data['value']:,} ريال
+ - نسبة المحتوى المحلي: {selected_comp_data['local_content']}%
+ - المورد: {selected_comp_data['supplier']}
+
+ البدائل المقترحة:
+ 1. {alternatives[0]['name']}:
+ - المحتوى المحلي: {alternatives[0]['local_content']}%
+ - معامل التكلفة: {alternatives[0]['cost_factor']:.2f}
+ - تقييم الجودة: {alternatives[0]['quality_rating']}/5
+ - الوصف: {alternatives[0]['description']}
+
+ 2. {alternatives[1]['name']}:
+ - المحتوى المحلي: {alternatives[1]['local_content']}%
+ - معامل التكلفة: {alternatives[1]['cost_factor']:.2f}
+ - تقييم الجودة: {alternatives[1]['quality_rating']}/5
+ - الوصف: {alternatives[1]['description']}
+
+ 3. {alternatives[2]['name']}:
+ - المحتوى المحلي: {alternatives[2]['local_content']}%
+ - معامل التكلفة: {alternatives[2]['cost_factor']:.2f}
+ - تقييم الجودة: {alternatives[2]['quality_rating']}/5
+ - الوصف: {alternatives[2]['description']}
+
+ المطلوب:
+ 1. تحليل مقارن شامل للبدائل من حيث المحتوى المحلي والتكلفة والجودة
+ 2. تحديد البديل الأفضل مع شرح أسباب اختياره
+ 3. تقديم توصيات إضافية لتحسين المحتوى المحلي لهذا المكون
+ 4. تحديد أي مخاطر محتملة في الانتقال للبديل المقترح
+
+ يرجى تقديم تحليل مهني ومختصر يركز على الجوانب ��لأكثر أهمية.
+ """
+
+ # استدعاء Claude للتحليل
+ claude_analysis = self.claude_service.chat_completion(
+ [{"role": "user", "content": prompt}]
+ )
+
+ if "error" not in claude_analysis:
+ # عرض تحليل Claude
+ st.markdown("##### تحليل متقدم للبدائل")
+ st.info(claude_analysis["content"])
+ else:
+ st.warning(f"تعذر إجراء التحليل المتقدم: {claude_analysis['error']}")
+ except Exception as e:
+ st.warning(f"تعذر إجراء التحليل المتقدم: {str(e)}")
+
+ # زر تطبيق البديل المختار
+ if st.button("تطبيق البديل المختار على المشروع"):
+ st.success("تم تطبيق البديل المختار على المشروع وتحديث نسبة المحتوى المحلي.")
+
+ def _render_faq_tab(self):
+ """عرض تبويب الأسئلة الشائعة"""
+
+ st.markdown("### الأسئلة الشائعة")
+
+ # البحث في الأسئلة الشائعة
+ search_query = st.text_input("البحث في الأسئلة الشائعة", key="faq_search")
+
+ # فلترة الأسئلة حسب البحث
+ if search_query:
+ filtered_faqs = [
+ faq for faq in self.faqs
+ if search_query.lower() in faq["question"].lower() or search_query.lower() in faq["answer"].lower()
+ ]
+ else:
+ filtered_faqs = self.faqs
+
+ # عرض الأسئلة والأجوبة
+ for i, faq in enumerate(filtered_faqs):
+ with st.expander(faq["question"]):
+ st.markdown(faq["answer"])
+
+ # زر التواصل مع الدعم
+ st.markdown("##### لم تجد إجابة لسؤالك؟")
+ col1, col2 = st.columns(2)
+
+ with col1:
+ if st.button("التواصل مع الدعم الفني", use_container_width=True):
+ st.info("سيتم التواصل معك قريباً من قبل فريق الدعم الفني.")
+
+ with col2:
+ if st.button("طرح سؤال جديد", use_container_width=True):
+ st.text_area("اكتب سؤالك هنا")
+ st.button("إرسال")
\ No newline at end of file