|
""" |
|
وحدة تحليل المخاطر لنظام إدارة المناقصات - 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 |
|
|
|
|
|
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, config=None, db=None): |
|
"""تهيئة محلل المخاطر""" |
|
self.config = config |
|
self.db = db |
|
self.analysis_in_progress = False |
|
self.current_project = None |
|
self.analysis_results = {} |
|
|
|
|
|
if config and hasattr(config, 'EXPORTS_PATH'): |
|
self.exports_path = Path(config.EXPORTS_PATH) |
|
else: |
|
self.exports_path = Path('data/exports') |
|
|
|
if not self.exports_path.exists(): |
|
self.exports_path.mkdir(parents=True, exist_ok=True) |
|
|
|
def analyze_risks(self, project_id, method="comprehensive", callback=None): |
|
"""تحليل مخاطر المشروع""" |
|
if self.analysis_in_progress: |
|
logger.warning("هناك عملية تحليل مخاطر جارية بالفعل") |
|
return False |
|
|
|
self.analysis_in_progress = True |
|
self.current_project = project_id |
|
self.analysis_results = { |
|
"project_id": project_id, |
|
"method": method, |
|
"analysis_start_time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
|
"status": "جاري التحليل", |
|
"identified_risks": [], |
|
"risk_categories": {}, |
|
"risk_matrix": {}, |
|
"mitigation_strategies": [], |
|
"summary": {} |
|
} |
|
|
|
|
|
thread = threading.Thread( |
|
target=self._analyze_risks_thread, |
|
args=(project_id, method, callback) |
|
) |
|
thread.daemon = True |
|
thread.start() |
|
|
|
return True |
|
|
|
def _analyze_risks_thread(self, project_id, method, callback): |
|
"""خيط تحليل المخاطر""" |
|
try: |
|
|
|
project_data = self._get_project_data(project_id) |
|
|
|
if not project_data: |
|
logger.error(f"لم يتم العثور على بيانات المشروع: {project_id}") |
|
self.analysis_results["status"] = "فشل التحليل" |
|
self.analysis_results["error"] = "لم يتم العثور على بيانات المشروع" |
|
return |
|
|
|
|
|
self._identify_risks(project_data, method) |
|
|
|
|
|
self._categorize_risks() |
|
|
|
|
|
self._create_risk_matrix() |
|
|
|
|
|
self._develop_mitigation_strategies(method) |
|
|
|
|
|
self._create_analysis_summary(method) |
|
|
|
|
|
self.analysis_results["status"] = "اكتمل التحليل" |
|
self.analysis_results["analysis_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
|
|
|
logger.info(f"اكتمل تحليل مخاطر المشروع: {project_id}") |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في تحليل مخاطر المشروع: {str(e)}") |
|
self.analysis_results["status"] = "فشل التحليل" |
|
self.analysis_results["error"] = str(e) |
|
|
|
finally: |
|
self.analysis_in_progress = False |
|
|
|
|
|
if callback and callable(callback): |
|
callback(self.analysis_results) |
|
|
|
def _get_project_data(self, project_id): |
|
"""الحصول على بيانات المشروع""" |
|
|
|
|
|
|
|
return { |
|
"id": project_id, |
|
"name": "مشروع الطرق السريعة", |
|
"client": "وزارة النقل", |
|
"description": "إنشاء طرق سريعة بطول 50 كم في المنطقة الشرقية", |
|
"start_date": "2025-05-01", |
|
"end_date": "2025-11-30", |
|
"status": "تخطيط", |
|
"budget": 50000000, |
|
"location": "المنطقة الشرقية", |
|
"project_type": "بنية تحتية", |
|
"complexity": "متوسط", |
|
"existing_risks": [ |
|
{"id": 1, "name": "تأخر توريد المواد", "probability": "متوسط", "impact": "عالي", "category": "توريد"}, |
|
{"id": 2, "name": "تغير أسعار المواد", "probability": "عالي", "impact": "عالي", "category": "مالي"}, |
|
{"id": 3, "name": "ظروف جوية غير مواتية", "probability": "منخفض", "impact": "متوسط", "category": "بيئي"}, |
|
{"id": 4, "name": "نقص العمالة", "probability": "متوسط", "impact": "متوسط", "category": "موارد بشرية"} |
|
] |
|
} |
|
|
|
def _identify_risks(self, project_data, method): |
|
"""تحديد المخاطر""" |
|
|
|
identified_risks = [] |
|
for risk in project_data["existing_risks"]: |
|
identified_risks.append({ |
|
"id": risk["id"], |
|
"name": risk["name"], |
|
"description": f"مخاطر {risk['name']} في المشروع", |
|
"category": risk["category"], |
|
"probability": risk["probability"], |
|
"impact": risk["impact"], |
|
"risk_score": self._calculate_risk_score(risk["probability"], risk["impact"]), |
|
"source": "existing" |
|
}) |
|
|
|
|
|
additional_risks = self._generate_additional_risks(project_data, method) |
|
identified_risks.extend(additional_risks) |
|
|
|
|
|
self.analysis_results["identified_risks"] = identified_risks |
|
|
|
def _generate_additional_risks(self, project_data, method): |
|
"""توليد مخاطر إضافية بناءً على بيانات المشروع""" |
|
additional_risks = [] |
|
|
|
|
|
if project_data["project_type"] == "بنية تحتية": |
|
additional_risks.extend([ |
|
{ |
|
"id": 101, |
|
"name": "مشاكل جيوتقنية", |
|
"description": "مشاكل غير متوقعة في التربة أو الظروف الجيولوجية", |
|
"category": "فني", |
|
"probability": "متوسط", |
|
"impact": "عالي", |
|
"risk_score": self._calculate_risk_score("متوسط", "عالي"), |
|
"source": "generated" |
|
}, |
|
{ |
|
"id": 102, |
|
"name": "تعارض مع مرافق قائمة", |
|
"description": "تعارض أعمال الحفر مع خطوط المرافق القائمة (كهرباء، مياه، اتصالات)", |
|
"category": "فني", |
|
"probability": "متوسط", |
|
"impact": "متوسط", |
|
"risk_score": self._calculate_risk_score("متوسط", "متوسط"), |
|
"source": "generated" |
|
} |
|
]) |
|
|
|
|
|
if project_data["location"] == "المنطقة الشرقية": |
|
additional_risks.extend([ |
|
{ |
|
"id": 201, |
|
"name": "ارتفاع درجات الحرارة", |
|
"description": "تأثير ارتفاع درجات الحرارة على إنتاجية العمل وجودة المواد", |
|
"category": "بيئي", |
|
"probability": "عالي", |
|
"impact": "متوسط", |
|
"risk_score": self._calculate_risk_score("عالي", "متوسط"), |
|
"source": "generated" |
|
}, |
|
{ |
|
"id": 202, |
|
"name": "رطوبة عالية", |
|
"description": "تأثير الرطوبة العالية على جودة المواد وتقنيات البناء", |
|
"category": "بيئي", |
|
"probability": "عالي", |
|
"impact": "منخفض", |
|
"risk_score": self._calculate_risk_score("عالي", "منخفض"), |
|
"source": "generated" |
|
} |
|
]) |
|
|
|
|
|
if project_data["complexity"] in ["متوسط", "عالي"]: |
|
additional_risks.extend([ |
|
{ |
|
"id": 301, |
|
"name": "تغييرات في نطاق العمل", |
|
"description": "طلبات تغيير من العميل أو تعديلات في متطلبات المشروع", |
|
"category": "إداري", |
|
"probability": "عالي", |
|
"impact": "عالي", |
|
"risk_score": self._calculate_risk_score("عالي", "عالي"), |
|
"source": "generated" |
|
}, |
|
{ |
|
"id": 302, |
|
"name": "تأخر الموافقات", |
|
"description": "تأخر الحصول على الموافقات والتصاريح اللازمة", |
|
"category": "تنظيمي", |
|
"probability": "متوسط", |
|
"impact": "عالي", |
|
"risk_score": self._calculate_risk_score("متوسط", "عالي"), |
|
"source": "generated" |
|
} |
|
]) |
|
|
|
|
|
if method == "comprehensive": |
|
additional_risks.extend([ |
|
{ |
|
"id": 401, |
|
"name": "مخاطر سياسية", |
|
"description": "تغييرات في السياسات الحكومية أو اللوائح التنظيمية", |
|
"category": "خارجي", |
|
"probability": "منخفض", |
|
"impact": "عالي", |
|
"risk_score": self._calculate_risk_score("منخفض", "عالي"), |
|
"source": "generated" |
|
}, |
|
{ |
|
"id": 402, |
|
"name": "مخاطر اقتصادية", |
|
"description": "تقلبات في أسعار العملات أو التضخم", |
|
"category": "مالي", |
|
"probability": "متوسط", |
|
"impact": "متوسط", |
|
"risk_score": self._calculate_risk_score("متوسط", "متوسط"), |
|
"source": "generated" |
|
}, |
|
{ |
|
"id": 403, |
|
"name": "مخاطر تقنية", |
|
"description": "مشاكل في التقنيات الجديدة أو المعدات", |
|
"category": "فني", |
|
"probability": "متوسط", |
|
"impact": "متوسط", |
|
"risk_score": self._calculate_risk_score("متوسط", "متوسط"), |
|
"source": "generated" |
|
} |
|
]) |
|
|
|
return additional_risks |
|
|
|
def _calculate_risk_score(self, probability, impact): |
|
"""حساب درجة المخاطرة""" |
|
|
|
probability_values = {"منخفض": 1, "متوسط": 2, "عالي": 3} |
|
impact_values = {"منخفض": 1, "متوسط": 2, "عالي": 3} |
|
|
|
|
|
p_value = probability_values.get(probability, 1) |
|
i_value = impact_values.get(impact, 1) |
|
|
|
return p_value * i_value |
|
|
|
def _categorize_risks(self): |
|
"""تصنيف المخاطر""" |
|
categories = {} |
|
|
|
for risk in self.analysis_results["identified_risks"]: |
|
category = risk["category"] |
|
if category not in categories: |
|
categories[category] = [] |
|
|
|
categories[category].append(risk) |
|
|
|
|
|
for category, risks in categories.items(): |
|
total_score = sum(risk["risk_score"] for risk in risks) |
|
avg_score = total_score / len(risks) if risks else 0 |
|
max_score = max(risk["risk_score"] for risk in risks) if risks else 0 |
|
|
|
categories[category] = { |
|
"risks": risks, |
|
"count": len(risks), |
|
"total_score": total_score, |
|
"avg_score": avg_score, |
|
"max_score": max_score |
|
} |
|
|
|
self.analysis_results["risk_categories"] = categories |
|
|
|
def _create_risk_matrix(self): |
|
"""إنشاء مصفوفة المخاطر""" |
|
matrix = { |
|
"high_impact": {"high_prob": [], "medium_prob": [], "low_prob": []}, |
|
"medium_impact": {"high_prob": [], "medium_prob": [], "low_prob": []}, |
|
"low_impact": {"high_prob": [], "medium_prob": [], "low_prob": []} |
|
} |
|
|
|
for risk in self.analysis_results["identified_risks"]: |
|
impact = risk["impact"].lower() |
|
probability = risk["probability"].lower() |
|
|
|
impact_key = f"{impact}_impact" |
|
if impact == "عالي": |
|
impact_key = "high_impact" |
|
elif impact == "متوسط": |
|
impact_key = "medium_impact" |
|
else: |
|
impact_key = "low_impact" |
|
|
|
prob_key = f"{probability}_prob" |
|
if probability == "عالي": |
|
prob_key = "high_prob" |
|
elif probability == "متوسط": |
|
prob_key = "medium_prob" |
|
else: |
|
prob_key = "low_prob" |
|
|
|
if impact_key in matrix and prob_key in matrix[impact_key]: |
|
matrix[impact_key][prob_key].append(risk) |
|
|
|
self.analysis_results["risk_matrix"] = matrix |
|
|
|
def _develop_mitigation_strategies(self, method): |
|
"""تطوير استراتيجيات التخفيف""" |
|
strategies = [] |
|
|
|
|
|
high_priority_risks = [] |
|
|
|
|
|
high_priority_risks.extend(self.analysis_results["risk_matrix"]["high_impact"]["high_prob"]) |
|
|
|
|
|
high_priority_risks.extend(self.analysis_results["risk_matrix"]["high_impact"]["medium_prob"]) |
|
|
|
|
|
high_priority_risks.extend(self.analysis_results["risk_matrix"]["medium_impact"]["high_prob"]) |
|
|
|
for risk in high_priority_risks: |
|
strategy = self._generate_mitigation_strategy(risk) |
|
strategies.append(strategy) |
|
|
|
|
|
if method == "comprehensive": |
|
medium_priority_risks = [] |
|
|
|
|
|
medium_priority_risks.extend(self.analysis_results["risk_matrix"]["high_impact"]["low_prob"]) |
|
|
|
|
|
medium_priority_risks.extend(self.analysis_results["risk_matrix"]["medium_impact"]["medium_prob"]) |
|
|
|
|
|
medium_priority_risks.extend(self.analysis_results["risk_matrix"]["low_impact"]["high_prob"]) |
|
|
|
for risk in medium_priority_risks: |
|
strategy = self._generate_mitigation_strategy(risk) |
|
strategies.append(strategy) |
|
|
|
self.analysis_results["mitigation_strategies"] = strategies |
|
|
|
def _generate_mitigation_strategy(self, risk): |
|
"""توليد استراتيجية تخفيف للمخاطر""" |
|
strategy_templates = { |
|
"توريد": [ |
|
"إنشاء قائمة بموردين بديلين", |
|
"التعاقد المسبق مع الموردين", |
|
"تخزين المواد الحرجة مسبقًا", |
|
"وضع خطة للتوريد المرحلي" |
|
], |
|
"مالي": [ |
|
"تضمين بند تعديل الأسعار في العقود", |
|
"تخصيص ميزانية احتياطية", |
|
"التأمين ضد المخاطر المالية", |
|
"تحديث دراسة الجدوى بانتظام" |
|
], |
|
"بيئي": [ |
|
"وضع خطة للطوارئ البيئية", |
|
"جدولة الأنشطة الحرجة في المواسم المناسبة", |
|
"توفير معدات حماية إضافية", |
|
"تطبيق تقنيات مقاومة للظروف البيئية" |
|
], |
|
"موارد بشرية": [ |
|
"التعاقد مع شركات توظيف إضافية", |
|
"تدريب فرق العمل على مهام متعددة", |
|
"وضع خطة للحوافز والمكافآت", |
|
"تطوير برنامج للاحتفاظ بالموظفين" |
|
], |
|
"فني": [ |
|
"إجراء اختبارات إضافية قبل التنفيذ", |
|
"الاستعانة بخبراء متخصصين", |
|
"تطبيق منهجية مراجعة التصميم", |
|
"إعداد خطط بديلة للحلول التقنية" |
|
], |
|
"إداري": [ |
|
"تطبيق إجراءات إدارة التغيير", |
|
"عقد اجتماعات دورية مع أصحاب المصلحة", |
|
"توثيق متطلبات المشروع بشكل تفصيلي", |
|
"تحديد نطاق العمل بوضوح في العقود" |
|
], |
|
"تنظيمي": [ |
|
"التواصل المبكر مع الجهات التنظيمية", |
|
"تعيين مستشار قانوني متخصص", |
|
"متابعة التحديثات التنظيمية بانتظام", |
|
"تخصيص وقت إضافي للحصول على الموافقات" |
|
], |
|
"خارجي": [ |
|
"متابعة التطورات السياسية والاقتصادية", |
|
"وضع خطط بديلة للسيناريوهات المختلفة", |
|
"التأمين ضد المخاطر الخارجية", |
|
"تنويع مصادر التوريد والتمويل" |
|
] |
|
} |
|
|
|
|
|
category = risk["category"] |
|
templates = strategy_templates.get(category, strategy_templates["إداري"]) |
|
|
|
|
|
import random |
|
strategy_text = random.choice(templates) |
|
|
|
return { |
|
"risk_id": risk["id"], |
|
"risk_name": risk["name"], |
|
"strategy": strategy_text, |
|
"priority": "عالية" if risk["risk_score"] >= 6 else "متوسطة" if risk["risk_score"] >= 3 else "منخفضة", |
|
"responsible": "مدير المشروع", |
|
"timeline": "قبل بدء المشروع" |
|
} |
|
|
|
def _create_analysis_summary(self, method): |
|
"""إنشاء ملخص التحليل""" |
|
total_risks = len(self.analysis_results["identified_risks"]) |
|
high_risks = len([r for r in self.analysis_results["identified_risks"] if r["risk_score"] >= 6]) |
|
medium_risks = len([r for r in self.analysis_results["identified_risks"] if 3 <= r["risk_score"] < 6]) |
|
low_risks = len([r for r in self.analysis_results["identified_risks"] if r["risk_score"] < 3]) |
|
|
|
|
|
category_distribution = {} |
|
for risk in self.analysis_results["identified_risks"]: |
|
category = risk["category"] |
|
if category not in category_distribution: |
|
category_distribution[category] = 0 |
|
category_distribution[category] += 1 |
|
|
|
|
|
avg_risk_score = sum(risk["risk_score"] for risk in self.analysis_results["identified_risks"]) / total_risks if total_risks > 0 else 0 |
|
|
|
|
|
summary = { |
|
"total_risks": total_risks, |
|
"high_risks": high_risks, |
|
"medium_risks": medium_risks, |
|
"low_risks": low_risks, |
|
"category_distribution": category_distribution, |
|
"avg_risk_score": avg_risk_score, |
|
"analysis_method": method, |
|
"recommendations": self._generate_recommendations(high_risks, medium_risks, low_risks, category_distribution) |
|
} |
|
|
|
self.analysis_results["summary"] = summary |
|
|
|
def _generate_recommendations(self, high_risks, medium_risks, low_risks, category_distribution): |
|
"""توليد توصيات بناءً على نتائج التحليل""" |
|
recommendations = [] |
|
|
|
|
|
if high_risks > 3: |
|
recommendations.append("يجب إجراء مراجعة شاملة لخطة المشروع نظرًا لوجود عدد كبير من المخاطر عالية الخطورة.") |
|
|
|
if high_risks > 0: |
|
recommendations.append("تطوير خطط استجابة تفصيلية لجميع المخاطر عالية الخطورة.") |
|
|
|
|
|
max_category = max(category_distribution.items(), key=lambda x: x[1], default=(None, 0)) |
|
if max_category[0]: |
|
recommendations.append(f"التركيز على إدارة مخاطر فئة '{max_category[0]}' حيث تمثل النسبة الأكبر من المخاطر المحددة.") |
|
|
|
|
|
recommendations.append("إجراء مراجعات دورية لسجل المخاطر وتحديثه بانتظام.") |
|
recommendations.append("تعيين مسؤولين محددين لمتابعة استراتيجيات التخفيف من المخاطر.") |
|
|
|
return recommendations |
|
|
|
def get_analysis_results(self): |
|
"""الحصول على نتائج التحليل""" |
|
return self.analysis_results |
|
|
|
def export_analysis_results(self, format="json"): |
|
"""تصدير نتائج التحليل""" |
|
if not self.analysis_results: |
|
logger.warning("لا توجد نتائج تحليل للتصدير") |
|
return None |
|
|
|
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') |
|
project_id = self.analysis_results.get("project_id", "unknown") |
|
|
|
if format == "json": |
|
filename = f"risk_analysis_{project_id}_{timestamp}.json" |
|
filepath = self.exports_path / filename |
|
|
|
with open(filepath, 'w', encoding='utf-8') as f: |
|
json.dump(self.analysis_results, f, ensure_ascii=False, indent=4) |
|
|
|
logger.info(f"تم تصدير نتائج التحليل إلى: {filepath}") |
|
return filepath |
|
|
|
else: |
|
logger.error(f"تنسيق التصدير غير مدعوم: {format}") |
|
return None |
|
|
|
|
|
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']}") |
|
st.markdown(f"**نوع المشروع**: {project['project_type']}") |
|
st.markdown(f"**مستوى التعقيد**: {project['complexity']}") |
|
|
|
|
|
analysis_method = st.radio( |
|
"طريقة التحليل", |
|
["أساسي", "شامل"], |
|
format_func=lambda x: "تحليل أساسي" if x == "أساسي" else "تحليل شامل" |
|
) |
|
|
|
|
|
if st.button("بدء تحليل المخاطر"): |
|
with st.spinner("جاري تحليل مخاطر المشروع..."): |
|
|
|
import time |
|
time.sleep(2) |
|
|
|
|
|
self.risk_analyzer.analyze_risks(project_id, method="comprehensive" if analysis_method == "شامل" else "basic") |
|
|
|
|
|
results = self.risk_analyzer.get_analysis_results() |
|
|
|
|
|
st.session_state.risk_analysis_results[str(project_id)] = results |
|
|
|
st.success("تم الانتهاء من تحليل المخاطر بنجاح!") |
|
st.experimental_rerun() |
|
|
|
|
|
if str(project_id) in st.session_state.risk_analysis_results: |
|
results = st.session_state.risk_analysis_results[str(project_id)] |
|
|
|
st.markdown("#### ملخص نتائج التحليل") |
|
|
|
if "summary" in results: |
|
summary = results["summary"] |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
self.ui.create_metric_card("إجمالي المخاطر", str(summary["total_risks"]), None, self.ui.COLORS['primary']) |
|
|
|
with col2: |
|
self.ui.create_metric_card("مخاطر عالية", str(summary["high_risks"]), None, self.ui.COLORS['danger']) |
|
|
|
with col3: |
|
self.ui.create_metric_card("مخاطر متوسطة", str(summary["medium_risks"]), None, self.ui.COLORS['warning']) |
|
|
|
|
|
st.markdown("#### توزيع المخاطر حسب الفئة") |
|
|
|
if "category_distribution" in summary: |
|
category_df = pd.DataFrame({ |
|
'الفئة': list(summary["category_distribution"].keys()), |
|
'عدد المخاطر': list(summary["category_distribution"].values()) |
|
}) |
|
|
|
st.bar_chart(category_df.set_index('الفئة')) |
|
|
|
|
|
st.markdown("#### التوصيات") |
|
|
|
if "recommendations" in summary: |
|
for i, recommendation in enumerate(summary["recommendations"]): |
|
st.markdown(f"{i+1}. {recommendation}") |
|
|
|
|
|
if st.button("تصدير نتائج التحليل"): |
|
with st.spinner("جاري تصدير النتائج..."): |
|
|
|
time.sleep(1) |
|
|
|
|
|
export_path = self.risk_analyzer.export_analysis_results() |
|
|
|
if export_path: |
|
st.success(f"تم تصدير نتائج التحليل بنجاح!") |
|
else: |
|
st.error("حدث خطأ أثناء تصدير النتائج.") |
|
|
|
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 "identified_risks" 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 risk in results["identified_risks"]: |
|
risk_copy = risk.copy() |
|
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 "identified_risks" in results: |
|
for risk in results["identified_risks"]: |
|
risk_copy = risk.copy() |
|
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( |
|
"الاحتمالية", |
|
["عالي", "متوسط", "منخفض"], |
|
key="risk_register_probability" |
|
) |
|
|
|
with col3: |
|
impact_filter = st.multiselect( |
|
"التأثير", |
|
["عالي", "متوسط", "منخفض"], |
|
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_data = [] |
|
for risk in filtered_risks: |
|
risk_data.append({ |
|
'المشروع': risk.get("project_name", ""), |
|
'اسم المخاطرة': risk["name"], |
|
'الوصف': risk.get("description", ""), |
|
'الفئة': risk["category"], |
|
'الاحتمالية': risk["probability"], |
|
'التأثير': risk["impact"], |
|
'درجة المخاطرة': risk["risk_score"] |
|
}) |
|
|
|
risk_df = pd.DataFrame(risk_data) |
|
|
|
|
|
risk_df = risk_df.sort_values(by='درجة المخاطرة', 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 "risk_matrix" in results: |
|
matrix = results["risk_matrix"] |
|
|
|
|
|
st.markdown("#### مصفوفة احتمالية وتأثير المخاطر") |
|
|
|
|
|
matrix_data = [ |
|
[len(matrix["high_impact"]["high_prob"]), len(matrix["high_impact"]["medium_prob"]), len(matrix["high_impact"]["low_prob"])], |
|
[len(matrix["medium_impact"]["high_prob"]), len(matrix["medium_impact"]["medium_prob"]), len(matrix["medium_impact"]["low_prob"])], |
|
[len(matrix["low_impact"]["high_prob"]), len(matrix["low_impact"]["medium_prob"]), len(matrix["low_impact"]["low_prob"])] |
|
] |
|
|
|
|
|
matrix_df = pd.DataFrame( |
|
matrix_data, |
|
columns=["احتمالية عالية", "احتمالية متوسطة", "احتمالية منخفضة"], |
|
index=["تأثير عالي", "تأثير متوسط", "تأثير منخفض"] |
|
) |
|
|
|
|
|
st.dataframe(matrix_df) |
|
|
|
|
|
st.markdown("#### تفاصيل المخاطر في المصفوفة") |
|
|
|
|
|
matrix_tabs = st.tabs([ |
|
"تأثير عالي / احتمالية عالية", |
|
"تأثير عالي / احتمالية متوسطة", |
|
"تأثير متوسط / احتمالية عالية", |
|
"تأثير متوسط / احتمالية متوسطة", |
|
"أخرى" |
|
]) |
|
|
|
|
|
with matrix_tabs[0]: |
|
self._display_cell_risks(matrix["high_impact"]["high_prob"], "تأثير عالي / احتمالية عالية") |
|
|
|
with matrix_tabs[1]: |
|
self._display_cell_risks(matrix["high_impact"]["medium_prob"], "تأثير عالي / احتمالية متوسطة") |
|
|
|
with matrix_tabs[2]: |
|
self._display_cell_risks(matrix["medium_impact"]["high_prob"], "تأثير متوسط / احتمالية عالية") |
|
|
|
with matrix_tabs[3]: |
|
self._display_cell_risks(matrix["medium_impact"]["medium_prob"], "تأثير متوسط / احتمالية متوسطة") |
|
|
|
with matrix_tabs[4]: |
|
|
|
other_risks = [] |
|
other_risks.extend(matrix["high_impact"]["low_prob"]) |
|
other_risks.extend(matrix["medium_impact"]["low_prob"]) |
|
other_risks.extend(matrix["low_impact"]["high_prob"]) |
|
other_risks.extend(matrix["low_impact"]["medium_prob"]) |
|
other_risks.extend(matrix["low_impact"]["low_prob"]) |
|
|
|
self._display_cell_risks(other_risks, "مخاطر أخرى") |
|
else: |
|
st.warning("لم يتم العثور على مصفوفة المخاطر للمشروع المحدد.") |
|
else: |
|
st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.") |
|
|
|
def _display_cell_risks(self, risks, cell_title): |
|
"""عرض المخاطر في خلية من مصفوفة المخاطر""" |
|
|
|
if risks: |
|
st.markdown(f"##### {cell_title} ({len(risks)} مخاطر)") |
|
|
|
|
|
risk_data = [] |
|
for risk in risks: |
|
risk_data.append({ |
|
'اسم المخاطرة': risk["name"], |
|
'الوصف': risk.get("description", ""), |
|
'الفئة': risk["category"], |
|
'درجة المخاطرة': risk["risk_score"] |
|
}) |
|
|
|
risk_df = pd.DataFrame(risk_data) |
|
|
|
|
|
st.dataframe(risk_df, use_container_width=True, hide_index=True) |
|
else: |
|
st.info(f"لا توجد مخاطر في خلية {cell_title}.") |
|
|
|
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 "mitigation_strategies" in results and results["mitigation_strategies"]: |
|
strategies = results["mitigation_strategies"] |
|
|
|
|
|
priority_filter = st.multiselect( |
|
"الأولوية", |
|
["عالية", "متوسطة", "منخفضة"], |
|
default=["عالية"], |
|
key="mitigation_priority" |
|
) |
|
|
|
|
|
filtered_strategies = strategies |
|
if priority_filter: |
|
filtered_strategies = [s for s in filtered_strategies if s["priority"] in priority_filter] |
|
|
|
|
|
if filtered_strategies: |
|
|
|
strategy_data = [] |
|
for strategy in filtered_strategies: |
|
strategy_data.append({ |
|
'المخاطرة': strategy["risk_name"], |
|
'استراتيجية التخفيف': strategy["strategy"], |
|
'الأولوية': strategy["priority"], |
|
'المسؤول': strategy["responsible"], |
|
'الإطار الزمني': strategy["timeline"] |
|
}) |
|
|
|
strategy_df = pd.DataFrame(strategy_data) |
|
|
|
|
|
priority_order = {"عالية": 0, "متوسطة": 1, "منخفضة": 2} |
|
strategy_df["priority_order"] = strategy_df["الأولوية"].map(priority_order) |
|
strategy_df = strategy_df.sort_values(by="priority_order") |
|
strategy_df = strategy_df.drop(columns=["priority_order"]) |
|
|
|
|
|
st.dataframe(strategy_df, use_container_width=True, hide_index=True) |
|
|
|
|
|
if st.button("تصدير استراتيجيات التخفيف"): |
|
with st.spinner("جاري تصدير استراتيجيات التخفيف..."): |
|
|
|
time.sleep(1) |
|
st.success("تم تصدير استراتيجيات التخفيف بنجاح!") |
|
else: |
|
st.info("لا توجد استراتيجيات تخفيف تطابق معايير الفلترة.") |
|
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() |
|
|