EGYADMIN commited on
Commit
b1ed9b7
·
verified ·
1 Parent(s): 4556572

Create modules/schedule_analyzer.py

Browse files
Files changed (1) hide show
  1. modules/schedule_analyzer.py +1124 -0
modules/schedule_analyzer.py ADDED
@@ -0,0 +1,1124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import datetime
5
+ import numpy as np
6
+ import pandas as pd
7
+ from typing import Dict, List, Any, Union, Tuple, Optional
8
+ from datetime import datetime, timedelta
9
+
10
+ class ScheduleAnalyzer:
11
+ """
12
+ فئة لتحليل الجدول الزمني للمناقصات
13
+ """
14
+
15
+ def __init__(self):
16
+ """
17
+ تهيئة محلل الجدول الزمني
18
+ """
19
+ # تحميل قوالب الجداول الزمنية
20
+ self.schedule_templates = self._load_schedule_templates()
21
+
22
+ # تحميل معايير تقييم الجداول الزمنية
23
+ self.evaluation_criteria = self._load_evaluation_criteria()
24
+
25
+ # تحميل قاعدة بيانات المشاريع السابقة
26
+ self.historical_projects = self._load_historical_projects()
27
+
28
+ def _load_schedule_templates(self) -> Dict[str, Dict[str, Any]]:
29
+ """
30
+ تحميل قوالب الجداول الزمنية حسب نوع المشروع
31
+ """
32
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
33
+ return {
34
+ "الإنشاءات": {
35
+ "phases": [
36
+ {
37
+ "name": "التخطيط والتصميم",
38
+ "duration_percentage": 0.15,
39
+ "activities": [
40
+ "إعداد المخططات الأولية",
41
+ "الحصول على الموافقات",
42
+ "إعداد المخططات التفصيلية",
43
+ "تصميم الأنظمة الكهربائية والميكانيكية",
44
+ "إعداد جداول الكميات"
45
+ ]
46
+ },
47
+ {
48
+ "name": "التجهيز والتعاقد",
49
+ "duration_percentage": 0.10,
50
+ "activities": [
51
+ "إعداد وثائق العطاء",
52
+ "طرح العطاء",
53
+ "تقييم العروض",
54
+ "توقيع العقود مع المقاولين",
55
+ "تجهيز الموقع"
56
+ ]
57
+ },
58
+ {
59
+ "name": "الأعمال الإنشائية",
60
+ "duration_percentage": 0.50,
61
+ "activities": [
62
+ "أعمال الحفر والأساسات",
63
+ "أعمال الهيكل الخرساني",
64
+ "أعمال البناء",
65
+ "أعمال التشطيبات الخارجية",
66
+ "أعمال التشطيبات الداخلية"
67
+ ]
68
+ },
69
+ {
70
+ "name": "الأنظمة والمرافق",
71
+ "duration_percentage": 0.15,
72
+ "activities": [
73
+ "تركيب الأنظمة الكهربائية",
74
+ "تركيب الأنظمة الميكانيكية",
75
+ "تركيب أنظمة التكييف",
76
+ "تركيب أنظمة السباكة",
77
+ "تركيب أنظمة الحريق والإنذار"
78
+ ]
79
+ },
80
+ {
81
+ "name": "الاختبار والتسليم",
82
+ "duration_percentage": 0.10,
83
+ "activities": [
84
+ "اختبار الأنظمة",
85
+ "تشغيل تجريبي",
86
+ "إصلاح الملاحظات",
87
+ "التسليم المبدئي",
88
+ "التسليم النهائي"
89
+ ]
90
+ }
91
+ ],
92
+ "critical_activities": [
93
+ "إعداد المخططات التفصيلية",
94
+ "أعمال الحفر والأساسات",
95
+ "أعمال الهيكل الخرساني",
96
+ "تركيب الأنظمة الكهربائية",
97
+ "اختبار الأنظمة"
98
+ ]
99
+ },
100
+ "تقنية المعلومات": {
101
+ "phases": [
102
+ {
103
+ "name": "التحليل والتخطيط",
104
+ "duration_percentage": 0.15,
105
+ "activities": [
106
+ "تحليل المتطلبات",
107
+ "دراسة الجدوى",
108
+ "تحديد نطاق المشروع",
109
+ "إعداد خطة المشروع",
110
+ "تحديد الموارد اللازمة"
111
+ ]
112
+ },
113
+ {
114
+ "name": "التصميم",
115
+ "duration_percentage": 0.20,
116
+ "activities": [
117
+ "تصميم هيكل النظام",
118
+ "تصميم قاعدة البيانات",
119
+ "تصميم واجهات المستخدم",
120
+ "تصميم الخوارزميات",
121
+ "توثيق التصميم"
122
+ ]
123
+ },
124
+ {
125
+ "name": "التطوير",
126
+ "duration_percentage": 0.40,
127
+ "activities": [
128
+ "تطوير النظام الأساسي",
129
+ "تطوير قاعدة البيانات",
130
+ "تطوير واجهات المستخدم",
131
+ "تطوير وحدات النظام",
132
+ "تكامل الوحدات"
133
+ ]
134
+ },
135
+ {
136
+ "name": "الاختبار",
137
+ "duration_percentage": 0.15,
138
+ "activities": [
139
+ "اختبار الوحدات",
140
+ "اختبار التكامل",
141
+ "اختبار النظام",
142
+ "اختبار القبول",
143
+ "إصلاح الأخطاء"
144
+ ]
145
+ },
146
+ {
147
+ "name": "النشر والتدريب",
148
+ "duration_percentage": 0.10,
149
+ "activities": [
150
+ "إعداد بيئة الإنتاج",
151
+ "نشر النظام",
152
+ "تدريب المستخدمين",
153
+ "إعداد وثائق المستخدم",
154
+ "الدعم الأولي"
155
+ ]
156
+ }
157
+ ],
158
+ "critical_activities": [
159
+ "تحليل المتطلبات",
160
+ "تصميم هيكل النظام",
161
+ "تطوير النظام الأساسي",
162
+ "تكامل الوحدات",
163
+ "اختبار القبول"
164
+ ]
165
+ },
166
+ "عام": {
167
+ "phases": [
168
+ {
169
+ "name": "التخطيط",
170
+ "duration_percentage": 0.15,
171
+ "activities": [
172
+ "تحديد الأهداف",
173
+ "تحديد النطاق",
174
+ "تحديد الموارد",
175
+ "تحديد الميزانية",
176
+ "إعداد خطة المشروع"
177
+ ]
178
+ },
179
+ {
180
+ "name": "التنفيذ",
181
+ "duration_percentage": 0.60,
182
+ "activities": [
183
+ "توفير الموارد",
184
+ "تنفيذ الأنشطة",
185
+ "مراقبة التقدم",
186
+ "إدارة المخاطر",
187
+ "ضبط الجودة"
188
+ ]
189
+ },
190
+ {
191
+ "name": "الإغلاق",
192
+ "duration_percentage": 0.25,
193
+ "activities": [
194
+ "اختبار المخرجات",
195
+ "تسليم المخرجات",
196
+ "توثيق الدروس المستفادة",
197
+ "إغلاق العقود",
198
+ "إغلاق المشروع"
199
+ ]
200
+ }
201
+ ],
202
+ "critical_activities": [
203
+ "تحديد النطاق",
204
+ "توفير الموارد",
205
+ "تنفيذ الأنشطة",
206
+ "اختبار المخرجات",
207
+ "تسليم المخرجات"
208
+ ]
209
+ }
210
+ }
211
+
212
+ def _load_evaluation_criteria(self) -> Dict[str, Dict[str, Any]]:
213
+ """
214
+ تحميل معايير تقييم الجداول الزمنية
215
+ """
216
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
217
+ return {
218
+ "realism": {
219
+ "weight": 0.3,
220
+ "criteria": [
221
+ "توافق مدة المشروع مع مشاريع مماثلة",
222
+ "توافق توزيع الموارد مع الأنشطة",
223
+ "مراعاة العوامل الخارجية والمخاطر"
224
+ ]
225
+ },
226
+ "completeness": {
227
+ "weight": 0.2,
228
+ "criteria": [
229
+ "شمولية الأنشطة",
230
+ "تحديد المعالم الرئيسية",
231
+ "تحديد نقاط التسليم"
232
+ ]
233
+ },
234
+ "resource_allocation": {
235
+ "weight": 0.2,
236
+ "criteria": [
237
+ "توزيع مناسب للموارد",
238
+ "تجنب تحميل زائد للموارد",
239
+ "تحديد المسؤوليات"
240
+ ]
241
+ },
242
+ "risk_management": {
243
+ "weight": 0.15,
244
+ "criteria": [
245
+ "تضمين احتياطيات زمنية",
246
+ "تحديد المسار الحرج",
247
+ "خطط بديلة للأنشطة الحرجة"
248
+ ]
249
+ },
250
+ "flexibility": {
251
+ "weight": 0.15,
252
+ "criteria": [
253
+ "قابلية التعديل والتحديث",
254
+ "آلية للتعامل مع التغييرات",
255
+ "مراقبة التقدم وتقييمه"
256
+ ]
257
+ }
258
+ }
259
+
260
+ def _load_historical_projects(self) -> Dict[str, Dict[str, Any]]:
261
+ """
262
+ تحميل بيانات المشاريع السابقة للمقارنة
263
+ """
264
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
265
+ return {
266
+ "الإنشاءات": {
267
+ "small": {
268
+ "avg_duration": 12, # متوسط المدة بالأشهر
269
+ "min_duration": 6,
270
+ "max_duration": 18
271
+ },
272
+ "medium": {
273
+ "avg_duration": 24,
274
+ "min_duration": 18,
275
+ "max_duration": 36
276
+ },
277
+ "large": {
278
+ "avg_duration": 48,
279
+ "min_duration": 36,
280
+ "max_duration": 60
281
+ }
282
+ },
283
+ "تقنية المعلومات": {
284
+ "small": {
285
+ "avg_duration": 6,
286
+ "min_duration": 3,
287
+ "max_duration": 9
288
+ },
289
+ "medium": {
290
+ "avg_duration": 12,
291
+ "min_duration": 9,
292
+ "max_duration": 18
293
+ },
294
+ "large": {
295
+ "avg_duration": 24,
296
+ "min_duration": 18,
297
+ "max_duration": 36
298
+ }
299
+ },
300
+ "عام": {
301
+ "small": {
302
+ "avg_duration": 6,
303
+ "min_duration": 3,
304
+ "max_duration": 12
305
+ },
306
+ "medium": {
307
+ "avg_duration": 12,
308
+ "min_duration": 9,
309
+ "max_duration": 24
310
+ },
311
+ "large": {
312
+ "avg_duration": 24,
313
+ "min_duration": 18,
314
+ "max_duration": 36
315
+ }
316
+ }
317
+ }
318
+
319
+ def analyze(self, extracted_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
320
+ """
321
+ تحليل الجدول الزمني بناءً على البيانات المستخرجة
322
+
323
+ المعاملات:
324
+ ----------
325
+ extracted_data : Dict[str, Any]
326
+ البيانات المستخرجة من المستندات
327
+ **kwargs : Dict[str, Any]
328
+ معاملات إضافية مثل نوع المشروع، الميزانية، تاريخ البدء، المدة
329
+
330
+ المخرجات:
331
+ --------
332
+ Dict[str, Any]
333
+ نتائج تحليل الجدول الزمني
334
+ """
335
+ results = {
336
+ "phases": [],
337
+ "critical_path": [],
338
+ "milestones": [],
339
+ "evaluation": {},
340
+ "optimization": [],
341
+ "risks": []
342
+ }
343
+
344
+ # تحديد نوع المشروع والقطاع
345
+ project_type = kwargs.get("project_type", "")
346
+ sector = self._determine_sector(project_type, extracted_data)
347
+
348
+ # استخراج معلومات الجدول الزمني من البيانات المستخرجة
349
+ schedule_info = self._extract_schedule_info(extracted_data)
350
+
351
+ # تحديد حجم المشروع
352
+ project_size = self._determine_project_size(kwargs.get("budget", 0), schedule_info.get("duration", 0))
353
+
354
+ # إعداد الجدول الزمني
355
+ phases = self._prepare_schedule(schedule_info, sector, project_size, kwargs)
356
+ results["phases"] = phases
357
+
358
+ # تحديد المسار الحرج
359
+ critical_path = self._identify_critical_path(phases, sector)
360
+ results["critical_path"] = critical_path
361
+
362
+ # تحديد المعالم الرئيسية
363
+ milestones = self._identify_milestones(phases, schedule_info)
364
+ results["milestones"] = milestones
365
+
366
+ # تقييم الجدول الزمني
367
+ evaluation = self._evaluate_schedule(phases, schedule_info, sector, project_size)
368
+ results["evaluation"] = evaluation
369
+
370
+ # اقتراحات لتحسين الجدول الزمني
371
+ optimization = self._generate_optimization_suggestions(phases, evaluation, schedule_info)
372
+ results["optimization"] = optimization
373
+
374
+ # تحليل المخاطر المتعلقة بالجدول الزمني
375
+ risks = self._analyze_schedule_risks(phases, schedule_info, sector)
376
+ results["risks"] = risks
377
+
378
+ return results
379
+
380
+ def _determine_sector(self, project_type: str, extracted_data: Dict[str, Any]) -> str:
381
+ """
382
+ تحديد القطاع بناءً على نوع المشروع والبيانات المستخرجة
383
+ """
384
+ # قاموس لتحويل أنواع المشاريع الشائعة إلى قطاعات
385
+ project_to_sector = {
386
+ "إنشاءات": "الإنشاءات",
387
+ "مباني": "الإنشاءات",
388
+ "طرق": "الإنشاءات",
389
+ "جسور": "الإنشاءات",
390
+ "تقنية معلومات": "تقنية المعلومات",
391
+ "برمجيات": "تقنية المعلومات",
392
+ "تطبيقات": "تقنية المعلومات"
393
+ }
394
+
395
+ # محاولة تحديد القطاع من نوع المشروع
396
+ if project_type:
397
+ for key, value in project_to_sector.items():
398
+ if key in project_type.lower():
399
+ return value
400
+
401
+ # إذا لم يتم تحديد القطاع من نوع المشروع، نحاول تحديده من البيانات المستخرجة
402
+ if "text" in extracted_data:
403
+ text = extracted_data["text"].lower()
404
+ sector_scores = {}
405
+
406
+ for key, value in project_to_sector.items():
407
+ score = text.count(key.lower())
408
+ if value not in sector_scores:
409
+ sector_scores[value] = 0
410
+ sector_scores[value] += score
411
+
412
+ # اختيار القطاع الأكثر ذكراً
413
+ if sector_scores:
414
+ max_sector = max(sector_scores, key=sector_scores.get)
415
+ if sector_scores[max_sector] > 0:
416
+ return max_sector
417
+
418
+ # القطاع الافتراضي إذا لم نتمكن من تحديده
419
+ return "عام"
420
+
421
+ def _extract_schedule_info(self, extracted_data: Dict[str, Any]) -> Dict[str, Any]:
422
+ """
423
+ استخراج معلومات الجدول الزمني من البيانات المستخرجة
424
+ """
425
+ schedule_info = {
426
+ "start_date": None,
427
+ "end_date": None,
428
+ "duration": None,
429
+ "activities": [],
430
+ "milestones": [],
431
+ "dependencies": []
432
+ }
433
+
434
+ # استخراج التواريخ من البيانات المستخرجة
435
+ if "dates" in extracted_data:
436
+ dates = extracted_data["dates"]
437
+
438
+ for date_info in dates:
439
+ date_type = date_info.get("type", "")
440
+ date_str = date_info.get("date", "")
441
+
442
+ if not date_str:
443
+ continue
444
+
445
+ try:
446
+ date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
447
+
448
+ if "بداية" in date_type.lower() or "بدأ" in date_type.lower() or "انطلاق" in date_type.lower():
449
+ if schedule_info["start_date"] is None or date_obj < schedule_info["start_date"]:
450
+ schedule_info["start_date"] = date_obj
451
+
452
+ if "نهاية" in date_type.lower() or "انتهاء" in date_type.lower() or "إغلاق" in date_type.lower():
453
+ if schedule_info["end_date"] is None or date_obj > schedule_info["end_date"]:
454
+ schedule_info["end_date"] = date_obj
455
+
456
+ # إذا كان هناك نوع تاريخ محدد للمعالم الرئيسية
457
+ if "معلم" in date_type.lower() or "محطة" in date_type.lower() or "مرحلة" in date_type.lower() or "تسليم" in date_type.lower():
458
+ schedule_info["milestones"].append({
459
+ "name": date_type,
460
+ "date": date_obj,
461
+ "description": date_info.get("context", "")
462
+ })
463
+ except Exception as e:
464
+ print(f"Error parsing date: {date_str}, {str(e)}")
465
+
466
+ # حساب المدة إذا كان هناك تاريخ بداية ونهاية
467
+ if schedule_info["start_date"] and schedule_info["end_date"]:
468
+ delta = schedule_info["end_date"] - schedule_info["start_date"]
469
+ schedule_info["duration"] = delta.days // 30 # تقريب المدة إلى الأشهر
470
+
471
+ # استخراج الأنشطة من المتطلبات
472
+ if "requirements" in extracted_data:
473
+ for req in extracted_data["requirements"]:
474
+ if "زمني" in req.get("category", "").lower() or "جدول" in req.get("title", "").lower() or "مدة" in req.get("title", "").lower():
475
+ activity = {
476
+ "name": req.get("title", ""),
477
+ "description": req.get("description", ""),
478
+ "duration": None # سيتم تقديره لاحقاً
479
+ }
480
+
481
+ # محاولة استخراج المدة من الوصف
482
+ duration_match = re.search(r'(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)', req.get("description", "").lower())
483
+ if duration_match:
484
+ duration_value = int(duration_match.group(1))
485
+ duration_unit = duration_match.group(2)
486
+
487
+ # تحويل المدة إلى أيام
488
+ if "يوم" in duration_unit:
489
+ activity["duration"] = duration_value
490
+ elif "أسبوع" in duration_unit:
491
+ activity["duration"] = duration_value * 7
492
+ elif "شهر" in duration_unit:
493
+ activity["duration"] = duration_value * 30
494
+ elif "سنة" in duration_unit:
495
+ activity["duration"] = duration_value * 365
496
+
497
+ schedule_info["activities"].append(activity)
498
+
499
+ # استخراج المعالم الرئيسية من النص
500
+ if "text" in extracted_data:
501
+ text = extracted_data["text"].lower()
502
+ milestone_patterns = [
503
+ r'(معلم|محطة|مرحلة|تسليم)[^:]*:(.*?)(?=\n|$)',
504
+ r'(معلم|محطة|مرحلة|تسليم)[^:]*بتاريخ[^:]*:(.*?)(?=\n|$)'
505
+ ]
506
+
507
+ for pattern in milestone_patterns:
508
+ matches = re.finditer(pattern, text)
509
+ for match in matches:
510
+ milestone_type = match.group(1).strip()
511
+ milestone_description = match.group(2).strip()
512
+
513
+ # محاولة استخراج التاريخ من الوصف
514
+ date_match = re.search(r'(\d{1,2})[/-](\d{1,2})[/-](\d{2,4})', milestone_description)
515
+ if date_match:
516
+ day = int(date_match.group(1))
517
+ month = int(date_match.group(2))
518
+ year = int(date_match.group(3))
519
+
520
+ if year < 100:
521
+ year += 2000
522
+
523
+ try:
524
+ date_obj = datetime(year, month, day).date()
525
+
526
+ # التحقق من عدم وجود هذا المعلم مسبقاً
527
+ if not any(milestone["description"] == milestone_description for milestone in schedule_info["milestones"]):
528
+ schedule_info["milestones"].append({
529
+ "name": milestone_type,
530
+ "date": date_obj,
531
+ "description": milestone_description
532
+ })
533
+ except Exception as e:
534
+ print(f"Error parsing milestone date: {day}/{month}/{year}, {str(e)}")
535
+
536
+ # استخراج المدة الإجمالية من النص إذا لم يتم تحديدها مسبقاً
537
+ if schedule_info["duration"] is None and "text" in extracted_data:
538
+ text = extracted_data["text"].lower()
539
+ duration_patterns = [
540
+ r'مدة المشروع[^:]*:?\s*(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)',
541
+ r'مدة التنفيذ[^:]*:?\s*(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)',
542
+ r'المدة الزمنية[^:]*:?\s*(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)'
543
+ ]
544
+
545
+ for pattern in duration_patterns:
546
+ match = re.search(pattern, text)
547
+ if match:
548
+ duration_value = int(match.group(1))
549
+ duration_unit = match.group(2)
550
+
551
+ # تحويل المدة إلى أشهر
552
+ if "يوم" in duration_unit:
553
+ schedule_info["duration"] = duration_value / 30
554
+ elif "أسبوع" in duration_unit:
555
+ schedule_info["duration"] = duration_value / 4
556
+ elif "شهر" in duration_unit:
557
+ schedule_info["duration"] = duration_value
558
+ elif "سنة" in duration_unit:
559
+ schedule_info["duration"] = duration_value * 12
560
+
561
+ break
562
+
563
+ return schedule_info
564
+
565
+ def _determine_project_size(self, budget: float, duration: int) -> str:
566
+ """
567
+ تحديد حجم المشروع بناءً على الميزانية والمدة
568
+ """
569
+ if budget == 0 and duration == 0:
570
+ return "medium"
571
+
572
+ # تحديد الحجم بناءً على الميزانية
573
+ size_by_budget = "medium"
574
+ if budget > 10000000: # أكثر من 10 مليون ريال
575
+ size_by_budget = "large"
576
+ elif budget < 1000000: # أقل من مليون ريال
577
+ size_by_budget = "small"
578
+
579
+ # تحديد الحجم بناءً على المدة
580
+ size_by_duration = "medium"
581
+ if duration > 24: # أكثر من 24 شهر
582
+ size_by_duration = "large"
583
+ elif duration < 6: # أقل من 6 أشهر
584
+ size_by_duration = "small"
585
+
586
+ # اختيار الحجم الأكبر
587
+ if size_by_budget == "large" or size_by_duration == "large":
588
+ return "large"
589
+ elif size_by_budget == "small" and size_by_duration == "small":
590
+ return "small"
591
+ else:
592
+ return "medium"
593
+
594
+ def _prepare_schedule(self, schedule_info: Dict[str, Any], sector: str, project_size: str, kwargs: Dict[str, Any]) -> List[Dict[str, Any]]:
595
+ """
596
+ إعداد الجدول الزمني بناءً على المعلومات المستخرجة والقوالب
597
+ """
598
+ # استخدام قالب الجدول الزمني المناسب للقطاع
599
+ schedule_template = self.schedule_templates.get(sector, self.schedule_templates["عام"])
600
+
601
+ # تحديد تاريخ البدء
602
+ start_date = schedule_info.get("start_date")
603
+ if start_date is None:
604
+ start_date = kwargs.get("start_date", datetime.now().date())
605
+
606
+ # تحديد المدة الإجمالية
607
+ duration = schedule_info.get("duration")
608
+ if duration is None:
609
+ duration = kwargs.get("duration", 0)
610
+
611
+ # إذا لم يتم تحديد المدة، استخدام متوسط المدة من بيانات المشاريع السابقة
612
+ if duration == 0 and sector in self.historical_projects and project_size in self.historical_projects[sector]:
613
+ duration = self.historical_projects[sector][project_size]["avg_duration"]
614
+
615
+ # إعداد المراحل
616
+ phases = []
617
+ current_start_date = start_date
618
+
619
+ for phase_template in schedule_template["phases"]:
620
+ phase_duration = int(duration * phase_template["duration_percentage"])
621
+ if phase_duration < 1:
622
+ phase_duration = 1 # الحد الأدنى للمدة هو شهر واحد
623
+
624
+ phase_end_date = current_start_date + timedelta(days=phase_duration * 30)
625
+
626
+ # إعداد الأنشطة
627
+ activities = []
628
+ activity_duration = phase_duration // len(phase_template["activities"])
629
+ if activity_duration < 1:
630
+ activity_duration = 1
631
+
632
+ activity_start_date = current_start_date
633
+
634
+ for activity_name in phase_template["activities"]:
635
+ activity_end_date = activity_start_date + timedelta(days=activity_duration * 30)
636
+
637
+ activity = {
638
+ "name": activity_name,
639
+ "start_date": activity_start_date.strftime("%Y-%m-%d"),
640
+ "end_date": activity_end_date.strftime("%Y-%m-%d"),
641
+ "duration": activity_duration
642
+ }
643
+
644
+ activities.append(activity)
645
+ activity_start_date = activity_end_date
646
+
647
+ phase = {
648
+ "name": phase_template["name"],
649
+ "start_date": current_start_date.strftime("%Y-%m-%d"),
650
+ "end_date": phase_end_date.strftime("%Y-%m-%d"),
651
+ "duration": phase_duration,
652
+ "activities": activities
653
+ }
654
+
655
+ phases.append(phase)
656
+ current_start_date = phase_end_date
657
+
658
+ return phases
659
+
660
+ def _identify_critical_path(self, phases: List[Dict[str, Any]], sector: str) -> List[str]:
661
+ """
662
+ تحديد المسار الحرج في الجدول الزمني
663
+ """
664
+ # استخدام قائمة الأنشطة الحرجة من القالب
665
+ schedule_template = self.schedule_templates.get(sector, self.schedule_templates["عام"])
666
+ template_critical_activities = schedule_template.get("critical_activities", [])
667
+
668
+ # تجميع كل الأنشطة من جميع المراحل
669
+ all_activities = []
670
+ for phase in phases:
671
+ for activity in phase.get("activities", []):
672
+ all_activities.append({
673
+ "phase": phase["name"],
674
+ "name": activity["name"],
675
+ "start_date": activity["start_date"],
676
+ "end_date": activity["end_date"],
677
+ "duration": activity["duration"]
678
+ })
679
+
680
+ # تحديد الأنشطة الحرجة
681
+ critical_path = []
682
+
683
+ # إضافة الأنشطة من القالب إذا وجدت
684
+ for template_activity in template_critical_activities:
685
+ for activity in all_activities:
686
+ if template_activity in activity["name"]:
687
+ critical_activity = f"{activity['phase']} - {activity['name']}"
688
+ if critical_activity not in critical_path:
689
+ critical_path.append(critical_activity)
690
+
691
+ # إذا لم يتم العثور على أنشطة حرجة، استخدام الأنشطة ذات المدة الأطول
692
+ if not critical_path:
693
+ sorted_activities = sorted(all_activities, key=lambda x: x["duration"], reverse=True)
694
+ for activity in sorted_activities[:5]: # اختيار أطول 5 أنشطة
695
+ critical_activity = f"{activity['phase']} - {activity['name']}"
696
+ critical_path.append(critical_activity)
697
+
698
+ return critical_path
699
+
700
+ def _identify_milestones(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> List[Dict[str, Any]]:
701
+ """
702
+ تحديد المعالم الرئيسية في الجدول الزمني
703
+ """
704
+ milestones = []
705
+
706
+ # إضافة المعالم المستخرجة من البيانات
707
+ for milestone in schedule_info.get("milestones", []):
708
+ milestones.append({
709
+ "name": milestone.get("name", "معلم"),
710
+ "date": milestone.get("date").strftime("%Y-%m-%d") if isinstance(milestone.get("date"), datetime) else milestone.get("date"),
711
+ "description": milestone.get("description", ""),
712
+ "source": "مستخرج"
713
+ })
714
+
715
+ # إضافة بداية ونهاية كل مرحلة كمعالم
716
+ for phase in phases:
717
+ # إضافة بداية المرحلة
718
+ start_milestone = {
719
+ "name": f"بداية {phase['name']}",
720
+ "date": phase["start_date"],
721
+ "description": f"تاريخ بدء مرحلة {phase['name']}",
722
+ "source": "مولد"
723
+ }
724
+ milestones.append(start_milestone)
725
+
726
+ # إضافة نهاية المرحلة
727
+ end_milestone = {
728
+ "name": f"نهاية {phase['name']}",
729
+ "date": phase["end_date"],
730
+ "description": f"تاريخ انتهاء مرحلة {phase['name']}",
731
+ "source": "مولد"
732
+ }
733
+ milestones.append(end_milestone)
734
+
735
+ # ترتيب المعالم حسب التاريخ
736
+ try:
737
+ milestones = sorted(milestones, key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"))
738
+ except Exception as e:
739
+ print(f"Error sorting milestones: {str(e)}")
740
+
741
+ return milestones
742
+
743
+ def _evaluate_schedule(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any], sector: str, project_size: str) -> Dict[str, Any]:
744
+ """
745
+ تقييم الجدول الزمني
746
+ """
747
+ evaluation = {
748
+ "overall_score": 0.0,
749
+ "criteria_scores": {},
750
+ "comments": []
751
+ }
752
+
753
+ # حساب المدة الإجمالية
754
+ total_duration = sum(phase["duration"] for phase in phases)
755
+
756
+ # تقييم واقعية الجدول الزمني
757
+ realism_score = self._evaluate_realism(total_duration, sector, project_size)
758
+ evaluation["criteria_scores"]["realism"] = realism_score
759
+
760
+ # تقييم اكتمال الجدول الزمني
761
+ completeness_score = self._evaluate_completeness(phases, schedule_info)
762
+ evaluation["criteria_scores"]["completeness"] = completeness_score
763
+
764
+ # تقييم توزيع الموارد
765
+ resource_score = self._evaluate_resource_allocation(phases)
766
+ evaluation["criteria_scores"]["resource_allocation"] = resource_score
767
+
768
+ # تقييم إدارة المخاطر
769
+ risk_score = self._evaluate_risk_management(phases, schedule_info)
770
+ evaluation["criteria_scores"]["risk_management"] = risk_score
771
+
772
+ # تقييم المرونة
773
+ flexibility_score = self._evaluate_flexibility(phases, schedule_info)
774
+ evaluation["criteria_scores"]["flexibility"] = flexibility_score
775
+
776
+ # حساب التقييم الإجمالي
777
+ overall_score = 0.0
778
+ for criterion, score in evaluation["criteria_scores"].items():
779
+ weight = self.evaluation_criteria[criterion]["weight"]
780
+ overall_score += score * weight
781
+
782
+ evaluation["overall_score"] = round(overall_score, 2)
783
+
784
+ # إضافة تعليقات
785
+ if overall_score >= 0.8:
786
+ evaluation["comments"].append("الجدول الزمني واقعي ومكتمل ويتضمن توزيعاً جيداً للموارد")
787
+ elif overall_score >= 0.6:
788
+ evaluation["comments"].append("الجدول الزمني جيد بشكل عام لكنه يحتاج إلى بعض التحسينات")
789
+ else:
790
+ evaluation["comments"].append("الجدول الزمني يحتاج إلى مراجعة شاملة وتحسينات كبيرة")
791
+
792
+ if realism_score < 0.6:
793
+ evaluation["comments"].append("المدة الإجمالية للمشروع غير واقعية مقارنة بمشاريع مماثلة")
794
+
795
+ if completeness_score < 0.6:
796
+ evaluation["comments"].append("الجدول الزمني يفتقر إلى بعض الأنشطة الهامة أو المعالم الرئيسية")
797
+
798
+ if resource_score < 0.6:
799
+ evaluation["comments"].append("توزيع الموارد غير متوازن وقد يؤدي إلى تأخير في المشروع")
800
+
801
+ if risk_score < 0.6:
802
+ evaluation["comments"].append("الجدول الزمني لا يتضمن احتياطيات كافية للتعامل مع المخاطر")
803
+
804
+ if flexibility_score < 0.6:
805
+ evaluation["comments"].append("الجدول الزمني يفتقر إلى المرونة اللازمة للتعامل مع التغييرات")
806
+
807
+ return evaluation
808
+
809
+ def _evaluate_realism(self, total_duration: int, sector: str, project_size: str) -> float:
810
+ """
811
+ تقييم واقعية الجدول الزمني
812
+ """
813
+ # المقارنة مع بيانات المشاريع السابقة
814
+ if sector in self.historical_projects and project_size in self.historical_projects[sector]:
815
+ historical_data = self.historical_projects[sector][project_size]
816
+ avg_duration = historical_data["avg_duration"]
817
+ min_duration = historical_data["min_duration"]
818
+ max_duration = historical_data["max_duration"]
819
+
820
+ # إذا كانت المدة ضمن النطاق المقبول
821
+ if min_duration <= total_duration <= max_duration:
822
+ # حساب الفرق بين المدة والمتوسط
823
+ diff = abs(total_duration - avg_duration) / (max_duration - min_duration)
824
+ score = 1.0 - diff
825
+ return max(0.5, score) # الحد الأدنى للتقييم هو 0.5
826
+ else:
827
+ # إذا كانت المدة خارج النطاق المقبول
828
+ return 0.3
829
+
830
+ # إذا لم تتوفر بيانات للمقارنة
831
+ return 0.5
832
+
833
+ def _evaluate_completeness(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> float:
834
+ """
835
+ تقييم اكتمال الجدول الزمني
836
+ """
837
+ # حساب عدد المراحل والأنشطة والمعالم
838
+ num_phases = len(phases)
839
+ num_activities = sum(len(phase.get("activities", [])) for phase in phases)
840
+ num_milestones = len(schedule_info.get("milestones", []))
841
+
842
+ # تقييم اكتمال المراحل
843
+ phases_score = min(1.0, num_phases / 5) # افتراض أن 5 مراحل هو العدد المثالي
844
+
845
+ # تقييم اكتمال الأنشطة
846
+ activities_score = min(1.0, num_activities / 20) # افتراض أن 20 نشاط هو العدد المثالي
847
+
848
+ # تقييم اكتمال المعالم
849
+ milestones_score = min(1.0, num_milestones / 10) # افتراض أن 10 معالم هو العدد المثالي
850
+
851
+ # حساب التقييم الإجمالي للاكتمال
852
+ completeness_score = (phases_score * 0.3) + (activities_score * 0.5) + (milestones_score * 0.2)
853
+
854
+ return round(completeness_score, 2)
855
+
856
+ def _evaluate_resource_allocation(self, phases: List[Dict[str, Any]]) -> float:
857
+ """
858
+ تقييم توزيع الموارد في الجدول الزمني
859
+ """
860
+ # في التطبيق الفعلي، هذا يحتاج إلى بيانات أكثر عن الموارد
861
+ # هذا تقييم مبسط بناءً على توزيع المدة على المراحل
862
+
863
+ # حساب إجمالي المدة
864
+ total_duration = sum(phase["duration"] for phase in phases)
865
+
866
+ # حساب الانحراف المعياري لمدة المراحل
867
+ durations = [phase["duration"] for phase in phases]
868
+ mean_duration = total_duration / len(phases)
869
+ variance = sum((d - mean_duration) ** 2 for d in durations) / len(phases)
870
+ std_dev = variance ** 0.5
871
+
872
+ # حساب معامل الاختلاف (نسبة الانحراف المعياري إلى المتوسط)
873
+ coef_of_variation = std_dev / mean_duration if mean_duration > 0 else 0
874
+
875
+ # حساب التقييم: كلما قل معامل الاختلاف، كان التوزيع أكثر توازناً
876
+ resource_score = max(0, 1.0 - coef_of_variation)
877
+
878
+ return round(resource_score, 2)
879
+
880
+ def _evaluate_risk_management(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> float:
881
+ """
882
+ تقييم إدارة المخاطر في الجدول الزمني
883
+ """
884
+ # في التطبيق الفعلي، هذا يحتاج إلى بيانات أكثر عن المخاطر
885
+ # هذا تقييم مبسط بناءً على وجود احتياطيات زمنية
886
+
887
+ # حساب نسبة الاحتياطي الزمني المقدرة
888
+ total_duration = sum(phase["duration"] for phase in phases)
889
+ estimated_buffer = total_duration * 0.1 # افتراض أن 10% من المدة الكلية يجب أن تكون احتياطي
890
+ actual_buffer = 0
891
+
892
+ # البحث عن أنشطة أو مراحل تمثل احتياطيات
893
+ for phase in phases:
894
+ phase_name = phase["name"].lower()
895
+ if "احتياطي" in phase_name or "طوارئ" in phase_name:
896
+ actual_buffer += phase["duration"]
897
+
898
+ for activity in phase.get("activities", []):
899
+ activity_name = activity["name"].lower()
900
+ if "احتياطي" in activity_name or "طوارئ" in activity_name:
901
+ actual_buffer += activity["duration"]
902
+
903
+ # حساب نسبة الاحتياطي الفعلي إلى المقدر
904
+ buffer_ratio = actual_buffer / estimated_buffer if estimated_buffer > 0 else 0
905
+ buffer_score = min(1.0, buffer_ratio)
906
+
907
+ # حساب تقييم المسار الحرج
908
+ critical_path_identified = 1.0 if len(schedule_info.get("critical_path", [])) > 0 else 0.0
909
+
910
+ # حساب التقييم الإجمالي لإدارة المخاطر
911
+ risk_score = (buffer_score * 0.6) + (critical_path_identified * 0.4)
912
+
913
+ return round(risk_score, 2)
914
+
915
+ def _evaluate_flexibility(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> float:
916
+ """
917
+ تقييم مرونة الجدول الزمني
918
+ """
919
+ # في التطبيق الفعلي، هذا يحتاج إلى بيانات أكثر عن آليات التعديل والمراقبة
920
+ # هذا تقييم مبسط بناءً على وجود فترات مراجعة وتقييم
921
+
922
+ # البحث عن أنشطة تتعلق بالمراجعة والتقييم
923
+ review_activities = 0
924
+ total_activities = 0
925
+
926
+ for phase in phases:
927
+ for activity in phase.get("activities", []):
928
+ total_activities += 1
929
+ activity_name = activity["name"].lower()
930
+ if "مراجعة" in activity_name or "تقييم" in activity_name or "متابعة" in activity_name:
931
+ review_activities += 1
932
+
933
+ # حساب نسبة أنشطة المراجعة والتقييم
934
+ review_ratio = review_activities / total_activities if total_activities > 0 else 0
935
+ review_score = min(1.0, review_ratio * 5) # افتراض أن 20% من الأنشطة يجب أن تكون للمراجعة والتقييم
936
+
937
+ # حساب التقييم الإجمالي للمرونة
938
+ flexibility_score = review_score
939
+
940
+ return round(flexibility_score, 2)
941
+
942
+ def _generate_optimization_suggestions(self, phases: List[Dict[str, Any]], evaluation: Dict[str, Any], schedule_info: Dict[str, Any]) -> List[str]:
943
+ """
944
+ إعداد اقتراحات لتحسين الجدول الزمني
945
+ """
946
+ optimization_suggestions = []
947
+
948
+ # اقتراحات بناءً على التقييم
949
+ criteria_scores = evaluation.get("criteria_scores", {})
950
+
951
+ # اقت��احات لتحسين واقعية الجدول الزمني
952
+ if criteria_scores.get("realism", 1.0) < 0.6:
953
+ optimization_suggestions.append(
954
+ "مراجعة المدة الإجمالية للمشروع ومقارنتها بمشاريع مماثلة للتأكد من واقعيتها"
955
+ )
956
+
957
+ # اقتراحات لتحسين اكتمال الجدول الزمني
958
+ if criteria_scores.get("completeness", 1.0) < 0.6:
959
+ optimization_suggestions.append(
960
+ "تفصيل الأنشطة بشكل أكبر وإضافة المعالم الرئيسية لكل مرحلة"
961
+ )
962
+
963
+ # اقتراحات لتحسين توزيع الموارد
964
+ if criteria_scores.get("resource_allocation", 1.0) < 0.6:
965
+ optimization_suggestions.append(
966
+ "تحسين توزيع الموارد بين المراحل المختلفة وتجنب التحميل الزائد للموارد"
967
+ )
968
+
969
+ # اقتراحات لتحسين إدارة المخاطر
970
+ if criteria_scores.get("risk_management", 1.0) < 0.6:
971
+ optimization_suggestions.append(
972
+ "إضافة احتياطيات زمنية كافية للتعامل مع المخاطر المحتملة وتأخيرات المشروع"
973
+ )
974
+ optimization_suggestions.append(
975
+ "تحديد المسار الحرج بوضوح وإعداد خطط بديلة للأنشطة الحرجة"
976
+ )
977
+
978
+ # اقتراحات لتحسين المرونة
979
+ if criteria_scores.get("flexibility", 1.0) < 0.6:
980
+ optimization_suggestions.append(
981
+ "إضافة نقاط مراجعة وتقييم دورية للتأكد من سير المشروع وفق الخطة"
982
+ )
983
+ optimization_suggestions.append(
984
+ "تطوير آلية واضحة للتعامل مع التغييرات وتحديث الجدول الزمني"
985
+ )
986
+
987
+ # اقتراحات عامة
988
+ optimization_suggestions.extend([
989
+ "استخدام أدوات إدارة المشاريع لمتابعة التقدم ومقارنته بالخطة الموضوعة",
990
+ "تحديد المسؤوليات بوضوح لكل نشاط من أنشطة المشروع",
991
+ "تحليل المسار الحرج وتقليل الاعتمادية بين الأنشطة لتقليل مخاطر التأخير"
992
+ ])
993
+
994
+ return optimization_suggestions
995
+
996
+ def _analyze_schedule_risks(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any], sector: str) -> List[Dict[str, Any]]:
997
+ """
998
+ تحليل المخاطر المتعلقة بالجدول الزمني
999
+ """
1000
+ risks = []
1001
+
1002
+ # مخاطر تتعلق بالمدة الإجمالية
1003
+ total_duration = sum(phase["duration"] for phase in phases)
1004
+
1005
+ if sector in self.historical_projects:
1006
+ historical_data = self.historical_projects[sector]["medium"] # افتراض حجم متوسط
1007
+ avg_duration = historical_data["avg_duration"]
1008
+
1009
+ if total_duration < avg_duration * 0.7:
1010
+ risks.append({
1011
+ "title": "مدة قصيرة جداً",
1012
+ "description": "المدة الإجمالية للمشروع أقل بكثير من متوسط مدة مشاريع مماثلة",
1013
+ "probability": "عالية",
1014
+ "impact": "عالي",
1015
+ "mitigation": [
1016
+ "إعادة تقدير المدة الإجمالية بناءً على مشاريع مماثلة",
1017
+ "تحديد الأنشطة التي يمكن تنفيذها بشكل متوازٍ لتقليل المدة",
1018
+ "توفير موارد إضافية لتسريع تنفيذ الأنشطة الحرجة"
1019
+ ]
1020
+ })
1021
+ elif total_duration > avg_duration * 1.3:
1022
+ risks.append({
1023
+ "title": "مدة طويلة جداً",
1024
+ "description": "المدة الإجمالية للمشروع أطول بكثير من متوسط مدة مشاريع مماثلة",
1025
+ "probability": "متوسطة",
1026
+ "impact": "متوسط",
1027
+ "mitigation": [
1028
+ "مراجعة تقديرات المدة لكل نشاط والتأكد من دقتها",
1029
+ "تحليل الأنشطة غير الضرورية وإمكانية حذفها أو دمجها",
1030
+ "تحسين كفاءة العمليات لتقليل المدة الإجمالية"
1031
+ ]
1032
+ })
1033
+
1034
+ # مخاطر تتعلق بالمسار الحرج
1035
+ if not schedule_info.get("critical_path"):
1036
+ risks.append({
1037
+ "title": "��دم تحديد المسار الحرج",
1038
+ "description": "لم يتم تحديد المسار الحرج في الجدول الزمني",
1039
+ "probability": "عالية",
1040
+ "impact": "عالي",
1041
+ "mitigation": [
1042
+ "تحديد المسار الحرج باستخدام طريقة المسار الحرج (CPM)",
1043
+ "إضافة احتياطيات زمنية للأنشطة الحرجة",
1044
+ "مراقبة الأنشطة الحرجة بشكل مكثف"
1045
+ ]
1046
+ })
1047
+
1048
+ # مخاطر تتعلق بالاحتياطيات الزمنية
1049
+ buffer_found = False
1050
+ for phase in phases:
1051
+ phase_name = phase["name"].lower()
1052
+ if "احتياطي" in phase_name or "طوارئ" in phase_name:
1053
+ buffer_found = True
1054
+ break
1055
+
1056
+ for activity in phase.get("activities", []):
1057
+ activity_name = activity["name"].lower()
1058
+ if "احتياطي" in activity_name or "طوارئ" in activity_name:
1059
+ buffer_found = True
1060
+ break
1061
+
1062
+ if buffer_found:
1063
+ break
1064
+
1065
+ if not buffer_found:
1066
+ risks.append({
1067
+ "title": "عدم وجود احتياطيات زمنية",
1068
+ "description": "لم يتم تضمين احتياطيات زمنية في الجدول الزمني للتعامل مع المخاطر والتأخيرات",
1069
+ "probability": "عالية",
1070
+ "impact": "عالي",
1071
+ "mitigation": [
1072
+ "إضافة احتياطيات زمنية بنسبة 10-15% من المدة الإجمالية",
1073
+ "توزيع الاحتياطيات على الأنشطة الحرجة",
1074
+ "إضافة معالم للمراجعة وإعادة التقييم"
1075
+ ]
1076
+ })
1077
+
1078
+ # مخاطر تتعلق بتداخل الأنشطة
1079
+ activities_count = sum(len(phase.get("activities", [])) for phase in phases)
1080
+ phases_count = len(phases)
1081
+
1082
+ if activities_count > 0 and phases_count > 0:
1083
+ avg_activities_per_phase = activities_count / phases_count
1084
+
1085
+ if avg_activities_per_phase > 7:
1086
+ risks.append({
1087
+ "title": "تداخل كبير في الأنشطة",
1088
+ "description": "عدد كبير من الأنشطة المتداخلة في كل مرحلة قد يؤدي إلى صعوبة في الإدارة والتنفيذ",
1089
+ "probability": "متوسطة",
1090
+ "impact": "متوسط",
1091
+ "mitigation": [
1092
+ "تقسيم المراحل الكبيرة إلى مراحل أصغر",
1093
+ "تحديد أولويات الأنشطة وتسلسلها بوضوح",
1094
+ "تخصيص موارد كافية لإدارة الأنشطة المتداخلة"
1095
+ ]
1096
+ })
1097
+
1098
+ # مخاطر خاصة بالقطاع
1099
+ if sector == "الإنشاءات":
1100
+ risks.append({
1101
+ "title": "تأثير الظروف الجوية",
1102
+ "description": "قد تؤثر الظروف الجوية على الأنشطة الخارجية وتؤدي إلى تأخير المشروع",
1103
+ "probability": "متوسطة",
1104
+ "impact": "متوسط",
1105
+ "mitigation": [
1106
+ "تضمين احتياطيات زمنية للظروف الجوية",
1107
+ "جدولة الأنشطة الخارجية في الفترات المناسبة من السنة",
1108
+ "إعداد خطط بديلة للأنشطة المتأثرة بالظروف الجوية"
1109
+ ]
1110
+ })
1111
+ elif sector == "تقنية المعلومات":
1112
+ risks.append({
1113
+ "title": "تغيير المتطلبات",
1114
+ "description": "قد تتغير متطلبات المشروع أثناء التنفيذ مما يؤدي إلى تأخير الجدول الزمني",
1115
+ "probability": "عالية",
1116
+ "impact": "عالي",
1117
+ "mitigation": [
1118
+ "توثيق المتطلبات بشكل دقيق والحصول على موافقة العميل",
1119
+ "استخدام منهجية مرنة تسمح بالتكيف مع التغييرات",
1120
+ "تضمين احتياطيات زمنية للتعامل مع تغييرات المتطلبات"
1121
+ ]
1122
+ })
1123
+
1124
+ return risks