|
""" |
|
تطبيق وحدة تحليل المخاطر |
|
""" |
|
|
|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
from datetime import datetime |
|
import random |
|
import os |
|
import time |
|
import io |
|
|
|
from utils.helpers import format_number, format_currency |
|
from utils.excel_handler import export_to_excel |
|
|
|
|
|
class RiskAnalysisApp: |
|
"""وحدة تحليل المخاطر""" |
|
|
|
def __init__(self): |
|
"""تهيئة وحدة تحليل المخاطر""" |
|
|
|
self.risk_categories = [ |
|
"مخاطر مالية", |
|
"مخاطر زمنية", |
|
"مخاطر فنية", |
|
"مخاطر إدارية", |
|
"مخاطر تنظيمية", |
|
"مخاطر سوقية", |
|
"مخاطر تعاقدية" |
|
] |
|
|
|
self.impact_levels = ["منخفض", "متوسط", "عالي"] |
|
self.probability_levels = ["غير محتمل", "محتمل", "مؤكد"] |
|
|
|
def render(self): |
|
"""عرض واجهة وحدة تحليل المخاطر""" |
|
|
|
st.markdown("<h1 class='module-title'>وحدة تحليل المخاطر</h1>", unsafe_allow_html=True) |
|
|
|
tabs = st.tabs([ |
|
"تحليل المخاطر", |
|
"سجل المخاطر", |
|
"مصفوفة المخاطر", |
|
"خطة الاستجابة للمخاطر" |
|
]) |
|
|
|
with tabs[0]: |
|
self._render_risk_analysis_tab() |
|
|
|
with tabs[1]: |
|
self._render_risk_register_tab() |
|
|
|
with tabs[2]: |
|
self._render_risk_matrix_tab() |
|
|
|
with tabs[3]: |
|
self._render_risk_response_tab() |
|
|
|
def _render_risk_analysis_tab(self): |
|
"""عرض تبويب تحليل المخاطر""" |
|
|
|
st.markdown("### تحليل المخاطر") |
|
|
|
|
|
if 'current_project' not in st.session_state or st.session_state.current_project is None: |
|
|
|
if 'projects' in st.session_state and st.session_state.projects: |
|
project_names = [p['name'] for p in st.session_state.projects] |
|
selected_project_name = st.selectbox("اختر المشروع", project_names) |
|
|
|
if selected_project_name: |
|
selected_project = next((p for p in st.session_state.projects if p['name'] == selected_project_name), None) |
|
if selected_project: |
|
st.session_state.current_project = selected_project |
|
else: |
|
st.warning("لم يتم العثور على المشروع المحدد.") |
|
return |
|
else: |
|
st.info("يرجى اختيار مشروع لتحليل مخاطره.") |
|
return |
|
else: |
|
st.warning("لا توجد مشاريع متاحة. يرجى إنشاء مشروع جديد أولاً.") |
|
return |
|
|
|
|
|
project = st.session_state.current_project |
|
|
|
col1, col2, col3 = st.columns(3) |
|
with col1: |
|
st.metric("اسم المشروع", project['name']) |
|
with col2: |
|
st.metric("رقم المناقصة", project['number']) |
|
with col3: |
|
st.metric("الجهة المالكة", project['client']) |
|
|
|
|
|
if 'risks' not in project: |
|
project['risks'] = [] |
|
|
|
|
|
with st.form("add_risk_form"): |
|
st.markdown("#### إضافة مخاطرة جديدة") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
risk_code = st.text_input("رمز المخاطرة", f"R{len(project['risks']) + 1}") |
|
risk_category = st.selectbox("فئة المخاطرة", self.risk_categories) |
|
impact = st.select_slider("التأثير", self.impact_levels, value="متوسط") |
|
|
|
with col2: |
|
risk_description = st.text_area("وصف المخاطرة", height=80) |
|
probability = st.select_slider("الاحتمالية", self.probability_levels, value="محتمل") |
|
response_strategy = st.text_area("استراتيجية الاستجابة", height=80) |
|
|
|
submitted = st.form_submit_button("إضافة المخاطرة") |
|
|
|
if submitted: |
|
|
|
if not risk_description: |
|
st.error("يرجى إدخال وصف المخاطرة.") |
|
else: |
|
|
|
new_risk = { |
|
'id': len(project['risks']) + 1, |
|
'risk_code': risk_code, |
|
'description': risk_description, |
|
'category': risk_category, |
|
'impact': impact, |
|
'probability': probability, |
|
'response_strategy': response_strategy, |
|
'status': "نشط", |
|
'created_at': datetime.now().strftime('%Y-%m-%d'), |
|
'risk_score': self._calculate_risk_score(impact, probability) |
|
} |
|
|
|
|
|
project['risks'].append(new_risk) |
|
|
|
st.success(f"تمت إضافة المخاطرة [{risk_code}] بنجاح!") |
|
st.balloons() |
|
|
|
|
|
st.markdown("#### خيارات تحليل المخاطر") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
automated_analysis = st.button("تحليل تلقائي للمخاطر") |
|
|
|
with col2: |
|
from_document_analysis = st.button("استيراد المخاطر من تحليل المستندات") |
|
|
|
if automated_analysis: |
|
with st.spinner("جاري تحليل المخاطر..."): |
|
time.sleep(2) |
|
self._generate_automated_risks(project) |
|
st.success("تم تحليل المخاطر بنجاح!") |
|
st.balloons() |
|
|
|
if from_document_analysis: |
|
with st.spinner("جاري استيراد المخاطر من تحليل المستندات..."): |
|
time.sleep(2) |
|
|
|
|
|
document_risks = self._get_risks_from_documents() |
|
|
|
if document_risks: |
|
existing_risk_codes = [r['risk_code'] for r in project['risks']] |
|
|
|
for risk in document_risks: |
|
|
|
if risk['risk_code'] not in existing_risk_codes: |
|
project['risks'].append(risk) |
|
|
|
st.success(f"تم استيراد {len(document_risks)} مخاطرة من تحليل المستندات!") |
|
else: |
|
st.warning("لم يتم العثور على مخاطر في المستندات.") |
|
|
|
|
|
if project['risks']: |
|
self._show_risk_summary(project['risks']) |
|
|
|
def _render_risk_register_tab(self): |
|
"""عرض تبويب سجل المخاطر""" |
|
|
|
st.markdown("### سجل المخاطر") |
|
|
|
|
|
if 'current_project' not in st.session_state or st.session_state.current_project is None: |
|
st.info("يرجى اختيار مشروع من تبويب تحليل المخاطر أولاً.") |
|
return |
|
|
|
project = st.session_state.current_project |
|
|
|
if 'risks' not in project or not project['risks']: |
|
st.info("لا توجد مخاطر مسجلة لهذا المشروع. يمكنك إضافة مخاطر من تبويب تحليل المخاطر.") |
|
return |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
search_term = st.text_input("البحث في سجل المخاطر") |
|
|
|
with col2: |
|
category_filter = st.multiselect("فلترة حسب الفئة", self.risk_categories) |
|
|
|
with col3: |
|
impact_filter = st.multiselect("فلترة حسب التأثير", self.impact_levels) |
|
|
|
|
|
filtered_risks = project['risks'] |
|
|
|
if search_term: |
|
filtered_risks = [r for r in filtered_risks if search_term.lower() in r.get('description', '').lower()] |
|
|
|
if category_filter: |
|
filtered_risks = [r for r in filtered_risks if r.get('category') in category_filter] |
|
|
|
if impact_filter: |
|
filtered_risks = [r for r in filtered_risks if r.get('impact') in impact_filter] |
|
|
|
|
|
if filtered_risks: |
|
|
|
risk_df = pd.DataFrame(filtered_risks) |
|
|
|
|
|
display_columns = [ |
|
'risk_code', 'description', 'category', 'impact', |
|
'probability', 'risk_score', 'status' |
|
] |
|
|
|
|
|
column_names = { |
|
'risk_code': 'رمز المخاطرة', |
|
'description': 'وصف المخاطرة', |
|
'category': 'الفئة', |
|
'impact': 'التأثير', |
|
'probability': 'الاحتمالية', |
|
'risk_score': 'درجة المخاطرة', |
|
'status': 'الحالة', |
|
'response_strategy': 'استراتيجية الاستجابة', |
|
'created_at': 'تاريخ الإنشاء' |
|
} |
|
|
|
|
|
if 'response_strategy' in risk_df.columns: |
|
display_columns.append('response_strategy') |
|
|
|
if 'created_at' in risk_df.columns: |
|
display_columns.append('created_at') |
|
|
|
|
|
available_columns = [col for col in display_columns if col in risk_df.columns] |
|
|
|
if available_columns: |
|
display_df = risk_df[available_columns].rename(columns=column_names) |
|
|
|
|
|
st.dataframe(display_df, use_container_width=True, hide_index=True) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
if st.button("تصدير سجل المخاطر إلى Excel"): |
|
st.success("تم تصدير سجل المخاطر بنجاح!") |
|
|
|
with col2: |
|
if st.button("طباعة تقرير المخاطر"): |
|
st.success("تم إنشاء تقرير المخاطر بنجاح!") |
|
else: |
|
st.warning("هناك مشكلة في بنية بيانات المخاطر. يرجى التحقق من سلامة البيانات.") |
|
else: |
|
st.info("لا توجد مخاطر تطابق معايير البحث.") |
|
|
|
def _render_risk_matrix_tab(self): |
|
"""عرض تبويب مصفوفة المخاطر""" |
|
|
|
st.markdown("### مصفوفة المخاطر") |
|
|
|
|
|
if 'current_project' not in st.session_state or st.session_state.current_project is None: |
|
st.info("يرجى اختيار مشروع من تبويب تحليل المخاطر أولاً.") |
|
return |
|
|
|
project = st.session_state.current_project |
|
|
|
if 'risks' not in project or not project['risks']: |
|
st.info("لا توجد مخاطر مسجلة لهذا المشروع. يمكنك إضافة مخاطر من تبويب تحليل المخاطر.") |
|
return |
|
|
|
|
|
impact_values = {"منخفض": 1, "متوسط": 2, "عالي": 3} |
|
probability_values = {"غير محتمل": 1, "محتمل": 2, "مؤكد": 3} |
|
|
|
|
|
matrix_data = [] |
|
|
|
for p in probability_values.keys(): |
|
for i in impact_values.keys(): |
|
p_value = probability_values[p] |
|
i_value = impact_values[i] |
|
risk_score = p_value * i_value |
|
|
|
|
|
if risk_score <= 2: |
|
color = 'green' |
|
elif risk_score <= 6: |
|
color = 'orange' |
|
else: |
|
color = 'red' |
|
|
|
|
|
cell_risks = [r for r in project['risks'] if r.get('impact') == i and r.get('probability') == p] |
|
|
|
|
|
matrix_data.append({ |
|
'احتمالية': p, |
|
'تأثير': i, |
|
'درجة_المخاطرة': risk_score, |
|
'عدد_المخاطر': len(cell_risks), |
|
'المخاطر': [r.get('risk_code') for r in cell_risks], |
|
'لون': color |
|
}) |
|
|
|
|
|
matrix_df = pd.DataFrame(matrix_data) |
|
|
|
|
|
fig = go.Figure() |
|
|
|
for index, row in matrix_df.iterrows(): |
|
|
|
if row['عدد_المخاطر'] > 0: |
|
cell_text = f"{', '.join(row['المخاطر'])}<br>({row['عدد_المخاطر']} مخاطر)" |
|
else: |
|
cell_text = '' |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
x=[row['تأثير']], |
|
y=[row['احتمالية']], |
|
mode='markers+text', |
|
marker=dict( |
|
color=row['لون'], |
|
size=20 + (row['عدد_المخاطر'] * 5), |
|
opacity=0.8 |
|
), |
|
text=cell_text, |
|
textposition="middle center", |
|
name=f"{row['احتمالية']} - {row['تأثير']}" |
|
)) |
|
|
|
|
|
fig.update_layout( |
|
title="مصفوفة المخاطر (الاحتمالية × التأثير)", |
|
xaxis=dict( |
|
title="التأثير", |
|
tickmode='array', |
|
tickvals=[1, 2, 3], |
|
ticktext=["منخفض", "متوسط", "عالي"], |
|
gridcolor='lightgray' |
|
), |
|
yaxis=dict( |
|
title="الاحتمالية", |
|
tickmode='array', |
|
tickvals=[1, 2, 3], |
|
ticktext=["غير محتمل", "محتمل", "مؤكد"], |
|
gridcolor='lightgray' |
|
), |
|
height=600 |
|
) |
|
|
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المخاطر حسب الفئة") |
|
|
|
|
|
category_counts = {} |
|
for r in project['risks']: |
|
category = r.get('category', 'أخرى') |
|
category_counts[category] = category_counts.get(category, 0) + 1 |
|
|
|
|
|
category_df = pd.DataFrame({ |
|
'الفئة': list(category_counts.keys()), |
|
'عدد المخاطر': list(category_counts.values()) |
|
}) |
|
|
|
|
|
fig = px.pie( |
|
category_df, |
|
values='عدد المخاطر', |
|
names='الفئة', |
|
title='توزيع المخاطر حسب الفئة', |
|
hole=0.4 |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
def _render_risk_response_tab(self): |
|
"""عرض تبويب خطة الاستجابة للمخاطر""" |
|
|
|
st.markdown("### خطة الاستجابة للمخاطر") |
|
|
|
|
|
if 'current_project' not in st.session_state or st.session_state.current_project is None: |
|
st.info("يرجى اختيار مشروع من تبويب تحليل المخاطر أولاً.") |
|
return |
|
|
|
project = st.session_state.current_project |
|
|
|
if 'risks' not in project or not project['risks']: |
|
st.info("لا توجد مخاطر مسجلة لهذا المشروع. يمكنك إضافة مخاطر من تبويب تحليل المخاطر.") |
|
return |
|
|
|
|
|
sorted_risks = sorted(project['risks'], key=lambda x: x.get('risk_score', 0), reverse=True) |
|
|
|
|
|
for i, risk in enumerate(sorted_risks): |
|
with st.expander(f"{risk.get('risk_code', '')}: {risk.get('description', 'بدون وصف')}", expanded=(i < 3)): |
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.markdown(f"**الفئة**: {risk.get('category', 'غير محدد')}") |
|
st.markdown(f"**التأثير**: {risk.get('impact', 'غير محدد')}") |
|
|
|
with col2: |
|
st.markdown(f"**الاحتمالية**: {risk.get('probability', 'غير محدد')}") |
|
st.markdown(f"**درجة المخاطرة**: {risk.get('risk_score', 'غير محدد')}") |
|
|
|
with col3: |
|
st.markdown(f"**الحالة**: {risk.get('status', 'نشط')}") |
|
risk_owner = risk.get('risk_owner', 'غير محدد') |
|
st.markdown(f"**مسؤول المخاطرة**: {risk_owner}") |
|
|
|
st.markdown("---") |
|
st.markdown("#### استراتيجية الاستجابة") |
|
current_strategy = risk.get('response_strategy', '') |
|
new_strategy = st.text_area(f"استراتيجية الاستجابة للمخاطرة {risk.get('risk_code', '')}", |
|
value=current_strategy, |
|
height=100, |
|
key=f"strategy_{risk.get('risk_code', '')}") |
|
|
|
|
|
if new_strategy != current_strategy: |
|
risk['response_strategy'] = new_strategy |
|
|
|
st.markdown("#### إجراءات التحكم") |
|
control_measures = risk.get('control_measures', []) |
|
|
|
if control_measures: |
|
for j, measure in enumerate(control_measures): |
|
st.markdown(f"{j+1}. {measure}") |
|
else: |
|
st.info("لم يتم تعريف إجراءات تحكم لهذه المخاطرة.") |
|
|
|
|
|
new_measure = st.text_input(f"إجراء تحكم جديد للمخاطرة {risk.get('risk_code', '')}", |
|
key=f"measure_{risk.get('risk_code', '')}") |
|
|
|
if st.button(f"إضافة إجراء", key=f"add_measure_{risk.get('risk_code', '')}"): |
|
if new_measure: |
|
if 'control_measures' not in risk: |
|
risk['control_measures'] = [] |
|
|
|
risk['control_measures'].append(new_measure) |
|
st.success(f"تم إضافة إجراء التحكم بنجاح!") |
|
st.rerun() |
|
else: |
|
st.error("يرجى إدخال إجراء التحكم.") |
|
|
|
|
|
if st.button("تصدير خطة الاستجابة للمخاطر"): |
|
st.success("تم تصدير خطة الاستجابة للمخاطر بنجاح!") |
|
|
|
def _calculate_risk_score(self, impact, probability): |
|
"""حساب درجة المخاطرة بناءً على التأثير والاحتمالية""" |
|
impact_values = {"منخفض": 1, "متوسط": 2, "عالي": 3} |
|
probability_values = {"غير محتمل": 1, "محتمل": 2, "مؤكد": 3} |
|
|
|
impact_value = impact_values.get(impact, 1) |
|
probability_value = probability_values.get(probability, 1) |
|
|
|
return impact_value * probability_value |
|
|
|
def _generate_automated_risks(self, project): |
|
"""توليد مخاطر تلقائية بناءً على خصائص المشروع""" |
|
|
|
|
|
common_risks = [ |
|
{ |
|
'risk_code': 'RF01', |
|
'description': 'غرامة تأخير مرتفعة (10% من قيمة العقد)', |
|
'category': 'مخاطر مالية', |
|
'impact': 'عالي', |
|
'probability': 'محتمل', |
|
'response_strategy': 'تخصيص مبلغ احتياطي للغرامات المحتملة ووضع خطة لإدارة الجدول الزمني بشكل فعال', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RF02', |
|
'description': 'متطلبات ضمان بنكي مرتفعة (15% من قيمة العقد)', |
|
'category': 'مخاطر مالية', |
|
'impact': 'متوسط', |
|
'probability': 'مؤكد', |
|
'response_strategy': 'التفاوض مع العميل لتخفيض نسبة الضمان البنكي أو تقسيمه على مراحل المشروع', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RF03', |
|
'description': 'شروط دفع متأخرة (60 يوم)', |
|
'category': 'مخاطر مالية', |
|
'impact': 'متوسط', |
|
'probability': 'مؤكد', |
|
'response_strategy': 'التخطيط للتدفق النقدي مع الأخذ بالاعتبار تأخر الدفعات وتأمين خط ائتمان احتياطي', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RT01', |
|
'description': 'مدة تنفيذ قصيرة (12 شهر)', |
|
'category': 'مخاطر زمنية', |
|
'impact': 'عالي', |
|
'probability': 'محتمل', |
|
'response_strategy': 'زيادة فريق العمل واستخدام موارد إضافية مع وضع خطة عمل تفصيلية ومراقبتها أسبوعياً', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RT02', |
|
'description': 'احتمالية تأخر توريد المواد الرئيسية', |
|
'category': 'مخاطر زمنية', |
|
'impact': 'عالي', |
|
'probability': 'محتمل', |
|
'response_strategy': 'تحديد المواد ذات فترات التوريد الطويلة وطلبها مبكراً مع التعاقد مع موردين بدلاء', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RTE01', |
|
'description': 'غموض في بعض المواصفات الفنية', |
|
'category': 'مخاطر فنية', |
|
'impact': 'متوسط', |
|
'probability': 'محتمل', |
|
'response_strategy': 'طلب توضيح من العميل قبل البدء بالتنفيذ وتوثيق جميع الردود والتوضيحات', |
|
'status': 'نشط', |
|
'risk_score': 4 |
|
}, |
|
{ |
|
'risk_code': 'RTE02', |
|
'description': 'تضارب بين المخططات والمواصفات', |
|
'category': 'مخاطر فنية', |
|
'impact': 'متوسط', |
|
'probability': 'محتمل', |
|
'response_strategy': 'مراجعة شاملة للمستندات وتوثيق التضاربات وطلب توضيح من العميل', |
|
'status': 'نشط', |
|
'risk_score': 4 |
|
}, |
|
{ |
|
'risk_code': 'RM01', |
|
'description': 'عدم وضوح آلية استلام الأعمال', |
|
'category': 'مخاطر إدارية', |
|
'impact': 'منخفض', |
|
'probability': 'محتمل', |
|
'response_strategy': 'طلب توضيح آلية الاستلام من العميل ووضع إجراءات داخلية للتحقق من جودة الأعمال قبل التقديم للاستلام', |
|
'status': 'نشط', |
|
'risk_score': 2 |
|
}, |
|
{ |
|
'risk_code': 'RR01', |
|
'description': 'شروط تعجيزية للمحتوى المحلي', |
|
'category': 'مخاطر تنظيمية', |
|
'impact': 'عالي', |
|
'probability': 'محتمل', |
|
'response_strategy': 'دراسة متطلبات المحتوى المحلي بدقة ووضع خطة لتحقيقها مع الاحتفاظ بسجلات التوثيق اللازمة', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RM01', |
|
'description': 'خطر التغييرات في أسعار المواد', |
|
'category': 'مخاطر سوقية', |
|
'impact': 'عالي', |
|
'probability': 'محتمل', |
|
'response_strategy': 'تثبيت أسعار المواد الرئيسية مع الموردين وإدراج بند تعديل الأسعار في العقد', |
|
'status': 'نشط', |
|
'risk_score': 6 |
|
}, |
|
{ |
|
'risk_code': 'RC01', |
|
'description': 'عدم وضوح بعض بنود العقد', |
|
'category': 'مخاطر تعاقدية', |
|
'impact': 'متوسط', |
|
'probability': 'محتمل', |
|
'response_strategy': 'مراجعة العقد من قبل مستشار قانوني متخصص وطلب توضيح للبنود الغامضة قبل التوقيع', |
|
'status': 'نشط', |
|
'risk_score': 4 |
|
} |
|
] |
|
|
|
|
|
existing_risk_codes = [r['risk_code'] for r in project['risks']] |
|
|
|
for risk in common_risks: |
|
|
|
if risk['risk_code'] not in existing_risk_codes: |
|
risk['id'] = len(project['risks']) + 1 |
|
risk['created_at'] = datetime.now().strftime('%Y-%m-%d') |
|
project['risks'].append(risk) |
|
|
|
def _get_risks_from_documents(self): |
|
"""استيراد المخاطر من تحليل المستندات""" |
|
|
|
|
|
|
|
|
|
document_risks = [ |
|
{ |
|
'risk_code': 'RD01', |
|
'description': 'غرامة تأخير مرتفعة تصل إلى 20% من قيمة العقد', |
|
'category': 'مخاطر مالية', |
|
'impact': 'عالي', |
|
'probability': 'مؤكد', |
|
'response_strategy': 'التفاوض على تخفيض الغرامة أو تقسيمها حسب مراحل المشروع مع وضع خطة محكمة للجدول الزمني', |
|
'status': 'نشط', |
|
'risk_score': 9, |
|
'created_at': datetime.now().strftime('%Y-%m-%d') |
|
}, |
|
{ |
|
'risk_code': 'RD02', |
|
'description': 'يحق للمالك إيقاف المشروع لمدة تصل إلى 90 يوم دون تعويض', |
|
'category': 'مخاطر تعاقدية', |
|
'impact': 'عالي', |
|
'probability': 'محتمل', |
|
'response_strategy': 'طلب إضافة بند للتعويض عن التكاليف الإضافية الناتجة عن الإيقاف لفترات طويلة', |
|
'status': 'نشط', |
|
'risk_score': 6, |
|
'created_at': datetime.now().strftime('%Y-%m-%d') |
|
}, |
|
{ |
|
'risk_code': 'RD03', |
|
'description': 'تحمل المقاول مسؤولية استخراج جميع التصاريح الحكومية', |
|
'category': 'مخاطر تنظيمية', |
|
'impact': 'متوسط', |
|
'probability': 'مؤكد', |
|
'response_strategy': 'حصر جميع التصاريح المطلوبة والبدء في إجراءات استخراجها مبكراً مع تخصيص فريق لمتابعتها', |
|
'status': 'نشط', |
|
'risk_score': 6, |
|
'created_at': datetime.now().strftime('%Y-%m-%d') |
|
}, |
|
{ |
|
'risk_code': 'RD04', |
|
'description': 'شروط الدفعة المقدمة مقيدة بضمان بنكي بقيمة 120% من قيمة الدفعة', |
|
'category': 'مخاطر مالية', |
|
'impact': 'متوسط', |
|
'probability': 'مؤكد', |
|
'response_strategy': 'التفاوض على خفض نسبة الضمان البنكي أو تقديم ضمان شركة بدلاً من الضمان البنكي', |
|
'status': 'نشط', |
|
'risk_score': 6, |
|
'created_at': datetime.now().strftime('%Y-%m-%d') |
|
} |
|
] |
|
|
|
return document_risks |
|
|
|
def _show_risk_summary(self, risks): |
|
"""عرض ملخص المخاطر""" |
|
|
|
st.markdown("#### ملخص المخاطر") |
|
|
|
|
|
total_risks = len(risks) |
|
risk_levels = { |
|
'عالية': len([r for r in risks if r.get('risk_score', 0) >= 6]), |
|
'متوسطة': len([r for r in risks if 3 <= r.get('risk_score', 0) < 6]), |
|
'منخفضة': len([r for r in risks if r.get('risk_score', 0) < 3]) |
|
} |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
st.metric("إجمالي المخاطر", total_risks) |
|
|
|
with col2: |
|
st.metric("المخاطر العالية", risk_levels['عالية'], delta=f"{risk_levels['عالية']/total_risks*100:.1f}%", delta_color="inverse") |
|
|
|
with col3: |
|
st.metric("المخاطر المتوسطة", risk_levels['متوسطة'], delta=f"{risk_levels['متوسطة']/total_risks*100:.1f}%", delta_color="off") |
|
|
|
with col4: |
|
st.metric("المخاطر المنخفضة", risk_levels['منخفضة'], delta=f"{risk_levels['منخفضة']/total_risks*100:.1f}%", delta_color="normal") |
|
|
|
|
|
risk_level_df = pd.DataFrame({ |
|
'مستوى المخاطرة': list(risk_levels.keys()), |
|
'عدد المخاطر': list(risk_levels.values()) |
|
}) |
|
|
|
fig = px.bar( |
|
risk_level_df, |
|
x='مستوى المخاطرة', |
|
y='عدد المخاطر', |
|
color='مستوى المخاطرة', |
|
color_discrete_map={ |
|
'عالية': 'red', |
|
'متوسطة': 'orange', |
|
'منخفضة': 'green' |
|
}, |
|
title='توزيع المخاطر حسب المستوى' |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### أعلى 5 مخاطر") |
|
|
|
|
|
sorted_risks = sorted(risks, key=lambda x: x.get('risk_score', 0), reverse=True) |
|
top_risks = sorted_risks[:5] |
|
|
|
|
|
if top_risks: |
|
top_risks_data = [] |
|
|
|
for r in top_risks: |
|
top_risks_data.append({ |
|
'رمز المخاطرة': r.get('risk_code', ''), |
|
'وصف المخاطرة': r.get('description', ''), |
|
'الفئة': r.get('category', ''), |
|
'التأثير': r.get('impact', ''), |
|
'الاحتمالية': r.get('probability', ''), |
|
'درجة المخاطرة': r.get('risk_score', 0) |
|
}) |
|
|
|
top_risks_df = pd.DataFrame(top_risks_data) |
|
st.dataframe(top_risks_df, use_container_width=True, hide_index=True) |
|
|