Spaces:
Paused
Paused
import os | |
import re | |
import json | |
import datetime | |
import numpy as np | |
import pandas as pd | |
from typing import Dict, List, Any, Union, Tuple, Optional | |
from datetime import datetime, timedelta | |
class ScheduleAnalyzer: | |
""" | |
فئة لتحليل الجدول الزمني للمناقصات | |
""" | |
def __init__(self): | |
""" | |
تهيئة محلل الجدول الزمني | |
""" | |
# تحميل قوالب الجداول الزمنية | |
self.schedule_templates = self._load_schedule_templates() | |
# تحميل معايير تقييم الجداول الزمنية | |
self.evaluation_criteria = self._load_evaluation_criteria() | |
# تحميل قاعدة بيانات المشاريع السابقة | |
self.historical_projects = self._load_historical_projects() | |
def _load_schedule_templates(self) -> Dict[str, Dict[str, Any]]: | |
""" | |
تحميل قوالب الجداول الزمنية حسب نوع المشروع | |
""" | |
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات | |
return { | |
"الإنشاءات": { | |
"phases": [ | |
{ | |
"name": "التخطيط والتصميم", | |
"duration_percentage": 0.15, | |
"activities": [ | |
"إعداد المخططات الأولية", | |
"الحصول على الموافقات", | |
"إعداد المخططات التفصيلية", | |
"تصميم الأنظمة الكهربائية والميكانيكية", | |
"إعداد جداول الكميات" | |
] | |
}, | |
{ | |
"name": "التجهيز والتعاقد", | |
"duration_percentage": 0.10, | |
"activities": [ | |
"إعداد وثائق العطاء", | |
"طرح العطاء", | |
"تقييم العروض", | |
"توقيع العقود مع المقاولين", | |
"تجهيز الموقع" | |
] | |
}, | |
{ | |
"name": "الأعمال الإنشائية", | |
"duration_percentage": 0.50, | |
"activities": [ | |
"أعمال الحفر والأساسات", | |
"أعمال الهيكل الخرساني", | |
"أعمال البناء", | |
"أعمال التشطيبات الخارجية", | |
"أعمال التشطيبات الداخلية" | |
] | |
}, | |
{ | |
"name": "الأنظمة والمرافق", | |
"duration_percentage": 0.15, | |
"activities": [ | |
"تركيب الأنظمة الكهربائية", | |
"تركيب الأنظمة الميكانيكية", | |
"تركيب أنظمة التكييف", | |
"تركيب أنظمة السباكة", | |
"تركيب أنظمة الحريق والإنذار" | |
] | |
}, | |
{ | |
"name": "الاختبار والتسليم", | |
"duration_percentage": 0.10, | |
"activities": [ | |
"اختبار الأنظمة", | |
"تشغيل تجريبي", | |
"إصلاح الملاحظات", | |
"التسليم المبدئي", | |
"التسليم النهائي" | |
] | |
} | |
], | |
"critical_activities": [ | |
"إعداد المخططات التفصيلية", | |
"أعمال الحفر والأساسات", | |
"أعمال الهيكل الخرساني", | |
"تركيب الأنظمة الكهربائية", | |
"اختبار الأنظمة" | |
] | |
}, | |
"تقنية المعلومات": { | |
"phases": [ | |
{ | |
"name": "التحليل والتخطيط", | |
"duration_percentage": 0.15, | |
"activities": [ | |
"تحليل المتطلبات", | |
"دراسة الجدوى", | |
"تحديد نطاق المشروع", | |
"إعداد خطة المشروع", | |
"تحديد الموارد اللازمة" | |
] | |
}, | |
{ | |
"name": "التصميم", | |
"duration_percentage": 0.20, | |
"activities": [ | |
"تصميم هيكل النظام", | |
"تصميم قاعدة البيانات", | |
"تصميم واجهات المستخدم", | |
"تصميم الخوارزميات", | |
"توثيق التصميم" | |
] | |
}, | |
{ | |
"name": "التطوير", | |
"duration_percentage": 0.40, | |
"activities": [ | |
"تطوير النظام الأساسي", | |
"تطوير قاعدة البيانات", | |
"تطوير واجهات المستخدم", | |
"تطوير وحدات النظام", | |
"تكامل الوحدات" | |
] | |
}, | |
{ | |
"name": "الاختبار", | |
"duration_percentage": 0.15, | |
"activities": [ | |
"اختبار الوحدات", | |
"اختبار التكامل", | |
"اختبار النظام", | |
"اختبار القبول", | |
"إصلاح الأخطاء" | |
] | |
}, | |
{ | |
"name": "النشر والتدريب", | |
"duration_percentage": 0.10, | |
"activities": [ | |
"إعداد بيئة الإنتاج", | |
"نشر النظام", | |
"تدريب المستخدمين", | |
"إعداد وثائق المستخدم", | |
"الدعم الأولي" | |
] | |
} | |
], | |
"critical_activities": [ | |
"تحليل المتطلبات", | |
"تصميم هيكل النظام", | |
"تطوير النظام الأساسي", | |
"تكامل الوحدات", | |
"اختبار القبول" | |
] | |
}, | |
"عام": { | |
"phases": [ | |
{ | |
"name": "التخطيط", | |
"duration_percentage": 0.15, | |
"activities": [ | |
"تحديد الأهداف", | |
"تحديد النطاق", | |
"تحديد الموارد", | |
"تحديد الميزانية", | |
"إعداد خطة المشروع" | |
] | |
}, | |
{ | |
"name": "التنفيذ", | |
"duration_percentage": 0.60, | |
"activities": [ | |
"توفير الموارد", | |
"تنفيذ الأنشطة", | |
"مراقبة التقدم", | |
"إدارة المخاطر", | |
"ضبط الجودة" | |
] | |
}, | |
{ | |
"name": "الإغلاق", | |
"duration_percentage": 0.25, | |
"activities": [ | |
"اختبار المخرجات", | |
"تسليم المخرجات", | |
"توثيق الدروس المستفادة", | |
"إغلاق العقود", | |
"إغلاق المشروع" | |
] | |
} | |
], | |
"critical_activities": [ | |
"تحديد النطاق", | |
"توفير الموارد", | |
"تنفيذ الأنشطة", | |
"اختبار المخرجات", | |
"تسليم المخرجات" | |
] | |
} | |
} | |
def _load_evaluation_criteria(self) -> Dict[str, Dict[str, Any]]: | |
""" | |
تحميل معايير تقييم الجداول الزمنية | |
""" | |
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات | |
return { | |
"realism": { | |
"weight": 0.3, | |
"criteria": [ | |
"توافق مدة المشروع مع مشاريع مماثلة", | |
"توافق توزيع الموارد مع الأنشطة", | |
"مراعاة العوامل الخارجية والمخاطر" | |
] | |
}, | |
"completeness": { | |
"weight": 0.2, | |
"criteria": [ | |
"شمولية الأنشطة", | |
"تحديد المعالم الرئيسية", | |
"تحديد نقاط التسليم" | |
] | |
}, | |
"resource_allocation": { | |
"weight": 0.2, | |
"criteria": [ | |
"توزيع مناسب للموارد", | |
"تجنب تحميل زائد للموارد", | |
"تحديد المسؤوليات" | |
] | |
}, | |
"risk_management": { | |
"weight": 0.15, | |
"criteria": [ | |
"تضمين احتياطيات زمنية", | |
"تحديد المسار الحرج", | |
"خطط بديلة للأنشطة الحرجة" | |
] | |
}, | |
"flexibility": { | |
"weight": 0.15, | |
"criteria": [ | |
"قابلية التعديل والتحديث", | |
"آلية للتعامل مع التغييرات", | |
"مراقبة التقدم وتقييمه" | |
] | |
} | |
} | |
def _load_historical_projects(self) -> Dict[str, Dict[str, Any]]: | |
""" | |
تحميل بيانات المشاريع السابقة للمقارنة | |
""" | |
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات | |
return { | |
"الإنشاءات": { | |
"small": { | |
"avg_duration": 12, # متوسط المدة بالأشهر | |
"min_duration": 6, | |
"max_duration": 18 | |
}, | |
"medium": { | |
"avg_duration": 24, | |
"min_duration": 18, | |
"max_duration": 36 | |
}, | |
"large": { | |
"avg_duration": 48, | |
"min_duration": 36, | |
"max_duration": 60 | |
} | |
}, | |
"تقنية المعلومات": { | |
"small": { | |
"avg_duration": 6, | |
"min_duration": 3, | |
"max_duration": 9 | |
}, | |
"medium": { | |
"avg_duration": 12, | |
"min_duration": 9, | |
"max_duration": 18 | |
}, | |
"large": { | |
"avg_duration": 24, | |
"min_duration": 18, | |
"max_duration": 36 | |
} | |
}, | |
"عام": { | |
"small": { | |
"avg_duration": 6, | |
"min_duration": 3, | |
"max_duration": 12 | |
}, | |
"medium": { | |
"avg_duration": 12, | |
"min_duration": 9, | |
"max_duration": 24 | |
}, | |
"large": { | |
"avg_duration": 24, | |
"min_duration": 18, | |
"max_duration": 36 | |
} | |
} | |
} | |
def analyze(self, extracted_data: Dict[str, Any], **kwargs) -> Dict[str, Any]: | |
""" | |
تحليل الجدول الزمني بناءً على البيانات المستخرجة | |
المعاملات: | |
---------- | |
extracted_data : Dict[str, Any] | |
البيانات المستخرجة من المستندات | |
**kwargs : Dict[str, Any] | |
معاملات إضافية مثل نوع المشروع، الميزانية، تاريخ البدء، المدة | |
المخرجات: | |
-------- | |
Dict[str, Any] | |
نتائج تحليل الجدول الزمني | |
""" | |
results = { | |
"phases": [], | |
"critical_path": [], | |
"milestones": [], | |
"evaluation": {}, | |
"optimization": [], | |
"risks": [] | |
} | |
# تحديد نوع المشروع والقطاع | |
project_type = kwargs.get("project_type", "") | |
sector = self._determine_sector(project_type, extracted_data) | |
# استخراج معلومات الجدول الزمني من البيانات المستخرجة | |
schedule_info = self._extract_schedule_info(extracted_data) | |
# تحديد حجم المشروع | |
project_size = self._determine_project_size(kwargs.get("budget", 0), schedule_info.get("duration", 0)) | |
# إعداد الجدول الزمني | |
phases = self._prepare_schedule(schedule_info, sector, project_size, kwargs) | |
results["phases"] = phases | |
# تحديد المسار الحرج | |
critical_path = self._identify_critical_path(phases, sector) | |
results["critical_path"] = critical_path | |
# تحديد المعالم الرئيسية | |
milestones = self._identify_milestones(phases, schedule_info) | |
results["milestones"] = milestones | |
# تقييم الجدول الزمني | |
evaluation = self._evaluate_schedule(phases, schedule_info, sector, project_size) | |
results["evaluation"] = evaluation | |
# اقتراحات لتحسين الجدول الزمني | |
optimization = self._generate_optimization_suggestions(phases, evaluation, schedule_info) | |
results["optimization"] = optimization | |
# تحليل المخاطر المتعلقة بالجدول الزمني | |
risks = self._analyze_schedule_risks(phases, schedule_info, sector) | |
results["risks"] = risks | |
return results | |
def _determine_sector(self, project_type: str, extracted_data: Dict[str, Any]) -> str: | |
""" | |
تحديد القطاع بناءً على نوع المشروع والبيانات المستخرجة | |
""" | |
# قاموس لتحويل أنواع المشاريع الشائعة إلى قطاعات | |
project_to_sector = { | |
"إنشاءات": "الإنشاءات", | |
"مباني": "الإنشاءات", | |
"طرق": "الإنشاءات", | |
"جسور": "الإنشاءات", | |
"تقنية معلومات": "تقنية المعلومات", | |
"برمجيات": "تقنية المعلومات", | |
"تطبيقات": "تقنية المعلومات" | |
} | |
# محاولة تحديد القطاع من نوع المشروع | |
if project_type: | |
for key, value in project_to_sector.items(): | |
if key in project_type.lower(): | |
return value | |
# إذا لم يتم تحديد القطاع من نوع المشروع، نحاول تحديده من البيانات المستخرجة | |
if "text" in extracted_data: | |
text = extracted_data["text"].lower() | |
sector_scores = {} | |
for key, value in project_to_sector.items(): | |
score = text.count(key.lower()) | |
if value not in sector_scores: | |
sector_scores[value] = 0 | |
sector_scores[value] += score | |
# اختيار القطاع الأكثر ذكراً | |
if sector_scores: | |
max_sector = max(sector_scores, key=sector_scores.get) | |
if sector_scores[max_sector] > 0: | |
return max_sector | |
# القطاع الافتراضي إذا لم نتمكن من تحديده | |
return "عام" | |
def _extract_schedule_info(self, extracted_data: Dict[str, Any]) -> Dict[str, Any]: | |
""" | |
استخراج معلومات الجدول الزمني من البيانات المستخرجة | |
""" | |
schedule_info = { | |
"start_date": None, | |
"end_date": None, | |
"duration": None, | |
"activities": [], | |
"milestones": [], | |
"dependencies": [] | |
} | |
# استخراج التواريخ من البيانات المستخرجة | |
if "dates" in extracted_data: | |
dates = extracted_data["dates"] | |
for date_info in dates: | |
date_type = date_info.get("type", "") | |
date_str = date_info.get("date", "") | |
if not date_str: | |
continue | |
try: | |
date_obj = datetime.strptime(date_str, "%Y-%m-%d").date() | |
if "بداية" in date_type.lower() or "بدأ" in date_type.lower() or "انطلاق" in date_type.lower(): | |
if schedule_info["start_date"] is None or date_obj < schedule_info["start_date"]: | |
schedule_info["start_date"] = date_obj | |
if "نهاية" in date_type.lower() or "انتهاء" in date_type.lower() or "إغلاق" in date_type.lower(): | |
if schedule_info["end_date"] is None or date_obj > schedule_info["end_date"]: | |
schedule_info["end_date"] = date_obj | |
# إذا كان هناك نوع تاريخ محدد للمعالم الرئيسية | |
if "معلم" in date_type.lower() or "محطة" in date_type.lower() or "مرحلة" in date_type.lower() or "تسليم" in date_type.lower(): | |
schedule_info["milestones"].append({ | |
"name": date_type, | |
"date": date_obj, | |
"description": date_info.get("context", "") | |
}) | |
except Exception as e: | |
print(f"Error parsing date: {date_str}, {str(e)}") | |
# حساب المدة إذا كان هناك تاريخ بداية ونهاية | |
if schedule_info["start_date"] and schedule_info["end_date"]: | |
delta = schedule_info["end_date"] - schedule_info["start_date"] | |
schedule_info["duration"] = delta.days // 30 # تقريب المدة إلى الأشهر | |
# استخراج الأنشطة من المتطلبات | |
if "requirements" in extracted_data: | |
for req in extracted_data["requirements"]: | |
if "زمني" in req.get("category", "").lower() or "جدول" in req.get("title", "").lower() or "مدة" in req.get("title", "").lower(): | |
activity = { | |
"name": req.get("title", ""), | |
"description": req.get("description", ""), | |
"duration": None # سيتم تقديره لاحقاً | |
} | |
# محاولة استخراج المدة من الوصف | |
duration_match = re.search(r'(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)', req.get("description", "").lower()) | |
if duration_match: | |
duration_value = int(duration_match.group(1)) | |
duration_unit = duration_match.group(2) | |
# تحويل المدة إلى أيام | |
if "يوم" in duration_unit: | |
activity["duration"] = duration_value | |
elif "أسبوع" in duration_unit: | |
activity["duration"] = duration_value * 7 | |
elif "شهر" in duration_unit: | |
activity["duration"] = duration_value * 30 | |
elif "سنة" in duration_unit: | |
activity["duration"] = duration_value * 365 | |
schedule_info["activities"].append(activity) | |
# استخراج المعالم الرئيسية من النص | |
if "text" in extracted_data: | |
text = extracted_data["text"].lower() | |
milestone_patterns = [ | |
r'(معلم|محطة|مرحلة|تسليم)[^:]*:(.*?)(?=\n|$)', | |
r'(معلم|محطة|مرحلة|تسليم)[^:]*بتاريخ[^:]*:(.*?)(?=\n|$)' | |
] | |
for pattern in milestone_patterns: | |
matches = re.finditer(pattern, text) | |
for match in matches: | |
milestone_type = match.group(1).strip() | |
milestone_description = match.group(2).strip() | |
# محاولة استخراج التاريخ من الوصف | |
date_match = re.search(r'(\d{1,2})[/-](\d{1,2})[/-](\d{2,4})', milestone_description) | |
if date_match: | |
day = int(date_match.group(1)) | |
month = int(date_match.group(2)) | |
year = int(date_match.group(3)) | |
if year < 100: | |
year += 2000 | |
try: | |
date_obj = datetime(year, month, day).date() | |
# التحقق من عدم وجود هذا المعلم مسبقاً | |
if not any(milestone["description"] == milestone_description for milestone in schedule_info["milestones"]): | |
schedule_info["milestones"].append({ | |
"name": milestone_type, | |
"date": date_obj, | |
"description": milestone_description | |
}) | |
except Exception as e: | |
print(f"Error parsing milestone date: {day}/{month}/{year}, {str(e)}") | |
# استخراج المدة الإجمالية من النص إذا لم يتم تحديدها مسبقاً | |
if schedule_info["duration"] is None and "text" in extracted_data: | |
text = extracted_data["text"].lower() | |
duration_patterns = [ | |
r'مدة المشروع[^:]*:?\s*(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)', | |
r'مدة التنفيذ[^:]*:?\s*(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)', | |
r'المدة الزمنية[^:]*:?\s*(\d+)\s+(يوم|أيام|أسبوع|أسابيع|شهر|شهور|سنة|سنوات)' | |
] | |
for pattern in duration_patterns: | |
match = re.search(pattern, text) | |
if match: | |
duration_value = int(match.group(1)) | |
duration_unit = match.group(2) | |
# تحويل المدة إلى أشهر | |
if "يوم" in duration_unit: | |
schedule_info["duration"] = duration_value / 30 | |
elif "أسبوع" in duration_unit: | |
schedule_info["duration"] = duration_value / 4 | |
elif "شهر" in duration_unit: | |
schedule_info["duration"] = duration_value | |
elif "سنة" in duration_unit: | |
schedule_info["duration"] = duration_value * 12 | |
break | |
return schedule_info | |
def _determine_project_size(self, budget: float, duration: int) -> str: | |
""" | |
تحديد حجم المشروع بناءً على الميزانية والمدة | |
""" | |
if budget == 0 and duration == 0: | |
return "medium" | |
# تحديد الحجم بناءً على الميزانية | |
size_by_budget = "medium" | |
if budget > 10000000: # أكثر من 10 مليون ريال | |
size_by_budget = "large" | |
elif budget < 1000000: # أقل من مليون ريال | |
size_by_budget = "small" | |
# تحديد الحجم بناءً على المدة | |
size_by_duration = "medium" | |
if duration > 24: # أكثر من 24 شهر | |
size_by_duration = "large" | |
elif duration < 6: # أقل من 6 أشهر | |
size_by_duration = "small" | |
# اختيار الحجم الأكبر | |
if size_by_budget == "large" or size_by_duration == "large": | |
return "large" | |
elif size_by_budget == "small" and size_by_duration == "small": | |
return "small" | |
else: | |
return "medium" | |
def _prepare_schedule(self, schedule_info: Dict[str, Any], sector: str, project_size: str, kwargs: Dict[str, Any]) -> List[Dict[str, Any]]: | |
""" | |
إعداد الجدول الزمني بناءً على المعلومات المستخرجة والقوالب | |
""" | |
# استخدام قالب الجدول الزمني المناسب للقطاع | |
schedule_template = self.schedule_templates.get(sector, self.schedule_templates["عام"]) | |
# تحديد تاريخ البدء | |
start_date = schedule_info.get("start_date") | |
if start_date is None: | |
start_date = kwargs.get("start_date", datetime.now().date()) | |
# تحديد المدة الإجمالية | |
duration = schedule_info.get("duration") | |
if duration is None: | |
duration = kwargs.get("duration", 0) | |
# إذا لم يتم تحديد المدة، استخدام متوسط المدة من بيانات المشاريع السابقة | |
if duration == 0 and sector in self.historical_projects and project_size in self.historical_projects[sector]: | |
duration = self.historical_projects[sector][project_size]["avg_duration"] | |
# إعداد المراحل | |
phases = [] | |
current_start_date = start_date | |
for phase_template in schedule_template["phases"]: | |
phase_duration = int(duration * phase_template["duration_percentage"]) | |
if phase_duration < 1: | |
phase_duration = 1 # الحد الأدنى للمدة هو شهر واحد | |
phase_end_date = current_start_date + timedelta(days=phase_duration * 30) | |
# إعداد الأنشطة | |
activities = [] | |
activity_duration = phase_duration // len(phase_template["activities"]) | |
if activity_duration < 1: | |
activity_duration = 1 | |
activity_start_date = current_start_date | |
for activity_name in phase_template["activities"]: | |
activity_end_date = activity_start_date + timedelta(days=activity_duration * 30) | |
activity = { | |
"name": activity_name, | |
"start_date": activity_start_date.strftime("%Y-%m-%d"), | |
"end_date": activity_end_date.strftime("%Y-%m-%d"), | |
"duration": activity_duration | |
} | |
activities.append(activity) | |
activity_start_date = activity_end_date | |
phase = { | |
"name": phase_template["name"], | |
"start_date": current_start_date.strftime("%Y-%m-%d"), | |
"end_date": phase_end_date.strftime("%Y-%m-%d"), | |
"duration": phase_duration, | |
"activities": activities | |
} | |
phases.append(phase) | |
current_start_date = phase_end_date | |
return phases | |
def _identify_critical_path(self, phases: List[Dict[str, Any]], sector: str) -> List[str]: | |
""" | |
تحديد المسار الحرج في الجدول الزمني | |
""" | |
# استخدام قائمة الأنشطة الحرجة من القالب | |
schedule_template = self.schedule_templates.get(sector, self.schedule_templates["عام"]) | |
template_critical_activities = schedule_template.get("critical_activities", []) | |
# تجميع كل الأنشطة من جميع المراحل | |
all_activities = [] | |
for phase in phases: | |
for activity in phase.get("activities", []): | |
all_activities.append({ | |
"phase": phase["name"], | |
"name": activity["name"], | |
"start_date": activity["start_date"], | |
"end_date": activity["end_date"], | |
"duration": activity["duration"] | |
}) | |
# تحديد الأنشطة الحرجة | |
critical_path = [] | |
# إضافة الأنشطة من القالب إذا وجدت | |
for template_activity in template_critical_activities: | |
for activity in all_activities: | |
if template_activity in activity["name"]: | |
critical_activity = f"{activity['phase']} - {activity['name']}" | |
if critical_activity not in critical_path: | |
critical_path.append(critical_activity) | |
# إذا لم يتم العثور على أنشطة حرجة، استخدام الأنشطة ذات المدة الأطول | |
if not critical_path: | |
sorted_activities = sorted(all_activities, key=lambda x: x["duration"], reverse=True) | |
for activity in sorted_activities[:5]: # اختيار أطول 5 أنشطة | |
critical_activity = f"{activity['phase']} - {activity['name']}" | |
critical_path.append(critical_activity) | |
return critical_path | |
def _identify_milestones(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> List[Dict[str, Any]]: | |
""" | |
تحديد المعالم الرئيسية في الجدول الزمني | |
""" | |
milestones = [] | |
# إضافة المعالم المستخرجة من البيانات | |
for milestone in schedule_info.get("milestones", []): | |
milestones.append({ | |
"name": milestone.get("name", "معلم"), | |
"date": milestone.get("date").strftime("%Y-%m-%d") if isinstance(milestone.get("date"), datetime) else milestone.get("date"), | |
"description": milestone.get("description", ""), | |
"source": "مستخرج" | |
}) | |
# إضافة بداية ونهاية كل مرحلة كمعالم | |
for phase in phases: | |
# إضافة بداية المرحلة | |
start_milestone = { | |
"name": f"بداية {phase['name']}", | |
"date": phase["start_date"], | |
"description": f"تاريخ بدء مرحلة {phase['name']}", | |
"source": "مولد" | |
} | |
milestones.append(start_milestone) | |
# إضافة نهاية المرحلة | |
end_milestone = { | |
"name": f"نهاية {phase['name']}", | |
"date": phase["end_date"], | |
"description": f"تاريخ انتهاء مرحلة {phase['name']}", | |
"source": "مولد" | |
} | |
milestones.append(end_milestone) | |
# ترتيب المعالم حسب التاريخ | |
try: | |
milestones = sorted(milestones, key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d")) | |
except Exception as e: | |
print(f"Error sorting milestones: {str(e)}") | |
return milestones | |
def _evaluate_schedule(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any], sector: str, project_size: str) -> Dict[str, Any]: | |
""" | |
تقييم الجدول الزمني | |
""" | |
evaluation = { | |
"overall_score": 0.0, | |
"criteria_scores": {}, | |
"comments": [] | |
} | |
# حساب المدة الإجمالية | |
total_duration = sum(phase["duration"] for phase in phases) | |
# تقييم واقعية الجدول الزمني | |
realism_score = self._evaluate_realism(total_duration, sector, project_size) | |
evaluation["criteria_scores"]["realism"] = realism_score | |
# تقييم اكتمال الجدول الزمني | |
completeness_score = self._evaluate_completeness(phases, schedule_info) | |
evaluation["criteria_scores"]["completeness"] = completeness_score | |
# تقييم توزيع الموارد | |
resource_score = self._evaluate_resource_allocation(phases) | |
evaluation["criteria_scores"]["resource_allocation"] = resource_score | |
# تقييم إدارة المخاطر | |
risk_score = self._evaluate_risk_management(phases, schedule_info) | |
evaluation["criteria_scores"]["risk_management"] = risk_score | |
# تقييم المرونة | |
flexibility_score = self._evaluate_flexibility(phases, schedule_info) | |
evaluation["criteria_scores"]["flexibility"] = flexibility_score | |
# حساب التقييم الإجمالي | |
overall_score = 0.0 | |
for criterion, score in evaluation["criteria_scores"].items(): | |
weight = self.evaluation_criteria[criterion]["weight"] | |
overall_score += score * weight | |
evaluation["overall_score"] = round(overall_score, 2) | |
# إضافة تعليقات | |
if overall_score >= 0.8: | |
evaluation["comments"].append("الجدول الزمني واقعي ومكتمل ويتضمن توزيعاً جيداً للموارد") | |
elif overall_score >= 0.6: | |
evaluation["comments"].append("الجدول الزمني جيد بشكل عام لكنه يحتاج إلى بعض التحسينات") | |
else: | |
evaluation["comments"].append("الجدول الزمني يحتاج إلى مراجعة شاملة وتحسينات كبيرة") | |
if realism_score < 0.6: | |
evaluation["comments"].append("المدة الإجمالية للمشروع غير واقعية مقارنة بمشاريع مماثلة") | |
if completeness_score < 0.6: | |
evaluation["comments"].append("الجدول الزمني يفتقر إلى بعض الأنشطة الهامة أو المعالم الرئيسية") | |
if resource_score < 0.6: | |
evaluation["comments"].append("توزيع الموارد غير متوازن وقد يؤدي إلى تأخير في المشروع") | |
if risk_score < 0.6: | |
evaluation["comments"].append("الجدول الزمني لا يتضمن احتياطيات كافية للتعامل مع المخاطر") | |
if flexibility_score < 0.6: | |
evaluation["comments"].append("الجدول الزمني يفتقر إلى المرونة اللازمة للتعامل مع التغييرات") | |
return evaluation | |
def _evaluate_realism(self, total_duration: int, sector: str, project_size: str) -> float: | |
""" | |
تقييم واقعية الجدول الزمني | |
""" | |
# المقارنة مع بيانات المشاريع السابقة | |
if sector in self.historical_projects and project_size in self.historical_projects[sector]: | |
historical_data = self.historical_projects[sector][project_size] | |
avg_duration = historical_data["avg_duration"] | |
min_duration = historical_data["min_duration"] | |
max_duration = historical_data["max_duration"] | |
# إذا كانت المدة ضمن النطاق المقبول | |
if min_duration <= total_duration <= max_duration: | |
# حساب الفرق بين المدة والمتوسط | |
diff = abs(total_duration - avg_duration) / (max_duration - min_duration) | |
score = 1.0 - diff | |
return max(0.5, score) # الحد الأدنى للتقييم هو 0.5 | |
else: | |
# إذا كانت المدة خارج النطاق المقبول | |
return 0.3 | |
# إذا لم تتوفر بيانات للمقارنة | |
return 0.5 | |
def _evaluate_completeness(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> float: | |
""" | |
تقييم اكتمال الجدول الزمني | |
""" | |
# حساب عدد المراحل والأنشطة والمعالم | |
num_phases = len(phases) | |
num_activities = sum(len(phase.get("activities", [])) for phase in phases) | |
num_milestones = len(schedule_info.get("milestones", [])) | |
# تقييم اكتمال المراحل | |
phases_score = min(1.0, num_phases / 5) # افتراض أن 5 مراحل هو العدد المثالي | |
# تقييم اكتمال الأنشطة | |
activities_score = min(1.0, num_activities / 20) # افتراض أن 20 نشاط هو العدد المثالي | |
# تقييم اكتمال المعالم | |
milestones_score = min(1.0, num_milestones / 10) # افتراض أن 10 معالم هو العدد المثالي | |
# حساب التقييم الإجمالي للاكتمال | |
completeness_score = (phases_score * 0.3) + (activities_score * 0.5) + (milestones_score * 0.2) | |
return round(completeness_score, 2) | |
def _evaluate_resource_allocation(self, phases: List[Dict[str, Any]]) -> float: | |
""" | |
تقييم توزيع الموارد في الجدول الزمني | |
""" | |
# في التطبيق الفعلي، هذا يحتاج إلى بيانات أكثر عن الموارد | |
# هذا تقييم مبسط بناءً على توزيع المدة على المراحل | |
# حساب إجمالي المدة | |
total_duration = sum(phase["duration"] for phase in phases) | |
# حساب الانحراف المعياري لمدة المراحل | |
durations = [phase["duration"] for phase in phases] | |
mean_duration = total_duration / len(phases) | |
variance = sum((d - mean_duration) ** 2 for d in durations) / len(phases) | |
std_dev = variance ** 0.5 | |
# حساب معامل الاختلاف (نسبة الانحراف المعياري إلى المتوسط) | |
coef_of_variation = std_dev / mean_duration if mean_duration > 0 else 0 | |
# حساب التقييم: كلما قل معامل الاختلاف، كان التوزيع أكثر توازناً | |
resource_score = max(0, 1.0 - coef_of_variation) | |
return round(resource_score, 2) | |
def _evaluate_risk_management(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> float: | |
""" | |
تقييم إدارة المخاطر في الجدول الزمني | |
""" | |
# في التطبيق الفعلي، هذا يحتاج إلى بيانات أكثر عن المخاطر | |
# هذا تقييم مبسط بناءً على وجود احتياطيات زمنية | |
# حساب نسبة الاحتياطي الزمني المقدرة | |
total_duration = sum(phase["duration"] for phase in phases) | |
estimated_buffer = total_duration * 0.1 # افتراض أن 10% من المدة الكلية يجب أن تكون احتياطي | |
actual_buffer = 0 | |
# البحث عن أنشطة أو مراحل تمثل احتياطيات | |
for phase in phases: | |
phase_name = phase["name"].lower() | |
if "احتياطي" in phase_name or "طوارئ" in phase_name: | |
actual_buffer += phase["duration"] | |
for activity in phase.get("activities", []): | |
activity_name = activity["name"].lower() | |
if "احتياطي" in activity_name or "طوارئ" in activity_name: | |
actual_buffer += activity["duration"] | |
# حساب نسبة الاحتياطي الفعلي إلى المقدر | |
buffer_ratio = actual_buffer / estimated_buffer if estimated_buffer > 0 else 0 | |
buffer_score = min(1.0, buffer_ratio) | |
# حساب تقييم المسار الحرج | |
critical_path_identified = 1.0 if len(schedule_info.get("critical_path", [])) > 0 else 0.0 | |
# حساب التقييم الإجمالي لإدارة المخاطر | |
risk_score = (buffer_score * 0.6) + (critical_path_identified * 0.4) | |
return round(risk_score, 2) | |
def _evaluate_flexibility(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any]) -> float: | |
""" | |
تقييم مرونة الجدول الزمني | |
""" | |
# في التطبيق الفعلي، هذا يحتاج إلى بيانات أكثر عن آليات التعديل والمراقبة | |
# هذا تقييم مبسط بناءً على وجود فترات مراجعة وتقييم | |
# البحث عن أنشطة تتعلق بالمراجعة والتقييم | |
review_activities = 0 | |
total_activities = 0 | |
for phase in phases: | |
for activity in phase.get("activities", []): | |
total_activities += 1 | |
activity_name = activity["name"].lower() | |
if "مراجعة" in activity_name or "تقييم" in activity_name or "متابعة" in activity_name: | |
review_activities += 1 | |
# حساب نسبة أنشطة المراجعة والتقييم | |
review_ratio = review_activities / total_activities if total_activities > 0 else 0 | |
review_score = min(1.0, review_ratio * 5) # افتراض أن 20% من الأنشطة يجب أن تكون للمراجعة والتقييم | |
# حساب التقييم الإجمالي للمرونة | |
flexibility_score = review_score | |
return round(flexibility_score, 2) | |
def _generate_optimization_suggestions(self, phases: List[Dict[str, Any]], evaluation: Dict[str, Any], schedule_info: Dict[str, Any]) -> List[str]: | |
""" | |
إعداد اقتراحات لتحسين الجدول الزمني | |
""" | |
optimization_suggestions = [] | |
# اقتراحات بناءً على التقييم | |
criteria_scores = evaluation.get("criteria_scores", {}) | |
# اقتراحات لتحسين واقعية الجدول الزمني | |
if criteria_scores.get("realism", 1.0) < 0.6: | |
optimization_suggestions.append( | |
"مراجعة المدة الإجمالية للمشروع ومقارنتها بمشاريع مماثلة للتأكد من واقعيتها" | |
) | |
# اقتراحات لتحسين اكتمال الجدول الزمني | |
if criteria_scores.get("completeness", 1.0) < 0.6: | |
optimization_suggestions.append( | |
"تفصيل الأنشطة بشكل أكبر وإضافة المعالم الرئيسية لكل مرحلة" | |
) | |
# اقتراحات لتحسين توزيع الموارد | |
if criteria_scores.get("resource_allocation", 1.0) < 0.6: | |
optimization_suggestions.append( | |
"تحسين توزيع الموارد بين المراحل المختلفة وتجنب التحميل الزائد للموارد" | |
) | |
# اقتراحات لتحسين إدارة المخاطر | |
if criteria_scores.get("risk_management", 1.0) < 0.6: | |
optimization_suggestions.append( | |
"إضافة احتياطيات زمنية كافية للتعامل مع المخاطر المحتملة وتأخيرات المشروع" | |
) | |
optimization_suggestions.append( | |
"تحديد المسار الحرج بوضوح وإعداد خطط بديلة للأنشطة الحرجة" | |
) | |
# اقتراحات لتحسين المرونة | |
if criteria_scores.get("flexibility", 1.0) < 0.6: | |
optimization_suggestions.append( | |
"إضافة نقاط مراجعة وتقييم دورية للتأكد من سير المشروع وفق الخطة" | |
) | |
optimization_suggestions.append( | |
"تطوير آلية واضحة للتعامل مع التغييرات وتحديث الجدول الزمني" | |
) | |
# اقتراحات عامة | |
optimization_suggestions.extend([ | |
"استخدام أدوات إدارة المشاريع لمتابعة التقدم ومقارنته بالخطة الموضوعة", | |
"تحديد المسؤوليات بوضوح لكل نشاط من أنشطة المشروع", | |
"تحليل المسار الحرج وتقليل الاعتمادية بين الأنشطة لتقليل مخاطر التأخير" | |
]) | |
return optimization_suggestions | |
def _analyze_schedule_risks(self, phases: List[Dict[str, Any]], schedule_info: Dict[str, Any], sector: str) -> List[Dict[str, Any]]: | |
""" | |
تحليل المخاطر المتعلقة بالجدول الزمني | |
""" | |
risks = [] | |
# مخاطر تتعلق بالمدة الإجمالية | |
total_duration = sum(phase["duration"] for phase in phases) | |
if sector in self.historical_projects: | |
historical_data = self.historical_projects[sector]["medium"] # افتراض حجم متوسط | |
avg_duration = historical_data["avg_duration"] | |
if total_duration < avg_duration * 0.7: | |
risks.append({ | |
"title": "مدة قصيرة جداً", | |
"description": "المدة الإجمالية للمشروع أقل بكثير من متوسط مدة مشاريع مماثلة", | |
"probability": "عالية", | |
"impact": "عالي", | |
"mitigation": [ | |
"إعادة تقدير المدة الإجمالية بناءً على مشاريع مماثلة", | |
"تحديد الأنشطة التي يمكن تنفيذها بشكل متوازٍ لتقليل المدة", | |
"توفير موارد إضافية لتسريع تنفيذ الأنشطة الحرجة" | |
] | |
}) | |
elif total_duration > avg_duration * 1.3: | |
risks.append({ | |
"title": "مدة طويلة جداً", | |
"description": "المدة الإجمالية للمشروع أطول بكثير من متوسط مدة مشاريع مماثلة", | |
"probability": "متوسطة", | |
"impact": "متوسط", | |
"mitigation": [ | |
"مراجعة تقديرات المدة لكل نشاط والتأكد من دقتها", | |
"تحليل الأنشطة غير الضرورية وإمكانية حذفها أو دمجها", | |
"تحسين كفاءة العمليات لتقليل المدة الإجمالية" | |
] | |
}) | |
# مخاطر تتعلق بالمسار الحرج | |
if not schedule_info.get("critical_path"): | |
risks.append({ | |
"title": "عدم تحديد المسار الحرج", | |
"description": "لم يتم تحديد المسار الحرج في الجدول الزمني", | |
"probability": "عالية", | |
"impact": "عالي", | |
"mitigation": [ | |
"تحديد المسار الحرج باستخدام طريقة المسار الحرج (CPM)", | |
"إضافة احتياطيات زمنية للأنشطة الحرجة", | |
"مراقبة الأنشطة الحرجة بشكل مكثف" | |
] | |
}) | |
# مخاطر تتعلق بالاحتياطيات الزمنية | |
buffer_found = False | |
for phase in phases: | |
phase_name = phase["name"].lower() | |
if "احتياطي" in phase_name or "طوارئ" in phase_name: | |
buffer_found = True | |
break | |
for activity in phase.get("activities", []): | |
activity_name = activity["name"].lower() | |
if "احتياطي" in activity_name or "طوارئ" in activity_name: | |
buffer_found = True | |
break | |
if buffer_found: | |
break | |
if not buffer_found: | |
risks.append({ | |
"title": "عدم وجود احتياطيات زمنية", | |
"description": "لم يتم تضمين احتياطيات زمنية في الجدول الزمني للتعامل مع المخاطر والتأخيرات", | |
"probability": "عالية", | |
"impact": "عالي", | |
"mitigation": [ | |
"إضافة احتياطيات زمنية بنسبة 10-15% من المدة الإجمالية", | |
"توزيع الاحتياطيات على الأنشطة الحرجة", | |
"إضافة معالم للمراجعة وإعادة التقييم" | |
] | |
}) | |
# مخاطر تتعلق بتداخل الأنشطة | |
activities_count = sum(len(phase.get("activities", [])) for phase in phases) | |
phases_count = len(phases) | |
if activities_count > 0 and phases_count > 0: | |
avg_activities_per_phase = activities_count / phases_count | |
if avg_activities_per_phase > 7: | |
risks.append({ | |
"title": "تداخل كبير في الأنشطة", | |
"description": "عدد كبير من الأنشطة المتداخلة في كل مرحلة قد يؤدي إلى صعوبة في الإدارة والتنفيذ", | |
"probability": "متوسطة", | |
"impact": "متوسط", | |
"mitigation": [ | |
"تقسيم المراحل الكبيرة إلى مراحل أصغر", | |
"تحديد أولويات الأنشطة وتسلسلها بوضوح", | |
"تخصيص موارد كافية لإدارة الأنشطة المتداخلة" | |
] | |
}) | |
# مخاطر خاصة بالقطاع | |
if sector == "الإنشاءات": | |
risks.append({ | |
"title": "تأثير الظروف الجوية", | |
"description": "قد تؤثر الظروف الجوية على الأنشطة الخارجية وتؤدي إلى تأخير المشروع", | |
"probability": "متوسطة", | |
"impact": "متوسط", | |
"mitigation": [ | |
"تضمين احتياطيات زمنية للظروف الجوية", | |
"جدولة الأنشطة الخارجية في الفترات المناسبة من السنة", | |
"إعداد خطط بديلة للأنشطة المتأثرة بالظروف الجوية" | |
] | |
}) | |
elif sector == "تقنية المعلومات": | |
risks.append({ | |
"title": "تغيير المتطلبات", | |
"description": "قد تتغير متطلبات المشروع أثناء التنفيذ مما يؤدي إلى تأخير الجدول الزمني", | |
"probability": "عالية", | |
"impact": "عالي", | |
"mitigation": [ | |
"توثيق المتطلبات بشكل دقيق والحصول على موافقة العميل", | |
"استخدام منهجية مرنة تسمح بالتكيف مع التغييرات", | |
"تضمين احتياطيات زمنية للتعامل مع تغييرات المتطلبات" | |
] | |
}) | |
return risks |