EGYADMIN commited on
Commit
e305028
·
verified ·
1 Parent(s): 4d05599

Upload 74 files

Browse files
README.md CHANGED
@@ -1,206 +1,110 @@
1
- ---
2
- license: mit
3
- title: WAHBI-AI
4
- sdk: streamlit
5
- emoji: 🚀
6
- colorFrom: green
7
- colorTo: green
8
- short_description: نظام إدارة المناقصات ودراسة المشاريع v2
9
- sdk_version: 1.44.0
10
- ---
11
- # نظام إدارة المناقصات
12
-
13
- ## نظرة عامة
14
- نظام إدارة المناقصات هو تطبيق متكامل مصمم لمساعدة الشركات والمؤسسات في إدارة عمليات المناقصات والعطاءات بكفاءة عالية. يوفر النظام مجموعة من الوحدات المتكاملة التي تغطي جميع جوانب إدارة المناقصات، بدءاً من تحليل المستندات وحتى إعداد التقارير النهائية.
15
 
16
- ## الوحدات الرئيسية
 
 
 
17
 
18
- ### 1. تحليل المستندات
19
- تمكن هذه الوحدة من استخراج المعلومات الرئيسية من مستندات المناقصات بشكل آلي، مما يوفر الوقت والجهد ويقلل من الأخطاء البشرية.
20
 
21
- ### 2. التسعير
22
- توفر هذه الوحدة أدوات متقدمة لإعداد جداول الكميات وتسعير بنود المناقصة، مع إمكانية تحليل الأسعار ومقارنتها بالأسعار المرجعية.
23
 
24
- ### 3. الموارد
25
- تساعد في إدارة الموارد البشرية والمعدات والمواد اللازمة لتنفيذ المشاريع، مع إمكانية تخصيص الموارد للمشاريع المختلفة.
 
 
26
 
27
- ### 4. تحليل المخاطر
28
- تمكن من تحديد وتقييم المخاطر المحتملة في المناقصات والمشاريع، مع اقتراح إجراءات للتخفيف من هذه المخاطر.
 
 
29
 
30
- ### 5. إدارة المشاريع
31
- توفر أدوات لمتابعة تنفيذ المشاريع وإدارة المهام والجداول الزمنية، مع إمكانية تتبع التقدم في العمل.
 
 
32
 
33
- ### 6. تحليل البيانات
34
- تمكن من تحليل بيانات المناقصات واستخراج المؤشرات والاتجاهات، مما يساعد في اتخاذ القرارات المستقبلية.
 
 
35
 
36
- ### 7. التقارير
37
- توفر إمكانية إعداد التقارير الفنية والمالية المختلفة، مع خيارات متعددة للتصدير والمشاركة.
38
 
39
- ### 8. الذكاء الاصطناعي
40
- تستخدم تقنيات الذكاء الاصطناعي في تحليل المناقصات واقتراح الحلول المناسبة، مع إمكانية التعلم من البيانات السابقة.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- ## متطلبات النظام
43
 
44
- ### متطلبات البرمجيات
45
- - Python 3.8 أو أحدث
46
- - Streamlit 1.32.0 أو أحدث
47
- - المكتبات المذكورة في ملف requirements.txt
48
 
49
- ### متطلبات الأجهزة
50
  - معالج: Intel Core i5 أو ما يعادله
51
- - ذاكرة: 8 GB RAM أو أكثر
52
- - مساحة تخزين: 1 GB من المساحة الحرة
53
- - اتصال بالإنترنت (لبعض الوظائف)
54
-
55
- ## التثبيت والإعداد
56
-
57
- ### 1. تثبيت Python
58
- تأكد من تثبيت Python 3.8 أو أحدث على جهازك. يمكنك تنزيله من [الموقع الرسمي](https://www.python.org/downloads/).
59
-
60
- ### 2. تنزيل النظام
61
- قم بتنزيل أو استنساخ مستودع النظام:
62
- ```
63
- git clone https://github.com/your-username/tender-management-system.git
64
- cd tender-management-system
65
- ```
66
 
67
- ### 3. إنشاء بيئة افتراضية (اختياري ولكن موصى به)
68
- ```
69
- python -m venv venv
70
- ```
71
 
72
- ### 4. تفعيل البيئة الافتراضية
73
- - في Windows:
74
- ```
75
- venv\Scripts\activate
76
- ```
77
- - في macOS/Linux:
78
- ```
79
- source venv/bin/activate
80
- ```
81
-
82
- ### 5. تثبيت المتطلبات
83
- ```
84
  pip install -r requirements.txt
85
  ```
86
 
87
- ### 6. تشغيل التطبيق
88
- ```
89
  streamlit run app.py
90
  ```
91
 
92
- ## استخدام النظام
93
-
94
- ### تسجيل الدخول
95
- - اسم المستخدم الافتراضي: admin
96
- - كلمة المرور الافتراضية: admin
97
-
98
- ### التنقل بين الوحدات
99
- يمكنك التنقل بين وحدات النظام المختلفة باستخدام القائمة الجانبية.
100
-
101
- ### تغيير الإعدادات
102
- يمكنك تغيير إعدادات النظام مثل اللغة والسمة من خلال قسم الإعدادات في القائمة الجانبية.
103
-
104
- ## وحدة التسعير
105
-
106
- ### الميزات الرئيسية
107
- - إنشاء وتحرير جداول الكميات
108
- - تسعير البنود باستخدام قاعدة بيانات الأسعار المرجعية
109
- - تحليل التكاليف وحساب الهوامش
110
- - مقارنة الأسعار مع المنافسين
111
- - تصدير جداول الكميات بتنسيقات مختلفة
112
-
113
- ### كيفية الاستخدام
114
- 1. انتقل إلى وحدة التسعير من القائمة الجانبية
115
- 2. اختر المناقصة التي تريد العمل عليها أو أنشئ مناقصة جديدة
116
- 3. قم بإضافة بنود جدول الكميات أو استيرادها من ملف خارجي
117
- 4. قم بتسعير البنود يدوياً أو باستخدام الأسعار المرجعية
118
- 5. قم بتحليل التكاليف وحساب الهوامش
119
- 6. قم بتصدير جدول الكميات النهائي
120
-
121
- ## وحدة الذكاء الاصطناعي
122
-
123
- ### الميزات الرئيسية
124
- - تحليل مستندات المناقصات باستخدام معالجة اللغة الطبيعية
125
- - اقتراح الأسعار المناسبة بناءً على البيانات السابقة
126
- - تقييم فرص الفوز بالمناقصات
127
- - تحليل المنافسين واستراتيجياتهم
128
- - توليد تقارير وملخصات ذكية
129
-
130
- ### كيفية الاستخدام
131
- 1. انتقل إلى وحدة الذكاء الاصطناعي من القائمة الجانبية
132
- 2. قم بتحميل مستندات المناقصة للتحليل
133
- 3. اختر نوع التحليل الذي تريد إجراءه
134
- 4. راجع النتائج والتوصيات
135
- 5. قم بتصدير التقارير والملخصات
136
-
137
- ## وحدة تحليل البيانات
138
-
139
- ### الميزات الرئيسية
140
- - عرض لوحات معلومات تفاعلية للمناقصات
141
- - تحليل اتجاهات الأسعار والتكاليف
142
- - تحليل أداء المنافسين
143
- - تحليل نسب النجاح والفشل
144
- - تصدير التقارير والرسوم البيانية
145
-
146
- ### كيفية الاستخدام
147
- 1. انتقل إلى وحدة تحليل البيانات من القائمة الجانبية
148
- 2. اختر نوع التحليل الذي تريد إجراءه
149
- 3. حدد نطاق البيانات والفترة الزمنية
150
- 4. قم بتخصيص الرسوم البيانية والتقارير
151
- 5. قم بتصدير النتائج بالتنسيق المطلوب
152
-
153
- ## وحدة الموارد
154
-
155
- ### الميزات الرئيسية
156
- - إدارة الموارد البشرية والمعدات والمواد
157
- - تخصيص الموارد للمشاريع المختلفة
158
- - تتبع توافر الموارد واستخدامها
159
- - تخطيط الاحتياجات المستقبلية من الموارد
160
- - تحليل تكاليف الموارد وكفاءتها
161
-
162
- ### كيفية الاستخدام
163
- 1. انتقل إلى وحدة الموارد من القائمة الجانبية
164
- 2. استعرض الموارد المتاحة (الموظفين، المعدات، المواد)
165
- 3. قم بتخصيص الموارد للمشاريع
166
- 4. قم بتحليل استخدام الموارد وتكاليفها
167
- 5. قم بتخطيط الاحتياجات المستقبلية من الموارد
168
-
169
- ## استكشاف الأخطاء وإصلاحها
170
-
171
- ### مشاكل تسجيل الدخول
172
- - تأكد من إدخال اسم المستخدم وكلمة المرور بشكل صحيح
173
- - تأكد من تفعيل لوحة المفاتيح باللغة الصحيحة
174
- - إذا نسيت كلمة المرور، اتصل بمسؤول النظام
175
-
176
- ### مشاكل في عرض البيانات
177
- - تأكد من اتصالك بالإنترنت إذا كانت البيانات تُجلب من مصدر خارجي
178
- - قم بتحديث الصفحة أو إعادة تشغيل التطبيق
179
- - تأكد من وجود البيانات المطلوبة في قاعدة البيانات
180
-
181
- ### مشاكل في تشغيل التطبيق
182
- - تأكد من تثبيت جميع المتطلبات بشكل صحيح
183
- - تأكد من استخدام إصدار Python المناسب
184
- - تحقق من وجود جميع الملفات والمجلدات المطلوبة
185
-
186
- ## الدعم والتواصل
187
-
188
- للحصول على المساعدة أو الإبلاغ عن مشكلة، يرجى التواصل معنا عبر:
189
- - البريد الإلكتروني: [email protected]
190
- - الهاتف: +966 12 345 6789
191
- - الموقع الإلكتروني: www.tender-system.com/support
192
-
193
- ## الترخيص
194
-
195
- هذا النظام مرخص بموجب رخصة MIT. راجع ملف LICENSE للحصول على مزيد من المعلومات.
196
-
197
- ## شكر وتقدير
198
-
199
- نشكر جميع المساهمين في تطوير هذا النظام، ونخص بالذكر:
200
- - فريق تطوير البرمجيات
201
- - فريق اختبار الجودة
202
- - المستخدمين الذين قدموا ملاحظات قيمة
203
 
204
- ---
 
 
 
 
205
 
206
- © 2025 نظام إدارة المناقصات. جميع الحقوق محفوظة.
 
 
 
 
 
 
1
+ <div align="center">
2
+ <img src="https://www2.0zz0.com/2024/11/20/07/988856043.png" alt="شعار الشركة" width="200"/>
3
+ <h1>نظام دراسة المناقصات و التسعير و تحليل البيانات</h1>
4
+ <p>نظام متكامل لإدارة وتحليل المناقصات والمشاريع باستخدام الذكاء الاصطناعي</p>
 
 
 
 
 
 
 
 
 
 
5
 
6
+ ![إصدار](https://img.shields.io/badge/الإصدار-2.0.0-blue)
7
+ ![الترخيص](https://img.shields.io/badge/الترخيص-MIT-green)
8
+ ![حالة التطوير](https://img.shields.io/badge/حالة_التطوير-نشط-success)
9
+ </div>
10
 
11
+ ## 🚀 نظرة عامة
12
+ نظام متطور لإدارة وتحليل المناقصات والمشاريع، يجمع بين تقنيات الذكاء الاصطناعي وأدوات التحليل المتقدمة لمساعدة المؤسسات في اتخاذ قرارات أفضل وأكثر دقة.
13
 
14
+ ## المميزات الرئيسية
 
15
 
16
+ ### 🤖 المساعد الذكي
17
+ - تحليل مستندات المناقصات تلقائياً
18
+ - استخراج المعلومات المهمة
19
+ - تقديم توصيات ذكية
20
 
21
+ ### 📊 إدارة المشاريع
22
+ - متابعة حالة المشاريع
23
+ - إدارة الموارد والميزانيات
24
+ - تحليل المخاطر
25
 
26
+ ### 💰 نظام التسعير
27
+ - تسعير البنود بدقة
28
+ - تحليل الأسعار المنافسة
29
+ - حساب التكاليف والهوامش
30
 
31
+ ### 📈 تحليل البيانات
32
+ - تحليل أداء المشاريع
33
+ - تقارير تفصيلية
34
+ - رؤى وتوصيات
35
 
36
+ ## 📁 هيكل المشروع
 
37
 
38
+ ```
39
+ tender_system/
40
+ ├── app.py # نقطة الدخول الرئيسية للتطبيق
41
+ ├── config.py # إعدادات التطبيق
42
+ ├── requirements.txt # متطلبات المكتبات
43
+ ├── README.md # توثيق النظام
44
+ ├── assets/ # الأصول الثابتة
45
+ │ ├── images/ # الصور والشعارات
46
+ │ └── css/ # ملفات التنسيق
47
+ ├── modules/ # وحدات النظام
48
+ │ ├── ai_assistant/ # المساعد الذكي
49
+ │ ├── pricing/ # وحدة التسعير
50
+ │ ├── project_management/ # إدارة المشاريع
51
+ │ ├── risk_analysis/ # تحليل المخاطر
52
+ │ ├── document_analysis/ # تحليل المستندات
53
+ │ ├── scheduling/ # الجدول الزمني
54
+ │ ├── reports/ # التقارير
55
+ │ ├── maps/ # الخرائط والمواقع
56
+ │ ├── resources/ # إدارة الموارد
57
+ │ └── translation/ # الترجمة
58
+ ├── database/ # قاعدة البيانات
59
+ │ ├── models.py # نماذج البيانات
60
+ │ └── db_connector.py # موصل قاعدة البيانات
61
+ ├── styling/ # تنسيق الواجهة
62
+ │ ├── enhanced_ui.py # تحسينات الواجهة
63
+ │ └── theme.py # السمات والألوان
64
+ └── tests/ # اختبارات النظام
65
+ ├── test_app.py # اختبارات التطبيق
66
+ └── test_modules.py # اختبارات الوحدات
67
+ ```
68
 
69
+ ## 🛠️ المتطلبات التقنية
70
 
71
+ ### البرمجيات
72
+ - Python 3.8+
73
+ - Streamlit 1.32.0+
74
+ - المكتبات المذكورة في `requirements.txt`
75
 
76
+ ### الأجهزة
77
  - معالج: Intel Core i5 أو ما يعادله
78
+ - ذاكرة: 8GB RAM أو أكثر
79
+ - مساحة تخزين: 1GB متاحة
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ ## البدء السريع
 
 
 
82
 
83
+ 1. تثبيت المتطلبات:
84
+ ```bash
 
 
 
 
 
 
 
 
 
 
85
  pip install -r requirements.txt
86
  ```
87
 
88
+ 2. تشغيل التطبيق:
89
+ ```bash
90
  streamlit run app.py
91
  ```
92
 
93
+ ## 📖 الوثائق
94
+ للمزيد من المعلومات والتفاصيل، يرجى الرجوع إلى:
95
+ - [دليل المستخدم](./docs/user_guide.md)
96
+ - [الوثائق التقنية](./docs/technical_docs.md)
97
+ - [دليل المطور](./docs/developer_guide.md)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
+ ## 🤝 المساهمة
100
+ نرحب بمساهماتكم! يرجى الاطلاع على [دليل المساهمة](CONTRIBUTING.md) للمزيد من المعلومات.
101
+
102
+ ## 📄 الترخيص
103
+ هذا المشروع مرخص تحت [رخصة MIT](LICENSE).
104
 
105
+ ## 📞 الدعم والتواصل
106
+ - البريد الإلكتروني: [email protected]
107
+ - الموقع: www.wahbi-ai.com
108
+
109
+ ---
110
+ © 2024 نظام دراسة المناقصات. جميع الحقوق محفوظة.
app.py CHANGED
@@ -20,6 +20,7 @@ from config_manager import ConfigManager
20
 
21
  # استيراد الوحدات
22
  from modules.document_analysis.document_app import DocumentAnalysisApp
 
23
  from modules.pricing.pricing_app import PricingApp
24
  from modules.resources.resources_app import ResourcesApp
25
  from modules.risk_analysis.risk_analyzer import RiskAnalysisApp
@@ -51,7 +52,7 @@ config_manager.set_page_config_if_needed(
51
  )
52
 
53
  # تطبيق التنسيق العام
54
- ui_enhancer = UIEnhancer(page_title="نظام تحليل المناقصات", page_icon="📊")
55
  ui_enhancer.apply_global_styles()
56
 
57
  # تطبيق الأنماط الموحدة المخصصة للنظام
@@ -61,19 +62,18 @@ with open("pricing_system/static/css/unified_style.css") as f:
61
  # إنشاء قائمة العناصر
62
  menu_items = [
63
  {"name": "لوحة المعلومات", "icon": "house"},
 
 
 
 
64
  {"name": "المناقصات والعقود", "icon": "file-text"},
65
  {"name": "تحليل المستندات", "icon": "file-earmark-text"},
66
- {"name": "نظام التسعير", "icon": "calculator"},
67
- {"name": "حاسبة تكاليف البناء", "icon": "building"},
68
  {"name": "الموارد والتكاليف", "icon": "people"},
69
  {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
70
- {"name": "إدارة المشاريع", "icon": "kanban"},
71
  {"name": "الخرائط والمواقع", "icon": "geo-alt"},
72
- {"name": "الجدول الزمني", "icon": "calendar3"},
73
  {"name": "الإشعارات", "icon": "bell"},
74
  {"name": "مقارنة المستندات", "icon": "files"},
75
  {"name": "الترجمة", "icon": "translate"},
76
- {"name": "المساعد الذكي", "icon": "robot"},
77
  {"name": "تحليل البيانات", "icon": "bar-chart"},
78
  {"name": "الإعدادات", "icon": "gear"}
79
  ]
@@ -83,7 +83,10 @@ selected = ui_enhancer.create_sidebar(menu_items)
83
 
84
  # تحديد الوحدة المطلوبة بناءً على اختيار المستخدم
85
  if selected == "لوحة المعلومات":
86
- ui_enhancer.create_header("لوحة المعلومات", "نظرة عامة على المناقصات والمشاريع")
 
 
 
87
 
88
  col1, col2, col3 = st.columns(3)
89
 
@@ -113,6 +116,10 @@ if selected == "لوحة المعلومات":
113
  st.button("عرض", key=f"view_{notification['title']}")
114
  st.divider()
115
 
 
 
 
 
116
  elif selected == "تحليل المستندات":
117
  document_app = DocumentAnalysisApp()
118
  document_app.run()
@@ -189,7 +196,7 @@ elif selected == "الترجمة":
189
 
190
  elif selected == "المساعد الذكي":
191
  ai_app = AIAssistantApp()
192
- ai_app.run()
193
 
194
  elif selected == "تحليل البيانات":
195
  data_analysis_app = DataAnalysisApp()
 
20
 
21
  # استيراد الوحدات
22
  from modules.document_analysis.document_app import DocumentAnalysisApp
23
+ from modules.data_analysis.data_analysis_app import DataAnalysisApp
24
  from modules.pricing.pricing_app import PricingApp
25
  from modules.resources.resources_app import ResourcesApp
26
  from modules.risk_analysis.risk_analyzer import RiskAnalysisApp
 
52
  )
53
 
54
  # تطبيق التنسيق العام
55
+ ui_enhancer = UIEnhancer(page_title="نظام دراسة المناقصات و التسعير و تحليل البيانات", page_icon="📊") #Modified
56
  ui_enhancer.apply_global_styles()
57
 
58
  # تطبيق الأنماط الموحدة المخصصة للنظام
 
62
  # إنشاء قائمة العناصر
63
  menu_items = [
64
  {"name": "لوحة المعلومات", "icon": "house"},
65
+ {"name": "المساعد الذكي", "icon": "robot"},
66
+ {"name": "إدارة المشاريع", "icon": "kanban"},
67
+ {"name": "نظام التسعير", "icon": "calculator"},
68
+ {"name": "الجدول الزمني", "icon": "calendar3"},
69
  {"name": "المناقصات والعقود", "icon": "file-text"},
70
  {"name": "تحليل المستندات", "icon": "file-earmark-text"},
 
 
71
  {"name": "الموارد والتكاليف", "icon": "people"},
72
  {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
 
73
  {"name": "الخرائط والمواقع", "icon": "geo-alt"},
 
74
  {"name": "الإشعارات", "icon": "bell"},
75
  {"name": "مقارنة المستندات", "icon": "files"},
76
  {"name": "الترجمة", "icon": "translate"},
 
77
  {"name": "تحليل البيانات", "icon": "bar-chart"},
78
  {"name": "الإعدادات", "icon": "gear"}
79
  ]
 
83
 
84
  # تحديد الوحدة المطلوبة بناءً على اختيار المستخدم
85
  if selected == "لوحة المعلومات":
86
+ st.markdown('<div class="header-container">', unsafe_allow_html=True)
87
+ st.markdown('<h1 class="header-title">لوحة المعلومات</h1>', unsafe_allow_html=True)
88
+ st.markdown('<p class="header-subtitle">نظرة عامة على المناقصات والمشاريع</p>', unsafe_allow_html=True)
89
+ st.markdown('</div>', unsafe_allow_html=True)
90
 
91
  col1, col2, col3 = st.columns(3)
92
 
 
116
  st.button("عرض", key=f"view_{notification['title']}")
117
  st.divider()
118
 
119
+ elif selected == "المناقصات والعقود":
120
+ st.markdown("### منصة اعتماد - المناقصات المتاحة")
121
+ st.markdown(f'<iframe src="https://tenders.etimad.sa/Tender/AllTendersForVisitor?PageNumber=1" width="100%" height="800" frameborder="0"></iframe>', unsafe_allow_html=True)
122
+
123
  elif selected == "تحليل المستندات":
124
  document_app = DocumentAnalysisApp()
125
  document_app.run()
 
196
 
197
  elif selected == "المساعد الذكي":
198
  ai_app = AIAssistantApp()
199
+ ai_app.render()
200
 
201
  elif selected == "تحليل البيانات":
202
  data_analysis_app = DataAnalysisApp()
data/exports/boq_20250404_004644.xlsx ADDED
Binary file (5.26 kB). View file
 
modules/ai_assistant/ai_app.py CHANGED
The diff for this file is too large to render. See raw diff
 
modules/ai_assistant/bim_analyzer.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import streamlit as st
3
+ import numpy as np
4
+ import pandas as pd
5
+ from datetime import datetime
6
+
7
+ class BIMAnalyzer:
8
+ """محلل نماذج BIM المتقدم"""
9
+
10
+ def __init__(self):
11
+ self.supported_formats = ['.ifc', '.rvt', '.nwd', '.dwg']
12
+
13
+ def analyze_bim_model(self, file):
14
+ """تحليل نموذج BIM"""
15
+ try:
16
+ # تحليل المكونات
17
+ components = self._analyze_components(file)
18
+ # تحليل التكاليف
19
+ costs = self._analyze_costs(components)
20
+ # تحليل التعارضات
21
+ clashes = self._analyze_clashes(file)
22
+
23
+ return {
24
+ 'components': components,
25
+ 'costs': costs,
26
+ 'clashes': clashes,
27
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
28
+ }
29
+ except Exception as e:
30
+ return {'error': str(e)}
31
+
32
+ def _analyze_components(self, file):
33
+ """تحليل مكونات النموذج"""
34
+ return {
35
+ 'structural': {
36
+ 'columns': {'count': 120, 'volume': 450.5},
37
+ 'beams': {'count': 350, 'volume': 680.2},
38
+ 'slabs': {'count': 45, 'volume': 1200.8}
39
+ },
40
+ 'architectural': {
41
+ 'walls': {'count': 280, 'area': 3500.6},
42
+ 'windows': {'count': 150, 'area': 450.3},
43
+ 'doors': {'count': 95, 'area': 220.5}
44
+ },
45
+ 'mep': {
46
+ 'hvac': {'length': 2800.5, 'units': 35},
47
+ 'plumbing': {'length': 1500.2, 'fixtures': 180},
48
+ 'electrical': {'length': 3500.8, 'fixtures': 420}
49
+ }
50
+ }
51
+
52
+ def _analyze_costs(self, components):
53
+ """تحليل التكاليف بناءً على المكونات"""
54
+ return {
55
+ 'structural': 2500000,
56
+ 'architectural': 1800000,
57
+ 'mep': 1200000,
58
+ 'total': 5500000
59
+ }
60
+
61
+ def _analyze_clashes(self, file):
62
+ """تحليل التعارضات في النموذج"""
63
+ return {
64
+ 'critical': 5,
65
+ 'major': 12,
66
+ 'minor': 28,
67
+ 'locations': [
68
+ {'level': 'Ground Floor', 'count': 15},
69
+ {'level': 'First Floor', 'count': 18},
70
+ {'level': 'Second Floor', 'count': 12}
71
+ ]
72
+ }
73
+
74
+ def render_analysis(self, analysis_results):
75
+ """عرض نتائج التحليل في واجهة المستخدم"""
76
+ st.header("تحليل نموذج BIM")
77
+
78
+ # عرض ملخص المكونات
79
+ st.subheader("ملخص المكونات")
80
+ components = analysis_results['components']
81
+
82
+ col1, col2, col3 = st.columns(3)
83
+
84
+ with col1:
85
+ st.metric("عدد الأعمدة", components['structural']['columns']['count'])
86
+ with col2:
87
+ st.metric("عدد الجسور", components['structural']['beams']['count'])
88
+ with col3:
89
+ st.metric("عدد البلاطات", components['structural']['slabs']['count'])
90
+
91
+ # عرض التكاليف
92
+ st.subheader("تحليل التكاليف")
93
+ costs = analysis_results['costs']
94
+
95
+ cost_data = pd.DataFrame({
96
+ 'القسم': ['الهيكل الإنشائي', 'المعماري', 'الكهروميكانيك'],
97
+ 'التكلفة': [costs['structural'], costs['architectural'], costs['mep']]
98
+ })
99
+
100
+ st.bar_chart(cost_data.set_index('القسم'))
101
+
102
+ # عرض التعارضات
103
+ st.subheader("تحليل التعارضات")
104
+ clashes = analysis_results['clashes']
105
+
106
+ col1, col2, col3 = st.columns(3)
107
+
108
+ with col1:
109
+ st.metric("تعارضات حرجة", clashes['critical'], delta="يجب المعالجة فوراً")
110
+ with col2:
111
+ st.metric("تعارضات رئيسية", clashes['major'])
112
+ with col3:
113
+ st.metric("تعارضات ثانوية", clashes['minor'])
modules/ai_assistant/cad_bim_analyzer.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ محلل الرسومات الهندسية وملفات BIM المتقدم
4
+ """
5
+
6
+ import ezdxf
7
+ import cv2
8
+ import numpy as np
9
+ from PIL import Image
10
+ import pytesseract
11
+ from shapely.geometry import Point, Polygon
12
+ import json
13
+ import os
14
+ import logging
15
+ from datetime import datetime
16
+
17
+ class CADBIMAnalyzer:
18
+ """محلل الرسومات الهندسية وملفات BIM"""
19
+
20
+ def __init__(self, claude_client=None):
21
+ """تهيئة المحلل"""
22
+ self.claude_client = claude_client
23
+ self.analysis_results = {}
24
+
25
+ def analyze_drawing(self, file_path):
26
+ """تحليل الرسم الهندسي"""
27
+ try:
28
+ file_extension = os.path.splitext(file_path)[1].lower()
29
+
30
+ if file_extension in ['.dwg', '.dxf']:
31
+ return self._analyze_cad_file(file_path)
32
+ elif file_extension == '.ifc':
33
+ return self._analyze_bim_file(file_path)
34
+ elif file_extension in ['.pdf', '.jpg', '.jpeg', '.png']:
35
+ return self._analyze_image_file(file_path)
36
+ else:
37
+ raise ValueError(f"نوع الملف غير مدعوم: {file_extension}")
38
+
39
+ except Exception as e:
40
+ logging.error(f"خطأ في تحليل الرسم: {str(e)}")
41
+ raise
42
+
43
+ def _analyze_cad_file(self, file_path):
44
+ """تحليل ملف CAD"""
45
+ doc = ezdxf.readfile(file_path)
46
+ msp = doc.modelspace()
47
+
48
+ # تحليل العناصر
49
+ elements = {
50
+ 'lines': len(msp.query('LINE')),
51
+ 'circles': len(msp.query('CIRCLE')),
52
+ 'arcs': len(msp.query('ARC')),
53
+ 'polylines': len(msp.query('LWPOLYLINE')),
54
+ 'dimensions': len(msp.query('DIMENSION')),
55
+ 'text': len(msp.query('TEXT')),
56
+ 'blocks': len(doc.blocks)
57
+ }
58
+
59
+ # تحليل الطبقات
60
+ layers = {layer.dxf.name: {
61
+ 'color': layer.dxf.color,
62
+ 'linetype': layer.dxf.linetype,
63
+ 'is_on': layer.dxf.on,
64
+ 'is_frozen': layer.dxf.frozen
65
+ } for layer in doc.layers}
66
+
67
+ # استخراج الأبعاد
68
+ extents = msp.get_extents()
69
+ if extents:
70
+ dimensions = {
71
+ 'width': abs(extents.size.x),
72
+ 'height': abs(extents.size.y),
73
+ 'area': abs(extents.size.x * extents.size.y)
74
+ }
75
+ else:
76
+ dimensions = {'width': 0, 'height': 0, 'area': 0}
77
+
78
+ # تحليل متقدم باستخدام Claude
79
+ if self.claude_client:
80
+ drawing_info = {
81
+ 'elements': elements,
82
+ 'layers': layers,
83
+ 'dimensions': dimensions
84
+ }
85
+
86
+ analysis_prompt = f"""
87
+ قم بتحليل معلومات الرسم الهندسي التالية وتقديم:
88
+ 1. وصف عام للرسم
89
+ 2. تحليل العناصر والطبقات
90
+ 3. تحديد نوع المشروع والتخصص
91
+ 4. توصيات فنية
92
+ 5. ملاحظات هامة
93
+
94
+ معلومات الرسم:
95
+ {json.dumps(drawing_info, indent=2, ensure_ascii=False)}
96
+ """
97
+
98
+ response = self.claude_client.messages.create(
99
+ model="claude-3-sonnet-20240229",
100
+ max_tokens=4000,
101
+ temperature=0.7,
102
+ messages=[{"role": "user", "content": analysis_prompt}]
103
+ )
104
+
105
+ ai_analysis = response.content[0].text
106
+ else:
107
+ ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
108
+
109
+ return {
110
+ 'file_info': {
111
+ 'path': file_path,
112
+ 'type': os.path.splitext(file_path)[1],
113
+ 'size': os.path.getsize(file_path),
114
+ 'modified': datetime.fromtimestamp(os.path.getmtime(file_path)).strftime('%Y-%m-%d %H:%M:%S')
115
+ },
116
+ 'elements': elements,
117
+ 'layers': layers,
118
+ 'dimensions': dimensions,
119
+ 'ai_analysis': ai_analysis
120
+ }
121
+
122
+ def _analyze_bim_file(self, file_path):
123
+ """تحليل ملف BIM"""
124
+ try:
125
+ # تحليل ملف IFC
126
+ import ifcopenshell
127
+ ifc_file = ifcopenshell.open(file_path)
128
+
129
+ # تحليل العناصر
130
+ elements = {
131
+ 'structural': self._analyze_structural_elements(ifc_file),
132
+ 'architectural': self._analyze_architectural_elements(ifc_file),
133
+ 'mep': self._analyze_mep_elements(ifc_file)
134
+ }
135
+
136
+ # تحليل الأبعاد
137
+ dimensions = self._analyze_dimensions(ifc_file)
138
+
139
+ # تحليل متقدم باستخدام Claude
140
+ if self.claude_client:
141
+ building_info = {
142
+ 'elements': elements,
143
+ 'dimensions': dimensions,
144
+ 'properties': self._extract_properties(ifc_file)
145
+ }
146
+
147
+ analysis_prompt = f"""
148
+ قم بتحليل معلومات نموذج BIM التالية وتقديم:
149
+ 1. تحليل شامل للنموذج
150
+ 2. تقييم جودة النمذجة
151
+ 3. توصيات للتحسين
152
+ 4. تقدير التكاليف
153
+ 5. تحليل الاستدامة
154
+
155
+ معلومات النموذج:
156
+ {json.dumps(building_info, indent=2, ensure_ascii=False)}
157
+ """
158
+
159
+ response = self.claude_client.messages.create(
160
+ model="claude-3-sonnet-20240229",
161
+ max_tokens=4000,
162
+ temperature=0.7,
163
+ messages=[{"role": "user", "content": analysis_prompt}]
164
+ )
165
+
166
+ ai_analysis = response.content[0].text
167
+ else:
168
+ ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
169
+
170
+ return {
171
+ 'file_info': {
172
+ 'path': file_path,
173
+ 'type': 'IFC',
174
+ 'schema': ifc_file.schema,
175
+ 'size': os.path.getsize(file_path)
176
+ },
177
+ 'elements': elements,
178
+ 'dimensions': dimensions,
179
+ 'analysis': ai_analysis
180
+ }
181
+
182
+ except Exception as e:
183
+ logging.error(f"خطأ في تحليل ملف BIM: {str(e)}")
184
+ raise
185
+
186
+ def _analyze_image_file(self, file_path):
187
+ """تحليل ملف صورة"""
188
+ # قراءة الصورة
189
+ if file_path.lower().endswith('.pdf'):
190
+ # TODO: Add PDF to image conversion
191
+ pass
192
+ else:
193
+ image = cv2.imread(file_path)
194
+
195
+ # استخراج النص
196
+ text = pytesseract.image_to_string(image, lang='ara+eng')
197
+
198
+ # تحليل الخطوط والأشكال
199
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
200
+ edges = cv2.Canny(gray, 50, 150)
201
+ lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100)
202
+
203
+ # تحليل متقدم باستخدام Claude
204
+ if self.claude_client:
205
+ image_info = {
206
+ 'dimensions': image.shape,
207
+ 'extracted_text': text,
208
+ 'detected_lines': len(lines) if lines is not None else 0
209
+ }
210
+
211
+ analysis_prompt = f"""
212
+ قم بتحليل معلومات الصورة الهندسية التالية وتقديم:
213
+ 1. وصف عام للصورة
214
+ 2. تحليل النص المستخرج
215
+ 3. تحليل الخطوط والأشكال
216
+ 4. تحديد نوع المشروع والتخصص
217
+ 5. توصيات وملاحظات
218
+
219
+ معلومات الصورة:
220
+ {json.dumps(image_info, indent=2, ensure_ascii=False)}
221
+ """
222
+
223
+ response = self.claude_client.messages.create(
224
+ model="claude-3-sonnet-20240229",
225
+ max_tokens=4000,
226
+ temperature=0.7,
227
+ messages=[{"role": "user", "content": analysis_prompt}]
228
+ )
229
+
230
+ ai_analysis = response.content[0].text
231
+ else:
232
+ ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
233
+
234
+ return {
235
+ 'file_info': {
236
+ 'path': file_path,
237
+ 'type': os.path.splitext(file_path)[1],
238
+ 'size': os.path.getsize(file_path),
239
+ 'modified': datetime.fromtimestamp(os.path.getmtime(file_path)).strftime('%Y-%m-%d %H:%M:%S')
240
+ },
241
+ 'dimensions': {
242
+ 'width': image.shape[1],
243
+ 'height': image.shape[0],
244
+ 'channels': image.shape[2]
245
+ },
246
+ 'extracted_text': text,
247
+ 'analysis': {
248
+ 'detected_lines': len(lines) if lines is not None else 0
249
+ },
250
+ 'ai_analysis': ai_analysis
251
+ }
modules/ai_assistant/contract_analyzer.py CHANGED
The diff for this file is too large to render. See raw diff
 
modules/ai_assistant/document_analyzer.py CHANGED
@@ -40,468 +40,468 @@ except ImportError:
40
  logger.warning("لم يتم العثور على مكتبة pdf2image. لن يمكن تحويل ملفات PDF إلى صور.")
41
 
42
 
43
- class TextExtractor:
44
- """فئة استخراج النصوص من المستندات"""
45
-
46
- def __init__(self, config=None):
47
- """تهيئة مستخرج النصوص"""
48
- self.config = config or {}
49
-
50
- def extract_from_pdf(self, file_path):
51
- """استخراج النص من ملف PDF"""
52
- try:
53
- # محاكاة استخراج النص من PDF
54
- # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل PyPDF2 أو pdfplumber
55
- return f"تم استخراج النص من ملف PDF: {file_path}"
56
- except Exception as e:
57
- logger.error(f"خطأ في استخراج النص من PDF: {str(e)}")
58
- return f"حدث خطأ أثناء استخراج النص: {str(e)}"
59
-
60
- def extract_from_docx(self, file_path):
61
- """استخراج النص من ملف DOCX"""
62
- try:
63
- # محاكاة استخراج النص من DOCX
64
- # في التطبيق الحقيقي، يمكن استخدام مكتبة python-docx
65
- return f"تم استخراج النص من ملف DOCX: {file_path}"
66
- except Exception as e:
67
- logger.error(f"خطأ في استخراج النص من DOCX: {str(e)}")
68
- return f"حدث خطأ أثناء استخراج النص: {str(e)}"
69
-
70
- def extract_from_image(self, file_path):
71
- """استخراج النص من صورة باستخدام OCR"""
72
- try:
73
- # محاكاة استخراج النص من صورة
74
- # في التطبيق الحقيقي، يمكن استخدام مكتبة pytesseract
75
- return f"تم استخراج النص من صورة: {file_path}"
76
- except Exception as e:
77
- logger.error(f"خطأ في استخراج النص من صورة: {str(e)}")
78
- return f"حدث خطأ أثناء استخراج النص: {str(e)}"
79
-
80
- def extract(self, file_path):
81
- """استخراج النص من ملف بناءً على نوعه"""
82
- _, ext = os.path.splitext(file_path)
83
- ext = ext.lower()
84
-
85
- if ext == '.pdf':
86
- return self.extract_from_pdf(file_path)
87
- elif ext in ('.doc', '.docx'):
88
- return self.extract_from_docx(file_path)
89
- elif ext in ('.jpg', '.jpeg', '.png'):
90
- return self.extract_from_image(file_path)
91
- else:
92
- return "نوع ملف غير مدعوم"
93
-
94
-
95
- class ItemExtractor:
96
- """فئة استخراج العناصر من المستندات"""
97
-
98
- def __init__(self, config=None):
99
- """تهيئة مستخرج العناصر"""
100
- self.config = config or {}
101
-
102
- def extract_tables(self, document):
103
- """استخراج الجداول من المستند"""
104
- try:
105
- # محاكاة استخراج الجداول
106
- # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل camelot-py أو tabula-py
107
- return [
108
- {
109
- "عنوان": "جدول البنود والكميات",
110
- "بيانات": [
111
- {"البند": "أعمال الحفر", "الكمية": 1000, "الوحدة": "م³", "السعر": 50, "الإجمالي": 50000},
112
- {"البند": "أعمال الخرسانة", "الكمية": 500, "الوحدة": "م³", "السعر": 300, "الإجمالي": 150000},
113
- {"البند": "أعمال التشطيبات", "الكمية": 2000, "الوحدة": "م²", "السعر": 100, "الإجمالي": 200000}
114
- ]
115
- },
116
- {
117
- "عنوان": "جدول الجدول الزمني",
118
- "بيانات": [
119
- {"المرحلة": "التصميم", "المدة": "30 يوم", "تاريخ البدء": "2025-04-01", "تاريخ الانتهاء": "2025-04-30"},
120
- {"المرحلة": "الإنشاء", "المدة": "180 يوم", "تاريخ البدء": "2025-05-01", "تاريخ الانتهاء": "2025-10-31"},
121
- {"المرحلة": "التسليم", "المدة": "30 يوم", "تاريخ البدء": "2025-11-01", "تاريخ الانتهاء": "2025-11-30"}
122
- ]
123
- }
124
- ]
125
- except Exception as e:
126
- logger.error(f"خطأ في استخراج الجداول: {str(e)}")
127
- return []
128
-
129
- def extract_items(self, file_path):
130
- """استخراج البنود من المستند"""
131
- try:
132
- # محاكاة استخراج البنود
133
- return [
134
- {"بند": "أعمال الحفر والردم", "قيمة": 250000, "نسبة": "10%"},
135
- {"بند": "أعمال الخرسانة المسلحة", "قيمة": 750000, "نسبة": "30%"},
136
- {"بند": "أعمال التشطيبات", "قيمة": 500000, "نسبة": "20%"},
137
- {"بند": "أعمال الكهرباء", "قيمة": 350000, "نسبة": "14%"},
138
- {"بند": "أعمال السباكة", "قيمة": 300000, "نسبة": "12%"},
139
- {"بند": "أعمال التكييف", "قيمة": 350000, "نسبة": "14%"}
140
- ]
141
- except Exception as e:
142
- logger.error(f"خطأ في استخراج البنود: {str(e)}")
143
- return []
144
-
145
- def extract(self, file_path):
146
- """استخراج جميع العناصر من المستند"""
147
- return {
148
- "بنود": self.extract_items(file_path),
149
- "جداول": self.extract_tables(file_path)
150
- }
151
-
152
-
153
- class DocumentParser:
154
- """فئة تحليل المستندات"""
155
-
156
- def __init__(self, config=None):
157
- """تهيئة محلل المستندات"""
158
- self.config = config or {}
159
- self.text_extractor = TextExtractor(config)
160
- self.item_extractor = ItemExtractor(config)
161
-
162
- def parse_contract(self, file_path):
163
- """تحليل مستند عقد"""
164
- try:
165
- # محاكاة تحليل عقد
166
- return {
167
- "نوع المستند": "عقد",
168
- "معلومات العقد": {
169
- "رقم العقد": "CT-2025-001",
170
- "تاريخ العقد": "2025-03-15",
171
- "قيمة العقد": "2,500,000 ريال",
172
- "مدة العقد": "12 شهر",
173
- "تاريخ البدء": "2025-04-01",
174
- "تاريخ الانتهاء": "2026-03-31"
175
- },
176
- "أطراف العقد": {
177
- "الطرف الأول": "وزارة الإسكان",
178
- "الطرف الثاني": "شركة الإنشاءات المتطورة"
179
- },
180
- "بنود العقد": [
181
- "يلتزم الطرف الثاني بتنفيذ المشروع وفقاً للمواصفات والشروط المرفقة",
182
- "مدة تنفيذ المشروع 12 شهراً من تاريخ استلام الموقع",
183
- "قيمة العقد 2,500,000 ريال شاملة جميع الضرائب والرسوم",
184
- "يتم الدفع على دفعات شهرية حسب نسبة الإنجاز",
185
- "غرامة التأخير 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%"
186
- ],
187
- "المرفقات": [
188
- "جدول الكميات",
189
- "المواصفات الفنية",
190
- "الجدول الزمني",
191
- "الضمانات والتأمينات"
192
- ],
193
- "درجة الثقة": "95%"
194
- }
195
- except Exception as e:
196
- logger.error(f"خطأ في تحليل العقد: {str(e)}")
197
- return {"error": f"حدث خطأ أثناء تحليل العقد: {str(e)}"}
198
-
199
- def parse_tender(self, file_path):
200
- """تحليل مستند مناقصة"""
201
- try:
202
- # محاكاة تحليل مناقصة
203
- return {
204
- "نوع المستند": "مناقصة",
205
- "معلومات المناقصة": {
206
- "رقم المناقصة": "T-2025-002",
207
- "اسم المشروع": "إنشاء مبنى إداري",
208
- "الجهة المالكة": "وزارة المالية",
209
- "تاريخ الطرح": "2025-03-01",
210
- "تاريخ الإقفال": "2025-04-15",
211
- "القيمة التقديرية": "3,000,000 ريال"
212
- },
213
- "شروط المناقصة": [
214
- "تصنيف المقاول: الدرجة الأولى في مجال المباني",
215
- "خبرة سابقة: 5 مشاريع مماثلة خلال الـ 10 سنوات الماضية",
216
- "الضمان الابتدائي: 2% من قيمة العطاء",
217
- "الضمان النهائي: 5% من قيمة العقد",
218
- "مدة تنفيذ المشروع: 18 شهراً"
219
- ],
220
- "المستندات المطلوبة": [
221
- "شهادة التصنيف",
222
- "السجل التجاري",
223
- "شهادة الزكاة والدخل",
224
- "شهادة التأمينات الاجتماعية",
225
- "قائمة المشاريع المماثلة"
226
- ],
227
- "معايير التقييم": [
228
- {"المعيار": "السعر", "الوزن": "50%"},
229
- {"المعيار": "الخبرة الفنية", "الوزن": "25%"},
230
- {"المعيار": "الجدول الزمني", "الوزن": "15%"},
231
- {"المعيار": "فريق العمل", "الوزن": "10%"}
232
- ],
233
- "درجة الثقة": "92%"
234
- }
235
- except Exception as e:
236
- logger.error(f"خطأ في تحليل المناقصة: {str(e)}")
237
- return {"error": f"حدث خطأ أثناء تحليل المناقصة: {str(e)}"}
238
-
239
- def parse_specifications(self, file_path):
240
- """تحليل كراسة الشروط والمواصفات"""
241
- try:
242
- # محاكاة تحليل كراسة الشروط والمواصفات
243
- return {
244
- "نوع المستند": "كراسة شروط ومواصفات",
245
- "معلومات المشروع": {
246
- "اسم المشروع": "إنشاء مبنى إداري",
247
- "الموقع": "الرياض - حي العليا",
248
- "المساحة": "5000 متر مربع",
249
- "عدد الطوابق": "5 طوابق"
250
- },
251
- "المواصفات الفنية": {
252
- "الهيكل الإنشائي": "خرسانة مسلحة",
253
- "الواجهات": "زجاج عاكس وحجر طبيعي",
254
- "التشطيبات الداخلية": "رخام للأرضيات، جبس للأسقف، دهانات عالية الجودة للجدران",
255
- "أنظمة الكهرباء": "نظام إنارة LED موفر للطاقة، نظام تحكم ذكي",
256
- "أنظمة التكييف": "نظام تكييف مركزي مع تحكم منفصل لكل منطقة",
257
- "أنظمة السلامة": "نظام إنذار وإطفاء حريق آلي، كاميرات مراقبة، نظام تحكم في الدخول"
258
- },
259
- "الشروط العامة": [
260
- "الالتزام بكود البناء السعودي",
261
- "الالتزام بمتطلبات الدفاع المدني",
262
- "الالتزام بمتطلبات الاستدامة وكفاءة الطاقة",
263
- "تقديم مخططات تنفيذية معتمدة قبل البدء في التنفيذ",
264
- "تقديم عينات للمواد للاعتماد قبل التوريد"
265
- ],
266
- "المرفقات": [
267
- "المخططات المعمارية",
268
- "المخططات الإنشائية",
269
- "مخططات الكهرباء",
270
- "مخططات التكييف",
271
- "مخططات السباكة",
272
- "جدول الكميات"
273
- ],
274
- "درجة الثقة": "90%"
275
- }
276
- except Exception as e:
277
- logger.error(f"خطأ في تحليل كراسة الشروط والمواصفات: {str(e)}")
278
- return {"error": f"حدث خطأ أثناء تحليل كراسة الشروط والمواصفات: {str(e)}"}
279
-
280
- def parse_dwg(self, file_path):
281
- """تحليل ملف DWG"""
282
- try:
283
- # محاكاة تحليل ملف DWG
284
- return {
285
- "نوع المستند": "ملف DWG",
286
- "معلومات الملف": {
287
- "اسم الملف": os.path.basename(file_path),
288
- "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
289
- "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
290
- },
291
- "محتويات الملف": {
292
- "عدد الطبقات": 15,
293
- "عدد الكائنات": 1250,
294
- "أبعاد الرسم": "50م × 30م"
295
- },
296
- "تحليل المساحات": {
297
- "المساحة الإجمالية": "4,500 م²",
298
- "مساحة البناء": "3,200 م²",
299
- "مساحة الخدمات": "800 م²",
300
- "مساحة الممرات": "500 م²"
301
- },
302
- "تحليل العناصر": {
303
- "عدد الغرف": 25,
304
- "عدد الأبواب": 40,
305
- "عدد النوافذ": 30,
306
- "عدد الأعمدة": 20
307
- },
308
- "ملاحظات": [
309
- "تصميم يتوافق مع متطلبات كود البناء السعودي",
310
- "توزيع جيد للمس��حات",
311
- "تصميم يراعي متطلبات ذوي الاحتياجات الخاصة",
312
- "تصميم يراعي متطلبات السلامة والإخلاء"
313
- ],
314
- "درجة الثقة": "85%"
315
- }
316
- except Exception as e:
317
- logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
318
- return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
319
-
320
- def parse(self, file_path):
321
- """تحليل المستند بناءً على نوعه"""
322
- try:
323
- _, ext = os.path.splitext(file_path)
324
- ext = ext.lower()
325
-
326
- # تحديد نوع المستند بناءً على محتواه (محاكاة)
327
- file_name = os.path.basename(file_path).lower()
328
-
329
- if ext == '.dwg':
330
- return self.parse_dwg(file_path)
331
- elif 'contract' in file_name or 'عقد' in file_name:
332
- return self.parse_contract(file_path)
333
- elif 'tender' in file_name or 'مناقصة' in file_name:
334
- return self.parse_tender(file_path)
335
- elif 'spec' in file_name or 'شروط' in file_name or 'مواصفات' in file_name:
336
- return self.parse_specifications(file_path)
337
- else:
338
- # تحليل عام للمستند
339
- return {
340
- "نوع المستند": "مستند عام",
341
- "معلومات الملف": {
342
- "اسم الملف": os.path.basename(file_path),
343
- "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
344
- "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
345
- },
346
- "محتوى المستند": {
347
- "نص": self.text_extractor.extract(file_path),
348
- "عناصر": self.item_extractor.extract(file_path)
349
- },
350
- "درجة الثقة": "75%"
351
- }
352
- except Exception as e:
353
- logger.error(f"خطأ في تحليل المستند: {str(e)}")
354
- return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
355
-
356
-
357
- class AIDocumentAnalyzer:
358
- """فئة تحليل المستندات باستخدام الذكاء الاصطناعي"""
359
-
360
- def __init__(self):
361
- """تهيئة محلل المستندات الذكي"""
362
- self.document_parser = DocumentParser()
363
- self.api_keys = {}
364
-
365
- def set_api_key(self, provider, key):
366
- """تعيين مفتاح API لمزود خدمة الذكاء الاصطناعي"""
367
- self.api_keys[provider] = key
368
-
369
- def get_api_key(self, provider):
370
- """الحصول على مفتاح API لمزود خدمة الذكاء الاصطناعي"""
371
- return self.api_keys.get(provider)
372
-
373
- def analyze_document(self, file_path, provider="local"):
374
- """تحليل المستند باستخدام الذكاء الاصطناعي"""
375
- try:
376
- # تحليل محلي للمستند
377
- local_analysis = self.document_parser.parse(file_path)
378
-
379
- if provider == "local":
380
- return local_analysis
381
-
382
- # تحليل باستخدام خدمات الذكاء الاصطناعي السحابية
383
- if provider == "openai":
384
- # محاكاة تحليل باستخدام OpenAI
385
- enhanced_analysis = self._enhance_with_openai(local_analysis)
386
- return enhanced_analysis
387
- elif provider == "claude":
388
- # محاكاة تحليل باستخدام Claude
389
- enhanced_analysis = self._enhance_with_claude(local_analysis)
390
- return enhanced_analysis
391
- else:
392
- return local_analysis
393
- except Exception as e:
394
- logger.error(f"خطأ في تحليل المستند: {str(e)}")
395
- return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
396
-
397
- def _enhance_with_openai(self, analysis):
398
- """تحسين التحليل باستخدام OpenAI"""
399
- # محاكاة تحسين التحليل باستخدام OpenAI
400
- analysis["مصدر التحليل"] = "OpenAI"
401
- analysis["درجة الثقة"] = "98%"
402
-
403
- # إضافة تحليل المخاطر
404
- if "تحليل المخاطر" not in analysis:
405
- analysis["تحليل المخاطر"] = [
406
- {"المخاطرة": "تأخر التوريدات", "الاحتمالية": "متوسطة", "التأثير": "عالي", "استراتيجية التخفيف": "وضع خطة توريدات بديلة"},
407
- {"المخاطرة": "زيادة أسعار المواد", "الاحتمالية": "عالية", "التأثير": "عالي", "استراتيجية التخفيف": "تثبيت أسعار المواد الرئيسية مع الموردين"},
408
- {"المخاطرة": "نقص العمالة الماهرة", "الاحتمالية": "متوسطة", "التأثير": "متوسط", "استراتيجية التخفيف": "التعاقد المسبق مع مقاولي الباطن"},
409
- {"المخاطرة": "تغيير نطاق العمل", "الاحتمالية": "منخفضة", "التأثير": "عالي", "استراتيجية التخفيف": "توثيق نطاق العمل بدقة وإدارة التغيير"}
410
- ]
411
-
412
- # إضافة توصيات
413
- if "التوصيات" not in analysis:
414
- analysis["التوصيات"] = [
415
- "مراجعة بنود العقد بدقة قبل التوقيع",
416
- "التأكد من وضوح نطاق العمل وعدم وجود غموض",
417
- "التحقق من توافق المواصفات الفنية مع المعايير المحلية",
418
- "وضع خطة إدارة مخاطر شاملة للمشروع",
419
- "تخصيص احتياطي للطوارئ بنسبة 10-15% من قيمة المشروع"
420
- ]
421
-
422
- return analysis
423
-
424
- def _enhance_with_claude(self, analysis):
425
- """تحسين التحليل باستخدام Claude"""
426
- # محاكاة تحسين التحليل باستخدام Claude
427
- analysis["مصدر التحليل"] = "Claude"
428
- analysis["درجة الثقة"] = "97%"
429
-
430
- # إضافة تحليل الفرص
431
- if "تحليل الفرص" not in analysis:
432
- analysis["تحليل الفرص"] = [
433
- {"الفرصة": "تحسين التصميم", "الفائدة": "تقليل التكلفة بنسبة 5-10%", "المتطلبات": "مراجعة هندسية شاملة"},
434
- {"الفرصة": "استخدام مواد بديلة", "الفائدة": "تقليل وقت التنفيذ", "المتطلبات": "اعتماد المواصفات الجديدة"},
435
- {"الفرصة": "زيادة المحتوى المحلي", "الفائدة": "تحسين التصنيف في برنامج القيمة المضافة", "المتطلبات": "تحديد الموردين المحليين"},
436
- {"الفرصة": "تطبيق تقنيات البناء الحديثة", "الفائدة": "تحسين الجودة وتقليل الهدر", "المتطلبات": "تدريب فريق العمل"}
437
- ]
438
-
439
- # إضافة ملخص تنفيذي
440
- if "الملخص التنفيذي" not in analysis:
441
- analysis["الملخص التنفيذي"] = """
442
- يتضمن هذا المستند تفاصيل مشروع إنشاء مبنى إداري بمساحة إجمالية 5000 متر مربع.
443
- المشروع يتكون من 5 طوابق ويتضمن مواصفات فنية عالية الجودة.
444
- تقدر تكلفة المشروع بحوالي 3 مليون ريال ومدة التنفيذ 18 شهراً.
445
- يتميز المشروع بتصميم يراعي متطلبات الاستدامة وكفاءة الطاقة.
446
- تم تحديد عدة مخاطر محتملة للمشروع مع استراتيجيات التخفيف المناسبة.
447
- كما تم تحديد عدة فرص لتحسين المشروع من حيث التكلفة والجودة ووقت التنفيذ.
448
- """
449
-
450
- return analysis
451
-
452
- def analyze_dwg(self, file_path, provider="local"):
453
- """تحليل ملف DWG باستخدام الذكاء الاصطناعي"""
454
- try:
455
- # تحليل محلي لملف DWG
456
- local_analysis = self.document_parser.parse_dwg(file_path)
457
-
458
- if provider == "local":
459
- return local_analysis
460
-
461
- # تحسين التحليل باستخدام خدمات الذكاء الاصطناعي
462
- if provider == "openai" or provider == "claude":
463
- # إضافة تحليل متقدم
464
- local_analysis["تحليل متقدم"] = {
465
- "تقييم التصميم": "جيد جداً",
466
- "كفاءة استخدام المساحة": "90%",
467
- "توافق مع المعايير": "متوافق مع كود البناء السعودي",
468
- "اقتراحات التحسين": [
469
- "تحسين توزيع المساحات لزيادة كفاءة استخدام المساحة",
470
- "تحسين تصميم الممرات لتسهيل الحركة",
471
- "إضافة عناصر تصميم مستدامة لتقليل استهلاك الطاقة",
472
- "تحسين تصميم الواجهات لزيادة الإضاءة الطبيعية"
473
- ]
474
- }
475
-
476
- # إضافة تقدير التكلفة
477
- local_analysis["تقدير التكلفة"] = {
478
- "التكلفة الإجمالية التقديرية": "3,200,000 ريال",
479
- "تكلفة المتر المربع": "800 ريال",
480
- "توزيع التكلفة": [
481
- {"البند": "الهيكل الإنشائي", "النسبة": "35%", "القيمة": "1,120,000 ريال"},
482
- {"البند": "التشطيبات", "النسبة": "25%", "القيمة": "800,000 ريال"},
483
- {"البند": "الأنظمة الكهربائية", "النسبة": "15%", "القيمة": "480,000 ريال"},
484
- {"البند": "الأنظمة الميكانيكية", "النسبة": "15%", "القيمة": "480,000 ريال"},
485
- {"البند": "الأعمال الخارجية", "النسبة": "10%", "القيمة": "320,000 ريال"}
486
- ]
487
- }
488
-
489
- # إضافة الجدول الزمني
490
- local_analysis["الجدول الزمني التقديري"] = {
491
- "المدة الإجمالية": "18 شهر",
492
- "المراحل": [
493
- {"المرحلة": "أعمال الحفر والأساسات", "المدة": "3 أشهر", "النسبة": "15%"},
494
- {"المرحلة": "الهيكل الإنشائي", "المدة": "6 أشهر", "النسبة": "35%"},
495
- {"المرحلة": "التشطيبات الداخلية", "المدة": "5 أشهر", "النسبة": "25%"},
496
- {"المرحلة": "الأنظمة الكهربائية والميكانيكية", "المدة": "3 أشهر", "النسبة": "15%"},
497
- {"المرحلة": "الأعمال الخارجية والتسليم", "المدة": "1 شهر", "النسبة": "10%"}
498
- ]
499
- }
500
-
501
- local_analysis["مصدر التحليل"] = provider.capitalize()
502
- local_analysis["درجة الثقة"] = "95%"
503
-
504
- return local_analysis
505
- except Exception as e:
506
- logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
507
  return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
 
40
  logger.warning("لم يتم العثور على مكتبة pdf2image. لن يمكن تحويل ملفات PDF إلى صور.")
41
 
42
 
43
+ class TextExtractor:
44
+ """فئة استخراج النصوص من المستندات"""
45
+
46
+ def __init__(self, config=None):
47
+ """تهيئة مستخرج النصوص"""
48
+ self.config = config or {}
49
+
50
+ def extract_from_pdf(self, file_path):
51
+ """استخراج النص من ملف PDF"""
52
+ try:
53
+ # محاكاة استخراج النص من PDF
54
+ # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل PyPDF2 أو pdfplumber
55
+ return f"تم استخراج النص من ملف PDF: {file_path}"
56
+ except Exception as e:
57
+ logger.error(f"خطأ في استخراج النص من PDF: {str(e)}")
58
+ return f"حدث خطأ أثناء استخراج النص: {str(e)}"
59
+
60
+ def extract_from_docx(self, file_path):
61
+ """استخراج النص من ملف DOCX"""
62
+ try:
63
+ # محاكاة استخراج النص من DOCX
64
+ # في التطبيق الحقيقي، يمكن استخدام مكتبة python-docx
65
+ return f"تم استخراج النص من ملف DOCX: {file_path}"
66
+ except Exception as e:
67
+ logger.error(f"خطأ في استخراج النص من DOCX: {str(e)}")
68
+ return f"حدث خطأ أثناء استخراج النص: {str(e)}"
69
+
70
+ def extract_from_image(self, file_path):
71
+ """استخراج النص من صورة باستخدام OCR"""
72
+ try:
73
+ # محاكاة استخراج النص من صورة
74
+ # في التطبيق الحقيقي، يمكن استخدام مكتبة pytesseract
75
+ return f"تم استخراج النص من صورة: {file_path}"
76
+ except Exception as e:
77
+ logger.error(f"خطأ في استخراج النص من صورة: {str(e)}")
78
+ return f"حدث خطأ أثناء استخراج النص: {str(e)}"
79
+
80
+ def extract(self, file_path):
81
+ """استخراج النص من ملف بناءً على نوعه"""
82
+ _, ext = os.path.splitext(file_path)
83
+ ext = ext.lower()
84
+
85
+ if ext == '.pdf':
86
+ return self.extract_from_pdf(file_path)
87
+ elif ext in ('.doc', '.docx'):
88
+ return self.extract_from_docx(file_path)
89
+ elif ext in ('.jpg', '.jpeg', '.png'):
90
+ return self.extract_from_image(file_path)
91
+ else:
92
+ return "نوع ملف غير مدعوم"
93
+
94
+
95
+ class ItemExtractor:
96
+ """فئة استخراج العناصر من المستندات"""
97
+
98
+ def __init__(self, config=None):
99
+ """تهيئة مستخرج العناصر"""
100
+ self.config = config or {}
101
+
102
+ def extract_tables(self, document):
103
+ """استخراج الجداول من المستند"""
104
+ try:
105
+ # محاكاة استخراج الجداول
106
+ # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل camelot-py أو tabula-py
107
+ return [
108
+ {
109
+ "عنوان": "جدول البنود والكميات",
110
+ "بيانات": [
111
+ {"البند": "أعمال الحفر", "الكمية": 1000, "الوحدة": "م³", "السعر": 50, "الإجمالي": 50000},
112
+ {"البند": "أعمال الخرسانة", "الكمية": 500, "الوحدة": "م³", "السعر": 300, "الإجمالي": 150000},
113
+ {"البند": "أعمال التشطيبات", "الكمية": 2000, "الوحدة": "م²", "السعر": 100, "الإجمالي": 200000}
114
+ ]
115
+ },
116
+ {
117
+ "عنوان": "جدول الجدول الزمني",
118
+ "بيانات": [
119
+ {"المرحلة": "التصميم", "المدة": "30 يوم", "تاريخ البدء": "2025-04-01", "تاريخ الانتهاء": "2025-04-30"},
120
+ {"المرحلة": "الإنشاء", "المدة": "180 يوم", "تاريخ البدء": "2025-05-01", "تاريخ الانتهاء": "2025-10-31"},
121
+ {"المرحلة": "التسليم", "المدة": "30 يوم", "تاريخ البدء": "2025-11-01", "تاريخ الانتهاء": "2025-11-30"}
122
+ ]
123
+ }
124
+ ]
125
+ except Exception as e:
126
+ logger.error(f"خطأ في استخراج الجداول: {str(e)}")
127
+ return []
128
+
129
+ def extract_items(self, file_path):
130
+ """استخراج البنود من المستند"""
131
+ try:
132
+ # محاكاة استخراج البنود
133
+ return [
134
+ {"بند": "أعمال الحفر والردم", "قيمة": 250000, "نسبة": "10%"},
135
+ {"بند": "أعمال الخرسانة المسلحة", "قيمة": 750000, "نسبة": "30%"},
136
+ {"بند": "أعمال التشطيبات", "قيمة": 500000, "نسبة": "20%"},
137
+ {"بند": "أعمال الكهرباء", "قيمة": 350000, "نسبة": "14%"},
138
+ {"بند": "أعمال السباكة", "قيمة": 300000, "نسبة": "12%"},
139
+ {"بند": "أعمال التكييف", "قيمة": 350000, "نسبة": "14%"}
140
+ ]
141
+ except Exception as e:
142
+ logger.error(f"خطأ في استخراج البنود: {str(e)}")
143
+ return []
144
+
145
+ def extract(self, file_path):
146
+ """استخراج جميع العناصر من المستند"""
147
+ return {
148
+ "بنود": self.extract_items(file_path),
149
+ "جداول": self.extract_tables(file_path)
150
+ }
151
+
152
+
153
+ class DocumentParser:
154
+ """فئة تحليل المستندات"""
155
+
156
+ def __init__(self, config=None):
157
+ """تهيئة محلل المستندات"""
158
+ self.config = config or {}
159
+ self.text_extractor = TextExtractor(config)
160
+ self.item_extractor = ItemExtractor(config)
161
+
162
+ def parse_contract(self, file_path):
163
+ """تحليل مستند عقد"""
164
+ try:
165
+ # محاكاة تحليل عقد
166
+ return {
167
+ "نوع المستند": "عقد",
168
+ "معلومات العقد": {
169
+ "رقم العقد": "CT-2025-001",
170
+ "تاريخ العقد": "2025-03-15",
171
+ "قيمة العقد": "2,500,000 ريال",
172
+ "مدة العقد": "12 شهر",
173
+ "تاريخ البدء": "2025-04-01",
174
+ "تاريخ الانتهاء": "2026-03-31"
175
+ },
176
+ "أطراف العقد": {
177
+ "الطرف الأول": "وزارة الإسكان",
178
+ "الطرف الثاني": "شركة الإنشاءات المتطورة"
179
+ },
180
+ "بنود العقد": [
181
+ "يلتزم الطرف الثاني بتنفيذ المشروع وفقاً للمواصفات والشروط المرفقة",
182
+ "مدة تنفيذ المشروع 12 شهراً من تاريخ استلام الموقع",
183
+ "قيمة العقد 2,500,000 ريال شاملة جميع الضرائب والرسوم",
184
+ "يتم الدفع على دفعات شهرية حسب نسبة الإنجاز",
185
+ "غرامة التأخير 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%"
186
+ ],
187
+ "المرفقات": [
188
+ "جدول الكميات",
189
+ "المواصفات الفنية",
190
+ "الجدول الزمني",
191
+ "الضمانات والتأمينات"
192
+ ],
193
+ "درجة الثقة": "95%"
194
+ }
195
+ except Exception as e:
196
+ logger.error(f"خطأ في تحليل العقد: {str(e)}")
197
+ return {"error": f"حدث خطأ أثناء تحليل العقد: {str(e)}"}
198
+
199
+ def parse_tender(self, file_path):
200
+ """تحليل مستند مناقصة"""
201
+ try:
202
+ # محاكاة تحليل مناقصة
203
+ return {
204
+ "نوع المستند": "مناقصة",
205
+ "معلومات المناقصة": {
206
+ "رقم المناقصة": "T-2025-002",
207
+ "اسم المشروع": "إنشاء مبنى إداري",
208
+ "الجهة المالكة": "وزارة المالية",
209
+ "تاريخ الطرح": "2025-03-01",
210
+ "تاريخ الإقفال": "2025-04-15",
211
+ "القيمة التقديرية": "3,000,000 ريال"
212
+ },
213
+ "شروط المناقصة": [
214
+ "تصنيف المقاول: الدرجة الأولى في مجال المباني",
215
+ "خبرة سابقة: 5 مشاريع مماثلة خلال الـ 10 سنوات الماضية",
216
+ "الضمان الابتدائي: 2% من قيمة العطاء",
217
+ "الضمان النهائي: 5% من قيمة العقد",
218
+ "مدة تنفيذ المشروع: 18 شهراً"
219
+ ],
220
+ "المستندات المطلوبة": [
221
+ "شهادة التصنيف",
222
+ "السجل التجاري",
223
+ "شهادة الزكاة والدخل",
224
+ "شهادة التأمينات الاجتماعية",
225
+ "قائمة المشاريع المماثلة"
226
+ ],
227
+ "معايير التقييم": [
228
+ {"المعيار": "السعر", "الوزن": "50%"},
229
+ {"المعيار": "الخبرة الفنية", "الوزن": "25%"},
230
+ {"المعيار": "الجدول الزمني", "الوزن": "15%"},
231
+ {"المعيار": "فريق العمل", "الوزن": "10%"}
232
+ ],
233
+ "درجة الثقة": "92%"
234
+ }
235
+ except Exception as e:
236
+ logger.error(f"خطأ في تحليل المناقصة: {str(e)}")
237
+ return {"error": f"حدث خطأ أثناء تحليل المناقصة: {str(e)}"}
238
+
239
+ def parse_specifications(self, file_path):
240
+ """تحليل كراسة الشروط والمواصفات"""
241
+ try:
242
+ # محاكاة تحليل كراسة الشروط والمواصفات
243
+ return {
244
+ "نوع المستند": "كراسة شروط ومواصفات",
245
+ "معلومات المشروع": {
246
+ "اسم المشروع": "إنشاء مبنى إداري",
247
+ "الموقع": "الرياض - حي العليا",
248
+ "المساحة": "5000 متر مربع",
249
+ "عدد الطوابق": "5 طوابق"
250
+ },
251
+ "المواصفات الفنية": {
252
+ "الهيكل الإنشائي": "خرسانة مسلحة",
253
+ "الواجهات": "زجاج عاكس وحجر طبيعي",
254
+ "التشطيبات الداخلية": "رخام للأرضيات، جبس للأسقف، دهانات عالية الجودة للجدران",
255
+ "أنظمة الكهرباء": "نظام إنارة LED موفر للطاقة، نظام تحكم ذكي",
256
+ "أنظمة التكييف": "نظام تكييف مركزي مع تحكم منفصل لكل منطقة",
257
+ "أنظمة السلامة": "نظام إنذار وإطفاء حريق آلي، كاميرات مراقبة، نظام تحكم في الدخول"
258
+ },
259
+ "الشروط العامة": [
260
+ "الالتزام بكود البناء السعودي",
261
+ "الالتزام بمتطلبات الدفاع المدني",
262
+ "الالتزام بمتطلبات الاستدامة وكفاءة الطاقة",
263
+ "تقديم مخططات تنفيذية معتمدة قبل البدء في التنفيذ",
264
+ "تقديم عينات للمواد للاعتماد قبل التوريد"
265
+ ],
266
+ "المرفقات": [
267
+ "المخططات المعمارية",
268
+ "المخططات الإنشائية",
269
+ "مخططات الكهرباء",
270
+ "مخططات التكييف",
271
+ "مخططات السباكة",
272
+ "جدول الكميات"
273
+ ],
274
+ "درجة الثقة": "90%"
275
+ }
276
+ except Exception as e:
277
+ logger.error(f"خطأ في تحليل كراسة الشروط والمواصفات: {str(e)}")
278
+ return {"error": f"حدث خطأ أثناء تحليل كراسة الشروط والمواصفات: {str(e)}"}
279
+
280
+ def parse_dwg(self, file_path):
281
+ """تحليل ملف DWG"""
282
+ try:
283
+ # محاكاة تحليل ملف DWG
284
+ return {
285
+ "نوع المستند": "ملف DWG",
286
+ "معلومات الملف": {
287
+ "اسم الملف": os.path.basename(file_path),
288
+ "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
289
+ "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
290
+ },
291
+ "محتويات الملف": {
292
+ "عدد الطبقات": 15,
293
+ "عدد الكائنات": 1250,
294
+ "أبعاد الرسم": "50م × 30م"
295
+ },
296
+ "تحليل المساحات": {
297
+ "المساحة الإجمالية": "4,500 م²",
298
+ "مساحة البناء": "3,200 م²",
299
+ "مساحة الخدمات": "800 م²",
300
+ "مساحة الممرات": "500 م²"
301
+ },
302
+ "تحليل العناصر": {
303
+ "عدد الغرف": 25,
304
+ "عدد الأبواب": 40,
305
+ "عدد النوافذ": 30,
306
+ "عدد الأعمدة": 20
307
+ },
308
+ "ملاحظات": [
309
+ "تصميم يتوافق مع متطلبات كود البناء السعودي",
310
+ "توزيع جيد للمساحات",
311
+ "تصميم يراعي متطلبات ذوي الاحتياجات الخاصة",
312
+ "تصميم يراعي متطلبات السلامة والإخلاء"
313
+ ],
314
+ "درجة الثقة": "85%"
315
+ }
316
+ except Exception as e:
317
+ logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
318
+ return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
319
+
320
+ def parse(self, file_path):
321
+ """تحليل المستند بناءً على نوعه"""
322
+ try:
323
+ _, ext = os.path.splitext(file_path)
324
+ ext = ext.lower()
325
+
326
+ # تحديد نوع المستند بناءً على محتواه (محاكاة)
327
+ file_name = os.path.basename(file_path).lower()
328
+
329
+ if ext == '.dwg':
330
+ return self.parse_dwg(file_path)
331
+ elif 'contract' in file_name or 'عقد' in file_name:
332
+ return self.parse_contract(file_path)
333
+ elif 'tender' in file_name or 'مناقصة' in file_name:
334
+ return self.parse_tender(file_path)
335
+ elif 'spec' in file_name or 'شروط' in file_name or 'مواصفات' in file_name:
336
+ return self.parse_specifications(file_path)
337
+ else:
338
+ # تحليل عام للمستند
339
+ return {
340
+ "نوع المستند": "مستند عام",
341
+ "معلومات الملف": {
342
+ "اسم الملف": os.path.basename(file_path),
343
+ "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
344
+ "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
345
+ },
346
+ "محتوى المستند": {
347
+ "نص": self.text_extractor.extract(file_path),
348
+ "عناصر": self.item_extractor.extract(file_path)
349
+ },
350
+ "درجة الثقة": "75%"
351
+ }
352
+ except Exception as e:
353
+ logger.error(f"خطأ في تحليل المستند: {str(e)}")
354
+ return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
355
+
356
+
357
+ class AIDocumentAnalyzer:
358
+ """فئة تحليل المستندات باستخدام الذكاء الاصطناعي"""
359
+
360
+ def __init__(self):
361
+ """تهيئة محلل المستندات الذكي"""
362
+ self.document_parser = DocumentParser()
363
+ self.api_keys = {}
364
+
365
+ def set_api_key(self, provider, key):
366
+ """تعيين مفتاح API لمزود خدمة الذكاء الاصطناعي"""
367
+ self.api_keys[provider] = key
368
+
369
+ def get_api_key(self, provider):
370
+ """الحصول على مفتاح API لمزود خدمة الذكاء الاصطناعي"""
371
+ return self.api_keys.get(provider)
372
+
373
+ def analyze_document(self, file_path, provider="local"):
374
+ """تحليل المستند باستخدام الذكاء الاصطناعي"""
375
+ try:
376
+ # تحليل محلي للمستند
377
+ local_analysis = self.document_parser.parse(file_path)
378
+
379
+ if provider == "local":
380
+ return local_analysis
381
+
382
+ # تحليل باستخدام خدمات الذكاء الاصطناعي السحابية
383
+ if provider == "openai":
384
+ # محاكاة تحليل باستخدام OpenAI
385
+ enhanced_analysis = self._enhance_with_openai(local_analysis)
386
+ return enhanced_analysis
387
+ elif provider == "claude":
388
+ # محاكاة تحليل باستخدام Claude
389
+ enhanced_analysis = self._enhance_with_claude(local_analysis)
390
+ return enhanced_analysis
391
+ else:
392
+ return local_analysis
393
+ except Exception as e:
394
+ logger.error(f"خطأ في تحليل المستند: {str(e)}")
395
+ return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
396
+
397
+ def _enhance_with_openai(self, analysis):
398
+ """تحسين التحليل باستخدام OpenAI"""
399
+ # محاكاة تحسين التحليل باستخدام OpenAI
400
+ analysis["مصدر التحليل"] = "OpenAI"
401
+ analysis["درجة الثقة"] = "98%"
402
+
403
+ # إضافة تحليل المخاطر
404
+ if "تحليل المخاطر" not in analysis:
405
+ analysis["تحليل المخاطر"] = [
406
+ {"المخاطرة": "تأخر التوريدات", "الاحتمالية": "متوسطة", "التأثير": "عالي", "استراتيجية التخفيف": "وضع خطة توريدات بديلة"},
407
+ {"المخاطرة": "زيادة أسعار المواد", "الاحتمالية": "عالية", "التأثير": "عالي", "استراتيجية التخفيف": "تثبيت أسعار المواد الرئيسية مع الموردين"},
408
+ {"المخاطرة": "نقص العمالة الماهرة", "الاحتمالية": "متوسطة", "التأثير": "متوسط", "استراتيجية التخفيف": "التعاقد المسبق مع مقاولي الباطن"},
409
+ {"المخاطرة": "تغيير نطاق العمل", "الاحتمالية": "منخفضة", "التأثير": "عالي", "استراتيجية التخفيف": "توثيق نطاق العمل بدقة وإدارة التغيير"}
410
+ ]
411
+
412
+ # إضافة توصيات
413
+ if "التوصيات" not in analysis:
414
+ analysis["التوصيات"] = [
415
+ "مراجعة بنود العقد بدقة قبل التوقيع",
416
+ "التأكد من وضوح نطاق العمل وعدم وجود غموض",
417
+ "التحقق من توافق المواصفات الفنية مع المعايير المحلية",
418
+ "وضع خطة إدارة مخاطر شاملة للمشروع",
419
+ "تخصيص احتياطي للطوارئ بنسبة 10-15% من قيمة المشروع"
420
+ ]
421
+
422
+ return analysis
423
+
424
+ def _enhance_with_claude(self, analysis):
425
+ """تحسين التحليل باستخدام Claude"""
426
+ # محاكاة تحسين التحليل باستخدام Claude
427
+ analysis["مصدر التحليل"] = "Claude"
428
+ analysis["درجة الثقة"] = "97%"
429
+
430
+ # إضافة تحليل الفرص
431
+ if "تحليل الفرص" not in analysis:
432
+ analysis["تحليل الفرص"] = [
433
+ {"الفرصة": "تحسين التصميم", "الفائدة": "تقليل التكلفة بنسبة 5-10%", "المتطلبات": "مراجعة هندسية شاملة"},
434
+ {"الفرصة": "استخدام مواد بديلة", "الفائدة": "تقليل وقت التنفيذ", "المتطلبات": "اعتماد المواصفات الجديدة"},
435
+ {"الفرصة": "زيادة المحتوى المحلي", "الفائدة": "تحسين التصنيف في برنامج القيمة المضافة", "المتطلبات": "تحديد الموردين المحليين"},
436
+ {"الفرصة": "تطبيق تقنيات البناء الحديثة", "الفائدة": "تحسين الجودة وتقليل الهدر", "المتطلبات": "تدريب فريق العمل"}
437
+ ]
438
+
439
+ # إضافة ملخص تنفيذي
440
+ if "الملخص التنفيذي" not in analysis:
441
+ analysis["الملخص التنفيذي"] = """
442
+ يتضمن هذا المستند تفاصيل مشروع إنشاء مبنى إداري بمساحة إجمالية 5000 متر مربع.
443
+ المشروع يتكون من 5 ��وابق ويتضمن مواصفات فنية عالية الجودة.
444
+ تقدر تكلفة المشروع بحوالي 3 مليون ريال ومدة التنفيذ 18 شهراً.
445
+ يتميز المشروع بتصميم يراعي متطلبات الاستدامة وكفاءة الطاقة.
446
+ تم تحديد عدة مخاطر محتملة للمشروع مع استراتيجيات التخفيف المناسبة.
447
+ كما تم تحديد عدة فرص لتحسين المشروع من حيث التكلفة والجودة ووقت التنفيذ.
448
+ """
449
+
450
+ return analysis
451
+
452
+ def analyze_dwg(self, file_path, provider="local"):
453
+ """تحليل ملف DWG باستخدام الذكاء الاصطناعي"""
454
+ try:
455
+ # تحليل محلي لملف DWG
456
+ local_analysis = self.document_parser.parse_dwg(file_path)
457
+
458
+ if provider == "local":
459
+ return local_analysis
460
+
461
+ # تحسين التحليل باستخدام خدمات الذكاء الاصطناعي
462
+ if provider == "openai" or provider == "claude":
463
+ # إضافة تحليل متقدم
464
+ local_analysis["تحليل متقدم"] = {
465
+ "تقييم التصميم": "جيد جداً",
466
+ "كفاءة استخدام المساحة": "90%",
467
+ "توافق مع المعايير": "متوافق مع كود البناء السعودي",
468
+ "اقتراحات التحسين": [
469
+ "تحسين توزيع المساحات لزيادة كفاءة استخدام المساحة",
470
+ "تحسين تصميم الممرات لتسهيل الحركة",
471
+ "إضافة عناصر تصميم مستدامة لتقليل استهلاك الطاقة",
472
+ "تحسين تصميم الواجهات لزيادة الإضاءة الطبيعية"
473
+ ]
474
+ }
475
+
476
+ # إضافة تقدير التكلفة
477
+ local_analysis["تقدير التكلفة"] = {
478
+ "التكلفة الإجمالية التقديرية": "3,200,000 ريال",
479
+ "تكلفة المتر المربع": "800 ريال",
480
+ "توزيع التكلفة": [
481
+ {"البند": "الهيكل الإنشائي", "النسبة": "35%", "القيمة": "1,120,000 ريال"},
482
+ {"البند": "التشطيبات", "النسبة": "25%", "القيمة": "800,000 ريال"},
483
+ {"البند": "الأنظمة الكهربائية", "النسبة": "15%", "القيمة": "480,000 ريال"},
484
+ {"البند": "الأنظمة الميكانيكية", "النسبة": "15%", "القيمة": "480,000 ريال"},
485
+ {"البند": "الأعمال الخارجية", "النسبة": "10%", "القيمة": "320,000 ريال"}
486
+ ]
487
+ }
488
+
489
+ # إضافة الجدول الزمني
490
+ local_analysis["الجدول الزمني التقديري"] = {
491
+ "المدة الإجمالية": "18 شهر",
492
+ "المراحل": [
493
+ {"المرحلة": "أعمال الحفر والأساسات", "المدة": "3 أشهر", "النسبة": "15%"},
494
+ {"المرحلة": "الهيكل الإنشائي", "المدة": "6 أشهر", "النسبة": "35%"},
495
+ {"المرحلة": "التشطيبات الداخلية", "المدة": "5 أشهر", "النسبة": "25%"},
496
+ {"المرحلة": "الأنظمة الكهربائية والميكانيكية", "المدة": "3 أشهر", "النسبة": "15%"},
497
+ {"المرحلة": "الأعمال الخارجية والتسليم", "المدة": "1 شهر", "النسبة": "10%"}
498
+ ]
499
+ }
500
+
501
+ local_analysis["مصدر التحليل"] = provider.capitalize()
502
+ local_analysis["درجة الثقة"] = "95%"
503
+
504
+ return local_analysis
505
+ except Exception as e:
506
+ logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
507
  return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
modules/ai_assistant/engineering_drawing_analyzer.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import cv2
3
+ import numpy as np
4
+ from PIL import Image
5
+ import pytesseract
6
+ import logging
7
+ from datetime import datetime
8
+
9
+ class EngineeringDrawingAnalyzer:
10
+ """محلل الرسومات الهندسية المتقدم"""
11
+
12
+ def __init__(self, claude_client=None):
13
+ self.claude_client = claude_client
14
+ self.supported_formats = ['.dwg', '.dxf', '.pdf', '.jpg', '.jpeg', '.png']
15
+
16
+ def analyze_drawing(self, file_path):
17
+ """تحليل الرسم الهندسي"""
18
+ try:
19
+ # قراءة الصورة
20
+ image = cv2.imread(file_path)
21
+
22
+ # تحليل الخطوط والأشكال
23
+ lines, shapes = self._analyze_shapes(image)
24
+
25
+ # استخراج النص والأبعاد
26
+ text, dimensions = self._extract_text_and_dimensions(image)
27
+
28
+ # تحليل متقدم باستخدام Claude
29
+ if self.claude_client:
30
+ drawing_info = {
31
+ 'lines': lines,
32
+ 'shapes': shapes,
33
+ 'text': text,
34
+ 'dimensions': dimensions
35
+ }
36
+
37
+ analysis_prompt = f"""
38
+ قم بتحليل معلومات الرسم الهندسي التالية وتقديم:
39
+ 1. تحليل شامل للرسم
40
+ 2. تحديد التخصص (إنشائي، معماري، ميكانيكي، كهربائي)
41
+ 3. تحليل التفاصيل الفنية
42
+ 4. توصيات للتحسين
43
+ 5. تقدير التكاليف
44
+
45
+ معلومات الرسم:
46
+ {json.dumps(drawing_info, indent=2, ensure_ascii=False)}
47
+ """
48
+
49
+ response = self.claude_client.messages.create(
50
+ model="claude-3-sonnet-20240229",
51
+ max_tokens=4000,
52
+ temperature=0.7,
53
+ messages=[{"role": "user", "content": analysis_prompt}]
54
+ )
55
+
56
+ ai_analysis = response.content[0].text
57
+ else:
58
+ ai_analysis = "تحليل الذكاء الاصطناعي غير متاح"
59
+
60
+ return {
61
+ 'file_info': {
62
+ 'path': file_path,
63
+ 'type': os.path.splitext(file_path)[1],
64
+ 'size': os.path.getsize(file_path)
65
+ },
66
+ 'analysis': {
67
+ 'lines': lines,
68
+ 'shapes': shapes,
69
+ 'text': text,
70
+ 'dimensions': dimensions,
71
+ 'ai_analysis': ai_analysis
72
+ }
73
+ }
74
+
75
+ except Exception as e:
76
+ logging.error(f"خطأ في تحليل الرسم: {str(e)}")
77
+ raise
78
+
79
+ def _analyze_shapes(self, image):
80
+ """تحليل الخطوط والأشكال"""
81
+ # تحويل الصورة إلى تدرج رمادي
82
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
83
+
84
+ # كشف الحواف
85
+ edges = cv2.Canny(gray, 50, 150)
86
+
87
+ # كشف الخطوط
88
+ lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100)
89
+
90
+ # كشف الأشكال
91
+ contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
92
+
93
+ shapes = []
94
+ for contour in contours:
95
+ approx = cv2.approxPolyDP(contour, 0.04 * cv2.arcLength(contour, True), True)
96
+ if len(approx) == 3:
97
+ shapes.append('triangle')
98
+ elif len(approx) == 4:
99
+ shapes.append('rectangle')
100
+ elif len(approx) == 5:
101
+ shapes.append('pentagon')
102
+ elif len(approx) > 5:
103
+ shapes.append('circle')
104
+
105
+ return {
106
+ 'line_count': len(lines) if lines is not None else 0,
107
+ 'line_angles': self._analyze_line_angles(lines) if lines is not None else [],
108
+ 'line_lengths': self._analyze_line_lengths(lines) if lines is not None else []
109
+ }, {
110
+ 'shape_count': len(shapes),
111
+ 'shape_types': dict(Counter(shapes))
112
+ }
113
+
114
+ def _extract_text_and_dimensions(self, image):
115
+ """استخراج النص والأبعاد"""
116
+ # استخراج النص
117
+ text = pytesseract.image_to_string(image, lang='ara+eng')
118
+
119
+ # تحليل الأبعاد
120
+ dimensions = self._analyze_dimensions_from_text(text)
121
+
122
+ return text, dimensions
123
+
124
+ def _analyze_dimensions_from_text(self, text):
125
+ """تحليل الأبعاد من النص"""
126
+ import re
127
+
128
+ # نمط للبحث عن الأبعاد
129
+ dimension_pattern = r'(\d+(?:\.\d+)?)\s*(م|متر|سم|مم|m|cm|mm)'
130
+
131
+ # البحث عن جميع ��لأبعاد
132
+ dimensions = re.findall(dimension_pattern, text)
133
+
134
+ # تحويل الأبعاد إلى قائمة منظمة
135
+ processed_dimensions = []
136
+ for value, unit in dimensions:
137
+ processed_dimensions.append({
138
+ 'value': float(value),
139
+ 'unit': unit
140
+ })
141
+
142
+ return processed_dimensions
modules/data_analysis/data_analysis_app.py CHANGED
@@ -1,502 +1,325 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import numpy as np
4
- import matplotlib.pyplot as plt
5
- import plotly.express as px
6
- import plotly.graph_objects as go
7
- import seaborn as sns
8
- from datetime import datetime
9
- import os
10
- import sys
11
- from pathlib import Path
12
-
13
- # إضافة المسار للوصول إلى الوحدات الأخرى
14
- current_dir = os.path.dirname(os.path.abspath(__file__))
15
- parent_dir = os.path.dirname(os.path.dirname(current_dir))
16
- if parent_dir not in sys.path:
17
- sys.path.append(parent_dir)
18
-
19
- class DataAnalysisApp:
20
- """تطبيق تحليل البيانات"""
21
-
22
- def __init__(self):
23
- """تهيئة تطبيق تحليل البيانات"""
24
- self.data = None
25
- self.file_path = None
26
-
27
- def run(self):
28
- """تشغيل تطبيق تحليل البيانات"""
29
- # استيراد مدير التكوين
30
- from config_manager import ConfigManager
31
-
32
- # محاولة تعيين تكوين الصفحة (سيتم تجاهلها إذا كان التكوين معينًا بالفعل)
33
- config_manager = ConfigManager()
34
- config_manager.set_page_config_if_needed(
35
- page_title="تحليل البيانات",
36
- page_icon="📊",
37
- layout="wide"
38
- )
39
-
40
- # عرض عنوان التطبيق
41
- st.title("تحليل البيانات")
42
- st.write("استخدم هذه الأداة لتحليل بيانات المناقصات والمشاريع")
43
-
44
- # إنشاء علامات تبويب للتطبيق
45
- tabs = st.tabs(["تحميل البيانات", "استكشاف البيانات", "تحليل متقدم", "التصور المرئي", "التقارير"])
46
-
47
- with tabs[0]:
48
- self._load_data_tab()
49
-
50
- with tabs[1]:
51
- self._explore_data_tab()
52
-
53
- with tabs[2]:
54
- self._advanced_analysis_tab()
55
-
56
- with tabs[3]:
57
- self._visualization_tab()
58
-
59
- with tabs[4]:
60
- self._reports_tab()
61
-
62
- def _load_data_tab(self):
63
- """علامة تبويب تحميل البيانات"""
64
- st.header("تحميل البيانات")
65
-
66
- # خيارات تحميل البيانات
67
- data_source = st.radio(
68
- "اختر مصدر البيانات:",
69
- ["تحميل ملف", "استيراد من قاعدة البيانات", "استخدام بيانات نموذجية"]
70
- )
71
-
72
- if data_source == "تحميل ملف":
73
- uploaded_file = st.file_uploader("اختر ملف CSV أو Excel", type=["csv", "xlsx", "xls"])
74
-
75
- if uploaded_file is not None:
76
- try:
77
- if uploaded_file.name.endswith('.csv'):
78
- self.data = pd.read_csv(uploaded_file)
79
- else:
80
- self.data = pd.read_excel(uploaded_file)
81
-
82
- st.success(f"تم تحميل الملف بنجاح! عدد الصفوف: {self.data.shape[0]}, عدد الأعمدة: {self.data.shape[1]}")
83
- st.write("معاينة البيانات:")
84
- st.dataframe(self.data.head())
85
- except Exception as e:
86
- st.error(f"حدث خطأ أثناء تحميل الملف: {str(e)}")
87
-
88
- elif data_source == "استيراد من قاعدة البيانات":
89
- st.info("هذه الميزة قيد التطوير")
90
-
91
- # محاكاة الاتصال بقاعدة البيانات
92
- if st.button("اتصال بقاعدة البيانات"):
93
- with st.spinner("جاري الاتصال بقاعدة البيانات..."):
94
- # محاكاة تأخير الاتصال
95
- import time
96
- time.sleep(2)
97
-
98
- # إنشاء بيانات نموذجية
99
- self.data = self._create_sample_data()
100
-
101
- st.success("تم الاتصال بقاعدة البيانات بنجاح!")
102
- st.write("معاينة البيانات:")
103
- st.dataframe(self.data.head())
104
-
105
- elif data_source == "استخدام بيانات نموذجية":
106
- if st.button("تحميل بيانات نموذجية"):
107
- self.data = self._create_sample_data()
108
- st.success("تم تحميل البيانات النموذجية بنجاح!")
109
- st.write("معاينة البيانات:")
110
- st.dataframe(self.data.head())
111
-
112
- def _explore_data_tab(self):
113
- """علامة تبويب استكشاف البيانات"""
114
- st.header("استكشاف البيانات")
115
-
116
- if self.data is None:
117
- st.info("الرجاء تحميل البيانات أولاً من علامة تبويب 'تحميل البيانات'")
118
- return
119
-
120
- # عرض معلومات عامة عن البيانات
121
- st.subheader("معلومات عامة")
122
- col1, col2 = st.columns(2)
123
-
124
- with col1:
125
- st.write(f"عدد الصفوف: {self.data.shape[0]}")
126
- st.write(f"عدد الأعمدة: {self.data.shape[1]}")
127
- st.write(f"القيم المفقودة: {self.data.isna().sum().sum()}")
128
-
129
- with col2:
130
- st.write(f"أنواع البيانات:")
131
- st.write(self.data.dtypes)
132
-
133
- # عرض إحصاءات وصفية
134
- st.subheader("إحصاءات وصفية")
135
- st.dataframe(self.data.describe())
136
-
137
- # عرض معلومات عن الأعمدة
138
- st.subheader("معلومات الأعمدة")
139
-
140
- selected_column = st.selectbox("اختر عمودًا لتحليله:", self.data.columns)
141
-
142
- if selected_column:
143
- col1, col2 = st.columns(2)
144
-
145
- with col1:
146
- st.write(f"نوع البيانات: {self.data[selected_column].dtype}")
147
- st.write(f"القيم الفريدة: {self.data[selected_column].nunique()}")
148
- st.write(f"القيم المفقودة: {self.data[selected_column].isna().sum()}")
149
-
150
- with col2:
151
- if pd.api.types.is_numeric_dtype(self.data[selected_column]):
152
- st.write(f"الحد الأدنى: {self.data[selected_column].min()}")
153
- st.write(f"الحد الأقصى: {self.data[selected_column].max()}")
154
- st.write(f"المتوسط: {self.data[selected_column].mean()}")
155
- st.write(f"الوسيط: {self.data[selected_column].median()}")
156
- else:
157
- st.write("القيم الأكثر تكرارًا:")
158
- st.write(self.data[selected_column].value_counts().head())
159
-
160
- # عرض رسم بياني للعمود المحدد
161
- st.subheader(f"رسم بياني لـ {selected_column}")
162
-
163
- if pd.api.types.is_numeric_dtype(self.data[selected_column]):
164
- fig = px.histogram(self.data, x=selected_column, title=f"توزيع {selected_column}")
165
- st.plotly_chart(fig, use_container_width=True)
166
- else:
167
- # الكود المعدل لحل مشكلة الرسم البياني
168
- value_counts_df = self.data[selected_column].value_counts().reset_index()
169
- value_counts_df.columns = ['القيمة', 'العدد'] # تسمية الأعمدة بأسماء واضحة
170
- fig = px.bar(value_counts_df, x='القيمة', y='العدد', title=f"توزيع {selected_column}")
171
- fig.update_layout(xaxis_title="القيمة", yaxis_title="العدد")
172
- st.plotly_chart(fig, use_container_width=True)
173
-
174
- def _advanced_analysis_tab(self):
175
- """علامة تبويب التحليل المتقدم"""
176
- st.header("تحليل متقدم")
177
-
178
- if self.data is None:
179
- st.info("الرجاء تحميل البيانات أولاً من علامة تبويب 'تحميل البيانات'")
180
- return
181
-
182
- # أنواع التحليل المتقدم
183
- analysis_type = st.selectbox(
184
- "اختر نوع التحليل:",
185
- ["تحليل الارتباط", "تحليل الاتجاهات", "تحليل المجموعات", "تحليل التباين"]
186
- )
187
-
188
- if analysis_type == "تحليل الارتباط":
189
- st.subheader("تحليل الارتباط")
190
-
191
- # اختيار الأعمدة الرقمية فقط
192
- numeric_columns = self.data.select_dtypes(include=['number']).columns.tolist()
193
-
194
- if len(numeric_columns) < 2:
195
- st.warning("يجب أن يكون هناك عمودان رقميان على الأقل لإجراء تحليل الارتباط")
196
- return
197
-
198
- # حساب مصفوفة الارتباط
199
- correlation_matrix = self.data[numeric_columns].corr()
200
-
201
- # عرض مصفوفة الارتباط
202
- st.write("مصفوفة الارتباط:")
203
- st.dataframe(correlation_matrix)
204
-
205
- # رسم خريطة حرارية للارتباط
206
- st.write("خريطة حرارية للارتباط:")
207
- fig = px.imshow(correlation_matrix, text_auto=True, aspect="auto",
208
- title="خريطة حرارية لمصفوفة الارتباط")
209
- st.plotly_chart(fig, use_container_width=True)
210
-
211
- # تح��يل الارتباط بين عمودين محددين
212
- st.subheader("تحليل الارتباط بين عمودين محددين")
213
-
214
- col1 = st.selectbox("اختر العمود الأول:", numeric_columns, key="corr_col1")
215
- col2 = st.selectbox("اختر العمود الثاني:", numeric_columns, key="corr_col2")
216
-
217
- if col1 != col2:
218
- # حساب معامل الارتباط
219
- correlation = self.data[col1].corr(self.data[col2])
220
-
221
- st.write(f"معامل الارتباط بين {col1} و {col2}: {correlation:.4f}")
222
-
223
- # رسم مخطط التشتت
224
- fig = px.scatter(self.data, x=col1, y=col2, title=f"مخطط التشتت: {col1} مقابل {col2}")
225
- fig.update_layout(xaxis_title=col1, yaxis_title=col2)
226
- st.plotly_chart(fig, use_container_width=True)
227
- else:
228
- st.warning("الرجاء اختيار عمودين مختلفين")
229
-
230
- elif analysis_type == "تحليل الاتجاهات":
231
- st.subheader("تحليل الاتجاهات")
232
- st.info("هذه الميزة قيد التطوير")
233
-
234
- elif analysis_type == "تحليل المجموعات":
235
- st.subheader("تحليل المجموعات")
236
- st.info("هذه الميزة قيد التطوير")
237
-
238
- elif analysis_type == "تحليل التباين":
239
- st.subheader("تحليل التباين")
240
- st.info("هذه الميزة قيد التطوير")
241
-
242
- def _visualization_tab(self):
243
- """علامة تبويب التصور المرئي"""
244
- st.header("التصور المرئي")
245
-
246
- if self.data is None:
247
- st.info("الرجاء تحميل البيانات أولاً من علامة تبويب 'تحميل البيانات'")
248
- return
249
-
250
- # أنواع الرسوم البيانية
251
- chart_type = st.selectbox(
252
- "اختر نوع الرسم البياني:",
253
- ["مخطط شريطي", "مخطط خطي", "مخطط دائري", "مخطط تشتت", "مخطط صندوقي", "مخطط حراري"]
254
- )
255
-
256
- # اختيار الأعمدة حسب نوع الرسم البياني
257
- if chart_type == "مخطط شريطي":
258
- st.subheader("مخطط شريطي")
259
-
260
- x_column = st.selectbox("اختر عمود المحور الأفقي (x):", self.data.columns, key="bar_x")
261
- y_column = st.selectbox("اختر عمود المحور الرأسي (y):",
262
- self.data.select_dtypes(include=['number']).columns.tolist(),
263
- key="bar_y")
264
-
265
- # خيارات إضافية
266
- color_column = st.selectbox("اختر عمود اللون (اختياري):",
267
- ["لا يوجد"] + self.data.columns.tolist(),
268
- key="bar_color")
269
-
270
- # إنشاء الرسم البياني
271
- if color_column == "لا يوجد":
272
- fig = px.bar(self.data, x=x_column, y=y_column, title=f"{y_column} حسب {x_column}")
273
- else:
274
- fig = px.bar(self.data, x=x_column, y=y_column, color=color_column,
275
- title=f"{y_column} حسب {x_column} (مصنف حسب {color_column})")
276
-
277
- fig.update_layout(xaxis_title=x_column, yaxis_title=y_column)
278
- st.plotly_chart(fig, use_container_width=True)
279
-
280
- elif chart_type == "مخطط خطي":
281
- st.subheader("مخطط خطي")
282
-
283
- x_column = st.selectbox("اختر عمود المحور الأفقي (x):", self.data.columns, key="line_x")
284
- y_columns = st.multiselect("اختر أعمدة المحور الرأسي (y):",
285
- self.data.select_dtypes(include=['number']).columns.tolist(),
286
- key="line_y")
287
-
288
- if y_columns:
289
- # إنشاء الرسم البياني
290
- fig = go.Figure()
291
-
292
- for y_column in y_columns:
293
- fig.add_trace(go.Scatter(x=self.data[x_column], y=self.data[y_column],
294
- mode='lines+markers', name=y_column))
295
-
296
- fig.update_layout(title=f"مخطط خطي", xaxis_title=x_column, yaxis_title="القيمة")
297
- st.plotly_chart(fig, use_container_width=True)
298
- else:
299
- st.warning("الرجاء اختيار عمود واحد على الأقل للمحور الرأسي")
300
-
301
- elif chart_type == "مخطط دائري":
302
- st.subheader("مخطط دائري")
303
-
304
- column = st.selectbox("اختر العمود:", self.data.columns, key="pie_column")
305
-
306
- # إنشاء الرسم البياني
307
- # تعديل لحل مشكلة مماثلة في مخطط دائري
308
- value_counts_df = self.data[column].value_counts().reset_index()
309
- value_counts_df.columns = ['القيمة', 'العدد']
310
- fig = px.pie(value_counts_df, names='القيمة', values='العدد', title=f"توزيع {column}")
311
- st.plotly_chart(fig, use_container_width=True)
312
-
313
- elif chart_type == "مخطط تشتت":
314
- st.subheader("مخطط تشتت")
315
-
316
- numeric_columns = self.data.select_dtypes(include=['number']).columns.tolist()
317
-
318
- if len(numeric_columns) < 2:
319
- st.warning("يجب أن يكون هناك عمودان رقميان على الأقل لإنشاء مخطط تشتت")
320
- return
321
-
322
- x_column = st.selectbox("اختر عمود المحور الأفقي (x):", numeric_columns, key="scatter_x")
323
- y_column = st.selectbox("اختر عمود المحور الرأسي (y):", numeric_columns, key="scatter_y")
324
-
325
- # خيارات إضافية
326
- color_column = st.selectbox("اختر عمود اللون (اختياري):",
327
- ["لا يوجد"] + self.data.columns.tolist(),
328
- key="scatter_color")
329
-
330
- size_column = st.selectbox("اختر عمود الحجم (اختياري):",
331
- ["لا يوجد"] + numeric_columns,
332
- key="scatter_size")
333
-
334
- # إنشاء الرسم البياني
335
- if color_column == "لا يوجد" and size_column == "لا يوجد":
336
- fig = px.scatter(self.data, x=x_column, y=y_column,
337
- title=f"{y_column} مقابل {x_column}")
338
- elif color_column != "لا يوجد" and size_column == "لا يوجد":
339
- fig = px.scatter(self.data, x=x_column, y=y_column, color=color_column,
340
- title=f"{y_column} مقابل {x_column} (مصنف حسب {color_column})")
341
- elif color_column == "لا يوجد" and size_column != "لا يوجد":
342
- fig = px.scatter(self.data, x=x_column, y=y_column, size=size_column,
343
- title=f"{y_column} مقابل {x_column} (الحجم حسب {size_column})")
344
- else:
345
- fig = px.scatter(self.data, x=x_column, y=y_column, color=color_column, size=size_column,
346
- title=f"{y_column} مقابل {x_column} (مصنف حسب {color_column}, الحجم حسب {size_column})")
347
-
348
- fig.update_layout(xaxis_title=x_column, yaxis_title=y_column)
349
- st.plotly_chart(fig, use_container_width=True)
350
-
351
- elif chart_type == "مخطط صندوقي":
352
- st.subheader("مخطط صندوقي")
353
-
354
- numeric_columns = self.data.select_dtypes(include=['number']).columns.tolist()
355
-
356
- if not numeric_columns:
357
- st.warning("يجب أن يكون هناك عمود رقمي واحد على الأقل لإنشاء مخطط صندوقي")
358
- return
359
-
360
- y_column = st.selectbox("اختر عمود القيمة:", numeric_columns, key="box_y")
361
-
362
- # خيارات إضافية
363
- x_column = st.selectbox("اختر عمود التصنيف (اختياري):",
364
- ["لا يوجد"] + self.data.columns.tolist(),
365
- key="box_x")
366
-
367
- # إنشاء الرسم البياني
368
- if x_column == "لا يوجد":
369
- fig = px.box(self.data, y=y_column, title=f"مخطط صندوقي لـ {y_column}")
370
- else:
371
- fig = px.box(self.data, x=x_column, y=y_column,
372
- title=f"مخطط صندوقي لـ {y_column} حسب {x_column}")
373
-
374
- st.plotly_chart(fig, use_container_width=True)
375
-
376
- elif chart_type == "مخطط حراري":
377
- st.subheader("مخطط حراري")
378
-
379
- numeric_columns = self.data.select_dtypes(include=['number']).columns.tolist()
380
-
381
- if len(numeric_columns) < 2:
382
- st.warning("يجب أن يكون هناك عمودان رقميان على الأقل لإنشاء مخطط حراري")
383
- return
384
-
385
- # اختيار الأعمدة للمخطط الحراري
386
- selected_columns = st.multiselect("اختر الأعمدة للمخطط الحرا��ي:",
387
- numeric_columns,
388
- default=numeric_columns[:5] if len(numeric_columns) > 5 else numeric_columns)
389
-
390
- if selected_columns:
391
- # حساب مصفوفة الارتباط
392
- correlation_matrix = self.data[selected_columns].corr()
393
-
394
- # إنشاء الرسم البياني
395
- fig = px.imshow(correlation_matrix, text_auto=True, aspect="auto",
396
- title="مخطط حراري لمصفوفة الارتباط")
397
- st.plotly_chart(fig, use_container_width=True)
398
- else:
399
- st.warning("الرجاء اختيار عمود واحد على الأقل")
400
-
401
- def _reports_tab(self):
402
- """علامة تبويب التقارير"""
403
- st.header("التقارير")
404
-
405
- if self.data is None:
406
- st.info("الرجاء تحميل البيانات أولاً من علامة تبويب 'تحميل البيانات'")
407
- return
408
-
409
- st.subheader("إنشاء تقرير")
410
-
411
- # خيارات التقرير
412
- report_type = st.selectbox(
413
- "اختر نوع التقرير:",
414
- ["تقرير ملخص", "تقرير تحليلي", "تقرير مقارنة"]
415
- )
416
-
417
- if report_type == "تقرير ملخص":
418
- st.write("محتوى التقرير:")
419
-
420
- # إنشاء ملخص للبيانات
421
- st.write("### ملخص البيانات")
422
- st.write(f"عدد الصفوف: {self.data.shape[0]}")
423
- st.write(f"عدد الأعمدة: {self.data.shape[1]}")
424
-
425
- # إحصاءات وصفية
426
- st.write("### إحصاءات وصفية")
427
- st.dataframe(self.data.describe())
428
-
429
- # معلومات عن القيم المفقودة
430
- st.write("### القيم المفقودة")
431
- missing_data = pd.DataFrame({
432
- 'العمود': self.data.columns,
433
- 'عدد القيم المفقودة': self.data.isna().sum().values,
434
- 'نسبة القيم المفقودة (%)': (self.data.isna().sum().values / len(self.data) * 100).round(2)
435
- })
436
- st.dataframe(missing_data)
437
-
438
- # توزيع البيانات الرقمية
439
- st.write("### توزيع البيانات الرقمية")
440
- numeric_columns = self.data.select_dtypes(include=['number']).columns.tolist()
441
-
442
- if numeric_columns:
443
- for i in range(0, len(numeric_columns), 2):
444
- cols = st.columns(2)
445
- for j in range(2):
446
- if i + j < len(numeric_columns):
447
- col = numeric_columns[i + j]
448
- with cols[j]:
449
- fig = px.histogram(self.data, x=col, title=f"توزيع {col}")
450
- st.plotly_chart(fig, use_container_width=True)
451
-
452
- # خيارات تصدير التقرير
453
- st.subheader("تصدير التقرير")
454
- export_format = st.radio("اختر صيغة التصدير:", ["PDF", "Excel", "HTML"])
455
-
456
- if st.button("تصدير التقرير"):
457
- st.success(f"تم تصدير التقرير بصيغة {export_format} بنجاح!")
458
-
459
- elif report_type == "تقرير تحليلي":
460
- st.info("هذه الميزة قيد التطوير")
461
-
462
- elif report_type == "تقرير مقارنة":
463
- st.info("هذه الميزة قيد التطوير")
464
-
465
- def _create_sample_data(self):
466
- """إنشاء بيانات نموذجية للمناقصات"""
467
- # إنشاء تواريخ عشوائية
468
- start_date = datetime(2023, 1, 1)
469
- end_date = datetime(2025, 3, 31)
470
- days = (end_date - start_date).days
471
-
472
- # إنشاء بيانات نموذجية
473
- data = {
474
- 'رقم المناقصة': [f'T-{i:04d}' for i in range(1, 101)],
475
- 'اسم المشروع': [f'مشروع {i}' for i in range(1, 101)],
476
- 'نوع المشروع': np.random.choice(['بناء', 'صيانة', 'تطوير', 'توريد', 'خدمات'], 100),
477
- 'الموقع': np.random.choice(['الرياض', 'جدة', 'الدمام', 'مكة', 'المدينة', 'تبوك', 'أبها'], 100),
478
- 'تاريخ الإعلان': [start_date + pd.Timedelta(days=np.random.randint(0, days)) for _ in range(100)],
479
- 'تاريخ الإغلاق': [start_date + pd.Timedelta(days=np.random.randint(30, days)) for _ in range(100)],
480
- 'الميزانية التقديرية': np.random.uniform(1000000, 50000000, 100),
481
- 'عدد المتقدمين': np.random.randint(1, 20, 100),
482
- 'سعر العرض': np.random.uniform(900000, 55000000, 100),
483
- 'نسبة الفوز (%)': np.random.uniform(0, 100, 100),
484
- 'مدة التنفيذ (أشهر)': np.random.randint(3, 36, 100),
485
- 'عدد العمال': np.random.randint(10, 500, 100),
486
- 'تكلفة المواد': np.random.uniform(500000, 30000000, 100),
487
- 'تكلفة العمالة': np.random.uniform(200000, 15000000, 100),
488
- 'تكلفة المعدات': np.random.uniform(100000, 10000000, 100),
489
- 'هامش الربح (%)': np.random.uniform(5, 25, 100),
490
- 'درجة المخاطرة': np.random.choice(['منخفضة', 'متوسطة', 'عالية'], 100),
491
- 'حالة المناقصة': np.random.choice(['جارية', 'مغلقة', 'ملغاة', 'فائزة', 'خاسرة'], 100)
492
- }
493
-
494
- # إنشاء DataFrame
495
- df = pd.DataFrame(data)
496
-
497
- # إضافة بعض العلاقات المنطقية
498
- df['إجمالي التكلفة'] = df['تكلفة المواد'] + df['تكلفة العمالة'] + df['تكلفة المعدات']
499
- df['الربح المتوقع'] = df['سعر العرض'] - df['إجمالي التكلفة']
500
- df['نسبة التكلفة من العرض (%)'] = (df['إجمالي التكلفة'] / df['سعر العرض'] * 100).round(2)
501
-
502
- return df
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime
7
+ import io
8
+ import os
9
+ import json
10
+ from pathlib import Path
11
+
12
+ class DataAnalysisApp:
13
+ def __init__(self):
14
+ self.data = None
15
+ self.file_path = None
16
+
17
+ # تهيئة حالة الجلسة
18
+ if 'analysis_data' not in st.session_state:
19
+ st.session_state.analysis_data = {
20
+ 'uploaded_files': {},
21
+ 'analysis_results': {},
22
+ 'ai_insights': {}
23
+ }
24
+
25
+ def run(self):
26
+ st.title("تحليل البيانات المتقدم")
27
+
28
+ # إنشاء التبويبات
29
+ tabs = st.tabs([
30
+ "تحميل وإدارة البيانات",
31
+ "التحليل الإحصائي",
32
+ "التحليل المرئي",
33
+ "تحليل الذكاء الاصطناعي",
34
+ "التقارير"
35
+ ])
36
+
37
+ with tabs[0]:
38
+ self._render_data_management()
39
+
40
+ with tabs[1]:
41
+ self._render_statistical_analysis()
42
+
43
+ with tabs[2]:
44
+ self._render_visualization()
45
+
46
+ with tabs[3]:
47
+ self._render_ai_analysis()
48
+
49
+ with tabs[4]:
50
+ self._render_reports()
51
+
52
+ def _render_data_management(self):
53
+ st.header("تحميل وإدارة البيانات")
54
+
55
+ # منطقة تحميل الملفات
56
+ uploaded_files = st.file_uploader(
57
+ "قم بتحميل ملفات البيانات",
58
+ type=["csv", "xlsx", "xls", "pdf"],
59
+ accept_multiple_files=True,
60
+ key="data_files"
61
+ )
62
+
63
+ if uploaded_files:
64
+ for file in uploaded_files:
65
+ try:
66
+ if file.name.endswith('.pdf'):
67
+ import PyPDF2
68
+ pdf_reader = PyPDF2.PdfReader(file)
69
+ text_content = ""
70
+ for page in pdf_reader.pages:
71
+ text_content += page.extract_text()
72
+
73
+ st.session_state.analysis_data['uploaded_files'][file.name] = {
74
+ 'data': text_content,
75
+ 'metadata': {
76
+ 'pages': len(pdf_reader.pages),
77
+ 'upload_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
78
+ }
79
+ }
80
+ st.success(f"تم تحميل الملف PDF {file.name} بنجاح!")
81
+
82
+ elif file.name.endswith('.csv'):
83
+ df = pd.read_csv(file)
84
+ st.session_state.analysis_data['uploaded_files'][file.name] = {
85
+ 'data': df,
86
+ 'metadata': {
87
+ 'rows': len(df),
88
+ 'columns': len(df.columns),
89
+ 'upload_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
90
+ }
91
+ }
92
+ st.success(f"تم تحميل الملف {file.name} بنجاح!")
93
+ else:
94
+ df = pd.read_excel(file)
95
+ st.session_state.analysis_data['uploaded_files'][file.name] = {
96
+ 'data': df,
97
+ 'metadata': {
98
+ 'rows': len(df),
99
+ 'columns': len(df.columns),
100
+ 'upload_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
101
+ }
102
+ }
103
+ st.success(f"تم تحميل الملف {file.name} بنجاح!")
104
+
105
+ except Exception as e:
106
+ st.error(f"خطأ في تحميل الملف {file.name}: {str(e)}")
107
+
108
+ # عرض الملفات المحملة
109
+ if st.session_state.analysis_data['uploaded_files']:
110
+ st.subheader("الملفات المحملة")
111
+ for filename, file_info in st.session_state.analysis_data['uploaded_files'].items():
112
+ with st.expander(f"📄 {filename}"):
113
+ st.write("معلومات الملف:")
114
+ if 'rows' in file_info['metadata']:
115
+ st.write(f"- عدد الصفوف: {file_info['metadata']['rows']}")
116
+ if 'columns' in file_info['metadata']:
117
+ st.write(f"- عدد الأعمدة: {file_info['metadata']['columns']}")
118
+ if 'pages' in file_info['metadata']:
119
+ st.write(f"- عدد الصفحات: {file_info['metadata']['pages']}")
120
+ st.write(f"- وقت التحميل: {file_info['metadata']['upload_time']}")
121
+
122
+ if st.button(f"عرض البيانات", key=f"show_{filename}"):
123
+ st.write(file_info['data'].head() if isinstance(file_info['data'], pd.DataFrame) else file_info['data'])
124
+
125
+ def _render_statistical_analysis(self):
126
+ st.header("التحليل الإحصائي")
127
+
128
+ if not st.session_state.analysis_data['uploaded_files']:
129
+ st.info("الرجاء تحميل البيانات أولاً")
130
+ return
131
+
132
+ # اختيار الملف للتحليل
133
+ selected_file = st.selectbox(
134
+ "اختر الملف للتحليل",
135
+ list(st.session_state.analysis_data['uploaded_files'].keys())
136
+ )
137
+
138
+ if selected_file:
139
+ data = st.session_state.analysis_data['uploaded_files'][selected_file]['data']
140
+ if isinstance(data, pd.DataFrame):
141
+ # الإحصاءات الوصفية
142
+ st.subheader("الإحصاءات الوصفية")
143
+ st.dataframe(data.describe())
144
+
145
+ # تحليل القيم المفقودة
146
+ st.subheader("تحليل القيم المفقودة")
147
+ missing_data = pd.DataFrame({
148
+ 'العمود': data.columns,
149
+ 'عدد القيم المفقودة': data.isnull().sum(),
150
+ 'نسبة القيم المفقودة (%)': (data.isnull().sum() / len(data) * 100).round(2)
151
+ })
152
+ st.dataframe(missing_data)
153
+ else:
154
+ st.info("لا يمكن إجراء تحليل إحصائي على ملفات PDF.")
155
+
156
+
157
+ def _render_visualization(self):
158
+ st.header("التحليل المرئي")
159
+
160
+ if not st.session_state.analysis_data['uploaded_files']:
161
+ st.info("الرجاء تحميل البيانات أولاً")
162
+ return
163
+
164
+ selected_file = st.selectbox(
165
+ "اختر الملف للتحليل المرئي",
166
+ list(st.session_state.analysis_data['uploaded_files'].keys()),
167
+ key="viz_file_select"
168
+ )
169
+
170
+ if selected_file:
171
+ data = st.session_state.analysis_data['uploaded_files'][selected_file]['data']
172
+ if isinstance(data, pd.DataFrame):
173
+ # اختيار نوع المخطط
174
+ chart_type = st.selectbox(
175
+ "اختر نوع المخطط",
176
+ ["رسم بياني شريطي", "رسم بياني خطي", "رسم بياني دائري", "مخطط التشتت", "مخطط الصندوق"],
177
+ key="chart_type"
178
+ )
179
+
180
+ # تخصيص المخطط
181
+ if chart_type == "رسم بياني شريطي":
182
+ x_col = st.selectbox("اختر محور X", data.columns, key="bar_x")
183
+ y_col = st.selectbox("اختر محور Y", data.select_dtypes(include=['number']).columns, key="bar_y")
184
+ fig = px.bar(data, x=x_col, y=y_col)
185
+ st.plotly_chart(fig, use_container_width=True)
186
+ elif chart_type == "رسم بياني خطي":
187
+ x_col = st.selectbox("اختر محور X", data.columns, key="line_x")
188
+ y_cols = st.multiselect("اختر محاور Y", data.select_dtypes(include=['number']).columns, key="line_y")
189
+ if y_cols:
190
+ fig = go.Figure()
191
+ for y_col in y_cols:
192
+ fig.add_trace(go.Scatter(x=data[x_col], y=data[y_col], mode='lines+markers', name=y_col))
193
+ fig.update_layout(title=f"مخطط خطي", xaxis_title=x_col, yaxis_title="القيمة")
194
+ st.plotly_chart(fig, use_container_width=True)
195
+ else:
196
+ st.warning("الرجاء اختيار عمود واحد على الأقل للمحور الرأسي")
197
+
198
+ elif chart_type == "رسم بياني دائري":
199
+ col = st.selectbox("اختر العمود", data.columns, key="pie_column")
200
+ value_counts_df = data[col].value_counts().reset_index()
201
+ value_counts_df.columns = ['القيمة', 'العدد']
202
+ fig = px.pie(value_counts_df, names='القيمة', values='العدد', title=f"توزيع {col}")
203
+ st.plotly_chart(fig, use_container_width=True)
204
+ elif chart_type == "مخطط التشتت":
205
+ numeric_columns = data.select_dtypes(include=['number']).columns.tolist()
206
+ if len(numeric_columns) < 2:
207
+ st.warning("يجب أن يكون هناك عمودان رقميان على الأقل لإنشاء مخطط تشتت")
208
+ return
209
+ x_column = st.selectbox("اختر عمود المحور الأفقي (x):", numeric_columns, key="scatter_x")
210
+ y_column = st.selectbox("اختر عمود المحور الرأسي (y):", numeric_columns, key="scatter_y")
211
+ fig = px.scatter(data, x=x_column, y=y_column)
212
+ st.plotly_chart(fig, use_container_width=True)
213
+ elif chart_type == "مخطط الصندوق":
214
+ numeric_columns = data.select_dtypes(include=['number']).columns.tolist()
215
+ if not numeric_columns:
216
+ st.warning("يجب أن يكون هناك عمود رقمي واحد على الأقل لإنشاء مخطط صندوقي")
217
+ return
218
+ y_column = st.selectbox("اختر عمود القيمة:", numeric_columns, key="box_y")
219
+ fig = px.box(data, y=y_column, title=f"مخطط صندوقي لـ {y_column}")
220
+ st.plotly_chart(fig, use_container_width=True)
221
+ else:
222
+ st.info("لا يمكن إنشاء مخططات مرئية من ملفات PDF.")
223
+
224
+ def _render_ai_analysis(self):
225
+ st.header("تحليل الذكاء الاصطناعي")
226
+
227
+ if not st.session_state.analysis_data['uploaded_files']:
228
+ st.info("الرجاء تحميل البيانات أولاً")
229
+ return
230
+
231
+ selected_file = st.selectbox(
232
+ "اختر الملف للتحليل",
233
+ list(st.session_state.analysis_data['uploaded_files'].keys()),
234
+ key="ai_file_select"
235
+ )
236
+
237
+ if selected_file:
238
+ data = st.session_state.analysis_data['uploaded_files'][selected_file]['data']
239
+ if isinstance(data, pd.DataFrame):
240
+ analysis_type = st.selectbox(
241
+ "اختر نوع التحليل",
242
+ ["تحليل الاتجاهات", "التنبؤ", "اكتشاف الأنماط", "تحليل العلاقات"]
243
+ )
244
+
245
+ if st.button("تحليل البيانات"):
246
+ with st.spinner("جاري تحليل البيانات..."):
247
+ # محاكاة تحليل الذكاء الاصطناعي
248
+ st.session_state.analysis_data['ai_insights'][selected_file] = {
249
+ 'trends': self._analyze_trends(data),
250
+ 'patterns': self._analyze_patterns(data),
251
+ 'correlations': self._analyze_correlations(data)
252
+ }
253
+
254
+ st.success("تم اكتمال التحليل!")
255
+
256
+ # عرض النتائج
257
+ st.json(st.session_state.analysis_data['ai_insights'][selected_file])
258
+ else:
259
+ st.info("لا يمكن إجراء تحليل ذكاء اصطناعي على ملفات PDF.")
260
+
261
+ def _render_reports(self):
262
+ st.header("التقارير")
263
+
264
+ if not st.session_state.analysis_data['uploaded_files']:
265
+ st.info("الرجاء تحميل البيانات أولاً")
266
+ return
267
+
268
+ # إنشاء تقرير
269
+ if st.button("إنشاء تقرير تحليلي شامل"):
270
+ report_data = self._generate_comprehensive_report()
271
+
272
+ # تصدير التقرير
273
+ output = io.BytesIO()
274
+ with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
275
+ for sheet_name, data in report_data.items():
276
+ pd.DataFrame(data).to_excel(writer, sheet_name=sheet_name)
277
+
278
+ st.download_button(
279
+ label="تحميل التقرير",
280
+ data=output.getvalue(),
281
+ file_name=f"analytical_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
282
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
283
+ )
284
+
285
+ def _analyze_trends(self, df):
286
+ """تحليل الاتجاهات في البيانات"""
287
+ # محاكاة تحليل الاتجاهات
288
+ return {
289
+ "trend_1": "اتجاه تصاعدي في المبيعات",
290
+ "trend_2": "انخفاض في التكاليف التشغيلية",
291
+ "trend_3": "زيادة في رضا العملاء"
292
+ }
293
+
294
+ def _analyze_patterns(self, df):
295
+ """اكتشاف الأنماط في البيانات"""
296
+ # محاكاة اكتشاف الأنماط
297
+ return {
298
+ "pattern_1": "نمط موسمي في الطلب",
299
+ "pattern_2": "نمط دوري في الإنتاج",
300
+ "pattern_3": "نمط جغرافي في التوزيع"
301
+ }
302
+
303
+ def _analyze_correlations(self, df):
304
+ """تحليل العلاقات بين المتغيرات"""
305
+ # محاكاة تحليل العلاقات
306
+ numeric_cols = df.select_dtypes(include=['number']).columns
307
+ correlations = df[numeric_cols].corr().round(2).to_dict()
308
+ return correlations
309
+
310
+ def _generate_comprehensive_report(self):
311
+ """إنشاء تقرير شامل"""
312
+ report = {
313
+ 'ملخص_البيانات': {},
314
+ 'التحليل_الإحصائي': {},
315
+ 'تحليل_الذكاء_الاصطناعي': {},
316
+ 'التوصيات': {}
317
+ }
318
+
319
+ for filename, file_info in st.session_state.analysis_data['uploaded_files'].items():
320
+ report['ملخص_البيانات'][filename] = file_info['metadata']
321
+
322
+ if 'ai_insights' in st.session_state.analysis_data:
323
+ report['تحليل_الذكاء_الاصطناعي'] = st.session_state.analysis_data['ai_insights']
324
+
325
+ return report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/document_analysis/analyzer.py CHANGED
@@ -1,281 +1,434 @@
1
- """
2
- وحدة تحليل المستندات لنظام إدارة المناقصات - Hybrid Face
3
- """
4
-
5
- import os
6
- import re
7
- import logging
8
- import threading
9
- from pathlib import Path
10
- import datetime
11
- import json
12
-
13
- # تهيئة السجل
14
- logging.basicConfig(
15
- level=logging.INFO,
16
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17
- )
18
- logger = logging.getLogger('document_analysis')
19
-
20
- class DocumentAnalyzer:
21
- """فئة تحليل المستندات"""
22
-
23
- def __init__(self, config=None):
24
- """تهيئة محلل المستندات"""
25
- self.config = config
26
- self.analysis_in_progress = False
27
- self.current_document = None
28
- self.analysis_results = {}
29
-
30
- # إنشاء مجلد المستندات إذا لم يكن موجوداً
31
- if config and hasattr(config, 'DOCUMENTS_PATH'):
32
- self.documents_path = Path(config.DOCUMENTS_PATH)
33
- else:
34
- self.documents_path = Path('data/documents')
35
-
36
- if not self.documents_path.exists():
37
- self.documents_path.mkdir(parents=True, exist_ok=True)
38
-
39
- def analyze_document(self, document_path, document_type="tender", callback=None):
40
- """تحليل مستند"""
41
- if self.analysis_in_progress:
42
- logger.warning("هناك عملية تحليل جارية بالفعل")
43
- return False
44
-
45
- if not os.path.exists(document_path):
46
- logger.error(f"المستند غير موجود: {document_path}")
47
- return False
48
-
49
- self.analysis_in_progress = True
50
- self.current_document = document_path
51
- self.analysis_results = {
52
- "document_path": document_path,
53
- "document_type": document_type,
54
- "analysis_start_time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
55
- "status": "جاري التحليل",
56
- "items": [],
57
- "entities": [],
58
- "dates": [],
59
- "amounts": [],
60
- "risks": []
61
- }
62
-
63
- # بدء التحليل في خيط منفصل
64
- thread = threading.Thread(
65
- target=self._analyze_document_thread,
66
- args=(document_path, document_type, callback)
67
- )
68
- thread.daemon = True
69
- thread.start()
70
-
71
- return True
72
-
73
- def _analyze_document_thread(self, document_path, document_type, callback):
74
- """خيط تحليل المستند"""
75
- try:
76
- # تحديد نوع المستند
77
- file_extension = os.path.splitext(document_path)[1].lower()
78
-
79
- if file_extension == '.pdf':
80
- self._analyze_pdf(document_path, document_type)
81
- elif file_extension == '.docx':
82
- self._analyze_docx(document_path, document_type)
83
- elif file_extension == '.xlsx':
84
- self._analyze_xlsx(document_path, document_type)
85
- elif file_extension == '.txt':
86
- self._analyze_txt(document_path, document_type)
87
- else:
88
- logger.error(f"نوع المستند غير مدعوم: {file_extension}")
89
- self.analysis_results["status"] = "فشل التحليل"
90
- self.analysis_results["error"] = "نوع المستند غير مدعوم"
91
-
92
- # تحديث حالة التحليل
93
- if self.analysis_results["status"] != "فشل التحليل":
94
- self.analysis_results["status"] = "اكتمل التحليل"
95
- self.analysis_results["analysis_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
96
-
97
- logger.info(f"اكتمل تحليل المستند: {document_path}")
98
-
99
- except Exception as e:
100
- logger.error(f"خطأ في تحليل المستند: {str(e)}")
101
- self.analysis_results["status"] = "فشل التحليل"
102
- self.analysis_results["error"] = str(e)
103
-
104
- finally:
105
- self.analysis_in_progress = False
106
-
107
- # استدعاء دالة الاستجابة إذا تم توفيرها
108
- if callback and callable(callback):
109
- callback(self.analysis_results)
110
-
111
- def _analyze_pdf(self, document_path, document_type):
112
- """تحليل مستند PDF"""
113
- try:
114
- # محاكاة تحليل مستند PDF
115
- logger.info(f"تحليل مستند PDF: {document_path}")
116
-
117
- # في التطبيق الفعلي، سيتم استخدام مكتبة مثل PyPDF2 أو pdfplumber
118
- # لاستخراج النص من ملف PDF وتحليله
119
-
120
- # محاكاة استخراج البنود
121
- self.analysis_results["items"] = [
122
- {"id": 1, "name": "أعمال ال��فر", "description": "حفر وإزالة التربة", "unit": "م³", "estimated_quantity": 1500},
123
- {"id": 2, "name": "أعمال الخرسانة", "description": "صب خرسانة مسلحة", "unit": "م³", "estimated_quantity": 750},
124
- {"id": 3, "name": "أعمال الأسفلت", "description": "تمهيد وفرش طبقة أسفلت", "unit": "م²", "estimated_quantity": 5000}
125
- ]
126
-
127
- # محاكاة استخراج الكيانات
128
- self.analysis_results["entities"] = [
129
- {"type": "client", "name": "وزارة النقل", "mentions": 5},
130
- {"type": "location", "name": "المنطقة الشرقية", "mentions": 3},
131
- {"type": "contractor", "name": "شركة المقاولات المتحدة", "mentions": 2}
132
- ]
133
-
134
- # محاكاة استخراج التواريخ
135
- self.analysis_results["dates"] = [
136
- {"type": "start_date", "date": "2025-05-01", "description": "تاريخ بدء المشروع"},
137
- {"type": "end_date", "date": "2025-11-30", "description": "تاريخ انتهاء المشروع"},
138
- {"type": "submission_date", "date": "2025-04-15", "description": "تاريخ تقديم العروض"}
139
- ]
140
-
141
- # محاكاة استخراج المبالغ
142
- self.analysis_results["amounts"] = [
143
- {"type": "estimated_cost", "amount": 5000000, "currency": "SAR", "description": "التكلفة التقديرية للمشروع"},
144
- {"type": "advance_payment", "amount": 500000, "currency": "SAR", "description": "الدفعة المقدمة (10%)"},
145
- {"type": "performance_bond", "amount": 250000, "currency": "SAR", "description": "ضمان حسن التنفيذ (5%)"}
146
- ]
147
-
148
- # محاكاة استخراج المخاطر
149
- self.analysis_results["risks"] = [
150
- {"type": "delay_risk", "description": "مخاطر التأخير في التنفيذ", "probability": "متوسط", "impact": "عالي"},
151
- {"type": "cost_risk", "description": "مخاطر زيادة التكاليف", "probability": "عالي", "impact": "عالي"},
152
- {"type": "quality_risk", "description": "مخاطر جودة التنفيذ", "probability": "منخفض", "impact": "متوسط"}
153
- ]
154
-
155
- except Exception as e:
156
- logger.error(f"خطأ في تحليل مستند PDF: {str(e)}")
157
- raise
158
-
159
- def _analyze_docx(self, document_path, document_type):
160
- """تحليل مستند Word"""
161
- try:
162
- # محاكاة تحليل مستند Word
163
- logger.info(f"تحليل مستند Word: {document_path}")
164
-
165
- # في التطبيق الفعلي، سيتم استخدام مكتبة مثل python-docx
166
- # لاستخراج النص من ملف Word وتحليله
167
-
168
- # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
169
- # (مشابه لتحليل PDF)
170
- self.analysis_results["items"] = [
171
- {"id": 1, "name": "توريد معدات", "description": "توريد معدات المشروع", "unit": "مجموعة", "estimated_quantity": 10},
172
- {"id": 2, "name": "تركيب المعدات", "description": "تركيب وتشغيل المعدات", "unit": "مجموعة", "estimated_quantity": 10},
173
- {"id": 3, "name": "التدريب", "description": "تدريب الموظفين على استخدام المعدات", "unit": "يوم", "estimated_quantity": 20}
174
- ]
175
-
176
- # محاكاة استخراج الكيانات والتواريخ والمبالغ والمخاطر
177
- # (مشابه لتحليل PDF)
178
-
179
- except Exception as e:
180
- logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
181
- raise
182
-
183
- def _analyze_xlsx(self, document_path, document_type):
184
- """تحليل مستند Excel"""
185
- try:
186
- # محاكاة تحليل مستند Excel
187
- logger.info(f"تحليل مستند Excel: {document_path}")
188
-
189
- # في التطبيق الفعلي، سيتم استخدام مكتبة مثل pandas أو openpyxl
190
- # لاستخراج البيانات من ملف Excel وتحليلها
191
-
192
- # محاكاة استخراج البنود
193
- self.analysis_results["items"] = [
194
- {"id": 1, "name": "بند 1", "description": "وصف البند 1", "unit": "وحدة", "estimated_quantity": 100},
195
- {"id": 2, "name": "بند 2", "description": "وصف البند 2", "unit": "وحدة", "estimated_quantity": 200},
196
- {"id": 3, "name": "بند 3", "description": "وصف البند 3", "unit": "وحدة", "estimated_quantity": 300}
197
- ]
198
-
199
- # محاكاة استخراج المبالغ
200
- self.analysis_results["amounts"] = [
201
- {"type": "item_cost", "amount": 10000, "currency": "SAR", "description": "تكلفة البند 1"},
202
- {"type": "item_cost", "amount": 20000, "currency": "SAR", "description": "تكلفة البند 2"},
203
- {"type": "item_cost", "amount": 30000, "currency": "SAR", "description": "تكلفة البند 3"}
204
- ]
205
-
206
- except Exception as e:
207
- logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
208
- raise
209
-
210
- def _analyze_txt(self, document_path, document_type):
211
- """تحليل مستند نصي"""
212
- try:
213
- # محاكاة تحليل مستند نصي
214
- logger.info(f"تحليل مستند نصي: {document_path}")
215
-
216
- # في التطبيق الفعلي، سيتم قراءة الملف النصي وتحليله
217
-
218
- # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
219
- # (مشابه للتحليلات الأخرى)
220
-
221
- except Exception as e:
222
- logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
223
- raise
224
-
225
- def get_analysis_status(self):
226
- """الحصول على حالة التحليل الحالي"""
227
- if not self.analysis_in_progress:
228
- if not self.analysis_results:
229
- return {"status": "لا يوجد تحليل جارٍ"}
230
- else:
231
- return {"status": self.analysis_results.get("status", "غير معروف")}
232
-
233
- return {
234
- "status": "جاري التحليل",
235
- "document_path": self.current_document,
236
- "start_time": self.analysis_results.get("analysis_start_time")
237
- }
238
-
239
- def get_analysis_results(self):
240
- """الحصول على نتائج التحليل"""
241
- return self.analysis_results
242
-
243
- def export_analysis_results(self, output_path=None):
244
- """تصدير نتائج التحليل إلى ملف JSON"""
245
- if not self.analysis_results:
246
- logger.warning("لا توجد نتائج تحليل للتصدير")
247
- return None
248
-
249
- if not output_path:
250
- # إنشاء اسم ملف افتراضي
251
- timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
252
- filename = f"analysis_results_{timestamp}.json"
253
- output_path = os.path.join(self.documents_path, filename)
254
-
255
- try:
256
- with open(output_path, 'w', encoding='utf-8') as f:
257
- json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
258
-
259
- logger.info(f"تم تصدير نتائج التحليل إلى: {output_path}")
260
- return output_path
261
-
262
- except Exception as e:
263
- logger.error(f"خطأ في تصدير نتائج التحليل: {str(e)}")
264
- return None
265
-
266
- def import_analysis_results(self, input_path):
267
- """استيراد نتائج التحليل من ملف JSON"""
268
- if not os.path.exists(input_path):
269
- logger.error(f"ملف نتائج التحليل غير موجود: {input_path}")
270
- return False
271
-
272
- try:
273
- with open(input_path, 'r', encoding='utf-8') as f:
274
- self.analysis_results = json.load(f)
275
-
276
- logger.info(f"تم استيراد نتائج التحليل من: {input_path}")
277
- return True
278
-
279
- except Exception as e:
280
- logger.error(f"خطأ في استيراد نتائج التحليل: {str(e)}")
281
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وحدة تحليل المستندات لنظام إدارة المناقصات - Hybrid Face
3
+ """
4
+
5
+ import os
6
+ import re
7
+ import logging
8
+ import threading
9
+ from pathlib import Path
10
+ import datetime
11
+ import json
12
+
13
+ # تهيئة السجل
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17
+ )
18
+ logger = logging.getLogger('document_analysis')
19
+
20
+ class DocumentAnalyzer:
21
+ """فئة تحليل المستندات"""
22
+
23
+ def __init__(self, config=None):
24
+ """تهيئة محلل المستندات"""
25
+ self.config = config
26
+ self.analysis_in_progress = False
27
+ self.current_document = None
28
+ self.analysis_results = {}
29
+
30
+ # إنشاء مجلد المستندات إذا لم يكن موجوداً
31
+ if config and hasattr(config, 'DOCUMENTS_PATH'):
32
+ self.documents_path = Path(config.DOCUMENTS_PATH)
33
+ else:
34
+ self.documents_path = Path('data/documents')
35
+
36
+ if not self.documents_path.exists():
37
+ self.documents_path.mkdir(parents=True, exist_ok=True)
38
+
39
+ def analyze_document(self, document_path, document_type="tender", callback=None):
40
+ """تحليل مستند"""
41
+ if self.analysis_in_progress:
42
+ logger.warning("هناك عملية تحليل جارية بالفعل")
43
+ return False
44
+
45
+ if not os.path.exists(document_path):
46
+ logger.error(f"المستند غير موجود: {document_path}")
47
+ return False
48
+
49
+ self.analysis_in_progress = True
50
+ self.current_document = document_path
51
+ self.analysis_results = {
52
+ "document_path": document_path,
53
+ "document_type": document_type,
54
+ "analysis_start_time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
55
+ "status": "جاري التحليل",
56
+ "items": [],
57
+ "entities": [],
58
+ "dates": [],
59
+ "amounts": [],
60
+ "risks": []
61
+ }
62
+
63
+ # بدء التحليل في خيط منفصل
64
+ thread = threading.Thread(
65
+ target=self._analyze_document_thread,
66
+ args=(document_path, document_type, callback)
67
+ )
68
+ thread.daemon = True
69
+ thread.start()
70
+
71
+ return True
72
+
73
+ def _analyze_document_thread(self, document_path, document_type, callback):
74
+ """خيط تحليل المستند"""
75
+ try:
76
+ # تحديد نوع المستند
77
+ file_extension = os.path.splitext(document_path)[1].lower()
78
+
79
+ if file_extension == '.pdf':
80
+ self.analysis_results = self._analyze_pdf(document_path, document_type)
81
+ elif file_extension == '.docx':
82
+ self._analyze_docx(document_path, document_type)
83
+ elif file_extension == '.xlsx':
84
+ self._analyze_xlsx(document_path, document_type)
85
+ elif file_extension == '.txt':
86
+ self._analyze_txt(document_path, document_type)
87
+ else:
88
+ logger.error(f"نوع المستند غير مدعوم: {file_extension}")
89
+ self.analysis_results["status"] = "فشل التحليل"
90
+ self.analysis_results["error"] = "نوع المستند غير مدعوم"
91
+
92
+ # تحديث حالة التحليل
93
+ if self.analysis_results["status"] != "فشل التحليل":
94
+ self.analysis_results["status"] = "اكتمل التحليل"
95
+ self.analysis_results["analysis_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
96
+
97
+ logger.info(f"اكتمل تحليل المستند: {document_path}")
98
+
99
+ except Exception as e:
100
+ logger.error(f"خطأ في تحليل المستند: {str(e)}")
101
+ self.analysis_results["status"] = "فشل التحليل"
102
+ self.analysis_results["error"] = str(e)
103
+
104
+ finally:
105
+ self.analysis_in_progress = False
106
+
107
+ # استدعاء دالة الاستجابة إذا تم توفيرها
108
+ if callback and callable(callback):
109
+ callback(self.analysis_results)
110
+
111
+ def _analyze_pdf(self, document_path, document_type):
112
+ """تحليل مستند PDF باستخدام الذكاء الاصطناعي"""
113
+ try:
114
+ # استخراج النص من PDF
115
+ text = self._extract_text_from_pdf(document_path)
116
+
117
+ # تحليل متقدم للمستند
118
+ analysis = {
119
+ "file_info": {
120
+ "name": os.path.basename(document_path),
121
+ "type": "PDF",
122
+ "size": os.path.getsize(document_path),
123
+ "pages": self._count_pages(document_path),
124
+ "create_date": self._get_creation_date(document_path),
125
+ "modify_date": time.ctime(os.path.getmtime(document_path))
126
+ },
127
+ "content_analysis": {
128
+ "contract_terms": self._analyze_contract_terms(text),
129
+ "financial_analysis": self._analyze_financial_terms(text),
130
+ "legal_analysis": self._analyze_legal_terms(text),
131
+ "risk_analysis": self._analyze_risks(text),
132
+ "conditions_analysis": self._analyze_conditions(text),
133
+ "technical_specs": self._analyze_technical_specs(text),
134
+ "key_dates": self._extract_key_dates(text),
135
+ "important_figures": self._extract_figures(text),
136
+ "entities": self._extract_entities(text)
137
+ },
138
+ "statistical_analysis": {
139
+ "word_count": len(text.split()),
140
+ "unique_terms": self._analyze_unique_terms(text),
141
+ "topic_distribution": self._analyze_topics(text),
142
+ "complexity_score": self._calculate_complexity(text)
143
+ },
144
+ "compliance_check": {
145
+ "missing_sections": self._check_missing_sections(text),
146
+ "required_terms": self._check_required_terms(text),
147
+ "compliance_score": self._calculate_compliance_score(text)
148
+ },
149
+ "summary": self._generate_summary(text),
150
+ "recommendations": self._generate_recommendations(text),
151
+ "related_documents": self._find_related_documents(document_path),
152
+ "version_info": self._get_version_info(document_path)
153
+ }
154
+
155
+ # إضافة تحليل متخصص حسب نوع المستند
156
+ if document_type == "tender":
157
+ analysis["tender_analysis"] = self._analyze_tender_specifics(text)
158
+ elif document_type == "contract":
159
+ analysis["contract_analysis"] = self._analyze_contract_specifics(text)
160
+ elif document_type == "technical":
161
+ analysis["technical_analysis"] = self._analyze_technical_specifics(text)
162
+
163
+ return analysis
164
+
165
+ except Exception as e:
166
+ logger.error(f"خطأ في تحليل PDF: {str(e)}")
167
+ raise
168
+
169
+ def _extract_text_from_pdf(self, document_path):
170
+ """استخراج النص من ملف PDF (تحتاج إلى مكتبة مثل PyPDF2 أو pdfplumber)"""
171
+ # Implementation using a PDF processing library like PyPDF2 or pdfplumber is needed here.
172
+ # This is a placeholder. Replace with actual PDF text extraction.
173
+ return "Placeholder text extracted from PDF"
174
+
175
+ def _analyze_contract_terms(self, text):
176
+ """تحليل بنود العقد"""
177
+ # Implementation for contract term analysis is needed here. This is a placeholder.
178
+ return "Placeholder contract terms analysis"
179
+
180
+ def _analyze_financial_terms(self, text):
181
+ """تحليل الجزء المالي"""
182
+ # Implementation for financial term analysis is needed here. This is a placeholder.
183
+ return "Placeholder financial terms analysis"
184
+
185
+ def _analyze_legal_terms(self, text):
186
+ """تحليل القانوني للعقد"""
187
+ # Implementation for legal term analysis is needed here. This is a placeholder.
188
+ return "Placeholder legal terms analysis"
189
+
190
+ def _analyze_risks(self, text):
191
+ """تحليل المخاطر"""
192
+ # Implementation for risk analysis is needed here. This is a placeholder.
193
+ return "Placeholder risk analysis"
194
+
195
+ def _analyze_conditions(self, text):
196
+ """دراسة كراسة الشروط"""
197
+ # Implementation for conditions analysis is needed here. This is a placeholder.
198
+ return "Placeholder conditions analysis"
199
+
200
+ def _generate_summary(self, text):
201
+ """توليد ملخص"""
202
+ # Implementation for summary generation is needed here. This is a placeholder.
203
+ return "Placeholder summary"
204
+
205
+ def _generate_recommendations(self, text):
206
+ """توليد التوصيات"""
207
+ # Implementation for recommendation generation is needed here. This is a placeholder.
208
+ return "Placeholder recommendations"
209
+
210
+
211
+
212
+ def _analyze_docx(self, document_path, document_type):
213
+ """تحليل مستند Word"""
214
+ try:
215
+ # محاكاة تحليل مستند Word
216
+ logger.info(f"تحليل مستند Word: {document_path}")
217
+
218
+ # في التطبيق الفعلي، سيتم استخدام مكتبة مثل python-docx
219
+ # لاستخراج النص من ملف Word وتحليله
220
+
221
+ # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
222
+ # (مشابه لتحليل PDF)
223
+ self.analysis_results["items"] = [
224
+ {"id": 1, "name": "توريد معدات", "description": "توريد معدات المشروع", "unit": "مجموعة", "estimated_quantity": 10},
225
+ {"id": 2, "name": "تركيب المعدات", "description": "تركيب وتشغيل المعدات", "unit": "مجموعة", "estimated_quantity": 10},
226
+ {"id": 3, "name": "التدريب", "description": "تدريب الموظفين على استخدام المعدات", "unit": "يوم", "estimated_quantity": 20}
227
+ ]
228
+
229
+ # محاكاة استخراج الكيانات والتواريخ والمبالغ والمخاطر
230
+ # (مشابه لتحليل PDF)
231
+
232
+ except Exception as e:
233
+ logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
234
+ raise
235
+
236
+ def _analyze_xlsx(self, document_path, document_type):
237
+ """تحليل مستند Excel"""
238
+ try:
239
+ # محاكاة تحليل مستند Excel
240
+ logger.info(f"تحليل مستند Excel: {document_path}")
241
+
242
+ # في التطبيق الفعلي، سيتم استخدام مكتبة مثل pandas أو openpyxl
243
+ # لاستخراج البيانات من ملف Excel وتحليلها
244
+
245
+ # محاكاة استخراج البنود
246
+ self.analysis_results["items"] = [
247
+ {"id": 1, "name": "بند 1", "description": "وصف البند 1", "unit": "وحدة", "estimated_quantity": 100},
248
+ {"id": 2, "name": "بند 2", "description": "وصف البند 2", "unit": "وحدة", "estimated_quantity": 200},
249
+ {"id": 3, "name": "بند 3", "description": "وصف البند 3", "unit": "وحدة", "estimated_quantity": 300}
250
+ ]
251
+
252
+ # محاكاة استخراج المبالغ
253
+ self.analysis_results["amounts"] = [
254
+ {"type": "item_cost", "amount": 10000, "currency": "SAR", "description": "تكلفة البند 1"},
255
+ {"type": "item_cost", "amount": 20000, "currency": "SAR", "description": "تكلفة البند 2"},
256
+ {"type": "item_cost", "amount": 30000, "currency": "SAR", "description": "تكلفة البند 3"}
257
+ ]
258
+
259
+ except Exception as e:
260
+ logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
261
+ raise
262
+
263
+ def _analyze_txt(self, document_path, document_type):
264
+ """تحليل مستند نصي"""
265
+ try:
266
+ # محاكاة تحليل مستند نصي
267
+ logger.info(f"تحليل مستند نصي: {document_path}")
268
+
269
+ # في التطبيق الفعلي، سيتم قراءة الملف النصي وتحليله
270
+
271
+ # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
272
+ # (مشابه للتحليلات الأخرى)
273
+
274
+ except Exception as e:
275
+ logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
276
+ raise
277
+
278
+ def get_analysis_status(self):
279
+ """الحصول على حالة التحليل الحالي"""
280
+ if not self.analysis_in_progress:
281
+ if not self.analysis_results:
282
+ return {"status": "لا يوجد تحليل جارٍ"}
283
+ else:
284
+ return {"status": self.analysis_results.get("status", "غير معروف")}
285
+
286
+ return {
287
+ "status": "جاري التحليل",
288
+ "document_path": self.current_document,
289
+ "start_time": self.analysis_results.get("analysis_start_time")
290
+ }
291
+
292
+ def get_analysis_results(self):
293
+ """الحصول على نتائج التحليل"""
294
+ return self.analysis_results
295
+
296
+ def export_analysis_results(self, output_path=None):
297
+ """تصدير نتائج التحليل إلى ملف JSON"""
298
+ if not self.analysis_results:
299
+ logger.warning("لا توجد نتائج تحليل للتصدير")
300
+ return None
301
+
302
+ if not output_path:
303
+ # إنشاء اسم ملف افتراضي
304
+ timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
305
+ filename = f"analysis_results_{timestamp}.json"
306
+ output_path = os.path.join(self.documents_path, filename)
307
+
308
+ try:
309
+ with open(output_path, 'w', encoding='utf-8') as f:
310
+ json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
311
+
312
+ logger.info(f"تم تصدير نتائج التحليل إلى: {output_path}")
313
+ return output_path
314
+
315
+ except Exception as e:
316
+ logger.error(f"خطأ في تصدير نتائج التحليل: {str(e)}")
317
+ return None
318
+
319
+ def import_analysis_results(self, input_path):
320
+ """استيراد نتائج التحليل من ملف JSON"""
321
+ if not os.path.exists(input_path):
322
+ logger.error(f"ملف نتائج التحليل غير موجود: {input_path}")
323
+ return False
324
+
325
+ try:
326
+ with open(input_path, 'r', encoding='utf-8') as f:
327
+ self.analysis_results = json.load(f)
328
+
329
+ logger.info(f"تم استيراد نتائج التحليل من: {input_path}")
330
+ return True
331
+
332
+ except Exception as e:
333
+ logger.error(f"خطأ في استيراد نتائج التحليل: {str(e)}")
334
+ return False
335
+
336
+ def _count_pages(self, document_path):
337
+ """حساب عدد صفحات المستند"""
338
+ try:
339
+ import PyPDF2
340
+ with open(document_path, 'rb') as file:
341
+ reader = PyPDF2.PdfReader(file)
342
+ return len(reader.pages)
343
+ except:
344
+ return 0
345
+
346
+ def _get_creation_date(self, document_path):
347
+ """استخراج تاريخ إنشاء المستند"""
348
+ try:
349
+ import PyPDF2
350
+ with open(document_path, 'rb') as file:
351
+ reader = PyPDF2.PdfReader(file)
352
+ if '/CreationDate' in reader.metadata:
353
+ return reader.metadata['/CreationDate']
354
+ return "غير متوفر"
355
+ except:
356
+ return "غير متوفر"
357
+
358
+ def _analyze_technical_specs(self, text):
359
+ """تحليل المواصفات الفنية"""
360
+ specs = {
361
+ "materials": self._extract_materials(text),
362
+ "measurements": self._extract_measurements(text),
363
+ "standards": self._extract_standards(text)
364
+ }
365
+ return specs
366
+
367
+ def _extract_key_dates(self, text):
368
+ """استخراج التواريخ المهمة"""
369
+ import re
370
+ date_pattern = r'\d{1,2}[-/]\d{1,2}[-/]\d{2,4}'
371
+ dates = re.findall(date_pattern, text)
372
+ return list(set(dates))
373
+
374
+ def _extract_figures(self, text):
375
+ """استخراج الأرقام والقيم المهمة"""
376
+ import re
377
+ # البحث عن القيم النقدية
378
+ currency_pattern = r'[\d,]+\.?\d*\s*(?:ريال|دولار|SAR|USD)'
379
+ currencies = re.findall(currency_pattern, text)
380
+
381
+ # البحث عن النسب المئوية
382
+ percentage_pattern = r'\d+\.?\d*\s*%'
383
+ percentages = re.findall(percentage_pattern, text)
384
+
385
+ return {
386
+ "currencies": currencies,
387
+ "percentages": percentages
388
+ }
389
+
390
+ def _analyze_unique_terms(self, text):
391
+ """تحليل المصطلحات الفريدة"""
392
+ words = set(text.split())
393
+ return list(words)
394
+
395
+ def _calculate_complexity(self, text):
396
+ """حساب مستوى تعقيد النص"""
397
+ words = text.split()
398
+ avg_word_length = sum(len(word) for word in words) / len(words)
399
+ sentences = text.split('.')
400
+ avg_sentence_length = len(words) / len(sentences)
401
+
402
+ # حساب درجة التعقيد (1-10)
403
+ complexity = min((avg_word_length * 0.5 + avg_sentence_length * 0.2), 10)
404
+ return round(complexity, 2)
405
+
406
+ def _check_missing_sections(self, text):
407
+ """التحقق من الأقسام المفقودة"""
408
+ required_sections = [
409
+ "نطاق العمل",
410
+ "المواصفات الفنية",
411
+ "الشروط العامة",
412
+ "الضمانات",
413
+ "الغرامات",
414
+ "شروط الدفع"
415
+ ]
416
+
417
+ missing = []
418
+ for section in required_sections:
419
+ if section not in text:
420
+ missing.append(section)
421
+
422
+ return missing
423
+
424
+ def _find_related_documents(self, document_path):
425
+ """البحث عن المستندات المرتبطة"""
426
+ directory = os.path.dirname(document_path)
427
+ base_name = os.path.basename(document_path)
428
+ related = []
429
+
430
+ for file in os.listdir(directory):
431
+ if file != base_name and file.startswith(base_name.split('_')[0]):
432
+ related.append(file)
433
+
434
+ return related
modules/document_comparison/document_comparison_app.py CHANGED
The diff for this file is too large to render. See raw diff
 
modules/project_management/project_management_app.py CHANGED
@@ -1,666 +1,666 @@
1
- """
2
- وحدة إدارة المشاريع - نظام تحليل المناقصات
3
- """
4
-
5
- import streamlit as st
6
- import pandas as pd
7
- import numpy as np
8
- from datetime import datetime, timedelta
9
- import os
10
- import time
11
- import io
12
- import sys
13
- from pathlib import Path
14
-
15
- # إضافة مسار المشروع للنظام
16
- sys.path.append(str(Path(__file__).parent.parent))
17
-
18
- # استيراد محسن واجهة المستخدم
19
- from styling.enhanced_ui import UIEnhancer
20
-
21
- class ProjectsApp:
22
- """وحدة إدارة المشاريع"""
23
-
24
- def __init__(self):
25
- """تهيئة وحدة إدارة المشاريع"""
26
- self.ui = UIEnhancer(page_title="إدارة المشاريع - نظام تحليل المناقصات", page_icon="📋")
27
- self.ui.apply_theme_colors()
28
-
29
- # تهيئة البيانات المبدئية
30
- if 'projects' not in st.session_state:
31
- st.session_state.projects = self._generate_sample_projects()
32
-
33
- def run(self):
34
- """تشغيل وحدة إدارة المشاريع"""
35
- # إنشاء قائمة العناصر
36
- menu_items = [
37
- {"name": "لوحة المعلومات", "icon": "house"},
38
- {"name": "المناقصات والعقود", "icon": "file-text"},
39
- {"name": "تحليل المستندات", "icon": "file-earmark-text"},
40
- {"name": "نظام التسعير", "icon": "calculator"},
41
- {"name": "حاسبة تكاليف البناء", "icon": "building"},
42
- {"name": "الموارد والتكاليف", "icon": "people"},
43
- {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
44
- {"name": "إدارة المشاريع", "icon": "kanban"},
45
- {"name": "الخرائط والمواقع", "icon": "geo-alt"},
46
- {"name": "الجدول الزمني", "icon": "calendar3"},
47
- {"name": "الإشعارات", "icon": "bell"},
48
- {"name": "مقارنة المستندات", "icon": "files"},
49
- {"name": "الترجمة", "icon": "translate"},
50
- {"name": "المساعد الذكي", "icon": "robot"},
51
- {"name": "التقارير", "icon": "bar-chart"},
52
- {"name": "الإعدادات", "icon": "gear"}
53
- ]
54
-
55
- # إنشاء الشريط الجانبي
56
- selected = self.ui.create_sidebar(menu_items)
57
-
58
- # إنشاء ترويسة الصفحة
59
- self.ui.create_header("إدارة المشاريع", "إدارة ومتابعة المشاريع والمناقصات")
60
-
61
- # عرض واجهة وحدة إدارة المشاريع
62
- tabs = st.tabs([
63
- "قائمة المشاريع",
64
- "إضافة مشروع جديد",
65
- "تفاصيل المشروع",
66
- "متابعة المشاريع"
67
- ])
68
-
69
- with tabs[0]:
70
- self._render_projects_list_tab()
71
-
72
- with tabs[1]:
73
- self._render_add_project_tab()
74
-
75
- with tabs[2]:
76
- self._render_project_details_tab()
77
-
78
- with tabs[3]:
79
- self._render_projects_tracking_tab()
80
-
81
- def _render_projects_list_tab(self):
82
- """عرض تبويب قائمة المشاريع"""
83
-
84
- st.markdown("### قائمة المشاريع")
85
-
86
- # فلترة المشاريع
87
- col1, col2, col3 = st.columns(3)
88
-
89
- with col1:
90
- search_term = st.text_input("البحث في المشاريع", key="project_search")
91
-
92
- with col2:
93
- status_filter = st.multiselect(
94
- "حالة المشروع",
95
- ["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"],
96
- default=["جديد", "قيد التسعير", "تم التقديم"],
97
- key="project_status_filter"
98
- )
99
-
100
- with col3:
101
- client_filter = st.multiselect(
102
- "الجهة المالكة",
103
- list(set([p['client'] for p in st.session_state.projects])),
104
- key="project_client_filter"
105
- )
106
-
107
- # تطبيق الفلترة
108
- filtered_projects = st.session_state.projects
109
-
110
- if search_term:
111
- filtered_projects = [p for p in filtered_projects if search_term.lower() in p['name'].lower() or search_term in p['number']]
112
-
113
- if status_filter:
114
- filtered_projects = [p for p in filtered_projects if p['status'] in status_filter]
115
-
116
- if client_filter:
117
- filtered_projects = [p for p in filtered_projects if p['client'] in client_filter]
118
-
119
- # تحويل المشاريع المفلترة إلى DataFrame للعرض
120
- if filtered_projects:
121
- projects_df = pd.DataFrame(filtered_projects)
122
-
123
- # اختيار وترتيب الأعمدة
124
- display_columns = [
125
- 'name', 'number', 'client', 'location', 'status',
126
- 'submission_date', 'tender_type', 'created_at'
127
- ]
128
-
129
- # تغيير أسماء الأعمدة للعرض
130
- column_names = {
131
- 'name': 'اسم المشروع',
132
- 'number': 'رقم المناقصة',
133
- 'client': 'الجهة المالكة',
134
- 'location': 'الموقع',
135
- 'status': 'الحالة',
136
- 'submission_date': 'تاريخ التقديم',
137
- 'tender_type': 'نوع المناقصة',
138
- 'created_at': 'تاريخ الإنشاء'
139
- }
140
-
141
- display_df = projects_df[display_columns].rename(columns=column_names)
142
-
143
- # تنسيق التواريخ
144
- date_columns = ['تاريخ التقديم', 'تاريخ الإنشاء']
145
- for col in date_columns:
146
- if col in display_df.columns:
147
- display_df[col] = pd.to_datetime(display_df[col]).dt.strftime('%Y-%m-%d')
148
-
149
- # عرض الجدول
150
- st.dataframe(display_df, use_container_width=True, hide_index=True)
151
-
152
- # زر تصدير المشاريع
153
- if st.button("تصدير المشاريع إلى Excel"):
154
- # محاكاة التصدير
155
- st.success("تم تصدير المشاريع بنجاح!")
156
- else:
157
- st.info("لا توجد مشاريع تطابق معايير البحث.")
158
-
159
- def _render_add_project_tab(self):
160
- """عرض تبويب إضافة مشروع جديد"""
161
-
162
- st.markdown("### إضافة مشروع جديد")
163
-
164
- # نموذج إدخال بيانات المشروع
165
- with st.form("new_project_form"):
166
- col1, col2 = st.columns(2)
167
-
168
- with col1:
169
- project_name = st.text_input("اسم المشروع", key="new_project_name")
170
- client = st.text_input("الجهة المالكة", key="new_project_client")
171
- location = st.text_input("الموقع", key="new_project_location")
172
- tender_type = st.selectbox(
173
- "نوع المناقصة",
174
- ["عامة", "خاصة", "أمر مباشر"],
175
- key="new_project_tender_type"
176
- )
177
-
178
- with col2:
179
- tender_number = st.text_input("رقم المناقصة", key="new_project_number")
180
- submission_date = st.date_input("تاريخ التقديم", key="new_project_submission_date")
181
- pricing_method = st.selectbox(
182
- "طريقة التسعير",
183
- ["قياسي", "غير متزن", "تنافسي", "موجه بالربحية"],
184
- key="new_project_pricing_method"
185
- )
186
- status = st.selectbox(
187
- "حالة المشروع",
188
- ["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"],
189
- index=0,
190
- key="new_project_status"
191
- )
192
-
193
- description = st.text_area("وصف المشروع", key="new_project_description")
194
-
195
- submitted = st.form_submit_button("إضافة المشروع")
196
-
197
- if submitted:
198
- # التحقق من تعبئة الحقول الإلزامية
199
- if not project_name or not tender_number or not client:
200
- st.error("يرجى تعبئة جميع الحقول الإلزامية (اسم المشروع، رقم المناقصة، الجهة المالكة).")
201
- else:
202
- # إنشاء مشروع جديد
203
- new_project = {
204
- 'id': len(st.session_state.projects) + 1,
205
- 'name': project_name,
206
- 'number': tender_number,
207
- 'client': client,
208
- 'location': location,
209
- 'description': description,
210
- 'status': status,
211
- 'tender_type': tender_type,
212
- 'pricing_method': pricing_method,
213
- 'submission_date': submission_date,
214
- 'created_at': datetime.now(),
215
- 'created_by_id': 1 # معرف المستخدم الحالي
216
- }
217
-
218
- # إضافة المشروع إلى قائمة المشاريع
219
- st.session_state.projects.append(new_project)
220
-
221
- # رسالة نجاح
222
- st.success(f"تم إضافة المشروع [{project_name}] بنجاح!")
223
-
224
- # تعيين المشروع الحالي
225
- st.session_state.current_project = new_project
226
-
227
- def _render_project_details_tab(self):
228
- """عرض تبويب تفاصيل المشروع"""
229
-
230
- st.markdown("### تفاصيل المشروع")
231
-
232
- # التحقق من وجود مشروع حالي
233
- if 'current_project' not in st.session_state or st.session_state.current_project is None:
234
- # إذا لم يكن هناك مشروع محدد، اعرض قائمة باختيار المشروع
235
- project_names = [p['name'] for p in st.session_state.projects]
236
- selected_project_name = st.selectbox("اختر المشروع", project_names)
237
-
238
- if selected_project_name:
239
- selected_project = next((p for p in st.session_state.projects if p['name'] == selected_project_name), None)
240
- if selected_project:
241
- st.session_state.current_project = selected_project
242
- else:
243
- st.warning("لم يتم العثور على المشروع المحدد.")
244
- return
245
- else:
246
- st.info("يرجى اختيار مشروع لعرض تفاصيله.")
247
- return
248
-
249
- # عرض تفاصيل المشروع
250
- project = st.session_state.current_project
251
-
252
- # عرض معلومات المشروع الأساسية
253
- col1, col2, col3 = st.columns(3)
254
-
255
- with col1:
256
- st.markdown(f"**اسم المشروع**: {project['name']}")
257
- st.markdown(f"**رقم المناقصة**: {project['number']}")
258
- st.markdown(f"**الجهة المالكة**: {project['client']}")
259
-
260
- with col2:
261
- st.markdown(f"**الموقع**: {project['location']}")
262
- st.markdown(f"**نوع المناقصة**: {project['tender_type']}")
263
- st.markdown(f"**حالة المشروع**: {project['status']}")
264
-
265
- with col3:
266
- st.markdown(f"**طريقة التسعير**: {project['pricing_method']}")
267
- st.markdown(f"**تاريخ التقديم**: {project['submission_date'].strftime('%Y-%m-%d') if isinstance(project['submission_date'], datetime) else project['submission_date']}")
268
- st.markdown(f"**تاريخ الإنشاء**: {project['created_at'].strftime('%Y-%m-%d') if isinstance(project['created_at'], datetime) else project['created_at']}")
269
-
270
- # عرض وصف المشروع
271
- st.markdown("#### وصف المشروع")
272
- st.text_area("", value=project.get('description', ''), disabled=True, height=100)
273
-
274
- # عرض المستندات المرتبطة بالمشروع
275
- st.markdown("#### مستندات المشروع")
276
-
277
- if 'documents' in project and project['documents']:
278
- docs_df = pd.DataFrame(project['documents'])
279
- st.dataframe(docs_df, use_container_width=True, hide_index=True)
280
- else:
281
- st.info("لا توجد مستندات مرتبطة بهذا المشروع حاليًا.")
282
-
283
- # زر إضافة مستندات
284
- if st.button("إضافة مستندات"):
285
- st.session_state.upload_documents = True
286
-
287
- # واجهة تحميل المستندات
288
- if 'upload_documents' in st.session_state and st.session_state.upload_documents:
289
- st.markdown("#### تحميل مستندات جديدة")
290
-
291
- uploaded_file = st.file_uploader("اختر ملفًا", type=['pdf', 'docx', 'xlsx', 'png', 'jpg', 'dwg'])
292
- doc_type = st.selectbox("نوع المستند", ["كراسة شروط", "عقد", "مخططات", "جدول كميات", "مواصفات فنية", "تعديلات وملاحق"])
293
-
294
- if uploaded_file and st.button("تحميل المستند"):
295
- # محاكاة تحميل المستند
296
- with st.spinner("جاري تحميل المستند..."):
297
- time.sleep(2)
298
-
299
- # إنشاء مستند جديد
300
- new_document = {
301
- 'filename': uploaded_file.name,
302
- 'type': doc_type,
303
- 'upload_date': datetime.now().strftime('%Y-%m-%d'),
304
- 'size': f"{uploaded_file.size / 1024:.1f} KB"
305
- }
306
-
307
- # إضافة المستند إلى المشروع
308
- if 'documents' not in project:
309
- project['documents'] = []
310
-
311
- project['documents'].append(new_document)
312
-
313
- st.success(f"تم تحميل المستند [{uploaded_file.name}] بنجاح!")
314
- st.session_state.upload_documents = False
315
- st.experimental_rerun()
316
-
317
- # عرض البنود والكميات
318
- st.markdown("#### بنود وكميات المشروع")
319
-
320
- if 'items' in project and project['items']:
321
- items_df = pd.DataFrame(project['items'])
322
- st.dataframe(items_df, use_container_width=True, hide_index=True)
323
-
324
- # زر لتحويل البنود إلى وحدة التسعير
325
- if st.button("تحويل البنود إلى وحدة التسعير"):
326
- if 'manual_items' not in st.session_state:
327
- st.session_state.manual_items = pd.DataFrame()
328
-
329
- st.session_state.manual_items = items_df.copy()
330
- st.success("تم تحويل البنود إلى وحدة التسعير بنجاح!")
331
- else:
332
- st.info("لا توجد بنود وكميات لهذا المشروع حاليًا.")
333
-
334
- # زر استيراد البنود من وحدة تحليل المستندات
335
- if st.button("استيراد البنود من تحليل المستندات"):
336
- st.warning("ميزة استيراد البنود من تحليل المستندات قيد التطوير.")
337
-
338
- # أزرار الإجراءات
339
- col1, col2, col3 = st.columns(3)
340
-
341
- with col1:
342
- if st.button("تعديل المشروع"):
343
- st.session_state.edit_project = True
344
- st.experimental_rerun()
345
-
346
- with col2:
347
- if st.button("تصدير بيانات المشروع"):
348
- st.success("تم تصدير بيانات المشروع بنجاح!")
349
-
350
- with col3:
351
- if st.button("إرسال للاعتماد"):
352
- st.success("تم إرسال المشروع للاعتماد بنجاح!")
353
-
354
- # نموذج تعديل المشروع
355
- if 'edit_project' in st.session_state and st.session_state.edit_project:
356
- st.markdown("#### تعديل المشروع")
357
-
358
- with st.form("edit_project_form"):
359
- col1, col2 = st.columns(2)
360
-
361
- with col1:
362
- project_name = st.text_input("اسم المشروع", value=project['name'])
363
- client = st.text_input("الجهة المالكة", value=project['client'])
364
- location = st.text_input("الموقع", value=project['location'])
365
- tender_type = st.selectbox(
366
- "نوع المناقصة",
367
- ["عامة", "خاصة", "أمر مباشر"],
368
- index=["عامة", "خاصة", "أمر مباشر"].index(project['tender_type'])
369
- )
370
-
371
- with col2:
372
- tender_number = st.text_input("رقم المناقصة", value=project['number'])
373
- submission_date = st.date_input(
374
- "تاريخ التقديم",
375
- value=datetime.strptime(project['submission_date'], "%Y-%m-%d") if isinstance(project['submission_date'], str) else project['submission_date']
376
- )
377
- pricing_method = st.selectbox(
378
- "طريقة التسعير",
379
- ["قياسي", "غير متزن", "تنافسي", "موجه بالربحية"],
380
- index=["قياسي", "غير متزن", "تنافسي", "موجه بالربحية"].index(project['pricing_method'])
381
- )
382
- status = st.selectbox(
383
- "حالة المشروع",
384
- ["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"],
385
- index=["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"].index(project['status'])
386
- )
387
-
388
- description = st.text_area("وصف المشروع", value=project.get('description', ''))
389
-
390
- col1, col2 = st.columns(2)
391
-
392
- with col1:
393
- submit = st.form_submit_button("حفظ التعديلات")
394
-
395
- with col2:
396
- cancel = st.form_submit_button("إلغاء")
397
-
398
- if submit:
399
- # تحديث بيانات المشروع
400
- project['name'] = project_name
401
- project['number'] = tender_number
402
- project['client'] = client
403
- project['location'] = location
404
- project['description'] = description
405
- project['status'] = status
406
- project['tender_type'] = tender_type
407
- project['pricing_method'] = pricing_method
408
- project['submission_date'] = submission_date
409
-
410
- st.success("تم تحديث بيانات المشروع بنجاح!")
411
- st.session_state.edit_project = False
412
- st.experimental_rerun()
413
-
414
- elif cancel:
415
- st.session_state.edit_project = False
416
- st.experimental_rerun()
417
-
418
- def _render_projects_tracking_tab(self):
419
- """عرض تبويب متابعة المشاريع"""
420
-
421
- st.markdown("### متابعة المشاريع")
422
-
423
- # عرض إحصائيات المشاريع
424
- col1, col2, col3, col4 = st.columns(4)
425
-
426
- projects = st.session_state.projects
427
-
428
- with col1:
429
- total_projects = len(projects)
430
- self.ui.create_metric_card("إجمالي المشاريع", str(total_projects), None, self.ui.COLORS['primary'])
431
-
432
- with col2:
433
- active_projects = len([p for p in projects if p['status'] in ["قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ"]])
434
- self.ui.create_metric_card("المشاريع النشطة", str(active_projects), None, self.ui.COLORS['success'])
435
-
436
- with col3:
437
- pending_submission = len([p for p in projects if p['status'] in ["جديد", "قيد التسعير"]])
438
- self.ui.create_metric_card("مشاريع قيد التسعير", str(pending_submission), None, self.ui.COLORS['warning'])
439
-
440
- with col4:
441
- completed_projects = len([p for p in projects if p['status'] in ["منتهي"]])
442
- self.ui.create_metric_card("المشاريع المنتهية", str(completed_projects), None, self.ui.COLORS['info'])
443
-
444
- # عرض رسم بياني لحالة المشاريع
445
- st.markdown("#### توزيع المشاريع حسب الحالة")
446
-
447
- status_counts = {}
448
- for p in projects:
449
- status = p['status']
450
- status_counts[status] = status_counts.get(status, 0) + 1
451
-
452
- status_df = pd.DataFrame({
453
- 'الحالة': list(status_counts.keys()),
454
- 'عدد المشاريع': list(status_counts.values())
455
- })
456
-
457
- st.bar_chart(status_df.set_index('الحالة'))
458
-
459
- # عرض المشاريع قيد المتابعة
460
- st.markdown("#### المشاريع قيد المتابعة")
461
-
462
- # عرض المشاريع النشطة المرتبة حسب تاريخ التقديم
463
- active_projects_list = [p for p in projects if p['status'] in ["قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ"]]
464
-
465
- if active_projects_list:
466
- # تحويل التواريخ إلى كائنات تاريخ إذا كانت نصوصًا
467
- for p in active_projects_list:
468
- if isinstance(p['submission_date'], str):
469
- p['submission_date'] = datetime.strptime(p['submission_date'], "%Y-%m-%d")
470
-
471
- # ترتيب المشاريع حسب تاريخ التقديم
472
- active_projects_list.sort(key=lambda x: x['submission_date'])
473
-
474
- # تحويل إلى DataFrame
475
- active_df = pd.DataFrame(active_projects_list)
476
-
477
- # اختيار وترتيب الأعمدة
478
- display_columns = [
479
- 'name', 'number', 'client', 'status',
480
- 'submission_date', 'tender_type'
481
- ]
482
-
483
- # تغيير أسماء الأعمدة
484
- column_names = {
485
- 'name': 'اسم المشروع',
486
- 'number': 'رقم المناقصة',
487
- 'client': 'الجهة المالكة',
488
- 'status': 'الحالة',
489
- 'submission_date': 'تاريخ التقديم',
490
- 'tender_type': 'نوع المناقصة'
491
- }
492
-
493
- # تنسيق البيانات
494
- display_df = active_df[display_columns].rename(columns=column_names)
495
- display_df['تاريخ التقديم'] = pd.to_datetime(display_df['تاريخ التقديم']).dt.strftime('%Y-%m-%d')
496
-
497
- # عرض الجدول
498
- st.dataframe(display_df, use_container_width=True, hide_index=True)
499
- else:
500
- st.info("لا توجد مشاريع نشطة حاليًا.")
501
-
502
- # عرض المشاريع المقبلة
503
- st.markdown("#### المواعيد المقبلة")
504
-
505
- upcoming_events = []
506
- today = datetime.now().date()
507
-
508
- for p in projects:
509
- submission_date = p['submission_date']
510
- if isinstance(submission_date, str):
511
- submission_date = datetime.strptime(submission_date, "%Y-%m-%d").date()
512
- elif isinstance(submission_date, datetime):
513
- submission_date = submission_date.date()
514
-
515
- # المشاريع التي موعد تقديمها خلال الأسبوعين القادمين
516
- if today <= submission_date <= today + timedelta(days=14) and p['status'] in ["قيد التسعير"]:
517
- days_left = (submission_date - today).days
518
- upcoming_events.append({
519
- 'المشروع': p['name'],
520
- 'الحدث': 'موعد تقديم المناقصة',
521
- 'التاريخ': submission_date.strftime('%Y-%m-%d'),
522
- 'الأيام المتبقية': days_left
523
- })
524
-
525
- if upcoming_events:
526
- events_df = pd.DataFrame(upcoming_events)
527
- st.dataframe(events_df, use_container_width=True, hide_index=True)
528
- else:
529
- st.info("لا توجد مواعيد قريبة.")
530
-
531
- def _generate_sample_projects(self):
532
- """توليد بيانات افتراضية للمشاريع"""
533
-
534
- projects = [
535
- {
536
- 'id': 1,
537
- 'name': "إنشاء مبنى مستشفى الولادة والأطفال بمنطقة الشرقية",
538
- 'number': "SHPD-2025-001",
539
- 'client': "وزارة الصحة",
540
- 'location': "الدمام، المنطقة الشرقية",
541
- 'description': "يشمل المشروع إنشاء وتجهيز مبنى مستشفى الولادة والأطفال بسعة 300 سرير، ويتكون المبنى من 4 طوابق بمساحة إجمالية 15,000 متر مربع.",
542
- 'status': "قيد التسعير",
543
- 'tender_type': "عامة",
544
- 'pricing_method': "قياسي",
545
- 'submission_date': (datetime.now() + timedelta(days=5)),
546
- 'created_at': datetime.now() - timedelta(days=10),
547
- 'created_by_id': 1,
548
- 'documents': [
549
- {
550
- 'filename': "كراسة الشروط والمواصفات.pdf",
551
- 'type': "كراسة شروط",
552
- 'upload_date': (datetime.now() - timedelta(days=9)).strftime('%Y-%m-%d'),
553
- 'size': "5.2 MB"
554
- },
555
- {
556
- 'filename': "المخططات الهندسية.dwg",
557
- 'type': "مخططات",
558
- 'upload_date': (datetime.now() - timedelta(days=8)).strftime('%Y-%m-%d'),
559
- 'size': "25.7 MB"
560
- },
561
- {
562
- 'filename': "جدول الكميات.xlsx",
563
- 'type': "جدول كميات",
564
- 'upload_date': (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d'),
565
- 'size': "1.8 MB"
566
- }
567
- ],
568
- 'items': [
569
- {
570
- 'رقم البند': "A1",
571
- 'وصف البند': "أعمال الحفر والردم",
572
- 'الوحدة': "م3",
573
- 'الكمية': 12500
574
- },
575
- {
576
- 'رقم البند': "A2",
577
- 'وصف البند': "أعمال الخرسانة المسلحة للأساسات",
578
- 'الوحدة': "م3",
579
- 'الكمية': 3500
580
- },
581
- {
582
- 'رقم البند': "A3",
583
- 'وصف البند': "أعمال حديد التسليح",
584
- 'الوحدة': "طن",
585
- 'الكمية': 450
586
- }
587
- ]
588
- },
589
- {
590
- 'id': 2,
591
- 'name': "صيانة وتطوير طريق الملك عبدالله",
592
- 'number': "MOT-2025-042",
593
- 'client': "وزارة النقل",
594
- 'location': "الرياض، المنطقة الوسطى",
595
- 'description': "صيانة وتطوير طريق الملك عبدالله بطول 25 كم، ويشمل المشروع إعادة الرصف وتحسين الإنارة وتركيب اللوحات الإرشادية.",
596
- 'status': "تم التقديم",
597
- 'tender_type': "عامة",
598
- 'pricing_method': "غير متزن",
599
- 'submission_date': (datetime.now() - timedelta(days=15)),
600
- 'created_at': datetime.now() - timedelta(days=45),
601
- 'created_by_id': 1
602
- },
603
- {
604
- 'id': 3,
605
- 'name': "إنشاء محطة معالجة مياه الصرف الصحي",
606
- 'number': "SWPC-2025-007",
607
- 'client': "شركة المياه الوطنية",
608
- 'location': "جدة، المنطقة الغربية",
609
- 'description': "إنشاء محطة معالجة مياه الصرف الصحي بطاقة استيعابية 50,000 م3/يوم، مع جميع الأعمال المدنية والكهروميكانيكية.",
610
- 'status': "تمت الترسية",
611
- 'tender_type': "عامة",
612
- 'pricing_method': "قياسي",
613
- 'submission_date': (datetime.now() - timedelta(days=90)),
614
- 'created_at': datetime.now() - timedelta(days=120),
615
- 'created_by_id': 1
616
- },
617
- {
618
- 'id': 4,
619
- 'name': "إنشاء منتزه الملك سلمان",
620
- 'number': "RAM-2025-015",
621
- 'client': "أمانة منطقة الرياض",
622
- 'location': "الرياض، المنطقة الوسطى",
623
- 'description': "إنشاء منتزه الملك سلمان على مساحة 500,000 متر مربع، ويشمل المشروع أعمال التشجير والتنسيق والمسطحات المائية والمباني الخدمية.",
624
- 'status': "قيد التنفيذ",
625
- 'tender_type': "عامة",
626
- 'pricing_method': "قياسي",
627
- 'submission_date': (datetime.now() - timedelta(days=180)),
628
- 'created_at': datetime.now() - timedelta(days=210),
629
- 'created_by_id': 1
630
- },
631
- {
632
- 'id': 5,
633
- 'name': "إنشاء مبنى مختبرات كلية العلوم",
634
- 'number': "KSU-2025-032",
635
- 'client': "جامعة الملك سعود",
636
- 'location': "الرياض، المنطقة الوسطى",
637
- 'description': "إنشاء مبنى المختبرات الجديد لكلية العلوم بمساحة 8,000 متر مربع، ويتكون من 3 طوابق ويشمل تجهيز المعامل والمختبرات العلمية.",
638
- 'status': "جديد",
639
- 'tender_type': "خاصة",
640
- 'pricing_method': "تنافسي",
641
- 'submission_date': (datetime.now() + timedelta(days=10)),
642
- 'created_at': datetime.now() - timedelta(days=5),
643
- 'created_by_id': 1
644
- },
645
- {
646
- 'id': 6,
647
- 'name': "توريد وتركيب أنظمة الطاقة الشمسية",
648
- 'number': "SEC-2025-098",
649
- 'client': "الشركة السعودية للكهرباء",
650
- 'location': "تبوك، المنطقة الشمالية",
651
- 'description': "توريد وتركيب أنظمة الطاقة الشمسية بقدرة 5 ميجاوات، مع جميع الأعمال المدنية والكهربائية.",
652
- 'status': "جديد",
653
- 'tender_type': "عامة",
654
- 'pricing_method': "قياسي",
655
- 'submission_date': (datetime.now() + timedelta(days=20)),
656
- 'created_at': datetime.now() - timedelta(days=2),
657
- 'created_by_id': 1
658
- }
659
- ]
660
-
661
- return projects
662
-
663
- # تشغيل التطبيق
664
- if __name__ == "__main__":
665
- projects_app = ProjectsApp()
666
- projects_app.run()
 
1
+ """
2
+ وحدة إدارة المشاريع - نظام تحليل المناقصات
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ from datetime import datetime, timedelta
9
+ import os
10
+ import time
11
+ import io
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ # إضافة مسار المشروع للنظام
16
+ sys.path.append(str(Path(__file__).parent.parent))
17
+
18
+ # استيراد محسن واجهة المستخدم
19
+ from styling.enhanced_ui import UIEnhancer
20
+
21
+ class ProjectsApp:
22
+ """وحدة إدارة المشاريع"""
23
+
24
+ def __init__(self):
25
+ """تهيئة وحدة إدارة المشاريع"""
26
+ self.ui = UIEnhancer(page_title="إدارة المشاريع - نظام تحليل المناقصات", page_icon="📋")
27
+ self.ui.apply_theme_colors()
28
+
29
+ # تهيئة البيانات المبدئية
30
+ if 'projects' not in st.session_state:
31
+ st.session_state.projects = self._generate_sample_projects()
32
+
33
+ def run(self):
34
+ """تشغيل وحدة إدارة المشاريع"""
35
+ # إنشاء قائمة العناصر
36
+ menu_items = [
37
+ {"name": "لوحة المعلومات", "icon": "house"},
38
+ {"name": "المناقصات والعقود", "icon": "file-text"},
39
+ {"name": "تحليل المستندات", "icon": "file-earmark-text"},
40
+ {"name": "نظام التسعير", "icon": "calculator"},
41
+ {"name": "حاسبة تكاليف البناء", "icon": "building"},
42
+ {"name": "الموارد والتكاليف", "icon": "people"},
43
+ {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
44
+ {"name": "إدارة المشاريع", "icon": "kanban"},
45
+ {"name": "الخرائط والمواقع", "icon": "geo-alt"},
46
+ {"name": "الجدول الزمني", "icon": "calendar3"},
47
+ {"name": "الإشعارات", "icon": "bell"},
48
+ {"name": "مقارنة المستندات", "icon": "files"},
49
+ {"name": "الترجمة", "icon": "translate"},
50
+ {"name": "المساعد الذكي", "icon": "robot"},
51
+ {"name": "التقارير", "icon": "bar-chart"},
52
+ {"name": "الإعدادات", "icon": "gear"}
53
+ ]
54
+
55
+ # إنشاء الشريط الجانبي
56
+ selected = self.ui.create_sidebar(menu_items)
57
+
58
+ # إنشاء ترويسة الصفحة
59
+ self.ui.create_header("إدارة المشاريع", "إدارة ومتابعة المشاريع والمناقصات")
60
+
61
+ # عرض واجهة وحدة إدارة المشاريع
62
+ tabs = st.tabs([
63
+ "قائمة المشاريع",
64
+ "إضافة مشروع جديد",
65
+ "تفاصيل المشروع",
66
+ "متابعة المشاريع"
67
+ ])
68
+
69
+ with tabs[0]:
70
+ self._render_projects_list_tab()
71
+
72
+ with tabs[1]:
73
+ self._render_add_project_tab()
74
+
75
+ with tabs[2]:
76
+ self._render_project_details_tab()
77
+
78
+ with tabs[3]:
79
+ self._render_projects_tracking_tab()
80
+
81
+ def _render_projects_list_tab(self):
82
+ """عرض تبويب قائمة المشاريع"""
83
+
84
+ st.markdown("### قائمة المشاريع")
85
+
86
+ # فلترة المشاريع
87
+ col1, col2, col3 = st.columns(3)
88
+
89
+ with col1:
90
+ search_term = st.text_input("البحث في المشاريع", key="project_search")
91
+
92
+ with col2:
93
+ status_filter = st.multiselect(
94
+ "حالة المشروع",
95
+ ["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"],
96
+ default=["جديد", "قيد التسعير", "تم التقديم"],
97
+ key="project_status_filter"
98
+ )
99
+
100
+ with col3:
101
+ client_filter = st.multiselect(
102
+ "الجهة المالكة",
103
+ list(set([p['client'] for p in st.session_state.projects])),
104
+ key="project_client_filter"
105
+ )
106
+
107
+ # تطبيق الفلترة
108
+ filtered_projects = st.session_state.projects
109
+
110
+ if search_term:
111
+ filtered_projects = [p for p in filtered_projects if search_term.lower() in p['name'].lower() or search_term in p['number']]
112
+
113
+ if status_filter:
114
+ filtered_projects = [p for p in filtered_projects if p['status'] in status_filter]
115
+
116
+ if client_filter:
117
+ filtered_projects = [p for p in filtered_projects if p['client'] in client_filter]
118
+
119
+ # تحويل المشاريع المفلترة إلى DataFrame للعرض
120
+ if filtered_projects:
121
+ projects_df = pd.DataFrame(filtered_projects)
122
+
123
+ # اختيار وترتيب الأعمدة
124
+ display_columns = [
125
+ 'name', 'number', 'client', 'location', 'status',
126
+ 'submission_date', 'tender_type', 'created_at'
127
+ ]
128
+
129
+ # تغيير أسماء الأعمدة للعرض
130
+ column_names = {
131
+ 'name': 'اسم المشروع',
132
+ 'number': 'رقم المناقصة',
133
+ 'client': 'الجهة المالكة',
134
+ 'location': 'الموقع',
135
+ 'status': 'الحالة',
136
+ 'submission_date': 'تاريخ التقديم',
137
+ 'tender_type': 'نوع المناقصة',
138
+ 'created_at': 'تاريخ الإنشاء'
139
+ }
140
+
141
+ display_df = projects_df[display_columns].rename(columns=column_names)
142
+
143
+ # تنسيق التواريخ
144
+ date_columns = ['تاريخ التقديم', 'تاريخ الإنشاء']
145
+ for col in date_columns:
146
+ if col in display_df.columns:
147
+ display_df[col] = pd.to_datetime(display_df[col]).dt.strftime('%Y-%m-%d')
148
+
149
+ # عرض الجدول
150
+ st.dataframe(display_df, use_container_width=True, hide_index=True)
151
+
152
+ # زر تصدير المشاريع
153
+ if st.button("تصدير المشاريع إلى Excel"):
154
+ # محاكاة التصدير
155
+ st.success("تم تصدير المشاريع بنجاح!")
156
+ else:
157
+ st.info("لا توجد مشاريع تطابق معايير البحث.")
158
+
159
+ def _render_add_project_tab(self):
160
+ """عرض تبويب إضافة مشروع جديد"""
161
+
162
+ st.markdown("### إضافة مشروع جديد")
163
+
164
+ # نموذج إدخال بيانات المشروع
165
+ with st.form("new_project_form"):
166
+ col1, col2 = st.columns(2)
167
+
168
+ with col1:
169
+ project_name = st.text_input("اسم المشروع", key="new_project_name")
170
+ client = st.text_input("الجهة المالكة", key="new_project_client")
171
+ location = st.text_input("الموقع", key="new_project_location")
172
+ tender_type = st.selectbox(
173
+ "نوع المناقصة",
174
+ ["عامة", "خاصة", "أمر مباشر"],
175
+ key="new_project_tender_type"
176
+ )
177
+
178
+ with col2:
179
+ tender_number = st.text_input("رقم المناقصة", key="new_project_number")
180
+ submission_date = st.date_input("تاريخ التقديم", key="new_project_submission_date")
181
+ pricing_method = st.selectbox(
182
+ "طريقة التسعير",
183
+ ["قياسي", "غير متزن", "تنافسي", "موجه بالربحية"],
184
+ key="new_project_pricing_method"
185
+ )
186
+ status = st.selectbox(
187
+ "حالة المشروع",
188
+ ["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"],
189
+ index=0,
190
+ key="new_project_status"
191
+ )
192
+
193
+ description = st.text_area("وصف المشروع", key="new_project_description")
194
+
195
+ submitted = st.form_submit_button("إضافة المشروع")
196
+
197
+ if submitted:
198
+ # التحقق من تعبئة الحقول الإلزامية
199
+ if not project_name or not tender_number or not client:
200
+ st.error("يرجى تعبئة جميع الحقول الإلزامية (اسم المشروع، رقم المناقصة، الجهة المالكة).")
201
+ else:
202
+ # إنشاء مشروع جديد
203
+ new_project = {
204
+ 'id': len(st.session_state.projects) + 1,
205
+ 'name': project_name,
206
+ 'number': tender_number,
207
+ 'client': client,
208
+ 'location': location,
209
+ 'description': description,
210
+ 'status': status,
211
+ 'tender_type': tender_type,
212
+ 'pricing_method': pricing_method,
213
+ 'submission_date': submission_date,
214
+ 'created_at': datetime.now(),
215
+ 'created_by_id': 1 # معرف المستخدم الحالي
216
+ }
217
+
218
+ # إضافة المشروع إلى قائمة المشاريع
219
+ st.session_state.projects.append(new_project)
220
+
221
+ # رسالة نجاح
222
+ st.success(f"تم إضافة المشروع [{project_name}] بنجاح!")
223
+
224
+ # تعيين المشروع الحالي
225
+ st.session_state.current_project = new_project
226
+
227
+ def _render_project_details_tab(self):
228
+ """عرض تبويب تفاصيل المشروع"""
229
+
230
+ st.markdown("### تفاصيل المشروع")
231
+
232
+ # التحقق من وجود مشروع حالي
233
+ if 'current_project' not in st.session_state or st.session_state.current_project is None:
234
+ # إذا لم يكن هناك مشروع محدد، اعرض قائمة باختيار المشروع
235
+ project_names = [p['name'] for p in st.session_state.projects]
236
+ selected_project_name = st.selectbox("اختر المشروع", project_names)
237
+
238
+ if selected_project_name:
239
+ selected_project = next((p for p in st.session_state.projects if p['name'] == selected_project_name), None)
240
+ if selected_project:
241
+ st.session_state.current_project = selected_project
242
+ else:
243
+ st.warning("لم يتم العثور على المشروع المحدد.")
244
+ return
245
+ else:
246
+ st.info("يرجى اختيار مشروع لعرض تفاصيله.")
247
+ return
248
+
249
+ # عرض تفاصيل المشروع
250
+ project = st.session_state.current_project
251
+
252
+ # عرض معلومات المشروع الأساسية
253
+ col1, col2, col3 = st.columns(3)
254
+
255
+ with col1:
256
+ st.markdown(f"**اسم المشروع**: {project.get('name', 'غير محدد')}")
257
+ st.markdown(f"**رقم المناقصة**: {project.get('tender_number', project.get('number', 'غير محدد'))}")
258
+ st.markdown(f"**الجهة المالكة**: {project.get('client', 'غير محدد')}")
259
+
260
+ with col2:
261
+ st.markdown(f"**الموقع**: {project['location']}")
262
+ st.markdown(f"**نوع المناقصة**: {project['tender_type']}")
263
+ st.markdown(f"**حالة المشروع**: {project['status']}")
264
+
265
+ with col3:
266
+ st.markdown(f"**طريقة التسعير**: {project['pricing_method']}")
267
+ st.markdown(f"**تاريخ التقديم**: {project['submission_date'].strftime('%Y-%m-%d') if isinstance(project['submission_date'], datetime) else project['submission_date']}")
268
+ st.markdown(f"**تاريخ الإنشاء**: {project['created_at'].strftime('%Y-%m-%d') if isinstance(project['created_at'], datetime) else project['created_at']}")
269
+
270
+ # عرض وصف المشروع
271
+ st.markdown("#### وصف المشروع")
272
+ st.text_area("", value=project.get('description', ''), disabled=True, height=100)
273
+
274
+ # عرض المستندات المرتبطة بالمشروع
275
+ st.markdown("#### مستندات المشروع")
276
+
277
+ if 'documents' in project and project['documents']:
278
+ docs_df = pd.DataFrame(project['documents'])
279
+ st.dataframe(docs_df, use_container_width=True, hide_index=True)
280
+ else:
281
+ st.info("لا توجد مستندات مرتبطة بهذا المشروع حاليًا.")
282
+
283
+ # زر إضافة مستندات
284
+ if st.button("إضافة مستندات"):
285
+ st.session_state.upload_documents = True
286
+
287
+ # واجهة تحميل المستندات
288
+ if 'upload_documents' in st.session_state and st.session_state.upload_documents:
289
+ st.markdown("#### تحميل مستندات جديدة")
290
+
291
+ uploaded_file = st.file_uploader("اختر ملفًا", type=['pdf', 'docx', 'xlsx', 'png', 'jpg', 'dwg'])
292
+ doc_type = st.selectbox("نوع المستند", ["كراسة شروط", "عقد", "مخططات", "جدول كميات", "مواصفات فنية", "تعديلات وملاحق"])
293
+
294
+ if uploaded_file and st.button("تحميل المستند"):
295
+ # محاكاة تحميل المستند
296
+ with st.spinner("جاري تحميل المستند..."):
297
+ time.sleep(2)
298
+
299
+ # إنشاء مستند جديد
300
+ new_document = {
301
+ 'filename': uploaded_file.name,
302
+ 'type': doc_type,
303
+ 'upload_date': datetime.now().strftime('%Y-%m-%d'),
304
+ 'size': f"{uploaded_file.size / 1024:.1f} KB"
305
+ }
306
+
307
+ # إضافة المستند إلى المشروع
308
+ if 'documents' not in project:
309
+ project['documents'] = []
310
+
311
+ project['documents'].append(new_document)
312
+
313
+ st.success(f"تم تحميل المستند [{uploaded_file.name}] بنجاح!")
314
+ st.session_state.upload_documents = False
315
+ st.experimental_rerun()
316
+
317
+ # عرض البنود والكميات
318
+ st.markdown("#### بنود وكميات المشروع")
319
+
320
+ if 'items' in project and project['items']:
321
+ items_df = pd.DataFrame(project['items'])
322
+ st.dataframe(items_df, use_container_width=True, hide_index=True)
323
+
324
+ # زر لتحويل البنود إلى وحدة التسعير
325
+ if st.button("تحويل البنود إلى وحدة التسعير"):
326
+ if 'manual_items' not in st.session_state:
327
+ st.session_state.manual_items = pd.DataFrame()
328
+
329
+ st.session_state.manual_items = items_df.copy()
330
+ st.success("تم تحويل البنود إلى وحدة التسعير بنجاح!")
331
+ else:
332
+ st.info("لا توجد بنود وكميات لهذا المشروع حاليًا.")
333
+
334
+ # زر استيراد البنود من وحدة تحليل المستندات
335
+ if st.button("استيراد البنود من تحليل المستندات"):
336
+ st.warning("ميزة استيراد البنود من تحليل المستندات قيد التطوير.")
337
+
338
+ # أزرار الإجراءات
339
+ col1, col2, col3 = st.columns(3)
340
+
341
+ with col1:
342
+ if st.button("تعديل المشروع"):
343
+ st.session_state.edit_project = True
344
+ st.experimental_rerun()
345
+
346
+ with col2:
347
+ if st.button("تصدير بيانات المشروع"):
348
+ st.success("تم تصدير بيانات المشروع بنجاح!")
349
+
350
+ with col3:
351
+ if st.button("إرسال للاعتماد"):
352
+ st.success("تم إرسال المشروع للاعتماد بنجاح!")
353
+
354
+ # نموذج تعديل المشروع
355
+ if 'edit_project' in st.session_state and st.session_state.edit_project:
356
+ st.markdown("#### تعديل المشروع")
357
+
358
+ with st.form("edit_project_form"):
359
+ col1, col2 = st.columns(2)
360
+
361
+ with col1:
362
+ project_name = st.text_input("اسم المشروع", value=project['name'])
363
+ client = st.text_input("الجهة المالكة", value=project['client'])
364
+ location = st.text_input("الموقع", value=project['location'])
365
+ tender_type = st.selectbox(
366
+ "نوع المناقصة",
367
+ ["عامة", "خاصة", "أمر مباشر"],
368
+ index=["عامة", "خاصة", "أمر مباشر"].index(project['tender_type'])
369
+ )
370
+
371
+ with col2:
372
+ tender_number = st.text_input("رقم المناقصة", value=project['number'])
373
+ submission_date = st.date_input(
374
+ "تاريخ التقديم",
375
+ value=datetime.strptime(project['submission_date'], "%Y-%m-%d") if isinstance(project['submission_date'], str) else project['submission_date']
376
+ )
377
+ pricing_method = st.selectbox(
378
+ "طريقة التسعير",
379
+ ["قياسي", "غير متزن", "تنافسي", "موجه بالربحية"],
380
+ index=["قياسي", "غير متزن", "تنافسي", "موجه بالربحية"].index(project['pricing_method'])
381
+ )
382
+ status = st.selectbox(
383
+ "حالة المشروع",
384
+ ["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"],
385
+ index=["جديد", "قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ", "منتهي", "ملغي"].index(project['status'])
386
+ )
387
+
388
+ description = st.text_area("وصف المشروع", value=project.get('description', ''))
389
+
390
+ col1, col2 = st.columns(2)
391
+
392
+ with col1:
393
+ submit = st.form_submit_button("حفظ التعديلات")
394
+
395
+ with col2:
396
+ cancel = st.form_submit_button("إلغاء")
397
+
398
+ if submit:
399
+ # تحديث بيانات المشروع
400
+ project['name'] = project_name
401
+ project['number'] = tender_number
402
+ project['client'] = client
403
+ project['location'] = location
404
+ project['description'] = description
405
+ project['status'] = status
406
+ project['tender_type'] = tender_type
407
+ project['pricing_method'] = pricing_method
408
+ project['submission_date'] = submission_date
409
+
410
+ st.success("تم تحديث بيانات المشروع بنجاح!")
411
+ st.session_state.edit_project = False
412
+ st.experimental_rerun()
413
+
414
+ elif cancel:
415
+ st.session_state.edit_project = False
416
+ st.experimental_rerun()
417
+
418
+ def _render_projects_tracking_tab(self):
419
+ """عرض تبويب متابعة المشاريع"""
420
+
421
+ st.markdown("### متابعة المشاريع")
422
+
423
+ # عرض إحصائيات المشاريع
424
+ col1, col2, col3, col4 = st.columns(4)
425
+
426
+ projects = st.session_state.projects
427
+
428
+ with col1:
429
+ total_projects = len(projects)
430
+ self.ui.create_metric_card("إجمالي المشاريع", str(total_projects), None, self.ui.COLORS['primary'])
431
+
432
+ with col2:
433
+ active_projects = len([p for p in projects if p['status'] in ["قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ"]])
434
+ self.ui.create_metric_card("المشاريع النشطة", str(active_projects), None, self.ui.COLORS['success'])
435
+
436
+ with col3:
437
+ pending_submission = len([p for p in projects if p['status'] in ["جديد", "قيد التسعير"]])
438
+ self.ui.create_metric_card("مشاريع قيد التسعير", str(pending_submission), None, self.ui.COLORS['warning'])
439
+
440
+ with col4:
441
+ completed_projects = len([p for p in projects if p['status'] in ["منتهي"]])
442
+ self.ui.create_metric_card("المشاريع المنتهية", str(completed_projects), None, self.ui.COLORS['info'])
443
+
444
+ # عرض رسم بياني لحالة المشاريع
445
+ st.markdown("#### توزيع المشاريع حسب الحالة")
446
+
447
+ status_counts = {}
448
+ for p in projects:
449
+ status = p['status']
450
+ status_counts[status] = status_counts.get(status, 0) + 1
451
+
452
+ status_df = pd.DataFrame({
453
+ 'الحالة': list(status_counts.keys()),
454
+ 'عدد المشاريع': list(status_counts.values())
455
+ })
456
+
457
+ st.bar_chart(status_df.set_index('الحالة'))
458
+
459
+ # عرض المشاريع قيد المتابعة
460
+ st.markdown("#### المشاريع قيد المتابعة")
461
+
462
+ # عرض المشاريع النشطة المرتبة حسب تاريخ التقديم
463
+ active_projects_list = [p for p in projects if p['status'] in ["قيد التسعير", "تم التقديم", "تمت الترسية", "قيد التنفيذ"]]
464
+
465
+ if active_projects_list:
466
+ # تحويل التواريخ إلى كائنات تاريخ إذا كانت نصوصًا
467
+ for p in active_projects_list:
468
+ if isinstance(p['submission_date'], str):
469
+ p['submission_date'] = datetime.strptime(p['submission_date'], "%Y-%m-%d")
470
+
471
+ # ترتيب المشاريع حسب تاريخ التقديم
472
+ active_projects_list.sort(key=lambda x: x['submission_date'])
473
+
474
+ # تحويل إلى DataFrame
475
+ active_df = pd.DataFrame(active_projects_list)
476
+
477
+ # اختيار وترتيب الأعمدة
478
+ display_columns = [
479
+ 'name', 'number', 'client', 'status',
480
+ 'submission_date', 'tender_type'
481
+ ]
482
+
483
+ # تغيير أسماء الأعمدة
484
+ column_names = {
485
+ 'name': 'اسم المشروع',
486
+ 'number': 'رقم المناقصة',
487
+ 'client': 'الجهة المالكة',
488
+ 'status': 'الحالة',
489
+ 'submission_date': 'تاريخ التقديم',
490
+ 'tender_type': 'نوع المناقصة'
491
+ }
492
+
493
+ # تنسيق البيانات
494
+ display_df = active_df[display_columns].rename(columns=column_names)
495
+ display_df['تاريخ التقديم'] = pd.to_datetime(display_df['تاريخ التقديم']).dt.strftime('%Y-%m-%d')
496
+
497
+ # عرض الجدول
498
+ st.dataframe(display_df, use_container_width=True, hide_index=True)
499
+ else:
500
+ st.info("لا توجد مشاريع نشطة حاليًا.")
501
+
502
+ # عرض المشاريع المقبلة
503
+ st.markdown("#### المواعيد المقبلة")
504
+
505
+ upcoming_events = []
506
+ today = datetime.now().date()
507
+
508
+ for p in projects:
509
+ submission_date = p['submission_date']
510
+ if isinstance(submission_date, str):
511
+ submission_date = datetime.strptime(submission_date, "%Y-%m-%d").date()
512
+ elif isinstance(submission_date, datetime):
513
+ submission_date = submission_date.date()
514
+
515
+ # المشاريع التي موعد تقديمها خلال الأسبوعين القادمين
516
+ if today <= submission_date <= today + timedelta(days=14) and p['status'] in ["قيد التسعير"]:
517
+ days_left = (submission_date - today).days
518
+ upcoming_events.append({
519
+ 'المشروع': p['name'],
520
+ 'الحدث': 'موعد تقديم المناقصة',
521
+ 'التاريخ': submission_date.strftime('%Y-%m-%d'),
522
+ 'الأيام المتبقية': days_left
523
+ })
524
+
525
+ if upcoming_events:
526
+ events_df = pd.DataFrame(upcoming_events)
527
+ st.dataframe(events_df, use_container_width=True, hide_index=True)
528
+ else:
529
+ st.info("لا توجد مواعيد قريبة.")
530
+
531
+ def _generate_sample_projects(self):
532
+ """توليد بيانات افتراضية للمشاريع"""
533
+
534
+ projects = [
535
+ {
536
+ 'id': 1,
537
+ 'name': "إنشاء مبنى مستشفى الولادة والأطفال بمنطقة الشرقية",
538
+ 'number': "SHPD-2025-001",
539
+ 'client': "وزارة الصحة",
540
+ 'location': "الدمام، المنطقة الشرقية",
541
+ 'description': "يشمل المشروع إنشاء وتجهيز مبنى مستشفى الولادة والأطفال بسعة 300 سرير، ويتكون المبنى من 4 طوابق بمساحة إجمالية 15,000 متر مربع.",
542
+ 'status': "قيد التسعير",
543
+ 'tender_type': "عامة",
544
+ 'pricing_method': "قياسي",
545
+ 'submission_date': (datetime.now() + timedelta(days=5)),
546
+ 'created_at': datetime.now() - timedelta(days=10),
547
+ 'created_by_id': 1,
548
+ 'documents': [
549
+ {
550
+ 'filename': "كراسة الشروط والمواصفات.pdf",
551
+ 'type': "كراسة شروط",
552
+ 'upload_date': (datetime.now() - timedelta(days=9)).strftime('%Y-%m-%d'),
553
+ 'size': "5.2 MB"
554
+ },
555
+ {
556
+ 'filename': "المخططات الهندسية.dwg",
557
+ 'type': "مخططات",
558
+ 'upload_date': (datetime.now() - timedelta(days=8)).strftime('%Y-%m-%d'),
559
+ 'size': "25.7 MB"
560
+ },
561
+ {
562
+ 'filename': "جدول الكميات.xlsx",
563
+ 'type': "جدول كميات",
564
+ 'upload_date': (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d'),
565
+ 'size': "1.8 MB"
566
+ }
567
+ ],
568
+ 'items': [
569
+ {
570
+ 'رقم البند': "A1",
571
+ 'وصف البند': "أعمال الحفر والردم",
572
+ 'الوحدة': "م3",
573
+ 'الكمية': 12500
574
+ },
575
+ {
576
+ 'رقم البند': "A2",
577
+ 'وصف البند': "أعمال الخرسانة المسلحة للأساسات",
578
+ 'الوحدة': "م3",
579
+ 'الكمية': 3500
580
+ },
581
+ {
582
+ 'رقم البند': "A3",
583
+ 'وصف البند': "أعمال حديد التسليح",
584
+ 'الوحدة': "طن",
585
+ 'الكمية': 450
586
+ }
587
+ ]
588
+ },
589
+ {
590
+ 'id': 2,
591
+ 'name': "صيانة وتطوير طريق الملك عبدالله",
592
+ 'number': "MOT-2025-042",
593
+ 'client': "وزارة النقل",
594
+ 'location': "الرياض، المنطقة الوسطى",
595
+ 'description': "صيانة وتطوير طريق الملك عبدالله بطول 25 كم، ويشمل المشروع إعادة الرصف وتحسين الإنارة وتركيب اللوحات الإرشادية.",
596
+ 'status': "تم التقديم",
597
+ 'tender_type': "عامة",
598
+ 'pricing_method': "غير متزن",
599
+ 'submission_date': (datetime.now() - timedelta(days=15)),
600
+ 'created_at': datetime.now() - timedelta(days=45),
601
+ 'created_by_id': 1
602
+ },
603
+ {
604
+ 'id': 3,
605
+ 'name': "إنشاء محطة معالجة مياه الصرف الصحي",
606
+ 'number': "SWPC-2025-007",
607
+ 'client': "شركة المياه الوطنية",
608
+ 'location': "جدة، المنطقة الغربية",
609
+ 'description': "إنشاء محطة معالجة مياه الصرف الصحي بطاقة استيعابية 50,000 م3/يوم، مع جميع الأعمال المدنية والكهروميكانيكية.",
610
+ 'status': "تمت الترسية",
611
+ 'tender_type': "عامة",
612
+ 'pricing_method': "قياسي",
613
+ 'submission_date': (datetime.now() - timedelta(days=90)),
614
+ 'created_at': datetime.now() - timedelta(days=120),
615
+ 'created_by_id': 1
616
+ },
617
+ {
618
+ 'id': 4,
619
+ 'name': "إنشاء منتزه الملك سلمان",
620
+ 'number': "RAM-2025-015",
621
+ 'client': "أمانة منطقة الرياض",
622
+ 'location': "الرياض، المنطقة الوسطى",
623
+ 'description': "إنشاء منتزه الملك سلمان على مساحة 500,000 متر مربع، ويشمل المشروع أعمال التشجير والتنسيق والمسطحات المائية والمباني الخدمية.",
624
+ 'status': "قيد التنفيذ",
625
+ 'tender_type': "عامة",
626
+ 'pricing_method': "قياسي",
627
+ 'submission_date': (datetime.now() - timedelta(days=180)),
628
+ 'created_at': datetime.now() - timedelta(days=210),
629
+ 'created_by_id': 1
630
+ },
631
+ {
632
+ 'id': 5,
633
+ 'name': "إنشاء مبنى مختبرات كلية العلوم",
634
+ 'number': "KSU-2025-032",
635
+ 'client': "جامعة الملك سعود",
636
+ 'location': "الرياض، المنطقة الوسطى",
637
+ 'description': "إنشاء مبنى المختبرات الجديد لكلية العلوم بمساحة 8,000 متر مربع، ويتكون من 3 طوابق ويشمل تجهيز المعامل والمختبرات العلمية.",
638
+ 'status': "جديد",
639
+ 'tender_type': "خاصة",
640
+ 'pricing_method': "تنافسي",
641
+ 'submission_date': (datetime.now() + timedelta(days=10)),
642
+ 'created_at': datetime.now() - timedelta(days=5),
643
+ 'created_by_id': 1
644
+ },
645
+ {
646
+ 'id': 6,
647
+ 'name': "توريد وتركيب أنظمة الطاقة الشمسية",
648
+ 'number': "SEC-2025-098",
649
+ 'client': "الشركة السعودية للكهرباء",
650
+ 'location': "تبوك، المنطقة الشمالية",
651
+ 'description': "توريد وتركيب أنظمة الطاقة الشمسية بقدرة 5 ميجاوات، مع جميع الأعمال المدنية والكهربائية.",
652
+ 'status': "جديد",
653
+ 'tender_type': "عامة",
654
+ 'pricing_method': "قياسي",
655
+ 'submission_date': (datetime.now() + timedelta(days=20)),
656
+ 'created_at': datetime.now() - timedelta(days=2),
657
+ 'created_by_id': 1
658
+ }
659
+ ]
660
+
661
+ return projects
662
+
663
+ # تشغيل التطبيق
664
+ if __name__ == "__main__":
665
+ projects_app = ProjectsApp()
666
+ projects_app.run()
modules/resources/resources_app.py CHANGED
@@ -24,6 +24,13 @@ class ResourcesApp:
24
 
25
  # تهيئة حالة الجلسة
26
  if 'resources_data' not in st.session_state:
 
 
 
 
 
 
 
27
  # إنشاء بيانات افتراضية للموارد البشرية
28
  np.random.seed(42)
29
 
@@ -207,6 +214,19 @@ class ResourcesApp:
207
 
208
  st.markdown("<h1 class='module-title'>وحدة الموارد</h1>", unsafe_allow_html=True)
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  tabs = st.tabs([
211
  "لوحة المعلومات",
212
  "الموارد البشرية",
@@ -251,25 +271,47 @@ class ResourcesApp:
251
 
252
  col1, col2, col3, col4 = st.columns(4)
253
 
 
254
  with col1:
255
  total_employees = len(employees_df)
256
  available_employees = len(employees_df[employees_df["متاح"] == True])
257
  st.metric("الموظفون", f"{available_employees}/{total_employees}")
258
 
 
259
  with col2:
260
  total_equipment = len(equipment_df)
261
  available_equipment = len(equipment_df[equipment_df["متاحة"] == True])
262
  st.metric("المعدات", f"{available_equipment}/{total_equipment}")
263
 
 
264
  with col3:
265
- total_materials = len(materials_df)
266
- low_stock_materials = len(materials_df[materials_df["الكمية المتاحة"] < 50])
267
- st.metric("المواد", f"{total_materials}", f"-{low_stock_materials} منخفضة المخزون")
268
-
 
 
 
 
 
 
 
 
 
 
269
  with col4:
270
- total_projects = len(projects_df)
271
- active_projects = len(projects_df[projects_df["الحالة"] == "قيد التنفيذ"])
272
- st.metric("المشاريع النشطة", f"{active_projects}/{total_projects}")
 
 
 
 
 
 
 
 
 
273
 
274
  # عرض توزيع الموارد البشرية حسب القسم
275
  st.markdown("#### توزيع الموارد البشرية حسب القسم")
@@ -380,20 +422,265 @@ class ResourcesApp:
380
  def _render_human_resources_tab(self):
381
  """عرض تبويب الموارد البشرية"""
382
  st.markdown("### الموارد البشرية")
383
- # إضافة محتوى تبويب الموارد البشرية هنا ...
384
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
  def _render_equipment_tab(self):
387
  """عرض تبويب المعدات"""
388
  st.markdown("### المعدات")
389
- # إضافة محتوى تبويب المعدات هنا ...
390
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
  def _render_materials_tab(self):
393
  """عرض تبويب المواد"""
394
  st.markdown("### المواد")
395
- # إضافة محتوى تبويب المواد هنا ...
396
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  def _render_resource_allocation_tab(self):
399
  """عرض تبويب تخصيص الموارد"""
 
24
 
25
  # تهيئة حالة الجلسة
26
  if 'resources_data' not in st.session_state:
27
+ st.session_state.resources_data = {}
28
+
29
+ # جلب بيانات المشروع المحفوظ
30
+ if 'saved_pricing' in st.session_state:
31
+ self.saved_project = st.session_state.saved_pricing[-1] if st.session_state.saved_pricing else None
32
+ else:
33
+ self.saved_project = None
34
  # إنشاء بيانات افتراضية للموارد البشرية
35
  np.random.seed(42)
36
 
 
214
 
215
  st.markdown("<h1 class='module-title'>وحدة الموارد</h1>", unsafe_allow_html=True)
216
 
217
+ # عرض بيانات المشروع المحفوظ
218
+ if self.saved_project:
219
+ with st.expander("تفاصيل المشروع المحفوظ", expanded=True):
220
+ st.markdown(f"**اسم المشروع:** {self.saved_project.get('project_name', 'غير محدد')}")
221
+ st.markdown(f"**إجمالي القيمة:** {self.saved_project.get('total_price', 0):,.2f} ريال")
222
+ st.markdown(f"**عدد البنود:** {len(self.saved_project.get('items', []))}")
223
+
224
+ # عرض جدول البنود
225
+ if 'items' in self.saved_project:
226
+ st.subheader("جدول الكميات")
227
+ df = pd.DataFrame(self.saved_project['items'])
228
+ st.dataframe(df)
229
+
230
  tabs = st.tabs([
231
  "لوحة المعلومات",
232
  "الموارد البشرية",
 
271
 
272
  col1, col2, col3, col4 = st.columns(4)
273
 
274
+ # الموظفون
275
  with col1:
276
  total_employees = len(employees_df)
277
  available_employees = len(employees_df[employees_df["متاح"] == True])
278
  st.metric("الموظفون", f"{available_employees}/{total_employees}")
279
 
280
+ # المعدات
281
  with col2:
282
  total_equipment = len(equipment_df)
283
  available_equipment = len(equipment_df[equipment_df["متاحة"] == True])
284
  st.metric("المعدات", f"{available_equipment}/{total_equipment}")
285
 
286
+ # المواد والبنود
287
  with col3:
288
+ if self.saved_project and 'items' in self.saved_project:
289
+ total_items = len(self.saved_project['items'])
290
+ total_value = sum(item.get('total_price', 0) for item in self.saved_project['items'])
291
+ st.metric(
292
+ "إجمالي البنود",
293
+ f"{total_items} بند",
294
+ f"{total_value:,.0f} ريال"
295
+ )
296
+ else:
297
+ total_materials = len(materials_df)
298
+ low_stock_materials = len(materials_df[materials_df["الكمية المتاحة"] < 50])
299
+ st.metric("المواد", f"{total_materials}", f"-{low_stock_materials} منخفضة المخزون")
300
+
301
+ # تحليل التكاليف
302
  with col4:
303
+ if self.saved_project and 'items' in self.saved_project:
304
+ items_df = pd.DataFrame(self.saved_project['items'])
305
+ if not items_df.empty:
306
+ avg_unit_price = items_df['unit_price'].mean() if 'unit_price' in items_df.columns else 0
307
+ st.metric(
308
+ "متوسط سعر الوحدة",
309
+ f"{avg_unit_price:,.2f} ريال"
310
+ )
311
+ else:
312
+ total_projects = len(projects_df)
313
+ active_projects = len(projects_df[projects_df["الحالة"] == "قيد التنفيذ"])
314
+ st.metric("المشاريع النشطة", f"{active_projects}/{total_projects}")
315
 
316
  # عرض توزيع الموارد البشرية حسب القسم
317
  st.markdown("#### توزيع الموارد البشرية حسب القسم")
 
422
  def _render_human_resources_tab(self):
423
  """عرض تبويب الموارد البشرية"""
424
  st.markdown("### الموارد البشرية")
425
+
426
+ # إضافة موظف جديد
427
+ with st.expander("إضافة موظف جديد"):
428
+ employee_form = st.form("add_employee_form")
429
+ with employee_form:
430
+ employee_data = {}
431
+ employee_data['name'] = st.text_input("الاسم")
432
+ employee_data['department'] = st.selectbox("القسم", [
433
+ "الهندسة",
434
+ "المشتريات",
435
+ "المالية",
436
+ "الموارد البشرية",
437
+ "تقنية المعلومات",
438
+ "التسويق",
439
+ "المشاريع",
440
+ "الجودة والسلامة",
441
+ "المكتب الفني"
442
+ ], key="employee_department_selectbox")
443
+ employee_data['position'] = st.selectbox("المنصب", [
444
+ "مدير مشروع",
445
+ "مهندس مدني",
446
+ "مهندس معماري",
447
+ "مهندس ميكانيكي",
448
+ "مهندس كهربائي",
449
+ "مهندس مساحة",
450
+ "فني مدني",
451
+ "فني كهرباء",
452
+ "فني مساحة",
453
+ "مراقب",
454
+ "سائق معدات",
455
+ "عامل مهني",
456
+ "عامل عادي"
457
+ ], key="employee_position_selectbox")
458
+
459
+ cols = st.columns(3)
460
+ with cols[0]:
461
+ employee_data['monthly_salary'] = st.number_input("الراتب الشهري", min_value=0)
462
+ with cols[1]:
463
+ employee_data['experience_years'] = st.number_input("سنوات الخبرة", min_value=0)
464
+ with cols[2]:
465
+ employee_data['availability'] = st.selectbox("الحالة", ["متاح", "غير متاح"], key="employee_availability_selectbox")
466
+
467
+ employee_data['skills'] = st.multiselect("المهارات", [
468
+ "إدارة مشاريع",
469
+ "تصميم إنشائي",
470
+ "تخطيط وجدولة",
471
+ "حساب كميات",
472
+ "AutoCAD",
473
+ "Revit",
474
+ "Primavera",
475
+ "MS Project",
476
+ "تقدير تكاليف",
477
+ "مراقبة جودة",
478
+ "إدارة سلامة"
479
+ ])
480
+
481
+ employee_data['certifications'] = st.multiselect("الشهادات", [
482
+ "PMP",
483
+ "عضوية الهيئة السعودية للمهندسين",
484
+ "OSHA",
485
+ "NEBOSH",
486
+ "AutoCAD Certified",
487
+ "Revit Certified"
488
+ ])
489
+
490
+ if st.form_submit_button("إضافة"):
491
+ employees_df = st.session_state.resources_data["employees"]
492
+ new_id = f"EMP-{len(employees_df) + 1:03d}"
493
+ employee_data['id'] = new_id
494
+ employees_df.loc[len(employees_df)] = employee_data
495
+ st.success("تمت إضافة الموظف بنجاح")
496
+
497
+ # عرض الموظفين الحاليين
498
+ employees_df = st.session_state.resources_data["employees"]
499
+
500
+ # فلترة الموظفين
501
+ col1, col2, col3 = st.columns(3)
502
+ with col1:
503
+ dept_filter = st.selectbox("تصفية حسب القسم", ["الكل"] + list(employees_df["القسم"].unique()), key="employee_department_filter")
504
+ with col2:
505
+ position_filter = st.selectbox("تصفية حسب المنصب", ["الكل"] + list(employees_df["المنصب"].unique()), key="employee_position_filter")
506
+ with col3:
507
+ availability_filter = st.selectbox("تصفية حسب التوفر", ["الكل", "متاح", "غير متاح"], key="employee_availability_filter")
508
+
509
+ filtered_df = employees_df.copy()
510
+ if dept_filter != "الكل":
511
+ filtered_df = filtered_df[filtered_df["القسم"] == dept_filter]
512
+ if position_filter != "الكل":
513
+ filtered_df = filtered_df[filtered_df["المنصب"] == position_filter]
514
+ if availability_filter != "الكل":
515
+ filtered_df = filtered_df[filtered_df["متاح"] == (availability_filter == "متاح")]
516
+
517
+ st.dataframe(filtered_df, use_container_width=True)
518
 
519
  def _render_equipment_tab(self):
520
  """عرض تبويب المعدات"""
521
  st.markdown("### المعدات")
522
+
523
+ # إضافة معدة جديدة
524
+ with st.expander("إضافة معدة جديدة"):
525
+ equipment_form = st.form("add_equipment_form")
526
+ with equipment_form:
527
+ equipment_data = {}
528
+ equipment_data['name'] = st.text_input("اسم المعدة")
529
+ equipment_data['category'] = st.selectbox("الفئة", [
530
+ "معدات الحفر والردم",
531
+ "معدات النقل",
532
+ "معدات الرفع",
533
+ "معدات الخرسانة",
534
+ "معدات الطرق",
535
+ "معدات الصرف الصحي",
536
+ "معدات السيول والكباري",
537
+ "معدات الضغط والتثبيت",
538
+ "معدات التوليد والطاقة",
539
+ "معدات القياس والمساحة"
540
+ ], key="equipment_category_selectbox")
541
+ equipment_data['brand'] = st.text_input("الماركة")
542
+ equipment_data['model'] = st.text_input("الموديل")
543
+ equipment_data['capacity'] = st.text_input("السعة/القدرة")
544
+ equipment_data['production_rate'] = st.text_input("معدل الإنتاجية")
545
+
546
+ cols = st.columns(4)
547
+ with cols[0]:
548
+ equipment_data['hourly_cost'] = st.number_input("التكلفة بالساعة", min_value=0)
549
+ with cols[1]:
550
+ equipment_data['daily_cost'] = st.number_input("التكلفة باليوم", min_value=0)
551
+ with cols[2]:
552
+ equipment_data['weekly_cost'] = st.number_input("التكلفة بالأسبوع", min_value=0)
553
+ with cols[3]:
554
+ equipment_data['monthly_cost'] = st.number_input("التكلفة بالشهر", min_value=0)
555
+
556
+ equipment_data['fuel_consumption'] = st.text_input("استهلاك الوقود")
557
+ equipment_data['maintenance_period'] = st.text_input("فترة الصيانة")
558
+ equipment_data['maintenance_cost'] = st.number_input("تكلفة الصيانة", min_value=0)
559
+ equipment_data['operator_required'] = st.checkbox("تتطلب مشغل")
560
+ equipment_data['description'] = st.text_area("الوصف")
561
+
562
+ if st.form_submit_button("إضافة"):
563
+ equipment_df = st.session_state.resources_data["equipment"]
564
+ new_id = f"EQP-{len(equipment_df) + 1:03d}"
565
+ equipment_data['id'] = new_id
566
+ equipment_df.loc[len(equipment_df)] = equipment_data
567
+ st.success("تمت إضافة المعدة بنجاح")
568
+
569
+ # عرض المعدات الحالية
570
+ equipment_df = st.session_state.resources_data["equipment"]
571
+
572
+ # فلترة المعدات
573
+ col1, col2 = st.columns(2)
574
+ with col1:
575
+ category_filter = st.selectbox("تصفية حسب الفئة", ["الكل"] + list(equipment_df["النوع"].unique()), key="equipment_category_filter")
576
+ with col2:
577
+ availability_filter = st.selectbox("تصفية حسب التوفر", ["الكل", "متاح", "غير متاح"], key="equipment_availability_filter")
578
+
579
+ filtered_df = equipment_df.copy()
580
+ if category_filter != "الكل":
581
+ filtered_df = filtered_df[filtered_df["النوع"] == category_filter]
582
+ if availability_filter != "الكل":
583
+ filtered_df = filtered_df[filtered_df["متاحة"] == (availability_filter == "متاح")]
584
+
585
+ st.dataframe(filtered_df, use_container_width=True)
586
+
587
+ # إحصائيات المعدات
588
+ st.markdown("### إحصائيات المعدات")
589
+ col1, col2, col3 = st.columns(3)
590
+
591
+ with col1:
592
+ total_equipment = len(equipment_df)
593
+ available_equipment = len(equipment_df[equipment_df["متاحة"] == True])
594
+ st.metric("إجمالي المعدات", f"{available_equipment}/{total_equipment}")
595
+
596
+ with col2:
597
+ avg_daily_cost = equipment_df["التكلفة اليومية"].mean()
598
+ st.metric("متوسط التكلفة اليومية", f"{avg_daily_cost:.2f} ريال")
599
+
600
+ with col3:
601
+ maintenance_equipment = len(equipment_df[equipment_df["الموقع"] == "في الصيانة"])
602
+ st.metric("المعدات في الصيانة", maintenance_equipment)
603
 
604
  def _render_materials_tab(self):
605
  """عرض تبويب المواد"""
606
  st.markdown("### المواد")
607
+
608
+ # إضافة مادة جديدة
609
+ with st.expander("إضافة مادة جديدة"):
610
+ material_form = st.form("add_material_form")
611
+ with material_form:
612
+ material_data = {}
613
+ material_data['name'] = st.text_input("اسم المادة")
614
+ material_data['category'] = st.selectbox("الفئة", [
615
+ "مواد الخرسانة",
616
+ "مواد البناء",
617
+ "مواد الطرق",
618
+ "مواد الصرف الصحي",
619
+ "مواد العزل",
620
+ "مواد التشطيبات",
621
+ "مواد كهربائية",
622
+ "موا�� ميكانيكية",
623
+ "مواد الري والزراعة"
624
+ ], key="material_category_selectbox")
625
+
626
+ material_data['unit'] = st.selectbox("وحدة القياس", [
627
+ "م3", "م2", "متر طولي", "طن", "كجم", "لتر", "قطعة"
628
+ ], key="material_unit_selectbox")
629
+
630
+ cols = st.columns(4)
631
+ with cols[0]:
632
+ material_data['quantity'] = st.number_input("الكمية المتاحة", min_value=0)
633
+ with cols[1]:
634
+ material_data['unit_cost'] = st.number_input("تكلفة الوحدة", min_value=0)
635
+ with cols[2]:
636
+ material_data['supplier'] = st.text_input("المورد")
637
+ with cols[3]:
638
+ material_data['lead_time'] = st.number_input("مدة التوريد (يوم)", min_value=1)
639
+
640
+ material_data['specifications'] = st.text_area("المواصفات")
641
+ material_data['notes'] = st.text_area("ملاحظات")
642
+
643
+ if st.form_submit_button("إضافة"):
644
+ materials_df = st.session_state.resources_data["materials"]
645
+ new_id = f"MAT-{len(materials_df) + 1:03d}"
646
+ material_data['id'] = new_id
647
+ materials_df.loc[len(materials_df)] = material_data
648
+ st.success("تمت إضافة المادة بنجاح")
649
+
650
+ # عرض المواد الحالية
651
+ materials_df = st.session_state.resources_data["materials"]
652
+
653
+ # فلترة المواد
654
+ col1, col2 = st.columns(2)
655
+ with col1:
656
+ category_filter = st.selectbox("تصفية حسب الفئة", ["الكل"] + list(materials_df["اسم المادة"].unique()), key="material_category_filter")
657
+ with col2:
658
+ supplier_filter = st.selectbox("تصفية حسب المورد", ["الكل"] + list(materials_df["المورد"].unique()), key="material_supplier_filter")
659
+
660
+ filtered_df = materials_df.copy()
661
+ if category_filter != "الكل":
662
+ filtered_df = filtered_df[filtered_df["اسم المادة"] == category_filter]
663
+ if supplier_filter != "الكل":
664
+ filtered_df = filtered_df[filtered_df["المورد"] == supplier_filter]
665
+
666
+ st.dataframe(filtered_df, use_container_width=True)
667
+
668
+ # إحصائيات المواد
669
+ st.markdown("### إحصائيات المواد")
670
+ col1, col2, col3 = st.columns(3)
671
+
672
+ with col1:
673
+ total_materials = len(materials_df)
674
+ low_stock_materials = len(materials_df[materials_df["الكمية المتاحة"] < 50])
675
+ st.metric("إجمالي المواد", total_materials, f"-{low_stock_materials} منخفضة المخزون")
676
+
677
+ with col2:
678
+ total_value = (materials_df["الكمية المتاحة"] * materials_df["تكلفة الوحدة"]).sum()
679
+ st.metric("القيمة الإجمالية للمخزون", f"{total_value:,.2f} ريال")
680
+
681
+ with col3:
682
+ avg_lead_time = materials_df["مدة التوريد (يوم)"].mean()
683
+ st.metric("متوسط مدة التوريد", f"{avg_lead_time:.1f} يوم")
684
 
685
  def _render_resource_allocation_tab(self):
686
  """عرض تبويب تخصيص الموارد"""
modules/risk_analysis/risk_analyzer.py CHANGED
@@ -195,21 +195,21 @@ class RiskAnalyzer:
195
 
196
  class RiskAnalysisApp:
197
  """تطبيق تحليل المخاطر"""
198
-
199
  def __init__(self):
200
  """تهيئة تطبيق تحليل المخاطر"""
201
  self.ui = UIEnhancer(page_title="تحليل المخاطر - نظام تحليل المناقصات", page_icon="⚠️")
202
  self.ui.apply_theme_colors()
203
  self.risk_analyzer = RiskAnalyzer()
204
-
205
  # تهيئة بيانات المشاريع
206
  if 'projects' not in st.session_state:
207
  st.session_state.projects = self._generate_sample_projects()
208
-
209
  # تهيئة نتائج التحليل
210
  if 'risk_analysis_results' not in st.session_state:
211
  st.session_state.risk_analysis_results = {}
212
-
213
  def run(self):
214
  """تشغيل تطبيق تحليل المخاطر"""
215
  # إنشاء قائمة العناصر
@@ -231,13 +231,13 @@ class RiskAnalysisApp:
231
  {"name": "التقارير", "icon": "bar-chart"},
232
  {"name": "الإعدادات", "icon": "gear"}
233
  ]
234
-
235
  # إنشاء الشريط الجانبي
236
  selected = self.ui.create_sidebar(menu_items)
237
-
238
  # إنشاء ترويسة الصفحة
239
  self.ui.create_header("تحليل المخاطر", "تحديد وتقييم وإدارة مخاطر المشاريع")
240
-
241
  # عرض واجهة تحليل المخاطر
242
  tabs = st.tabs([
243
  "لوحة المعلومات",
@@ -246,36 +246,36 @@ class RiskAnalysisApp:
246
  "مصفوفة المخاطر",
247
  "استراتيجيات التخفيف"
248
  ])
249
-
250
  with tabs[0]:
251
  self._render_dashboard_tab()
252
-
253
  with tabs[1]:
254
  self._render_analysis_tab()
255
-
256
  with tabs[2]:
257
  self._render_risk_register_tab()
258
-
259
  with tabs[3]:
260
  self._render_risk_matrix_tab()
261
-
262
  with tabs[4]:
263
  self._render_mitigation_strategies_tab()
264
-
265
  def _render_dashboard_tab(self):
266
  """عرض تبويب لوحة المعلومات"""
267
-
268
  st.markdown("### لوحة معلومات تحليل المخاطر")
269
-
270
  # عرض إحصائيات المخاطر
271
  col1, col2, col3, col4 = st.columns(4)
272
-
273
  # الحصول على إحصائيات المخاطر
274
  total_risks = 0
275
  high_risks = 0
276
  medium_risks = 0
277
  low_risks = 0
278
-
279
  for project_id, results in st.session_state.risk_analysis_results.items():
280
  if "identified_risks" in results:
281
  project_risks = results["identified_risks"]
@@ -283,25 +283,25 @@ class RiskAnalysisApp:
283
  high_risks += len([r for r in project_risks if r["risk_score"] >= 6])
284
  medium_risks += len([r for r in project_risks if 3 <= r["risk_score"] < 6])
285
  low_risks += len([r for r in project_risks if r["risk_score"] < 3])
286
-
287
  with col1:
288
  self.ui.create_metric_card("إجمالي المخاطر", str(total_risks), None, self.ui.COLORS['primary'])
289
-
290
  with col2:
291
  self.ui.create_metric_card("مخاطر عالية", str(high_risks), None, self.ui.COLORS['danger'])
292
-
293
  with col3:
294
  self.ui.create_metric_card("مخاطر متوسطة", str(medium_risks), None, self.ui.COLORS['warning'])
295
-
296
  with col4:
297
  self.ui.create_metric_card("مخاطر منخفضة", str(low_risks), None, self.ui.COLORS['success'])
298
-
299
  # عرض توزيع المخاطر حسب الفئة
300
  st.markdown("#### توزيع المخاطر حسب الفئة")
301
-
302
  # جمع بيانات توزيع المخاطر
303
  category_distribution = {}
304
-
305
  for project_id, results in st.session_state.risk_analysis_results.items():
306
  if "identified_risks" in results:
307
  for risk in results["identified_risks"]:
@@ -309,24 +309,24 @@ class RiskAnalysisApp:
309
  if category not in category_distribution:
310
  category_distribution[category] = 0
311
  category_distribution[category] += 1
312
-
313
  if category_distribution:
314
  # تحويل البيانات إلى DataFrame
315
  category_df = pd.DataFrame({
316
  'الفئة': list(category_distribution.keys()),
317
  'عدد المخاطر': list(category_distribution.values())
318
  })
319
-
320
  # عرض الرسم البياني
321
  st.bar_chart(category_df.set_index('الفئة'))
322
  else:
323
  st.info("لا توجد بيانات كافية لعرض توزيع المخاطر.")
324
-
325
  # عرض المشاريع ذات المخاطر العالية
326
  st.markdown("#### المشاريع ذات المخاطر العالية")
327
-
328
  high_risk_projects = []
329
-
330
  for project_id, results in st.session_state.risk_analysis_results.items():
331
  if "identified_risks" in results:
332
  project_high_risks = len([r for r in results["identified_risks"] if r["risk_score"] >= 6])
@@ -340,78 +340,80 @@ class RiskAnalysisApp:
340
  'الجهة المالكة': project["client"],
341
  'عدد المخاطر العالية': project_high_risks
342
  })
343
-
344
  if high_risk_projects:
345
  high_risk_df = pd.DataFrame(high_risk_projects)
346
  st.dataframe(high_risk_df, use_container_width=True, hide_index=True)
347
  else:
348
  st.info("لا توجد مشاريع ذات مخاطر عالية حاليًا.")
349
-
350
  def _render_analysis_tab(self):
351
  """عرض تبويب تحليل المخاطر"""
352
-
353
  st.markdown("### تحليل مخاطر المشروع")
354
-
355
  # اختيار المشروع
356
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
357
  selected_project = st.selectbox("اختر المشروع", project_options)
358
-
359
  if selected_project:
360
  # استخراج معرف المشروع من الاختيار
361
  project_index = project_options.index(selected_project)
362
  project = st.session_state.projects[project_index]
363
  project_id = project["id"]
364
-
365
  # عرض معلومات المشروع
366
  col1, col2 = st.columns(2)
367
-
368
  with col1:
369
  st.markdown(f"**اسم المشروع**: {project['name']}")
370
  st.markdown(f"**رقم المناقصة**: {project['number']}")
371
  st.markdown(f"**الجهة المالكة**: {project['client']}")
372
-
373
  with col2:
374
- st.markdown(f"**الموقع**: {project['location']}")
375
- st.markdown(f"**نوع المشروع**: {project['project_type']}")
376
- st.markdown(f"**مستوى التعقيد**: {project['complexity']}")
377
-
 
 
378
  # زر بدء التحليل
379
  if st.button("بدء تحليل المخاطر"):
380
  with st.spinner("جاري تحليل مخاطر المشروع..."):
381
  # محاكاة وقت المعالجة
382
  import time
383
  time.sleep(2)
384
-
385
  # إجراء تحليل المخاطر (Using the new RiskAnalyzer)
386
  results = self.risk_analyzer.render_risk_analysis(project)
387
-
388
  # تخزين النتائج في حالة الجلسة
389
  st.session_state.risk_analysis_results[str(project_id)] = {"analysis_results": results}
390
-
391
  st.success("تم الانتهاء من تحليل المخاطر بنجاح!")
392
- st.experimental_rerun()
393
-
394
-
395
  def _render_risk_register_tab(self):
396
  """عرض تبويب سجل المخاطر"""
397
-
398
  st.markdown("### سجل المخاطر")
399
-
400
  # اختيار المشروع
401
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
402
  project_options.insert(0, "جميع المشاريع")
403
  selected_project_option = st.selectbox("اختر المشروع", project_options, key="risk_register_project")
404
-
405
  # جمع المخاطر المحددة
406
  all_risks = []
407
-
408
  if selected_project_option == "جميع المشاريع":
409
  # جمع المخاطر من جميع المشاريع
410
  for project_id, results in st.session_state.risk_analysis_results.items():
411
  if "analysis_results" in results:
412
  project = next((p for p in st.session_state.projects if p["id"] == int(project_id)), None)
413
  project_name = project["name"] if project else f"مشروع {project_id}"
414
-
415
  for index, row in results["analysis_results"].iterrows():
416
  risk_copy = row.to_dict()
417
  risk_copy["project_name"] = project_name
@@ -421,7 +423,7 @@ class RiskAnalysisApp:
421
  project_index = project_options.index(selected_project_option) - 1 # -1 لأننا أضفنا "جميع المشاريع" في البداية
422
  project = st.session_state.projects[project_index]
423
  project_id = project["id"]
424
-
425
  # جمع المخاطر من المشروع المحدد
426
  if str(project_id) in st.session_state.risk_analysis_results:
427
  results = st.session_state.risk_analysis_results[str(project_id)]
@@ -430,54 +432,54 @@ class RiskAnalysisApp:
430
  risk_copy = row.to_dict()
431
  risk_copy["project_name"] = project["name"]
432
  all_risks.append(risk_copy)
433
-
434
  # فلترة المخاطر
435
  col1, col2, col3 = st.columns(3)
436
-
437
  with col1:
438
  category_filter = st.multiselect(
439
  "فئة المخاطر",
440
  list(set(risk["category"] for risk in all_risks)) if all_risks else [],
441
  key="risk_register_category"
442
  )
443
-
444
  with col2:
445
  probability_filter = st.multiselect(
446
  "الاحتمالية",
447
  list(set(risk["probability"] for risk in all_risks)) if all_risks else [],
448
  key="risk_register_probability"
449
  )
450
-
451
  with col3:
452
  impact_filter = st.multiselect(
453
  "التأثير",
454
  list(set(risk["impact"] for risk in all_risks)) if all_risks else [],
455
  key="risk_register_impact"
456
  )
457
-
458
  # تطبيق الفلترة
459
  filtered_risks = all_risks
460
-
461
  if category_filter:
462
  filtered_risks = [risk for risk in filtered_risks if risk["category"] in category_filter]
463
-
464
  if probability_filter:
465
  filtered_risks = [risk for risk in filtered_risks if risk["probability"] in probability_filter]
466
-
467
  if impact_filter:
468
  filtered_risks = [risk for risk in filtered_risks if risk["impact"] in impact_filter]
469
-
470
  # عرض سجل المخاطر
471
  if filtered_risks:
472
  # تحويل المخاطر إلى DataFrame
473
  risk_df = pd.DataFrame(filtered_risks)
474
-
475
  # ترتيب المخاطر حسب درجة المخاطرة (تنازليًا)
476
  risk_df = risk_df.sort_values(by='risk_score', ascending=False)
477
-
478
  # عرض الجدول
479
  st.dataframe(risk_df, use_container_width=True, hide_index=True)
480
-
481
  # زر تصدير سجل المخاطر
482
  if st.button("تصدير سجل المخاطر"):
483
  with st.spinner("جاري تصدير سجل المخاطر..."):
@@ -486,26 +488,26 @@ class RiskAnalysisApp:
486
  st.success("تم تصدير سجل المخاطر بنجاح!")
487
  else:
488
  st.info("لا توجد مخاطر تطابق معايير البحث أو لم يتم إجراء تحليل للمخاطر بعد.")
489
-
490
  def _render_risk_matrix_tab(self):
491
  """عرض تبويب مصفوفة المخاطر"""
492
-
493
  st.markdown("### مصفوفة المخاطر")
494
-
495
  # اختيار المشروع
496
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
497
  selected_project = st.selectbox("اختر المشروع", project_options, key="risk_matrix_project")
498
-
499
  if selected_project:
500
  # استخراج معرف المشروع من الاختيار
501
  project_index = project_options.index(selected_project)
502
  project = st.session_state.projects[project_index]
503
  project_id = project["id"]
504
-
505
  # التحقق من وجود نتائج تحليل للمشروع
506
  if str(project_id) in st.session_state.risk_analysis_results:
507
  results = st.session_state.risk_analysis_results[str(project_id)]
508
-
509
  if "analysis_results" in results:
510
  risks_df = results["analysis_results"]
511
  self.risk_analyzer._render_risk_matrix(risks_df)
@@ -513,26 +515,26 @@ class RiskAnalysisApp:
513
  st.warning("لم يتم العثور على مصفوفة المخاطر للمشروع المحدد.")
514
  else:
515
  st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.")
516
-
517
  def _render_mitigation_strategies_tab(self):
518
  """عرض تبويب استراتيجيات التخفيف"""
519
-
520
  st.markdown("### استراتيجيات التخفيف من المخاطر")
521
-
522
  # اختيار المشروع
523
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
524
  selected_project = st.selectbox("اختر المشروع", project_options, key="mitigation_project")
525
-
526
  if selected_project:
527
  # استخراج معرف المشروع من الاختيار
528
  project_index = project_options.index(selected_project)
529
  project = st.session_state.projects[project_index]
530
  project_id = project["id"]
531
-
532
  # التحقق من وجود نتائج تحليل للمشروع
533
  if str(project_id) in st.session_state.risk_analysis_results:
534
  results = st.session_state.risk_analysis_results[str(project_id)]
535
-
536
  if "analysis_results" in results and not results["analysis_results"].empty:
537
  risks_df = results["analysis_results"]
538
  self.risk_analyzer._render_risk_response_plan(risks_df)
@@ -540,10 +542,10 @@ class RiskAnalysisApp:
540
  st.warning("لم يتم العثور على استراتيجيات تخفيف للمشروع المحدد.")
541
  else:
542
  st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.")
543
-
544
  def _generate_sample_projects(self):
545
  """توليد بيانات افتراضية للمشاريع"""
546
-
547
  return [
548
  {
549
  'id': 1,
 
195
 
196
  class RiskAnalysisApp:
197
  """تطبيق تحليل المخاطر"""
198
+
199
  def __init__(self):
200
  """تهيئة تطبيق تحليل المخاطر"""
201
  self.ui = UIEnhancer(page_title="تحليل المخاطر - نظام تحليل المناقصات", page_icon="⚠️")
202
  self.ui.apply_theme_colors()
203
  self.risk_analyzer = RiskAnalyzer()
204
+
205
  # تهيئة بيانات المشاريع
206
  if 'projects' not in st.session_state:
207
  st.session_state.projects = self._generate_sample_projects()
208
+
209
  # تهيئة نتائج التحليل
210
  if 'risk_analysis_results' not in st.session_state:
211
  st.session_state.risk_analysis_results = {}
212
+
213
  def run(self):
214
  """تشغيل تطبيق تحليل المخاطر"""
215
  # إنشاء قائمة العناصر
 
231
  {"name": "التقارير", "icon": "bar-chart"},
232
  {"name": "الإعدادات", "icon": "gear"}
233
  ]
234
+
235
  # إنشاء الشريط الجانبي
236
  selected = self.ui.create_sidebar(menu_items)
237
+
238
  # إنشاء ترويسة الصفحة
239
  self.ui.create_header("تحليل المخاطر", "تحديد وتقييم وإدارة مخاطر المشاريع")
240
+
241
  # عرض واجهة تحليل المخاطر
242
  tabs = st.tabs([
243
  "لوحة المعلومات",
 
246
  "مصفوفة المخاطر",
247
  "استراتيجيات التخفيف"
248
  ])
249
+
250
  with tabs[0]:
251
  self._render_dashboard_tab()
252
+
253
  with tabs[1]:
254
  self._render_analysis_tab()
255
+
256
  with tabs[2]:
257
  self._render_risk_register_tab()
258
+
259
  with tabs[3]:
260
  self._render_risk_matrix_tab()
261
+
262
  with tabs[4]:
263
  self._render_mitigation_strategies_tab()
264
+
265
  def _render_dashboard_tab(self):
266
  """عرض تبويب لوحة المعلومات"""
267
+
268
  st.markdown("### لوحة معلومات تحليل المخاطر")
269
+
270
  # عرض إحصائيات المخاطر
271
  col1, col2, col3, col4 = st.columns(4)
272
+
273
  # الحصول على إحصائيات المخاطر
274
  total_risks = 0
275
  high_risks = 0
276
  medium_risks = 0
277
  low_risks = 0
278
+
279
  for project_id, results in st.session_state.risk_analysis_results.items():
280
  if "identified_risks" in results:
281
  project_risks = results["identified_risks"]
 
283
  high_risks += len([r for r in project_risks if r["risk_score"] >= 6])
284
  medium_risks += len([r for r in project_risks if 3 <= r["risk_score"] < 6])
285
  low_risks += len([r for r in project_risks if r["risk_score"] < 3])
286
+
287
  with col1:
288
  self.ui.create_metric_card("إجمالي المخاطر", str(total_risks), None, self.ui.COLORS['primary'])
289
+
290
  with col2:
291
  self.ui.create_metric_card("مخاطر عالية", str(high_risks), None, self.ui.COLORS['danger'])
292
+
293
  with col3:
294
  self.ui.create_metric_card("مخاطر متوسطة", str(medium_risks), None, self.ui.COLORS['warning'])
295
+
296
  with col4:
297
  self.ui.create_metric_card("مخاطر منخفضة", str(low_risks), None, self.ui.COLORS['success'])
298
+
299
  # عرض توزيع المخاطر حسب الفئة
300
  st.markdown("#### توزيع المخاطر حسب الفئة")
301
+
302
  # جمع بيانات توزيع المخاطر
303
  category_distribution = {}
304
+
305
  for project_id, results in st.session_state.risk_analysis_results.items():
306
  if "identified_risks" in results:
307
  for risk in results["identified_risks"]:
 
309
  if category not in category_distribution:
310
  category_distribution[category] = 0
311
  category_distribution[category] += 1
312
+
313
  if category_distribution:
314
  # تحويل البيانات إلى DataFrame
315
  category_df = pd.DataFrame({
316
  'الفئة': list(category_distribution.keys()),
317
  'عدد المخاطر': list(category_distribution.values())
318
  })
319
+
320
  # عرض الرسم البياني
321
  st.bar_chart(category_df.set_index('الفئة'))
322
  else:
323
  st.info("لا توجد بيانات كافية لعرض توزيع المخاطر.")
324
+
325
  # عرض المشاريع ذات المخاطر العالية
326
  st.markdown("#### المشاريع ذات المخاطر العالية")
327
+
328
  high_risk_projects = []
329
+
330
  for project_id, results in st.session_state.risk_analysis_results.items():
331
  if "identified_risks" in results:
332
  project_high_risks = len([r for r in results["identified_risks"] if r["risk_score"] >= 6])
 
340
  'الجهة المالكة': project["client"],
341
  'عدد المخاطر العالية': project_high_risks
342
  })
343
+
344
  if high_risk_projects:
345
  high_risk_df = pd.DataFrame(high_risk_projects)
346
  st.dataframe(high_risk_df, use_container_width=True, hide_index=True)
347
  else:
348
  st.info("لا توجد مشاريع ذات مخاطر عالية حاليًا.")
349
+
350
  def _render_analysis_tab(self):
351
  """عرض تبويب تحليل المخاطر"""
352
+
353
  st.markdown("### تحليل مخاطر المشروع")
354
+
355
  # اختيار المشروع
356
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
357
  selected_project = st.selectbox("اختر المشروع", project_options)
358
+
359
  if selected_project:
360
  # استخراج معرف المشروع من الاختيار
361
  project_index = project_options.index(selected_project)
362
  project = st.session_state.projects[project_index]
363
  project_id = project["id"]
364
+
365
  # عرض معلومات المشروع
366
  col1, col2 = st.columns(2)
367
+
368
  with col1:
369
  st.markdown(f"**اسم المشروع**: {project['name']}")
370
  st.markdown(f"**رقم المناقصة**: {project['number']}")
371
  st.markdown(f"**الجهة المالكة**: {project['client']}")
372
+
373
  with col2:
374
+ st.markdown(f"**موقع المشروع**: {project['location']}")
375
+ project_type = project.get('type', project.get('tender_type', 'غير محدد'))
376
+ st.markdown(f"**نوع المشروع**: {project_type}")
377
+ complexity = project.get('complexity', 'غير محدد')
378
+ st.markdown(f"**مستوى التعقيد**: {complexity}")
379
+
380
  # زر بدء التحليل
381
  if st.button("بدء تحليل المخاطر"):
382
  with st.spinner("جاري تحليل مخاطر المشروع..."):
383
  # محاكاة وقت المعالجة
384
  import time
385
  time.sleep(2)
386
+
387
  # إجراء تحليل المخاطر (Using the new RiskAnalyzer)
388
  results = self.risk_analyzer.render_risk_analysis(project)
389
+
390
  # تخزين النتائج في حالة الجلسة
391
  st.session_state.risk_analysis_results[str(project_id)] = {"analysis_results": results}
392
+
393
  st.success("تم الانتهاء من تحليل المخاطر بنجاح!")
394
+ st.rerun()
395
+
396
+
397
  def _render_risk_register_tab(self):
398
  """عرض تبويب سجل المخاطر"""
399
+
400
  st.markdown("### سجل المخاطر")
401
+
402
  # اختيار المشروع
403
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
404
  project_options.insert(0, "جميع المشاريع")
405
  selected_project_option = st.selectbox("اختر المشروع", project_options, key="risk_register_project")
406
+
407
  # جمع المخاطر المحددة
408
  all_risks = []
409
+
410
  if selected_project_option == "جميع المشاريع":
411
  # جمع المخاطر من جميع المشاريع
412
  for project_id, results in st.session_state.risk_analysis_results.items():
413
  if "analysis_results" in results:
414
  project = next((p for p in st.session_state.projects if p["id"] == int(project_id)), None)
415
  project_name = project["name"] if project else f"مشروع {project_id}"
416
+
417
  for index, row in results["analysis_results"].iterrows():
418
  risk_copy = row.to_dict()
419
  risk_copy["project_name"] = project_name
 
423
  project_index = project_options.index(selected_project_option) - 1 # -1 لأننا أضفنا "جميع المشاريع" في البداية
424
  project = st.session_state.projects[project_index]
425
  project_id = project["id"]
426
+
427
  # جمع المخاطر من المشروع المحدد
428
  if str(project_id) in st.session_state.risk_analysis_results:
429
  results = st.session_state.risk_analysis_results[str(project_id)]
 
432
  risk_copy = row.to_dict()
433
  risk_copy["project_name"] = project["name"]
434
  all_risks.append(risk_copy)
435
+
436
  # فلترة المخاطر
437
  col1, col2, col3 = st.columns(3)
438
+
439
  with col1:
440
  category_filter = st.multiselect(
441
  "فئة المخاطر",
442
  list(set(risk["category"] for risk in all_risks)) if all_risks else [],
443
  key="risk_register_category"
444
  )
445
+
446
  with col2:
447
  probability_filter = st.multiselect(
448
  "الاحتمالية",
449
  list(set(risk["probability"] for risk in all_risks)) if all_risks else [],
450
  key="risk_register_probability"
451
  )
452
+
453
  with col3:
454
  impact_filter = st.multiselect(
455
  "التأثير",
456
  list(set(risk["impact"] for risk in all_risks)) if all_risks else [],
457
  key="risk_register_impact"
458
  )
459
+
460
  # تطبيق الفلترة
461
  filtered_risks = all_risks
462
+
463
  if category_filter:
464
  filtered_risks = [risk for risk in filtered_risks if risk["category"] in category_filter]
465
+
466
  if probability_filter:
467
  filtered_risks = [risk for risk in filtered_risks if risk["probability"] in probability_filter]
468
+
469
  if impact_filter:
470
  filtered_risks = [risk for risk in filtered_risks if risk["impact"] in impact_filter]
471
+
472
  # عرض سجل المخاطر
473
  if filtered_risks:
474
  # تحويل المخاطر إلى DataFrame
475
  risk_df = pd.DataFrame(filtered_risks)
476
+
477
  # ترتيب المخاطر حسب درجة المخاطرة (تنازليًا)
478
  risk_df = risk_df.sort_values(by='risk_score', ascending=False)
479
+
480
  # عرض الجدول
481
  st.dataframe(risk_df, use_container_width=True, hide_index=True)
482
+
483
  # زر تصدير سجل المخاطر
484
  if st.button("تصدير سجل المخاطر"):
485
  with st.spinner("جاري تصدير سجل المخاطر..."):
 
488
  st.success("تم تصدير سجل المخاطر بنجاح!")
489
  else:
490
  st.info("لا توجد مخاطر تطابق معايير البحث أو لم يتم إجراء تحليل للمخاطر بعد.")
491
+
492
  def _render_risk_matrix_tab(self):
493
  """عرض تبويب مصفوفة المخاطر"""
494
+
495
  st.markdown("### مصفوفة المخاطر")
496
+
497
  # اختيار المشروع
498
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
499
  selected_project = st.selectbox("اختر المشروع", project_options, key="risk_matrix_project")
500
+
501
  if selected_project:
502
  # استخراج معرف المشروع من الاختيار
503
  project_index = project_options.index(selected_project)
504
  project = st.session_state.projects[project_index]
505
  project_id = project["id"]
506
+
507
  # التحقق من وجود نتائج تحليل للمشروع
508
  if str(project_id) in st.session_state.risk_analysis_results:
509
  results = st.session_state.risk_analysis_results[str(project_id)]
510
+
511
  if "analysis_results" in results:
512
  risks_df = results["analysis_results"]
513
  self.risk_analyzer._render_risk_matrix(risks_df)
 
515
  st.warning("لم يتم العثور على مصفوفة المخاطر للمشروع المحدد.")
516
  else:
517
  st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.")
518
+
519
  def _render_mitigation_strategies_tab(self):
520
  """عرض تبويب استراتيجيات التخفيف"""
521
+
522
  st.markdown("### استراتيجيات التخفيف من المخاطر")
523
+
524
  # اختيار المشروع
525
  project_options = [f"{p['name']} ({p['number']})" for p in st.session_state.projects]
526
  selected_project = st.selectbox("اختر المشروع", project_options, key="mitigation_project")
527
+
528
  if selected_project:
529
  # استخراج معرف المشروع من الاختيار
530
  project_index = project_options.index(selected_project)
531
  project = st.session_state.projects[project_index]
532
  project_id = project["id"]
533
+
534
  # التحقق من وجود نتائج تحليل للمشروع
535
  if str(project_id) in st.session_state.risk_analysis_results:
536
  results = st.session_state.risk_analysis_results[str(project_id)]
537
+
538
  if "analysis_results" in results and not results["analysis_results"].empty:
539
  risks_df = results["analysis_results"]
540
  self.risk_analyzer._render_risk_response_plan(risks_df)
 
542
  st.warning("لم يتم العثور على استراتيجيات تخفيف للمشروع المحدد.")
543
  else:
544
  st.warning("لم يتم إجراء تحليل للمخاطر لهذا المشروع بعد.")
545
+
546
  def _generate_sample_projects(self):
547
  """توليد بيانات افتراضية للمشاريع"""
548
+
549
  return [
550
  {
551
  'id': 1,
modules/scheduling/schedule_app.py CHANGED
@@ -1,4 +1,5 @@
1
 
 
2
  import streamlit as st
3
  import pandas as pd
4
  import plotly.figure_factory as ff
@@ -152,17 +153,34 @@ class ScheduleApp:
152
  return
153
 
154
  st.subheader("تفاصيل المشروع")
155
- col1, col2 = st.columns(2)
 
 
 
156
  with col1:
157
  st.write(f"اسم المشروع: {project['project_name']}")
158
- st.write(f"إجمالي القيمة: {project['total_price']:,.2f} ريال")
 
 
159
  with col2:
 
 
 
 
 
160
  project['project_duration'] = st.number_input(
161
  "مدة المشروع (بالأيام)",
162
  min_value=30,
163
  max_value=1800,
164
  value=project.get('project_duration', 180)
165
  )
 
 
 
 
 
 
 
166
 
167
  self._generate_and_display_schedule(project)
168
 
@@ -179,46 +197,137 @@ class ScheduleApp:
179
 
180
  def _initialize_schedule_items(self, project):
181
  project['schedule_items'] = []
 
 
 
 
 
 
 
 
182
  for item in project['items']:
183
- relative_duration = int((item['total_price'] / project['total_price']) * project['project_duration'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  schedule_item = {
185
- 'Task': item.get('description', ''),
186
- 'Start': datetime.now().strftime('%Y-%m-%d'),
187
- 'Finish': (datetime.now() + timedelta(days=relative_duration)).strftime('%Y-%m-%d'),
188
- 'Duration': relative_duration,
189
- 'Dependencies': '',
190
  'Progress': 0,
191
- 'Resource': ''
 
192
  }
193
  project['schedule_items'].append(schedule_item)
 
194
 
195
  def _edit_schedule(self, schedule_items):
196
- return st.data_editor(
197
- pd.DataFrame(schedule_items),
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  column_config={
199
- "Task": "البند",
200
- "Start": st.column_config.DateColumn("تاريخ البداية"),
201
- "Finish": st.column_config.DateColumn("تاريخ النهاية"),
202
- "Duration": "المدة (أيام)",
203
- "Dependencies": "الاعتماديات",
204
- "Progress": st.column_config.NumberColumn("نسبة الإنجاز %", min_value=0, max_value=100),
205
- "Resource": "الموارد"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  },
 
207
  use_container_width=True,
208
- hide_index=True
 
209
  )
 
 
210
 
211
  def _display_gantt_chart(self, schedule_items):
212
  st.subheader("مخطط جانت")
213
  df = pd.DataFrame(schedule_items)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  fig = ff.create_gantt(
215
  df,
216
- colors={
217
- 'Complete': 'rgb(0, 255, 100)',
218
- 'Incomplete': 'rgb(160, 160, 160)'
219
- },
220
- index_col='Resource',
221
- show_colorbar=True,
222
  group_tasks=True,
223
  showgrid_x=True,
224
  showgrid_y=True
@@ -236,14 +345,74 @@ class ScheduleApp:
236
  def _display_progress_report(self, schedule_items):
237
  st.subheader("تقرير تقدم المشروع")
238
  df = pd.DataFrame(schedule_items)
239
- avg_progress = df['Progress'].mean()
240
 
241
  col1, col2, col3 = st.columns(3)
242
  with col1:
243
  st.metric("متوسط نسبة الإنجاز", f"{avg_progress:.1f}%")
244
  with col2:
245
- completed_tasks = len(df[df['Progress'] == 100])
246
  st.metric("البنود المكتملة", f"{completed_tasks} من {len(df)}")
247
  with col3:
248
- not_started = len(df[df['Progress'] == 0])
249
  st.metric("البنود غير المبدوءة", not_started)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ import os
3
  import streamlit as st
4
  import pandas as pd
5
  import plotly.figure_factory as ff
 
153
  return
154
 
155
  st.subheader("تفاصيل المشروع")
156
+
157
+ # عرض التفاصيل الأساسية
158
+ col1, col2, col3 = st.columns(3)
159
+
160
  with col1:
161
  st.write(f"اسم المشروع: {project['project_name']}")
162
+ st.write(f"رقم المشروع: {project.get('project_number', 'غير محدد')}")
163
+ st.write(f"العميل: {project.get('client', 'غير محدد')}")
164
+
165
  with col2:
166
+ st.write(f"إجمالي القيمة: {project['total_price']:,.2f} ريال")
167
+ st.write(f"الموقع: {project.get('location', 'غير محدد')}")
168
+ st.write(f"الحالة: {project.get('status', 'قيد التنفيذ')}")
169
+
170
+ with col3:
171
  project['project_duration'] = st.number_input(
172
  "مدة المشروع (بالأيام)",
173
  min_value=30,
174
  max_value=1800,
175
  value=project.get('project_duration', 180)
176
  )
177
+ st.write(f"تاريخ البدء: {project.get('start_date', 'غير محدد')}")
178
+ st.write(f"تاريخ الانتهاء المتوقع: {project.get('end_date', 'غير محدد')}")
179
+
180
+ # عرض وصف المشروع إذا كان متوفراً
181
+ if project.get('description'):
182
+ st.write("وصف المشروع:")
183
+ st.write(project['description'])
184
 
185
  self._generate_and_display_schedule(project)
186
 
 
197
 
198
  def _initialize_schedule_items(self, project):
199
  project['schedule_items'] = []
200
+ start_date = datetime.now()
201
+ total_project_price = project.get('total_price', 0)
202
+
203
+ if total_project_price == 0:
204
+ total_project_price = sum(item.get('total_price', 0) for item in project['items'])
205
+ project['total_price'] = total_project_price
206
+
207
+ current_date = start_date
208
  for item in project['items']:
209
+ item_price = item.get('total_price', 0)
210
+ if total_project_price > 0:
211
+ duration = max(1, int((item_price / total_project_price) * project['project_duration']))
212
+ else:
213
+ duration = max(1, int(project['project_duration'] / len(project['items'])))
214
+
215
+ end_date = current_date + timedelta(days=duration)
216
+
217
+ # تجميع المواد المطلوبة من تحليل البند
218
+ materials_list = []
219
+ if 'materials' in item:
220
+ for material in item['materials']:
221
+ materials_list.append(f"{material['name']} - {material['quantity']} {material['unit']}")
222
+
223
+ # إنشاء وصف تفصيلي للبند من جدول الكميات
224
+ # البحث باستخدام وصف البند
225
+ boq_item = next((x for x in project.get('items', [])
226
+ if x.get('description', '').strip().lower() == item.get('description', '').strip().lower()
227
+ or (x.get('code', '') == item.get('code', '') if item.get('code') else False)), None)
228
+
229
+ if boq_item:
230
+ task_description = f"البند: {boq_item.get('code', '')} - {boq_item.get('description', '')}\n"
231
+ task_description += f"الكمية: {boq_item.get('quantity', 0)} {boq_item.get('unit', '')}\n"
232
+ task_description += f"سعر الوحدة: {boq_item.get('unit_price', 0):,.2f} ريال\n"
233
+ task_description += f"الإجمالي: {boq_item.get('total_price', 0):,.2f} ريال"
234
+ else:
235
+ task_description = f"{item.get('code', '')} - {item.get('description', '')}"
236
+
237
  schedule_item = {
238
+ 'Task': task_description,
239
+ 'Start': current_date.strftime('%Y-%m-%d'),
240
+ 'Finish': end_date.strftime('%Y-%m-%d'),
241
+ 'Duration': duration,
 
242
  'Progress': 0,
243
+ 'Resource': item.get('category', 'الموارد الأساسية'),
244
+ 'materials': ', '.join(materials_list) if materials_list else ''
245
  }
246
  project['schedule_items'].append(schedule_item)
247
+ current_date = end_date
248
 
249
  def _edit_schedule(self, schedule_items):
250
+ df = pd.DataFrame(schedule_items)
251
+
252
+ # تحويل التواريخ من نص إلى تاريخ
253
+ df['Start'] = pd.to_datetime(df['Start'])
254
+ df['Finish'] = pd.to_datetime(df['Finish'])
255
+
256
+ # إضافة عمود للمواد المطلوبة
257
+ if 'materials' not in df.columns:
258
+ df['materials'] = ''
259
+
260
+ # حساب المدة بين تاريخ البداية والنهاية
261
+ df['Duration'] = (pd.to_datetime(df['Finish']) - pd.to_datetime(df['Start'])).dt.days
262
+
263
+ edited_df = st.data_editor(
264
+ df,
265
  column_config={
266
+ "Task": st.column_config.TextColumn(
267
+ "البند",
268
+ width="large",
269
+ help="وصف البند"
270
+ ),
271
+ "Start": st.column_config.DatetimeColumn(
272
+ "تاريخ البداية",
273
+ format="YYYY-MM-DD",
274
+ step=86400
275
+ ),
276
+ "Finish": st.column_config.DatetimeColumn(
277
+ "تاريخ النهاية",
278
+ format="YYYY-MM-DD",
279
+ step=86400
280
+ ),
281
+ "Duration": st.column_config.NumberColumn(
282
+ "المدة (أيام)",
283
+ min_value=1,
284
+ format="%d"
285
+ ),
286
+ "Progress": st.column_config.ProgressColumn(
287
+ "نسبة الإنجاز",
288
+ min_value=0,
289
+ max_value=100,
290
+ format="%d%%"
291
+ ),
292
+ "Resource": st.column_config.TextColumn(
293
+ "الموارد",
294
+ width="medium"
295
+ ),
296
+ "materials": st.column_config.TextColumn(
297
+ "المواد المطلوبة",
298
+ width="large"
299
+ )
300
  },
301
+ num_rows="dynamic",
302
  use_container_width=True,
303
+ hide_index=True,
304
+ key="schedule_editor"
305
  )
306
+
307
+ return edited_df
308
 
309
  def _display_gantt_chart(self, schedule_items):
310
  st.subheader("مخطط جانت")
311
  df = pd.DataFrame(schedule_items)
312
+
313
+ # تحويل التواريخ إلى datetime
314
+ df['Start'] = pd.to_datetime(df['Start'])
315
+ df['Finish'] = pd.to_datetime(df['Finish'])
316
+
317
+ # تأكد من أن كل القيم في عمود Resource هي نصوص
318
+ df['Resource'] = df['Resource'].astype(str)
319
+
320
+ # إنشاء رقم تسلسلي كمؤشر
321
+ df['ID'] = range(len(df))
322
+
323
+ # تحديد الألوان للمهام
324
+ colors = ['rgb(46, 137, 205)', 'rgb(82, 189, 97)', 'rgb(241, 196, 15)', 'rgb(231, 76, 60)']
325
+
326
  fig = ff.create_gantt(
327
  df,
328
+ colors=colors,
329
+ index_col='ID',
330
+ show_colorbar=False, # إخفاء شريط الألوان
 
 
 
331
  group_tasks=True,
332
  showgrid_x=True,
333
  showgrid_y=True
 
345
  def _display_progress_report(self, schedule_items):
346
  st.subheader("تقرير تقدم المشروع")
347
  df = pd.DataFrame(schedule_items)
348
+ avg_progress = df['Progress'].mean() if 'Progress' in df.columns else 0
349
 
350
  col1, col2, col3 = st.columns(3)
351
  with col1:
352
  st.metric("متوسط نسبة الإنجاز", f"{avg_progress:.1f}%")
353
  with col2:
354
+ completed_tasks = len(df[df['Progress'] == 100]) if 'Progress' in df.columns else 0
355
  st.metric("البنود المكتملة", f"{completed_tasks} من {len(df)}")
356
  with col3:
357
+ not_started = len(df[df['Progress'] == 0]) if 'Progress' in df.columns else len(df)
358
  st.metric("البنود غير المبدوءة", not_started)
359
+
360
+ # أزرار الحفظ والتصدير
361
+ col1, col2 = st.columns(2)
362
+ with col1:
363
+ if st.button("💾 حفظ الجدول الزمني", key="save_schedule_btn"):
364
+ self._save_schedule_to_db(df)
365
+ st.success("تم حفظ الجدول الزمني بنجاح!")
366
+
367
+ with col2:
368
+ if st.button("📊 تصدير إلى Excel", key="export_schedule_btn"):
369
+ self._export_schedule_to_excel(df)
370
+
371
+ def _save_schedule_to_db(self, df):
372
+ """حفظ الجدول الزمني في قاعدة البيانات"""
373
+ try:
374
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
375
+ schedule_data = {
376
+ 'timestamp': timestamp,
377
+ 'project_name': st.session_state.current_project.get('name', 'مشروع جديد'),
378
+ 'schedule_items': df.to_dict('records')
379
+ }
380
+
381
+ if 'saved_schedules' not in st.session_state:
382
+ st.session_state.saved_schedules = []
383
+
384
+ st.session_state.saved_schedules.append(schedule_data)
385
+
386
+ except Exception as e:
387
+ st.error(f"حدث خطأ أثناء حفظ الجدول الزمني: {str(e)}")
388
+
389
+ def _export_schedule_to_excel(self, df):
390
+ """تصدير الجدول الزمني إلى ملف Excel"""
391
+ try:
392
+ export_path = "data/exports"
393
+ os.makedirs(export_path, exist_ok=True)
394
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
395
+ excel_file = f"{export_path}/schedule_{timestamp}.xlsx"
396
+
397
+ # إنشاء كاتب Excel
398
+ with pd.ExcelWriter(excel_file, engine='openpyxl') as writer:
399
+ df.to_excel(writer, index=False, sheet_name='الجدول الزمني')
400
+ worksheet = writer.sheets['الجدول الزمني']
401
+
402
+ # إضافة معلومات الملخص
403
+ worksheet['A1'] = f"اسم المشروع: {st.session_state.current_project.get('name', 'مشروع جديد')}"
404
+ worksheet['A2'] = f"تاريخ التصدير: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
405
+
406
+ # توفير زر التحميل
407
+ with open(excel_file, 'rb') as f:
408
+ excel_data = f.read()
409
+ st.download_button(
410
+ label="تحميل ملف Excel",
411
+ data=excel_data,
412
+ file_name=f"schedule_{timestamp}.xlsx",
413
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
414
+ )
415
+ st.success("تم تصدير الجدول الزمني بنجاح!")
416
+
417
+ except Exception as e:
418
+ st.error(f"حدث خطأ أثناء تصدير الجدول الزمني: {str(e)}")
requirements.txt CHANGED
@@ -34,4 +34,24 @@ pytest==7.4.0
34
  pytest-cov==4.1.0
35
  reportlab==4.0.8
36
  rouge-score==0.1.2
37
- python-Levenshtein==0.25.1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  pytest-cov==4.1.0
35
  reportlab==4.0.8
36
  rouge-score==0.1.2
37
+ python-Levenshtein==0.25.1
38
+ ezdxf>=1.0.0
39
+ ifcopenshell>=0.7.0
40
+ opencv-python>=4.8.0
41
+ pytesseract>=0.3.10
42
+ numpy>=1.26.0
43
+ Pillow>=10.0.0
44
+ shapely>=2.0.0
45
+ matplotlib>=3.8.0
46
+ scikit-image>=0.21.0
47
+ tensorflow>=2.15.0
48
+ torch>=2.1.0
49
+ transformers>=4.36.0
50
+ shapely>=2.0.0
51
+ pillow>=10.0.0
52
+ opencv-python
53
+ pandas
54
+ plotly
55
+ pytesseract
56
+ shapely
57
+ pdf2image
styling/enhanced_ui.py CHANGED
@@ -11,7 +11,7 @@ import os
11
 
12
  class UIEnhancer:
13
  """فئة لتحسين واجهة المستخدم وتوحيد التصميم عبر النظام"""
14
-
15
  # ألوان النظام
16
  COLORS = {
17
  'primary': '#1E88E5', # أزرق
@@ -27,7 +27,7 @@ class UIEnhancer:
27
  'text': '#212121', # أسود
28
  'border': '#E0E0E0' # رمادي حدود
29
  }
30
-
31
  # أحجام الخطوط
32
  FONT_SIZES = {
33
  'xs': '0.75rem',
@@ -40,34 +40,34 @@ class UIEnhancer:
40
  '4xl': '2.25rem',
41
  '5xl': '3rem'
42
  }
43
-
44
  def __init__(self, page_title="نظام تحليل المناقصات", page_icon="📊"):
45
  """تهيئة محسن واجهة المستخدم"""
46
  self.page_title = page_title
47
  self.page_icon = page_icon
48
  self.theme_mode = "light" # الوضع الافتراضي هو الوضع الفاتح
49
-
50
  # تهيئة متغير السمة في حالة الجلسة إذا لم يكن موجوداً
51
  if 'theme' not in st.session_state:
52
  st.session_state.theme = 'light'
53
-
54
  def apply_global_styles(self):
55
  """تطبيق التنسيقات العامة على الصفحة"""
56
  # تعريف CSS العام
57
  css = f"""
58
  @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap');
59
-
60
  * {{
61
  font-family: 'Tajawal', sans-serif;
62
  direction: rtl;
63
  }}
64
-
65
  h1, h2, h3, h4, h5, h6 {{
66
  font-family: 'Tajawal', sans-serif;
67
  font-weight: 700;
68
  color: {self.COLORS['dark']};
69
  }}
70
-
71
  .module-title {{
72
  color: {self.COLORS['primary']};
73
  font-size: {self.FONT_SIZES['3xl']};
@@ -75,11 +75,11 @@ class UIEnhancer:
75
  border-bottom: 2px solid {self.COLORS['primary']};
76
  padding-bottom: 0.5rem;
77
  }}
78
-
79
  .stTabs [data-baseweb="tab-list"] {{
80
  gap: 2px;
81
  }}
82
-
83
  .stTabs [data-baseweb="tab"] {{
84
  height: 50px;
85
  white-space: pre-wrap;
@@ -89,82 +89,86 @@ class UIEnhancer:
89
  padding-top: 10px;
90
  padding-bottom: 10px;
91
  }}
92
-
93
  .stTabs [aria-selected="true"] {{
94
  background-color: {self.COLORS['primary']};
95
  color: white;
96
  }}
97
-
98
  div[data-testid="stSidebarNav"] li div a span {{
99
  direction: rtl;
100
  text-align: right;
101
  font-family: 'Tajawal', sans-serif;
102
  }}
103
-
104
  div[data-testid="stSidebarNav"] {{
105
  background-color: {self.COLORS['light']};
106
  }}
107
-
108
  div[data-testid="stSidebarNav"] li div {{
109
  margin-right: 0;
110
  margin-left: auto;
111
  }}
112
-
113
  div[data-testid="stSidebarNav"] li div a {{
114
  padding-right: 10px;
115
  padding-left: 0;
116
  }}
117
-
118
  div[data-testid="stSidebarNav"] li div a:hover {{
119
  background-color: {self.COLORS['primary'] + '20'};
120
  }}
121
-
122
  div[data-testid="stSidebarNav"] li div[aria-selected="true"] {{
123
  background-color: {self.COLORS['primary'] + '40'};
124
  }}
125
-
126
  div[data-testid="stSidebarNav"] li div[aria-selected="true"] a span {{
127
  color: {self.COLORS['primary']};
128
  font-weight: 500;
129
  }}
130
-
131
  .metric-card {{
132
  background-color: white;
133
  border-radius: 10px;
134
  padding: 20px;
135
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
136
  text-align: center;
137
- transition: transform 0.3s ease;
 
 
138
  }}
139
-
140
  .metric-card:hover {{
141
  transform: translateY(-5px);
 
 
142
  }}
143
-
144
  .metric-value {{
145
  font-size: 2.5rem;
146
  font-weight: 700;
147
  margin: 10px 0;
148
  }}
149
-
150
  .metric-label {{
151
  font-size: 1rem;
152
  color: #666;
153
  }}
154
-
155
  .metric-change {{
156
  font-size: 0.9rem;
157
  margin-top: 5px;
158
  }}
159
-
160
  .metric-change-positive {{
161
  color: {self.COLORS['success']};
162
  }}
163
-
164
  .metric-change-negative {{
165
  color: {self.COLORS['danger']};
166
  }}
167
-
168
  .custom-button {{
169
  background-color: {self.COLORS['primary']};
170
  color: white;
@@ -175,11 +179,11 @@ class UIEnhancer:
175
  cursor: pointer;
176
  transition: background-color 0.3s ease;
177
  }}
178
-
179
  .custom-button:hover {{
180
  background-color: {self.COLORS['secondary']};
181
  }}
182
-
183
  .custom-card {{
184
  background-color: white;
185
  border-radius: 10px;
@@ -187,59 +191,54 @@ class UIEnhancer:
187
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
188
  margin-bottom: 20px;
189
  }}
190
-
191
  .header-container {{
192
- display: flex;
193
- justify-content: space-between;
194
- align-items: center;
195
  margin-bottom: 2rem;
196
- padding-bottom: 1rem;
197
- border-bottom: 1px solid {self.COLORS['border']};
198
  }}
199
-
200
  .header-title {{
201
- color: {self.COLORS['primary']};
202
- font-size: {self.FONT_SIZES['3xl']};
203
  margin: 0;
 
204
  }}
205
-
206
  .header-subtitle {{
207
- color: {self.COLORS['dark']};
208
- font-size: {self.FONT_SIZES['lg']};
209
- margin: 0;
210
- }}
211
-
212
- .header-actions {{
213
- display: flex;
214
- gap: 10px;
215
  }}
216
-
217
  /* تنسيق الجداول */
218
  div[data-testid="stTable"] table {{
219
  width: 100%;
220
  border-collapse: collapse;
221
  }}
222
-
223
  div[data-testid="stTable"] thead tr th {{
224
  background-color: {self.COLORS['primary']};
225
  color: white;
226
  text-align: right;
227
  padding: 12px;
228
  }}
229
-
230
  div[data-testid="stTable"] tbody tr:nth-child(even) {{
231
  background-color: {self.COLORS['light']};
232
  }}
233
-
234
  div[data-testid="stTable"] tbody tr:hover {{
235
  background-color: {self.COLORS['primary'] + '10'};
236
  }}
237
-
238
  div[data-testid="stTable"] tbody tr td {{
239
  padding: 10px;
240
  text-align: right;
241
  }}
242
-
243
  /* تنسيق النماذج */
244
  div[data-testid="stForm"] {{
245
  background-color: white;
@@ -247,18 +246,18 @@ class UIEnhancer:
247
  padding: 20px;
248
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
249
  }}
250
-
251
  button[kind="primaryFormSubmit"] {{
252
  background-color: {self.COLORS['primary']};
253
  color: white;
254
  }}
255
-
256
  button[kind="secondaryFormSubmit"] {{
257
  background-color: {self.COLORS['light']};
258
  color: {self.COLORS['dark']};
259
  border: 1px solid {self.COLORS['border']};
260
  }}
261
-
262
  /* تنسيق الرسوم البيانية */
263
  div[data-testid="stVegaLiteChart"] {{
264
  background-color: white;
@@ -267,10 +266,10 @@ class UIEnhancer:
267
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
268
  }}
269
  """
270
-
271
  # تطبيق CSS
272
  st.markdown(f'<style>{css}</style>', unsafe_allow_html=True)
273
-
274
  def apply_theme_colors(self):
275
  """تطبيق ألوان السمة الحالية"""
276
  # تحديد ألوان السمة بناءً على الوضع
@@ -282,7 +281,7 @@ class UIEnhancer:
282
  self.COLORS['background'] = '#FFFFFF'
283
  self.COLORS['text'] = '#212121'
284
  self.COLORS['border'] = '#E0E0E0'
285
-
286
  # تطبيق CSS للسمة
287
  theme_css = f"""
288
  body {{
@@ -290,18 +289,18 @@ class UIEnhancer:
290
  color: {self.COLORS['text']};
291
  }}
292
  """
293
-
294
  st.markdown(f'<style>{theme_css}</style>', unsafe_allow_html=True)
295
-
296
  def toggle_theme(self):
297
  """تبديل وضع السمة بين الفاتح والداكن"""
298
  if self.theme_mode == "light":
299
  self.theme_mode = "dark"
300
  else:
301
  self.theme_mode = "light"
302
-
303
  self.apply_theme_colors()
304
-
305
  def create_sidebar(self, menu_items):
306
  """إنشاء الشريط الجانبي مع قائمة العناصر"""
307
  with st.sidebar:
@@ -314,7 +313,7 @@ class UIEnhancer:
314
  """,
315
  unsafe_allow_html=True
316
  )
317
-
318
  # إضافة معلومات المستخدم
319
  st.markdown(
320
  f"""
@@ -323,14 +322,14 @@ class UIEnhancer:
323
  م
324
  </div>
325
  <p style="margin-top: 10px; font-weight: bold;">مهندس تامر الجوهري</p>
326
- <p style="margin-top: -15px; font-size: 0.8rem; color: #666;">مدير تقنية معلومات المنطقة الشمالية </p>
327
  </div>
328
  """,
329
  unsafe_allow_html=True
330
  )
331
-
332
  st.divider()
333
-
334
  # إنشاء القائمة
335
  selected = st.radio(
336
  "القائمة الرئيسية",
@@ -338,9 +337,9 @@ class UIEnhancer:
338
  format_func=lambda x: x,
339
  label_visibility="collapsed"
340
  )
341
-
342
  st.divider()
343
-
344
  # إضافة معلومات النظام
345
  st.markdown(
346
  """
@@ -351,37 +350,36 @@ class UIEnhancer:
351
  """,
352
  unsafe_allow_html=True
353
  )
354
-
355
  return selected
356
-
357
- def create_header(self, title, subtitle=None, show_actions=True):
358
- """إنشاء ترويسة الصفحة"""
359
- # إنشاء معرفات فريدة للأزرار
360
- add_button_key = f"add_button_{title}"
361
- update_button_key = f"update_button_{title}"
362
-
363
- col1, col2 = st.columns([3, 1])
364
-
365
- with col1:
366
- st.markdown(f'<h1 class="header-title">{title}</h1>', unsafe_allow_html=True)
367
- if subtitle:
368
- st.markdown(f'<p class="header-subtitle">{subtitle}</p>', unsafe_allow_html=True)
369
 
 
 
 
 
 
 
 
 
 
 
370
  if show_actions:
 
 
 
371
  with col2:
372
- col2_1, col2_2 = st.columns(2)
373
- with col2_1:
374
- st.button("إضافة جديد", key=add_button_key)
375
- with col2_2:
376
- st.button("تحديث", key=update_button_key)
377
-
378
  st.divider()
379
-
380
  def create_metric_card(self, label, value, change=None, color=None):
381
  """إنشاء بطاقة مقياس"""
382
  if color is None:
383
  color = self.COLORS['primary']
384
-
385
  change_html = ""
386
  if change is not None:
387
  if change.startswith("+"):
@@ -393,9 +391,9 @@ class UIEnhancer:
393
  else:
394
  change_class = ""
395
  change_icon = ""
396
-
397
  change_html = f'<div class="metric-change {change_class}">{change_icon} {change}</div>'
398
-
399
  st.markdown(
400
  f"""
401
  <div class="metric-card" style="border-top: 4px solid {color};">
@@ -406,12 +404,12 @@ class UIEnhancer:
406
  """,
407
  unsafe_allow_html=True
408
  )
409
-
410
  def create_card(self, title, content, color=None):
411
  """إنشاء بطاقة عامة"""
412
  if color is None:
413
  color = self.COLORS['primary']
414
-
415
  st.markdown(
416
  f"""
417
  <div class="custom-card" style="border-top: 4px solid {color};">
@@ -421,39 +419,39 @@ class UIEnhancer:
421
  """,
422
  unsafe_allow_html=True
423
  )
424
-
425
  def create_button(self, label, color=None, icon=None, key=None):
426
  """إنشاء زر مخصص"""
427
  if color is None:
428
  color = self.COLORS['primary']
429
-
430
  # إنشاء معرف فريد للزر إذا لم يتم توفيره
431
  if key is None:
432
  key = f"button_{label}_{hash(label)}"
433
-
434
  icon_html = f"{icon} " if icon else ""
435
-
436
  return st.button(
437
  f"{icon_html}{label}",
438
  key=key
439
  )
440
-
441
  def create_tabs(self, tab_names):
442
  """إنشاء تبويبات"""
443
  return st.tabs(tab_names)
444
-
445
  def create_expander(self, title, expanded=False, key=None):
446
  """إنشاء عنصر قابل للتوسيع"""
447
  # إنشاء معرف فريد للعنصر إذا لم يتم توفيره
448
  if key is None:
449
  key = f"expander_{title}_{hash(title)}"
450
-
451
  return st.expander(title, expanded=expanded, key=key)
452
-
453
  def create_data_table(self, data, use_container_width=True, hide_index=True):
454
  """إنشاء جدول بيانات"""
455
  return st.dataframe(data, use_container_width=use_container_width, hide_index=hide_index)
456
-
457
  def create_chart(self, chart_type, data, **kwargs):
458
  """إنشاء رسم بياني"""
459
  if chart_type == "bar":
@@ -464,119 +462,119 @@ class UIEnhancer:
464
  return st.area_chart(data, **kwargs)
465
  else:
466
  return st.bar_chart(data, **kwargs)
467
-
468
  def create_form(self, title, key=None):
469
  """إنشاء نموذج"""
470
  # إنشاء معرف فريد للنموذج إذا لم يتم توفيره
471
  if key is None:
472
  key = f"form_{title}_{hash(title)}"
473
-
474
  return st.form(key=key)
475
-
476
  def create_file_uploader(self, label, types=None, key=None):
477
  """إنشاء أداة رفع الملفات"""
478
  # إنشاء معرف فريد لأداة رفع الملفات إذا لم يتم توفيره
479
  if key is None:
480
  key = f"file_uploader_{label}_{hash(label)}"
481
-
482
  return st.file_uploader(label, type=types, key=key)
483
-
484
  def create_date_input(self, label, value=None, key=None):
485
  """إنشاء حقل إدخال تاريخ"""
486
  # إنشاء معرف فريد لحقل إدخال التاريخ إذا لم يتم توفيره
487
  if key is None:
488
  key = f"date_input_{label}_{hash(label)}"
489
-
490
  return st.date_input(label, value=value, key=key)
491
-
492
  def create_select_box(self, label, options, index=0, key=None):
493
  """إنشاء قائمة منسدلة"""
494
  # إنشاء معرف فريد للقائمة المنسدلة إذا لم يتم توفيره
495
  if key is None:
496
  key = f"select_box_{label}_{hash(label)}"
497
-
498
  return st.selectbox(label, options, index=index, key=key)
499
-
500
  def create_multi_select(self, label, options, default=None, key=None):
501
  """إنشاء قائمة اختيار متعدد"""
502
  # إنشاء معرف فريد لقائمة الاختيار المتعدد إذا لم يتم توفيره
503
  if key is None:
504
  key = f"multi_select_{label}_{hash(label)}"
505
-
506
  return st.multiselect(label, options, default=default, key=key)
507
-
508
  def create_slider(self, label, min_value, max_value, value=None, step=1, key=None):
509
  """إنشاء شريط تمرير"""
510
  # إنشاء معرف فريد لشريط التمرير إذا لم يتم توفيره
511
  if key is None:
512
  key = f"slider_{label}_{hash(label)}"
513
-
514
  return st.slider(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
515
-
516
  def create_text_input(self, label, value="", key=None):
517
  """إنشاء حقل إدخال نصي"""
518
  # إنشاء معرف فريد لحقل الإدخال النصي إذا لم يتم توفيره
519
  if key is None:
520
  key = f"text_input_{label}_{hash(label)}"
521
-
522
  return st.text_input(label, value=value, key=key)
523
-
524
  def create_text_area(self, label, value="", height=None, key=None):
525
  """إنشاء منطقة نص"""
526
  # إنشاء معرف فريد لمنطقة النص إذا لم يتم توفيره
527
  if key is None:
528
  key = f"text_area_{label}_{hash(label)}"
529
-
530
  return st.text_area(label, value=value, height=height, key=key)
531
-
532
  def create_number_input(self, label, min_value=None, max_value=None, value=0, step=1, key=None):
533
  """إنشاء حقل إدخال رقمي"""
534
  # إنشاء معرف فريد لحقل الإدخال الرقمي إذا لم يتم توفيره
535
  if key is None:
536
  key = f"number_input_{label}_{hash(label)}"
537
-
538
  return st.number_input(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
539
-
540
  def create_checkbox(self, label, value=False, key=None):
541
  """إنشاء خانة اختيار"""
542
  # إنشاء معرف فريد لخانة الاختيار إذا لم يتم توفيره
543
  if key is None:
544
  key = f"checkbox_{label}_{hash(label)}"
545
-
546
  return st.checkbox(label, value=value, key=key)
547
-
548
  def create_radio(self, label, options, index=0, key=None):
549
  """إنشاء أزرار راديو"""
550
  # إنشاء معرف فريد لأزرار الراديو إذا لم يتم توفيره
551
  if key is None:
552
  key = f"radio_{label}_{hash(label)}"
553
-
554
  return st.radio(label, options, index=index, key=key)
555
-
556
  def create_progress_bar(self, value, key=None):
557
  """إنشاء شريط تقدم"""
558
  # إنشاء معرف فريد لشريط التقدم إذا لم يتم توفيره
559
  if key is None:
560
  key = f"progress_bar_{value}_{hash(str(value))}"
561
-
562
  return st.progress(value, key=key)
563
-
564
  def create_spinner(self, text="جاري التحميل..."):
565
  """إنشاء مؤشر تحميل"""
566
  return st.spinner(text)
567
-
568
  def create_success_message(self, message):
569
  """إنشاء رسالة نجاح"""
570
  return st.success(message)
571
-
572
  def create_error_message(self, message):
573
  """إنشاء رسالة خطأ"""
574
  return st.error(message)
575
-
576
  def create_warning_message(self, message):
577
  """إنشاء رسالة تحذير"""
578
  return st.warning(message)
579
-
580
  def create_info_message(self, message):
581
  """إنشاء رسالة معلومات"""
582
- return st.info(message)
 
11
 
12
  class UIEnhancer:
13
  """فئة لتحسين واجهة المستخدم وتوحيد التصميم عبر النظام"""
14
+
15
  # ألوان النظام
16
  COLORS = {
17
  'primary': '#1E88E5', # أزرق
 
27
  'text': '#212121', # أسود
28
  'border': '#E0E0E0' # رمادي حدود
29
  }
30
+
31
  # أحجام الخطوط
32
  FONT_SIZES = {
33
  'xs': '0.75rem',
 
40
  '4xl': '2.25rem',
41
  '5xl': '3rem'
42
  }
43
+
44
  def __init__(self, page_title="نظام تحليل المناقصات", page_icon="📊"):
45
  """تهيئة محسن واجهة المستخدم"""
46
  self.page_title = page_title
47
  self.page_icon = page_icon
48
  self.theme_mode = "light" # الوضع الافتراضي هو الوضع الفاتح
49
+
50
  # تهيئة متغير السمة في حالة الجلسة إذا لم يكن موجوداً
51
  if 'theme' not in st.session_state:
52
  st.session_state.theme = 'light'
53
+
54
  def apply_global_styles(self):
55
  """تطبيق التنسيقات العامة على الصفحة"""
56
  # تعريف CSS العام
57
  css = f"""
58
  @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&display=swap');
59
+
60
  * {{
61
  font-family: 'Tajawal', sans-serif;
62
  direction: rtl;
63
  }}
64
+
65
  h1, h2, h3, h4, h5, h6 {{
66
  font-family: 'Tajawal', sans-serif;
67
  font-weight: 700;
68
  color: {self.COLORS['dark']};
69
  }}
70
+
71
  .module-title {{
72
  color: {self.COLORS['primary']};
73
  font-size: {self.FONT_SIZES['3xl']};
 
75
  border-bottom: 2px solid {self.COLORS['primary']};
76
  padding-bottom: 0.5rem;
77
  }}
78
+
79
  .stTabs [data-baseweb="tab-list"] {{
80
  gap: 2px;
81
  }}
82
+
83
  .stTabs [data-baseweb="tab"] {{
84
  height: 50px;
85
  white-space: pre-wrap;
 
89
  padding-top: 10px;
90
  padding-bottom: 10px;
91
  }}
92
+
93
  .stTabs [aria-selected="true"] {{
94
  background-color: {self.COLORS['primary']};
95
  color: white;
96
  }}
97
+
98
  div[data-testid="stSidebarNav"] li div a span {{
99
  direction: rtl;
100
  text-align: right;
101
  font-family: 'Tajawal', sans-serif;
102
  }}
103
+
104
  div[data-testid="stSidebarNav"] {{
105
  background-color: {self.COLORS['light']};
106
  }}
107
+
108
  div[data-testid="stSidebarNav"] li div {{
109
  margin-right: 0;
110
  margin-left: auto;
111
  }}
112
+
113
  div[data-testid="stSidebarNav"] li div a {{
114
  padding-right: 10px;
115
  padding-left: 0;
116
  }}
117
+
118
  div[data-testid="stSidebarNav"] li div a:hover {{
119
  background-color: {self.COLORS['primary'] + '20'};
120
  }}
121
+
122
  div[data-testid="stSidebarNav"] li div[aria-selected="true"] {{
123
  background-color: {self.COLORS['primary'] + '40'};
124
  }}
125
+
126
  div[data-testid="stSidebarNav"] li div[aria-selected="true"] a span {{
127
  color: {self.COLORS['primary']};
128
  font-weight: 500;
129
  }}
130
+
131
  .metric-card {{
132
  background-color: white;
133
  border-radius: 10px;
134
  padding: 20px;
135
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
136
  text-align: center;
137
+ transition: all 0.3s ease;
138
+ border: 2px solid transparent;
139
+ cursor: pointer;
140
  }}
141
+
142
  .metric-card:hover {{
143
  transform: translateY(-5px);
144
+ border-color: {self.COLORS['primary']};
145
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
146
  }}
147
+
148
  .metric-value {{
149
  font-size: 2.5rem;
150
  font-weight: 700;
151
  margin: 10px 0;
152
  }}
153
+
154
  .metric-label {{
155
  font-size: 1rem;
156
  color: #666;
157
  }}
158
+
159
  .metric-change {{
160
  font-size: 0.9rem;
161
  margin-top: 5px;
162
  }}
163
+
164
  .metric-change-positive {{
165
  color: {self.COLORS['success']};
166
  }}
167
+
168
  .metric-change-negative {{
169
  color: {self.COLORS['danger']};
170
  }}
171
+
172
  .custom-button {{
173
  background-color: {self.COLORS['primary']};
174
  color: white;
 
179
  cursor: pointer;
180
  transition: background-color 0.3s ease;
181
  }}
182
+
183
  .custom-button:hover {{
184
  background-color: {self.COLORS['secondary']};
185
  }}
186
+
187
  .custom-card {{
188
  background-color: white;
189
  border-radius: 10px;
 
191
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
192
  margin-bottom: 20px;
193
  }}
194
+
195
  .header-container {{
196
+ background: linear-gradient(135deg, {self.COLORS['primary']}, {self.COLORS['secondary']});
197
+ padding: 2rem;
198
+ border-radius: 15px;
199
  margin-bottom: 2rem;
200
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 
201
  }}
202
+
203
  .header-title {{
204
+ color: #ffffff;
205
+ font-size: 2rem;
206
  margin: 0;
207
+ font-weight: 700;
208
  }}
209
+
210
  .header-subtitle {{
211
+ color: rgba(255, 255, 255, 0.9);
212
+ font-size: 1.1rem;
213
+ margin: 0.5rem 0 0 0;
 
 
 
 
 
214
  }}
215
+
216
  /* تنسيق الجداول */
217
  div[data-testid="stTable"] table {{
218
  width: 100%;
219
  border-collapse: collapse;
220
  }}
221
+
222
  div[data-testid="stTable"] thead tr th {{
223
  background-color: {self.COLORS['primary']};
224
  color: white;
225
  text-align: right;
226
  padding: 12px;
227
  }}
228
+
229
  div[data-testid="stTable"] tbody tr:nth-child(even) {{
230
  background-color: {self.COLORS['light']};
231
  }}
232
+
233
  div[data-testid="stTable"] tbody tr:hover {{
234
  background-color: {self.COLORS['primary'] + '10'};
235
  }}
236
+
237
  div[data-testid="stTable"] tbody tr td {{
238
  padding: 10px;
239
  text-align: right;
240
  }}
241
+
242
  /* تنسيق النماذج */
243
  div[data-testid="stForm"] {{
244
  background-color: white;
 
246
  padding: 20px;
247
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
248
  }}
249
+
250
  button[kind="primaryFormSubmit"] {{
251
  background-color: {self.COLORS['primary']};
252
  color: white;
253
  }}
254
+
255
  button[kind="secondaryFormSubmit"] {{
256
  background-color: {self.COLORS['light']};
257
  color: {self.COLORS['dark']};
258
  border: 1px solid {self.COLORS['border']};
259
  }}
260
+
261
  /* تنسيق الرسوم البيانية */
262
  div[data-testid="stVegaLiteChart"] {{
263
  background-color: white;
 
266
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
267
  }}
268
  """
269
+
270
  # تطبيق CSS
271
  st.markdown(f'<style>{css}</style>', unsafe_allow_html=True)
272
+
273
  def apply_theme_colors(self):
274
  """تطبيق ألوان السمة الحالية"""
275
  # تحديد ألوان السمة بناءً على الوضع
 
281
  self.COLORS['background'] = '#FFFFFF'
282
  self.COLORS['text'] = '#212121'
283
  self.COLORS['border'] = '#E0E0E0'
284
+
285
  # تطبيق CSS للسمة
286
  theme_css = f"""
287
  body {{
 
289
  color: {self.COLORS['text']};
290
  }}
291
  """
292
+
293
  st.markdown(f'<style>{theme_css}</style>', unsafe_allow_html=True)
294
+
295
  def toggle_theme(self):
296
  """تبديل وضع السمة بين الفاتح والداكن"""
297
  if self.theme_mode == "light":
298
  self.theme_mode = "dark"
299
  else:
300
  self.theme_mode = "light"
301
+
302
  self.apply_theme_colors()
303
+
304
  def create_sidebar(self, menu_items):
305
  """إنشاء الشريط الجانبي مع قائمة العناصر"""
306
  with st.sidebar:
 
313
  """,
314
  unsafe_allow_html=True
315
  )
316
+
317
  # إضافة معلومات المستخدم
318
  st.markdown(
319
  f"""
 
322
  م
323
  </div>
324
  <p style="margin-top: 10px; font-weight: bold;">مهندس تامر الجوهري</p>
325
+ <p style="margin-top: -15px; font-size: 0.8rem; color: #666;">مدير تقنية معلومات المنطقة الشمالية</p>
326
  </div>
327
  """,
328
  unsafe_allow_html=True
329
  )
330
+
331
  st.divider()
332
+
333
  # إنشاء القائمة
334
  selected = st.radio(
335
  "القائمة الرئيسية",
 
337
  format_func=lambda x: x,
338
  label_visibility="collapsed"
339
  )
340
+
341
  st.divider()
342
+
343
  # إضافة معلومات النظام
344
  st.markdown(
345
  """
 
350
  """,
351
  unsafe_allow_html=True
352
  )
353
+
354
  return selected
355
+
356
+ def create_header(self, title, subtitle=None, show_actions=True, icon=None):
357
+ """إنشاء ترويسة الصفحة مع أيقونة وتصميم محسن"""
 
 
 
 
 
 
 
 
 
 
358
 
359
+ st.markdown(
360
+ f"""
361
+ <div class="header-container">
362
+ <h1 class="header-title">{title}</h1>
363
+ {f'<p class="header-subtitle">{subtitle}</p>' if subtitle else ''}
364
+ </div>
365
+ """,
366
+ unsafe_allow_html=True
367
+ )
368
+
369
  if show_actions:
370
+ col1, col2 = st.columns([3, 1])
371
+ with col1:
372
+ st.button("إضافة جديد", key=f"add_button_{title}", use_container_width=True)
373
  with col2:
374
+ st.button("تحديث", key=f"update_button_{title}", use_container_width=True)
375
+
 
 
 
 
376
  st.divider()
377
+
378
  def create_metric_card(self, label, value, change=None, color=None):
379
  """إنشاء بطاقة مقياس"""
380
  if color is None:
381
  color = self.COLORS['primary']
382
+
383
  change_html = ""
384
  if change is not None:
385
  if change.startswith("+"):
 
391
  else:
392
  change_class = ""
393
  change_icon = ""
394
+
395
  change_html = f'<div class="metric-change {change_class}">{change_icon} {change}</div>'
396
+
397
  st.markdown(
398
  f"""
399
  <div class="metric-card" style="border-top: 4px solid {color};">
 
404
  """,
405
  unsafe_allow_html=True
406
  )
407
+
408
  def create_card(self, title, content, color=None):
409
  """إنشاء بطاقة عامة"""
410
  if color is None:
411
  color = self.COLORS['primary']
412
+
413
  st.markdown(
414
  f"""
415
  <div class="custom-card" style="border-top: 4px solid {color};">
 
419
  """,
420
  unsafe_allow_html=True
421
  )
422
+
423
  def create_button(self, label, color=None, icon=None, key=None):
424
  """إنشاء زر مخصص"""
425
  if color is None:
426
  color = self.COLORS['primary']
427
+
428
  # إنشاء معرف فريد للزر إذا لم يتم توفيره
429
  if key is None:
430
  key = f"button_{label}_{hash(label)}"
431
+
432
  icon_html = f"{icon} " if icon else ""
433
+
434
  return st.button(
435
  f"{icon_html}{label}",
436
  key=key
437
  )
438
+
439
  def create_tabs(self, tab_names):
440
  """إنشاء تبويبات"""
441
  return st.tabs(tab_names)
442
+
443
  def create_expander(self, title, expanded=False, key=None):
444
  """إنشاء عنصر قابل للتوسيع"""
445
  # إنشاء معرف فريد للعنصر إذا لم يتم توفيره
446
  if key is None:
447
  key = f"expander_{title}_{hash(title)}"
448
+
449
  return st.expander(title, expanded=expanded, key=key)
450
+
451
  def create_data_table(self, data, use_container_width=True, hide_index=True):
452
  """إنشاء جدول بيانات"""
453
  return st.dataframe(data, use_container_width=use_container_width, hide_index=hide_index)
454
+
455
  def create_chart(self, chart_type, data, **kwargs):
456
  """إنشاء رسم بياني"""
457
  if chart_type == "bar":
 
462
  return st.area_chart(data, **kwargs)
463
  else:
464
  return st.bar_chart(data, **kwargs)
465
+
466
  def create_form(self, title, key=None):
467
  """إنشاء نموذج"""
468
  # إنشاء معرف فريد للنموذج إذا لم يتم توفيره
469
  if key is None:
470
  key = f"form_{title}_{hash(title)}"
471
+
472
  return st.form(key=key)
473
+
474
  def create_file_uploader(self, label, types=None, key=None):
475
  """إنشاء أداة رفع الملفات"""
476
  # إنشاء معرف فريد لأداة رفع الملفات إذا لم يتم توفيره
477
  if key is None:
478
  key = f"file_uploader_{label}_{hash(label)}"
479
+
480
  return st.file_uploader(label, type=types, key=key)
481
+
482
  def create_date_input(self, label, value=None, key=None):
483
  """إنشاء حقل إدخال تاريخ"""
484
  # إنشاء معرف فريد لحقل إدخال التاريخ إذا لم يتم توفيره
485
  if key is None:
486
  key = f"date_input_{label}_{hash(label)}"
487
+
488
  return st.date_input(label, value=value, key=key)
489
+
490
  def create_select_box(self, label, options, index=0, key=None):
491
  """إنشاء قائمة منسدلة"""
492
  # إنشاء معرف فريد للقائمة المنسدلة إذا لم يتم توفيره
493
  if key is None:
494
  key = f"select_box_{label}_{hash(label)}"
495
+
496
  return st.selectbox(label, options, index=index, key=key)
497
+
498
  def create_multi_select(self, label, options, default=None, key=None):
499
  """إنشاء قائمة اختيار متعدد"""
500
  # إنشاء معرف فريد لقائمة الاختيار المتعدد إذا لم يتم توفيره
501
  if key is None:
502
  key = f"multi_select_{label}_{hash(label)}"
503
+
504
  return st.multiselect(label, options, default=default, key=key)
505
+
506
  def create_slider(self, label, min_value, max_value, value=None, step=1, key=None):
507
  """إنشاء شريط تمرير"""
508
  # إنشاء معرف فريد لشريط التمرير إذا لم يتم توفيره
509
  if key is None:
510
  key = f"slider_{label}_{hash(label)}"
511
+
512
  return st.slider(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
513
+
514
  def create_text_input(self, label, value="", key=None):
515
  """إنشاء حقل إدخال نصي"""
516
  # إنشاء معرف فريد لحقل الإدخال النصي إذا لم يتم توفيره
517
  if key is None:
518
  key = f"text_input_{label}_{hash(label)}"
519
+
520
  return st.text_input(label, value=value, key=key)
521
+
522
  def create_text_area(self, label, value="", height=None, key=None):
523
  """إنشاء منطقة نص"""
524
  # إنشاء معرف فريد لمنطقة النص إذا لم يتم توفيره
525
  if key is None:
526
  key = f"text_area_{label}_{hash(label)}"
527
+
528
  return st.text_area(label, value=value, height=height, key=key)
529
+
530
  def create_number_input(self, label, min_value=None, max_value=None, value=0, step=1, key=None):
531
  """إنشاء حقل إدخال رقمي"""
532
  # إنشاء معرف فريد لحقل الإدخال الرقمي إذا لم يتم توفيره
533
  if key is None:
534
  key = f"number_input_{label}_{hash(label)}"
535
+
536
  return st.number_input(label, min_value=min_value, max_value=max_value, value=value, step=step, key=key)
537
+
538
  def create_checkbox(self, label, value=False, key=None):
539
  """إنشاء خانة اختيار"""
540
  # إنشاء معرف فريد لخانة الاختيار إذا لم يتم توفيره
541
  if key is None:
542
  key = f"checkbox_{label}_{hash(label)}"
543
+
544
  return st.checkbox(label, value=value, key=key)
545
+
546
  def create_radio(self, label, options, index=0, key=None):
547
  """إنشاء أزرار راديو"""
548
  # إنشاء معرف فريد لأزرار الراديو إذا لم يتم توفيره
549
  if key is None:
550
  key = f"radio_{label}_{hash(label)}"
551
+
552
  return st.radio(label, options, index=index, key=key)
553
+
554
  def create_progress_bar(self, value, key=None):
555
  """إنشاء شريط تقدم"""
556
  # إنشاء معرف فريد لشريط التقدم إذا لم يتم توفيره
557
  if key is None:
558
  key = f"progress_bar_{value}_{hash(str(value))}"
559
+
560
  return st.progress(value, key=key)
561
+
562
  def create_spinner(self, text="جاري التحميل..."):
563
  """إنشاء مؤشر تحميل"""
564
  return st.spinner(text)
565
+
566
  def create_success_message(self, message):
567
  """إنشاء رسالة نجاح"""
568
  return st.success(message)
569
+
570
  def create_error_message(self, message):
571
  """إنشاء رسالة خطأ"""
572
  return st.error(message)
573
+
574
  def create_warning_message(self, message):
575
  """إنشاء رسالة تحذير"""
576
  return st.warning(message)
577
+
578
  def create_info_message(self, message):
579
  """إنشاء رسالة معلومات"""
580
+ return st.info(message)