|
""" |
|
وحدة تحليل المخاطر لنظام إدارة المناقصات - Hybrid Face |
|
""" |
|
|
|
import os |
|
import logging |
|
import threading |
|
import datetime |
|
import json |
|
import math |
|
from pathlib import Path |
|
|
|
|
|
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_map = { |
|
"منخفض": 1, |
|
"متوسط": 2, |
|
"عالي": 3 |
|
} |
|
|
|
impact_map = { |
|
"منخفض": 1, |
|
"متوسط": 2, |
|
"عالي": 3 |
|
} |
|
|
|
prob_value = probability_map.get(probability, 1) |
|
impact_value = impact_map.get(impact, 1) |
|
|
|
return prob_value * impact_value |
|
|
|
def _categorize_risks(self): |
|
"""تصنيف المخاطر""" |
|
categories = {} |
|
|
|
for risk in self.analysis_results["identified_risks"]: |
|
category = risk["category"] |
|
|
|
if category not in categories: |
|
categories[category] = { |
|
"count": 0, |
|
"risks": [], |
|
"avg_score": 0, |
|
"max_score": 0 |
|
} |
|
|
|
categories[category]["count"] += 1 |
|
categories[category]["risks"].append(risk) |
|
categories[category]["max_score"] = max(categories[category]["max_score"], risk["risk_score"]) |
|
|
|
|
|
for category in categories: |
|
total_score = sum(risk["risk_score"] for risk in categories[category]["risks"]) |
|
categories[category]["avg_score"] = total_score / categories[category]["count"] |
|
|
|
|
|
sorted_categories = dict(sorted( |
|
categories.items(), |
|
key=lambda item: item[1]["avg_score"], |
|
reverse=True |
|
)) |
|
|
|
self.analysis_results["risk_categories"] = sorted_categories |
|
|
|
def _create_risk_matrix(self): |
|
"""إنشاء مصفوفة المخاطر""" |
|
matrix = { |
|
"high_impact": { |
|
"high_probability": [], |
|
"medium_probability": [], |
|
"low_probability": [] |
|
}, |
|
"medium_impact": { |
|
"high_probability": [], |
|
"medium_probability": [], |
|
"low_probability": [] |
|
}, |
|
"low_impact": { |
|
"high_probability": [], |
|
"medium_probability": [], |
|
"low_probability": [] |
|
} |
|
} |
|
|
|
for risk in self.analysis_results["identified_risks"]: |
|
impact = risk["impact"].lower() |
|
probability = risk["probability"].lower() |
|
|
|
impact_key = f"{impact}_impact" |
|
probability_key = f"{probability}_probability" |
|
|
|
if impact_key in matrix and probability_key in matrix[impact_key]: |
|
matrix[impact_key][probability_key].append(risk) |
|
|
|
self.analysis_results["risk_matrix"] = matrix |
|
|
|
def _develop_mitigation_strategies(self, method): |
|
"""تطوير استراتيجيات التخفيف""" |
|
mitigation_strategies = [] |
|
|
|
for risk in self.analysis_results["identified_risks"]: |
|
strategy = self._generate_mitigation_strategy(risk, method) |
|
mitigation_strategies.append(strategy) |
|
|
|
self.analysis_results["mitigation_strategies"] = mitigation_strategies |
|
|
|
def _generate_mitigation_strategy(self, risk, method): |
|
"""توليد استراتيجية تخفيف للمخاطرة""" |
|
strategy = { |
|
"risk_id": risk["id"], |
|
"risk_name": risk["name"], |
|
"risk_score": risk["risk_score"], |
|
"strategy_type": "", |
|
"actions": [], |
|
"responsible": "", |
|
"timeline": "", |
|
"cost_impact": 0 |
|
} |
|
|
|
|
|
if risk["risk_score"] >= 6: |
|
strategy["strategy_type"] = "تجنب" |
|
strategy["responsible"] = "مدير المشروع" |
|
strategy["timeline"] = "فوري" |
|
strategy["cost_impact"] = "عالي" |
|
elif risk["risk_score"] >= 3: |
|
strategy["strategy_type"] = "تخفيف" |
|
strategy["responsible"] = "مشرف القسم المعني" |
|
strategy["timeline"] = "خلال أسبوعين" |
|
strategy["cost_impact"] = "متوسط" |
|
else: |
|
strategy["strategy_type"] = "قبول" |
|
strategy["responsible"] = "فريق المشروع" |
|
strategy["timeline"] = "مراقبة مستمرة" |
|
strategy["cost_impact"] = "منخفض" |
|
|
|
|
|
if risk["category"] == "توريد": |
|
strategy["actions"] = [ |
|
"تحديد موردين بدلاء", |
|
"وضع جدول زمني للتوريد مع هوامش زمنية", |
|
"التعاقد المسبق على المواد الرئيسية" |
|
] |
|
elif risk["category"] == "مالي": |
|
strategy["actions"] = [ |
|
"تضمين بند تعديل الأسعار في العقود", |
|
"وضع ميزانية احتياطية", |
|
"التحوط ضد تقلبات الأسعار" |
|
] |
|
elif risk["category"] == "بيئي": |
|
strategy["actions"] = [ |
|
"وضع خطة للطوارئ الجوية", |
|
"جدولة الأنشطة الحساسة في الأوقات المناسبة", |
|
"توفير معدات وقاية مناسبة" |
|
] |
|
elif risk["category"] == "موارد بشرية": |
|
strategy["actions"] = [ |
|
"التعاقد المسبق مع مقاولي الباطن", |
|
"وضع خطة لتدريب وتأهيل العمالة", |
|
"تحفيز العاملين للحفاظ عليهم" |
|
] |
|
elif risk["category"] == "فني": |
|
strategy["actions"] = [ |
|
"إجراء دراسات فنية إضافية", |
|
"الاستعانة بخبراء متخصصين", |
|
"وضع خطط بديلة للحلول الفنية" |
|
] |
|
elif risk["category"] == "إداري": |
|
strategy["actions"] = [ |
|
"تحديد نطاق العمل بدقة في العقود", |
|
"وضع إجراءات واضحة لإدارة التغيير", |
|
"التواصل المستمر مع العميل" |
|
] |
|
elif risk["category"] == "تنظيمي": |
|
strategy["actions"] = [ |
|
"متابعة التغييرات في اللوائح والأنظمة", |
|
"التنسيق المبكر مع الجهات المعنية", |
|
"تعيين مستشار قانوني للمشروع" |
|
] |
|
else: |
|
strategy["actions"] = [ |
|
"مراقبة المخاطر بشكل دوري", |
|
"وضع خطة استجابة", |
|
"تخصيص موارد احتياطية" |
|
] |
|
|
|
|
|
if method == "comprehensive" and len(strategy["actions"]) < 5: |
|
strategy["actions"].append("إجراء مراجعات دورية لفعالية استراتيجية التخفيف") |
|
strategy["actions"].append("توثيق الدروس المستفادة لتحسين إدارة المخاطر المستقبلية") |
|
|
|
return strategy |
|
|
|
def _create_analysis_summary(self, method): |
|
"""إنشاء ملخص التحليل""" |
|
identified_risks = self.analysis_results["identified_risks"] |
|
|
|
|
|
total_risks = len(identified_risks) |
|
high_risks = sum(1 for risk in identified_risks if risk["risk_score"] >= 6) |
|
medium_risks = sum(1 for risk in identified_risks if 3 <= risk["risk_score"] < 6) |
|
low_risks = sum(1 for risk in identified_risks if risk["risk_score"] < 3) |
|
|
|
|
|
avg_risk_score = sum(risk["risk_score"] for risk in identified_risks) / total_risks if total_risks > 0 else 0 |
|
|
|
|
|
categories = self.analysis_results["risk_categories"] |
|
top_categories = list(categories.keys())[:3] if len(categories) >= 3 else list(categories.keys()) |
|
|
|
|
|
critical_risks = [risk for risk in identified_risks if risk["risk_score"] >= 6] |
|
critical_risks = sorted(critical_risks, key=lambda x: x["risk_score"], reverse=True) |
|
top_critical_risks = critical_risks[:3] if len(critical_risks) >= 3 else critical_risks |
|
|
|
|
|
summary = { |
|
"total_risks": total_risks, |
|
"risk_distribution": { |
|
"high_risks": high_risks, |
|
"medium_risks": medium_risks, |
|
"low_risks": low_risks |
|
}, |
|
"avg_risk_score": avg_risk_score, |
|
"top_risk_categories": top_categories, |
|
"critical_risks": [risk["name"] for risk in top_critical_risks], |
|
"overall_risk_level": self._determine_overall_risk_level(avg_risk_score, high_risks, total_risks), |
|
"recommendations": self._generate_recommendations(method, high_risks, avg_risk_score) |
|
} |
|
|
|
self.analysis_results["summary"] = summary |
|
|
|
def _determine_overall_risk_level(self, avg_risk_score, high_risks, total_risks): |
|
"""تحديد مستوى المخاطرة الإجمالي""" |
|
if avg_risk_score >= 5 or (high_risks / total_risks >= 0.3 if total_risks > 0 else False): |
|
return "عالي" |
|
elif avg_risk_score >= 3 or (high_risks / total_risks >= 0.1 if total_risks > 0 else False): |
|
return "متوسط" |
|
else: |
|
return "منخفض" |
|
|
|
def _generate_recommendations(self, method, high_risks, avg_risk_score): |
|
"""توليد توصيات بناءً على نتائج التحليل""" |
|
recommendations = [] |
|
|
|
if high_risks > 0: |
|
recommendations.append("التركيز على استراتيجيات تخفيف المخاطر عالية الدرجة") |
|
|
|
if avg_risk_score >= 4: |
|
recommendations.append("إجراء مراجعة شاملة لخطة إدارة المخاطر بشكل دوري") |
|
recommendations.append("تخصيص ميزانية احتياطية كافية للتعامل مع المخاطر المحتملة") |
|
|
|
recommendations.append("توثيق المخاطر واستراتيجيات التخفيف في سجل المخاطر") |
|
recommendations.append("تعيين مسؤولين محددين لمتابعة تنفيذ استراتيجيات التخفيف") |
|
|
|
if method == "comprehensive": |
|
recommendations.append("إجراء تحليل كمي للمخاطر لتقدير التأثير المالي والزمني") |
|
recommendations.append("تطوير مؤشرات إنذار مبكر لرصد المخاطر قبل حدوثها") |
|
recommendations.append("إشراك أصحاب المصلحة في عملية تحديد وتقييم المخاطر") |
|
|
|
return recommendations |
|
|
|
def get_analysis_status(self): |
|
"""الحصول على حالة التحليل الحالي""" |
|
if not self.analysis_in_progress: |
|
if not self.analysis_results: |
|
return {"status": "لا يوجد تحليل جارٍ"} |
|
else: |
|
return {"status": self.analysis_results.get("status", "غير معروف")} |
|
|
|
return { |
|
"status": "جاري التحليل", |
|
"project_id": self.current_project, |
|
"start_time": self.analysis_results.get("analysis_start_time") |
|
} |
|
|
|
def get_analysis_results(self): |
|
"""الحصول على نتائج التحليل""" |
|
return self.analysis_results |
|
|
|
def export_analysis_results(self, output_path=None): |
|
"""تصدير نتائج التحليل إلى ملف JSON""" |
|
if not self.analysis_results: |
|
logger.warning("لا توجد نتائج تحليل للتصدير") |
|
return None |
|
|
|
if not output_path: |
|
|
|
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') |
|
filename = f"risk_analysis_results_{timestamp}.json" |
|
output_path = os.path.join(self.exports_path, filename) |
|
|
|
try: |
|
with open(output_path, 'w', encoding='utf-8') as f: |
|
json.dump(self.analysis_results, f, ensure_ascii=False, indent=4) |
|
|
|
logger.info(f"تم تصدير نتائج تحليل المخاطر إلى: {output_path}") |
|
return output_path |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في تصدير نتائج تحليل المخاطر: {str(e)}") |
|
return None |
|
|
|
def import_analysis_results(self, input_path): |
|
"""استيراد نتائج التحليل من ملف JSON""" |
|
if not os.path.exists(input_path): |
|
logger.error(f"ملف نتائج التحليل غير موجود: {input_path}") |
|
return False |
|
|
|
try: |
|
with open(input_path, 'r', encoding='utf-8') as f: |
|
self.analysis_results = json.load(f) |
|
|
|
logger.info(f"تم استيراد نتائج تحليل المخاطر من: {input_path}") |
|
return True |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في استيراد نتائج تحليل المخاطر: {str(e)}") |
|
return False |
|
|