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""" +
+
+ {user_input} +
+
+ """, 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""" +
+
+ {response} +
+
+ """, 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