""" تطبيق وحدة تحليل المخاطر """ 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("

وحدة تحليل المخاطر

", 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: # تحويل المخاطر إلى DataFrame 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': 'تاريخ الإنشاء' } # إعداد DataFrame للعرض 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 # إنشاء ضبط مصفوفة المخاطر (3×3) impact_values = {"منخفض": 1, "متوسط": 2, "عالي": 3} probability_values = {"غير محتمل": 1, "محتمل": 2, "مؤكد": 3} # إنشاء DataFrame لتمثيل مصفوفة المخاطر 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 }) # تحويل إلى DataFrame matrix_df = pd.DataFrame(matrix_data) # رسم مصفوفة المخاطر باستخدام Plotly fig = go.Figure() for index, row in matrix_df.iterrows(): # إنشاء نص الخلية if row['عدد_المخاطر'] > 0: cell_text = f"{', '.join(row['المخاطر'])}
({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 # إنشاء DataFrame 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) # عرض أعلى 5 مخاطر من حيث درجة المخاطرة st.markdown("#### أعلى 5 مخاطر") # ترتيب المخاطر حسب درجة المخاطرة sorted_risks = sorted(risks, key=lambda x: x.get('risk_score', 0), reverse=True) top_risks = sorted_risks[:5] # إنشاء DataFrame للعرض 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)