| """ |
| وحدة تحليل المخاطر لنظام إدارة المناقصات - Hybrid Face |
| """ |
|
|
| import os |
| import logging |
| import threading |
| import datetime |
| import json |
| import math |
| import streamlit as st |
| from pathlib import Path |
| import pandas as pd |
| import numpy as np |
| import matplotlib.pyplot as plt |
| import sys |
| import plotly.express as px |
|
|
| |
| sys.path.append(str(Path(__file__).parent.parent)) |
|
|
| |
| from styling.enhanced_ui import UIEnhancer |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
| ) |
| logger = logging.getLogger('risk_analysis') |
|
|
| """ |
| محلل المخاطر المتقدم للمشاريع |
| """ |
|
|
| class RiskAnalyzer: |
| def __init__(self): |
| self.risk_categories = [ |
| "مخاطر السوق", |
| "مخاطر التنفيذ", |
| "مخاطر العقود", |
| "مخاطر التمويل", |
| "مخاطر الموارد" |
| ] |
|
|
| self.impact_levels = { |
| "منخفض": 1, |
| "متوسط": 2, |
| "عالي": 3, |
| "حرج": 4 |
| } |
|
|
| self.probability_levels = { |
| "نادر": 1, |
| "محتمل": 2, |
| "مرجح": 3, |
| "شبه مؤكد": 4 |
| } |
|
|
| def analyze_risks(self, project_data): |
| """تحليل المخاطر للمشروع""" |
| risks = [] |
|
|
| |
| market_risks = self._analyze_market_risks(project_data) |
| risks.extend(market_risks) |
|
|
| |
| execution_risks = self._analyze_execution_risks(project_data) |
| risks.extend(execution_risks) |
|
|
| |
| financial_risks = self._analyze_financial_risks(project_data) |
| risks.extend(financial_risks) |
|
|
| return pd.DataFrame(risks) |
|
|
| def calculate_risk_score(self, probability, impact): |
| """حساب درجة الخطر""" |
| return self.probability_levels[probability] * self.impact_levels[impact] |
|
|
| def render_risk_analysis(self, project_data): |
| """عرض تحليل المخاطر""" |
| st.header("تحليل المخاطر") |
|
|
| |
| risks_df = self.analyze_risks(project_data) |
|
|
| |
| self._render_risk_matrix(risks_df) |
|
|
| |
| self._render_risk_details(risks_df) |
|
|
| |
| self._render_risk_response_plan(risks_df) |
|
|
| def _render_risk_matrix(self, risks_df): |
| """عرض مصفوفة المخاطر""" |
| st.subheader("مصفوفة المخاطر") |
|
|
| |
| matrix_data = np.zeros((4, 4)) |
| for _, risk in risks_df.iterrows(): |
| prob_idx = self.probability_levels[risk['probability']] - 1 |
| impact_idx = self.impact_levels[risk['impact']] - 1 |
| matrix_data[prob_idx, impact_idx] += 1 |
|
|
| fig = px.imshow( |
| matrix_data, |
| labels=dict(x="التأثير", y="الاحتمالية"), |
| x=list(self.impact_levels.keys()), |
| y=list(self.probability_levels.keys()) |
| ) |
| st.plotly_chart(fig) |
|
|
| def _render_risk_details(self, risks_df): |
| """عرض تفاصيل المخاطر""" |
| st.subheader("تفاصيل المخاطر") |
|
|
| |
| risks_df['risk_score'] = risks_df.apply( |
| lambda x: self.calculate_risk_score(x['probability'], x['impact']), |
| axis=1 |
| ) |
|
|
| |
| st.dataframe( |
| risks_df.sort_values('risk_score', ascending=False), |
| use_container_width=True |
| ) |
|
|
| def _render_risk_response_plan(self, risks_df): |
| """عرض خطة الاستجابة للمخاطر""" |
| st.subheader("خطة الاستجابة للمخاطر") |
|
|
| |
| high_risks = risks_df[risks_df['risk_score'] >= 9] |
|
|
| for _, risk in high_risks.iterrows(): |
| with st.expander(f"{risk['category']} - {risk['description']}"): |
| st.write("**استراتيجية الاستجابة:**", risk['response_strategy']) |
| st.write("**خطة العمل:**", risk['action_plan']) |
| st.write("**المسؤول:**", risk['responsible']) |
| st.write("**الموعد النهائي:**", risk['deadline']) |
|
|
| def _analyze_market_risks(self, project_data): |
| """تحليل مخاطر السوق""" |
| return [ |
| { |
| 'category': 'مخاطر السوق', |
| 'description': 'تقلبات أسعار المواد الخام', |
| 'probability': 'مرجح', |
| 'impact': 'عالي', |
| 'response_strategy': 'تحوط', |
| 'action_plan': 'التعاقد المسبق مع الموردين وتثبيت الأسعار', |
| 'responsible': 'مدير المشتريات', |
| 'deadline': '2024-03-01' |
| }, |
| |
| ] |
|
|
| def _analyze_execution_risks(self, project_data): |
| """تحليل مخاطر التنفيذ""" |
| return [ |
| { |
| 'category': 'مخاطر التنفيذ', |
| 'description': 'تأخر في جدول التنفيذ', |
| 'probability': 'محتمل', |
| 'impact': 'عالي', |
| 'response_strategy': 'تخفيف', |
| 'action_plan': 'إعداد خطة تسريع وتحديد المسار الحرج', |
| 'responsible': 'مدير المشروع', |
| 'deadline': '2024-02-15' |
| }, |
| |
| ] |
|
|
| def _analyze_financial_risks(self, project_data): |
| """تحليل المخاطر المالية""" |
| return [ |
| { |
| 'category': 'مخاطر التمويل', |
| 'description': 'تأخر الدفعات', |
| 'probability': 'محتمل', |
| 'impact': 'عالي', |
| 'response_strategy': 'نقل', |
| 'action_plan': 'التأمين على مخاطر عدم السداد', |
| 'responsible': 'المدير المالي', |
| 'deadline': '2024-02-01' |
| }, |
| |
| ] |
|
|
|
|
| class RiskAnalysisApp: |
| """تطبيق تحليل المخاطر""" |
|
|
| def __init__(self): |
| """تهيئة تطبيق تحليل المخاطر""" |
| self.ui = UIEnhancer(page_title="تحليل المخاطر - نظام تحليل المناقصات", page_icon="⚠️") |
| self.ui.apply_theme_colors() |
| self.risk_analyzer = RiskAnalyzer() |
|
|
| |
| if 'projects' not in st.session_state: |
| st.session_state.projects = self._generate_sample_projects() |
|
|
| |
| if 'risk_analysis_results' not in st.session_state: |
| st.session_state.risk_analysis_results = {} |
|
|
| def run(self): |
| """تشغيل تطبيق تحليل المخاطر""" |
| |
| menu_items = [ |
| {"name": "لوحة المعلومات", "icon": "house"}, |
| {"name": "المناقصات والعقود", "icon": "file-text"}, |
| {"name": "تحليل المستندات", "icon": "file-earmark-text"}, |
| {"name": "نظام التسعير", "icon": "calculator"}, |
| {"name": "حاسبة تكاليف البناء", "icon": "building"}, |
| {"name": "الموارد والتكاليف", "icon": "people"}, |
| {"name": "تحليل المخاطر", "icon": "exclamation-triangle"}, |
| {"name": "إدارة المشاريع", "icon": "kanban"}, |
| {"name": "الخرائط والمواقع", "icon": "geo-alt"}, |
| {"name": "الجدول الزمني", "icon": "calendar3"}, |
| {"name": "الإشعارات", "icon": "bell"}, |
| {"name": "مقارنة المستندات", "icon": "files"}, |
| {"name": "الترجمة", "icon": "translate"}, |
| {"name": "المساعد الذكي", "icon": "robot"}, |
| {"name": "التقارير", "icon": "bar-chart"}, |
| {"name": "الإعدادات", "icon": "gear"} |
| ] |
|
|
| |
| selected = self.ui.create_sidebar(menu_items) |
|
|
| |
| self.ui.create_header("تحليل المخاطر", "تحديد وتقييم وإدارة مخاطر المشاريع") |
|
|
| |
| tabs = st.tabs([ |
| "لوحة المعلومات", |
| "تحليل المخاطر", |
| "سجل المخاطر", |
| "مصفوفة المخاطر", |
| "استراتيجيات التخفيف" |
| ]) |
|
|
| with tabs[0]: |
| self._render_dashboard_tab() |
|
|
| with tabs[1]: |
| self._render_analysis_tab() |
|
|
| with tabs[2]: |
| self._render_risk_register_tab() |
|
|
| with tabs[3]: |
| self._render_risk_matrix_tab() |
|
|
| with tabs[4]: |
| self._render_mitigation_strategies_tab() |
|
|
| def _render_dashboard_tab(self): |
| """عرض تبويب لوحة المعلومات""" |
|
|
| st.markdown("### لوحة معلومات تحليل المخاطر") |
|
|
| |
| col1, col2, col3, col4 = st.columns(4) |
|
|
| |
| total_risks = 0 |
| high_risks = 0 |
| medium_risks = 0 |
| low_risks = 0 |
|
|
| for project_id, results in st.session_state.risk_analysis_results.items(): |
| if "identified_risks" in results: |
| project_risks = results["identified_risks"] |
| total_risks += len(project_risks) |
| high_risks += len([r for r in project_risks if r["risk_score"] >= 6]) |
| medium_risks += len([r for r in project_risks if 3 <= r["risk_score"] < 6]) |
| low_risks += len([r for r in project_risks if r["risk_score"] < 3]) |
|
|
| with col1: |
| self.ui.create_metric_card("إجمالي المخاطر", str(total_risks), None, self.ui.COLORS['primary']) |
|
|
| with col2: |
| self.ui.create_metric_card("مخاطر عالية", str(high_risks), None, self.ui.COLORS['danger']) |
|
|
| with col3: |
| self.ui.create_metric_card("مخاطر متوسطة", str(medium_risks), None, self.ui.COLORS['warning']) |
|
|
| with col4: |
| self.ui.create_metric_card("مخاطر منخفضة", str(low_risks), None, self.ui.COLORS['success']) |
|
|
| |
| st.markdown("#### توزيع المخاطر حسب الفئة") |
|
|
| |
| category_distribution = {} |
|
|
| for project_id, results in st.session_state.risk_analysis_results.items(): |
| if "identified_risks" in results: |
| for risk in results["identified_risks"]: |
| category = risk["category"] |
| if category not in category_distribution: |
| category_distribution[category] = 0 |
| category_distribution[category] += 1 |
|
|
| if category_distribution: |
| |
| category_df = pd.DataFrame({ |
| 'الفئة': list(category_distribution.keys()), |
| 'عدد المخاطر': list(category_distribution.values()) |
| }) |
|
|
| |
| st.bar_chart(category_df.set_index('الفئة')) |
| else: |
| st.info("لا توجد بيانات كافية لعرض توزيع المخاطر.") |
|
|
| |
| st.markdown("#### المشاريع ذات المخاطر العالية") |
|
|
| high_risk_projects = [] |
|
|
| for project_id, results in st.session_state.risk_analysis_results.items(): |
| if "identified_risks" in results: |
| project_high_risks = len([r for r in results["identified_risks"] if r["risk_score"] >= 6]) |
| if project_high_risks > 0: |
| |
| project = next((p for p in st.session_state.projects if p["id"] == int(project_id)), None) |
| if project: |
| high_risk_projects.append({ |
| 'اسم المشروع': project["name"], |
| 'رقم المناقصة': project["number"], |
| 'الجهة المالكة': project["client"], |
| 'عدد المخاطر العالية': project_high_risks |
| }) |
|
|
| if high_risk_projects: |
| high_risk_df = pd.DataFrame(high_risk_projects) |
| st.dataframe(high_risk_df, use_container_width=True, hide_index=True) |
| else: |
| st.info("لا توجد مشاريع ذات مخاطر عالية حاليًا.") |
|
|
| def _render_analysis_tab(self): |
| """عرض تبويب تحليل المخاطر""" |
|
|
| st.markdown("### تحليل مخاطر المشروع") |
|
|
| |
| project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects] |
| selected_project = st.selectbox("اختر المشروع", project_options) |
|
|
| if selected_project: |
| |
| project_index = project_options.index(selected_project) |
| project = st.session_state.projects[project_index] |
| project_id = project["id"] |
|
|
| |
| col1, col2 = st.columns(2) |
|
|
| with col1: |
| st.markdown(f"**اسم المشروع**: {project['name']}") |
| st.markdown(f"**رقم المناقصة**: {project['number']}") |
| st.markdown(f"**الجهة المالكة**: {project['client']}") |
|
|
| with col2: |
| st.markdown(f"**موقع المشروع**: {project['location']}") |
| project_type = project.get('type', project.get('tender_type', 'غير محدد')) |
| st.markdown(f"**نوع المشروع**: {project_type}") |
| complexity = project.get('complexity', 'غير محدد') |
| st.markdown(f"**مستوى التعقيد**: {complexity}") |
|
|
| |
| if st.button("بدء تحليل المخاطر"): |
| with st.spinner("جاري تحليل مخاطر المشروع..."): |
| |
| import time |
| time.sleep(2) |
|
|
| |
| results = self.risk_analyzer.render_risk_analysis(project) |
|
|
| |
| st.session_state.risk_analysis_results[str(project_id)] = {"analysis_results": results} |
|
|
| st.success("تم الانتهاء من تحليل المخاطر بنجاح!") |
| st.rerun() |
|
|
|
|
| def _render_risk_register_tab(self): |
| """عرض تبويب سجل المخاطر""" |
|
|
| st.markdown("### سجل المخاطر") |
|
|
| |
| project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects] |
| project_options.insert(0, "جميع المشاريع") |
| selected_project_option = st.selectbox("اختر المشروع", project_options, key="risk_register_project") |
|
|
| |
| all_risks = [] |
|
|
| if selected_project_option == "جميع المشاريع": |
| |
| for project_id, results in st.session_state.risk_analysis_results.items(): |
| if "analysis_results" in results: |
| project = next((p for p in st.session_state.projects if p["id"] == int(project_id)), None) |
| project_name = project["name"] if project else f"مشروع {project_id}" |
|
|
| for index, row in results["analysis_results"].iterrows(): |
| risk_copy = row.to_dict() |
| risk_copy["project_name"] = project_name |
| all_risks.append(risk_copy) |
| else: |
| |
| project_index = project_options.index(selected_project_option) - 1 |
| project = st.session_state.projects[project_index] |
| project_id = project["id"] |
|
|
| |
| if str(project_id) in st.session_state.risk_analysis_results: |
| results = st.session_state.risk_analysis_results[str(project_id)] |
| if "analysis_results" in results: |
| for index, row in results["analysis_results"].iterrows(): |
| risk_copy = row.to_dict() |
| risk_copy["project_name"] = project["name"] |
| all_risks.append(risk_copy) |
|
|
| |
| col1, col2, col3 = st.columns(3) |
|
|
| with col1: |
| category_filter = st.multiselect( |
| "فئة المخاطر", |
| list(set(risk["category"] for risk in all_risks)) if all_risks else [], |
| key="risk_register_category" |
| ) |
|
|
| with col2: |
| probability_filter = st.multiselect( |
| "الاحتمالية", |
| list(set(risk["probability"] for risk in all_risks)) if all_risks else [], |
| key="risk_register_probability" |
| ) |
|
|
| with col3: |
| impact_filter = st.multiselect( |
| "التأثير", |
| list(set(risk["impact"] for risk in all_risks)) if all_risks else [], |
| key="risk_register_impact" |
| ) |
|
|
| |
| filtered_risks = all_risks |
|
|
| if category_filter: |
| filtered_risks = [risk for risk in filtered_risks if risk["category"] in category_filter] |
|
|
| if probability_filter: |
| filtered_risks = [risk for risk in filtered_risks if risk["probability"] in probability_filter] |
|
|
| if impact_filter: |
| filtered_risks = [risk for risk in filtered_risks if risk["impact"] in impact_filter] |
|
|
| |
| if filtered_risks: |
| |
| risk_df = pd.DataFrame(filtered_risks) |
|
|
| |
| risk_df = risk_df.sort_values(by='risk_score', ascending=False) |
|
|
| |
| st.dataframe(risk_df, use_container_width=True, hide_index=True) |
|
|
| |
| if st.button("تصدير سجل المخاطر"): |
| with st.spinner("جاري تصدير سجل المخاطر..."): |
| |
| time.sleep(1) |
| st.success("تم تصدير سجل المخاطر بنجاح!") |
| else: |
| st.info("لا توجد مخاطر تطابق معايير البحث أو لم يتم إجراء تحليل للمخاطر بعد.") |
|
|
| def _render_risk_matrix_tab(self): |
| """عرض تبويب مصفوفة المخاطر""" |
|
|
| st.markdown("### مصفوفة المخاطر") |
|
|
| |
| project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects] |
| selected_project = st.selectbox("اختر المشروع", project_options, key="risk_matrix_project") |
|
|
| if selected_project: |
| |
| project_index = project_options.index(selected_project) |
| project = st.session_state.projects[project_index] |
| project_id = project["id"] |
|
|
| |
| if str(project_id) in st.session_state.risk_analysis_results: |
| results = st.session_state.risk_analysis_results[str(project_id)] |
|
|
| if "analysis_results" in results: |
| risks_df = results["analysis_results"] |
| self.risk_analyzer._render_risk_matrix(risks_df) |
| else: |
| st.warning("لم يتم العثور على مصفوفة المخاطر للمشروع المحدد.") |
| else: |
| st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.") |
|
|
| def _render_mitigation_strategies_tab(self): |
| """عرض تبويب استراتيجيات التخفيف""" |
|
|
| st.markdown("### استراتيجيات التخفيف من المخاطر") |
|
|
| |
| project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects] |
| selected_project = st.selectbox("اختر المشروع", project_options, key="mitigation_project") |
|
|
| if selected_project: |
| |
| project_index = project_options.index(selected_project) |
| project = st.session_state.projects[project_index] |
| project_id = project["id"] |
|
|
| |
| if str(project_id) in st.session_state.risk_analysis_results: |
| results = st.session_state.risk_analysis_results[str(project_id)] |
|
|
| if "analysis_results" in results and not results["analysis_results"].empty: |
| risks_df = results["analysis_results"] |
| self.risk_analyzer._render_risk_response_plan(risks_df) |
| else: |
| st.warning("لم يتم العثور على استراتيجيات تخفيف للمشروع المحدد.") |
| else: |
| st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.") |
|
|
| def _generate_sample_projects(self): |
| """توليد بيانات افتراضية للمشاريع""" |
|
|
| return [ |
| { |
| 'id': 1, |
| 'name': "إنشاء مبنى مستشفى الولادة والأطفال بمنطقة الشرقية", |
| 'number': "SHPD-2025-001", |
| 'client': "وزارة الصحة", |
| 'location': "الدمام، المنطقة الشرقية", |
| 'description': "يشمل المشروع إنشاء وتجهيز مبنى مستشفى الولادة والأطفال بسعة 300 سرير، ويتكون المبنى من 4 طوابق بمساحة إجمالية 15,000 متر مربع.", |
| 'status': "قيد التسعير", |
| 'tender_type': "عامة", |
| 'pricing_method': "قياسي", |
| 'submission_date': (datetime.datetime.now() + datetime.timedelta(days=5)), |
| 'created_at': datetime.datetime.now() - datetime.timedelta(days=10), |
| 'created_by_id': 1, |
| 'project_type': "مباني", |
| 'complexity': "عالي" |
| }, |
| { |
| 'id': 2, |
| 'name': "صيانة وتطوير طريق الملك عبدالله", |
| 'number': "MOT-2025-042", |
| 'client': "وزارة النقل", |
| 'location': "الرياض، المنطقة الوسطى", |
| 'description': "صيانة وتطوير طريق الملك عبدالله بطول 25 كم، ويشمل المشروع إعادة الرصف وتحسين الإنارة وتركيب اللوحات الإرشادية.", |
| 'status': "تم التقديم", |
| 'tender_type': "عامة", |
| 'pricing_method': "غير متزن", |
| 'submission_date': (datetime.datetime.now() - datetime.timedelta(days=15)), |
| 'created_at': datetime.datetime.now() - datetime.timedelta(days=45), |
| 'created_by_id': 1, |
| 'project_type': "بنية تحتية", |
| 'complexity': "متوسط" |
| }, |
| { |
| 'id': 3, |
| 'name': "إنشاء محطة معالجة مياه الصرف الصحي", |
| 'number': "SWPC-2025-007", |
| 'client': "شركة المياه الوطنية", |
| 'location': "جدة، المنطقة الغربية", |
| 'description': "إنشاء محطة معالجة مياه الصرف الصحي بطاقة استيعابية 50,000 م3/يوم، مع جميع الأعمال المدنية والكهروميكانيكية.", |
| 'status': "تمت الترسية", |
| 'tender_type': "عامة", |
| 'pricing_method': "قياسي", |
| 'submission_date': (datetime.datetime.now() - datetime.timedelta(days=90)), |
| 'created_at': datetime.datetime.now() - datetime.timedelta(days=120), |
| 'created_by_id': 1, |
| 'project_type': "بنية تحتية", |
| 'complexity': "عالي" |
| } |
| ] |
|
|
| |
| if __name__ == "__main__": |
| risk_app = RiskAnalysisApp() |
| risk_app.run() |