WAHBi-AI-V2 / analysis /requirement_analyzer.py
EGYADMIN's picture
Create analysis/requirement_analyzer.py
a3b5c66 verified
# إذا كانت الجملة الأولى طويلة، اختصرها
if len(first_sentence) > 50:
if ":" in first_sentence:
# استخدام النص قبل العلامة : كعنوان
title = first_sentence.split(":")[0].strip()
else:
# اختصار الجملة الأولى
words = first_sentence.split()
title = " ".join(words[:7]) + "..."
else:
title = first_sentence
return title
def _categorize_requirements(self, requirements: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""
تصنيف المتطلبات إلى فئات
المعاملات:
----------
requirements : List[Dict[str, Any]]
قائمة المتطلبات
المخرجات:
--------
Dict[str, List[Dict[str, Any]]]
المتطلبات مصنفة حسب الفئة
"""
categorized = {
"technical": [],
"financial": [],
"legal": []
}
for req in requirements:
description = req.get("description", "").lower()
title = req.get("title", "").lower()
text = title + " " + description
# تحديد الفئة بناءً على الكلمات المفتاحية
if any(keyword in text for keyword in ["فني", "تقني", "هندسي", "مواصفات", "جودة", "معايير", "أداء", "تصميم", "مخطط"]):
req["category"] = "technical"
categorized["technical"].append(req)
elif any(keyword in text for keyword in ["مالي", "سعر", "تكلفة", "دفع", "ضمان", "تأمين", "غرامة", "ميزانية", "ضريبة", "محاسبة"]):
req["category"] = "financial"
categorized["financial"].append(req)
elif any(keyword in text for keyword in ["قانوني", "شرط", "عقد", "التزام", "تعاقد", "نظام", "لائحة", "تشريع", "ترخيص", "حقوق"]):
req["category"] = "legal"
categorized["legal"].append(req)
else:
# إذا لم يتطابق مع أي فئة، افترض أنه متطلب فني
req["category"] = "technical"
categorized["technical"].append(req)
return categorized
def _analyze_importance_difficulty(self, categorized_requirements: Dict[str, List[Dict[str, Any]]]) -> Dict[str, List[Dict[str, Any]]]:
"""
تحليل أهمية وصعوبة المتطلبات
المعاملات:
----------
categorized_requirements : Dict[str, List[Dict[str, Any]]]
المتطلبات مصنفة حسب الفئة
المخرجات:
--------
Dict[str, List[Dict[str, Any]]]
المتطلبات بعد تحليل الأهمية والصعوبة
"""
analyzed = {}
for category, reqs in categorized_requirements.items():
analyzed_reqs = []
for req in reqs:
# تحليل الأهمية
req["importance"] = self._determine_importance(req)
# تحليل صعوبة التنفيذ
req["difficulty"] = self._determine_difficulty(req)
# تحديد الفجوات
req["gaps"] = self._identify_gaps(req)
# اقتراح تحسينات
req["improvements"] = self._suggest_improvements(req)
analyzed_reqs.append(req)
analyzed[category] = analyzed_reqs
return analyzed
def _determine_importance(self, requirement: Dict[str, Any]) -> str:
"""
تحديد أهمية المتطلب
المعاملات:
----------
requirement : Dict[str, Any]
المتطلب
المخرجات:
--------
str
درجة الأهمية
"""
text = requirement.get("description", "").lower()
# البحث عن كلمات تدل على الإلزامية
if any(keyword in text for keyword in ["يجب", "إلزامي", "ضروري", "لا بد", "لابد", "مطلوب", "يلتزم", "ملزم"]):
return "إلزامي"
# البحث عن كلمات تدل على الأهمية الثانوية
elif any(keyword in text for keyword in ["يفضل", "مرغوب", "محبذ", "يحبذ", "يستحسن"]):
return "ثانوي"
# إذا لم يتطابق مع أي حالة، افترض أنه متطلب عادي
else:
return "عادي"
def _determine_difficulty(self, requirement: Dict[str, Any]) -> int:
"""
تحديد صعوبة تنفيذ المتطلب
المعاملات:
----------
requirement : Dict[str, Any]
المتطلب
المخرجات:
--------
int
درجة الصعوبة (1-5)
"""
text = requirement.get("description", "").lower()
category = requirement.get("category", "")
importance = requirement.get("importance", "")
# تعيين درجة صعوبة أساسية
base_difficulty = 3 # متوسط
# زيادة الصعوبة للمتطلبات الإلزامية
if importance == "إلزامي":
base_difficulty += 1
# زيادة الصعوبة بناءً على طول النص
if len(text) > 200:
base_difficulty += 0.5
# زيادة الصعوبة بناءً على الكلمات المفتاحية
if any(keyword in text for keyword in ["معقد", "صعب", "متقدم", "عالي", "متطور", "خبير", "مخصص", "خاص"]):
base_difficulty += 1
# تخفيض الصعوبة للمتطلبات البسيطة
if any(keyword in text for keyword in ["بسيط", "سهل", "عادي", "أساسي", "تقليدي"]):
base_difficulty -= 1
# ضبط الصعوبة في النطاق 1-5
return max(1, min(5, round(base_difficulty)))
def _identify_gaps(self, requirement: Dict[str, Any]) -> str:
"""
تحديد الفجوات في المتطلب
المعاملات:
----------
requirement : Dict[str, Any]
المتطلب
المخرجات:
--------
str
الفجوات المحددة
"""
text = requirement.get("description", "")
gaps = []
# التحقق من وضوح المتطلب
if len(text) < 50:
gaps.append("وصف قصير وغير مفصل")
# التحقق من وجود معايير قياس
if not any(keyword in text.lower() for keyword in ["معيار", "قياس", "مؤشر", "مستوى", "نسبة", "كمية", "عدد"]):
gaps.append("لا يحتوي على معايير قياس واضحة")
# التحقق من وجود توصيف زمني
if not any(keyword in text.lower() for keyword in ["مدة", "فترة", "خلال", "زمن", "تاريخ", "موعد", "يوم", "أسبوع", "شهر"]):
gaps.append("لا يحدد إطار زمني")
# التحقق من وجود مسؤولية تنفيذ
if not any(keyword in text.lower() for keyword in ["مسؤول", "مسؤولية", "مقاول", "مورد", "مقدم", "طرف", "مكلف"]):
gaps.append("لا يحدد المسؤولية عن التنفيذ")
# إرجاع الفجوات
if gaps:
return "، ".join(gaps)
else:
return "لا توجد فجوات واضحة"
def _suggest_improvements(self, requirement: Dict[str, Any]) -> str:
"""
اقتراح تحسينات للمتطلب
المعاملات:
----------
requirement : Dict[str, Any]
المتطلب
المخرجات:
--------
str
التحسينات المقترحة
"""
gaps = requirement.get("gaps", "")
category = requirement.get("category", "")
improvements = []
# اقتراح تحسينات بناءً على الفجوات
if "وصف قصير" in gaps:
improvements.append("توضيح المتطلب بشكل أكثر تفصيلاً")
if "معايير قياس" in gaps:
improvements.append("إضافة معايير قياس كمية ونوعية")
if "إطار زمني" in gaps:
improvements.append("تحديد إطار زمني واضح للتنفيذ")
if "المسؤولية" in gaps:
improvements.append("تحديد المسؤولية عن التنفيذ بوضوح")
# اقتراح تحسينات بناءً على الفئة
if category == "technical":
if not improvements:
improvements.append("إضافة مراجع للمعايير الفنية والمواصفات")
elif category == "financial":
if not improvements:
improvements.append("توضيح آلية الدفع والتسعير")
elif category == "legal":
if not improvements:
improvements.append("إضافة المرجعية القانونية والتنظيمية")
# إرجاع التحسينات
if improvements:
return "، ".join(improvements)
else:
return "المتطلب واضح ومكتمل"
def _extract_local_content_requirements(self, text: str) -> List[Dict[str, Any]]:
"""
استخراج متطلبات المحتوى المحلي
المعاملات:
----------
text : str
النص المستخرج من المناقصة
المخرجات:
--------
List[Dict[str, Any]]
قائمة متطلبات المحتوى المحلي
"""
# الكلمات المفتاحية للمحتوى المحلي
local_content_keywords = [
"المحتوى المحلي", "التوطين", "السعودة", "المنتجات المحلية",
"الصناعة المحلية", "نسبة المحتوى", "المكون المحلي", "منشأ محلي",
"نطاقات", "توظيف السعوديين", "توظيف المواطنين", "القيمة المضافة المحلية"
]
local_content_requirements = []
# البحث عن أقسام المحتوى المحلي
for keyword in local_content_keywords:
pattern = rf"((?:{keyword}|{keyword.upper()}).*?(?:\n\n|\Z))"
matches = re.finditer(pattern, text, re.DOTALL)
for match in matches:
section_text = match.group(1).strip()
# استخراج النسب المئوية
percentage_matches = re.findall(r'(\d+(?:\.\d+)?)(?:\s*%|\s*في المائة|\s*بالمائة)', section_text)
if percentage_matches:
for percentage in percentage_matches:
requirement = {
"title": f"متطلب المحتوى المحلي - {percentage}%",
"description": section_text,
"category": "local_content",
"importance": "إلزامي",
"percentage": float(percentage),
"difficulty": 4, # افتراضي: صعوبة عالية
"gaps": self._identify_gaps({"description": section_text}),
"improvements": "توضيح آلية حساب وتوثيق نسبة المحتوى المحلي"
}
local_content_requirements.append(requirement)
else:
# إذا لم يتم العثور على نسب، أضف المتطلب بدون نسبة
requirement = {
"title": "متطلب المحتوى المحلي",
"description": section_text,
"category": "local_content",
"importance": "إلزامي",
"difficulty": 3, # افتراضي: صعوبة متوسطة
"gaps": "لا يحدد نسبة محتوى محلي واضحة",
"improvements": "تحديد نسبة المحتوى المحلي المطلوبة بشكل صريح"
}
local_content_requirements.append(requirement)
# إزالة التكرارات
unique_requirements = []
descriptions = set()
for req in local_content_requirements:
desc = req["description"]
if desc not in descriptions:
descriptions.add(desc)
unique_requirements.append(req)
return unique_requirements
def _generate_requirements_summary(self, categorized_requirements: Dict[str, List[Dict[str, Any]]],
local_content_requirements: List[Dict[str, Any]],
total_count: int, mandatory_count: int, avg_difficulty: float) -> str:
"""
إعداد ملخص المتطلبات
المعاملات:
----------
categorized_requirements : Dict[str, List[Dict[str, Any]]]
المتطلبات مصنفة حسب الفئة
local_content_requirements : List[Dict[str, Any]]
متطلبات المحتوى المحلي
total_count : int
إجمالي عدد المتطلبات
mandatory_count : int
عدد المتطلبات الإلزامية
avg_difficulty : float
متوسط صعوبة التنفيذ
المخرجات:
--------
str
ملخص المتطلبات
"""
# إعداد الملخص
summary = f"تم تحليل {total_count} متطلب، منها {mandatory_count} متطلب إلزامي. "
# توزيع المتطلبات حسب الفئة
tech_count = len(categorized_requirements.get("technical", []))
fin_count = len(categorized_requirements.get("financial", []))
legal_count = len(categorized_requirements.get("legal", []))
local_count = len(local_content_requirements)
summary += f"تتوزع المتطلبات إلى {tech_count} متطلب فني، و{fin_count} متطلب مالي، و{legal_count} متطلب قانوني، و{local_count} متطلب للمحتوى المحلي. "
# صعوبة التنفيذ
if avg_difficulty >= 4:
summary += "متوسط صعوبة تنفيذ المتطلبات مرتفع، مما يشير إلى تعقيد المشروع. "
elif avg_difficulty >= 3:
summary += "متوسط صعوبة تنفيذ المتطلبات متوسط. "
else:
summary += "متوسط صعوبة تنفيذ المتطلبات منخفض، مما يشير إلى إمكانية تنفيذ المشروع بسهولة نسبية. "
# متطلبات المحتوى المحلي
if local_count > 0:
local_percentages = [req.get("percentage", 0) for req in local_content_requirements if "percentage" in req]
if local_percentages:
max_percentage = max(local_percentages)
summary += f"يتطلب المشروع تحقيق نسبة محتوى محلي لا تقل عن {max_percentage}%. "
else:
summary += "يتضمن المشروع متطلبات للمحتوى المحلي، لكن النسبة المطلوبة غير محددة بوضوح. "
else:
summary += "لم يتم تحديد متطلبات واضحة للمحتوى المحلي. "
# تقييم عام
if mandatory_count / total_count > 0.7:
summary += "نسبة المتطلبات الإلزامية مرتفعة، مما يشير إلى تشدد في شروط المناقصة. "
if avg_difficulty > 3.5 and mandatory_count / total_count > 0.6:
summary += "يجب الانتباه إلى ارتفاع صعوبة التنفيذ ونسبة المتطلبات الإلزامية، مما قد يزيد من مخاطر المشروع."
return summary
def _generate_template_requirements(self) -> List[Dict[str, Any]]:
"""
إنشاء متطلبات افتراضية من القوالب
المخرجات:
--------
List[Dict[str, Any]]
قائمة المتطلبات الافتراضية
"""
# إنشاء نسخة من قوالب المتطلبات
requirements = []
# إضافة متطلبات من كل فئة
for category, templates in self.requirement_templates.items():
for template in templates:
requirement = template.copy()
requirement["category"] = category
requirements.append(requirement)
return requirements
def _clean_requirements(self, requirements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
تنظيف وتوحيد المتطلبات
المعاملات:
----------
requirements : List[Dict[str, Any]]
قائمة المتطلبات
المخرجات:
--------
List[Dict[str, Any]]
قائمة المتطلبات المنظفة
"""
cleaned = []
descriptions = set()
for req in requirements:
# تنظيف الوصف
if "description" in req:
req["description"] = req["description"].strip()
# تجاهل المتطلبات المكررة
if req.get("description", "") in descriptions:
continue
# إضافة الوصف إلى مجموعة الأوصاف
descriptions.add(req.get("description", ""))
# التأكد من وجود عنوان
if "title" not in req or not req["title"]:
req["title"] = self._extract_requirement_title(req.get("description", ""))
# إضافة المتطلب إلى القائمة المنظفة
cleaned.append(req)
return cleaned
def _load_requirement_templates(self) -> Dict[str, List[Dict[str, Any]]]:
"""
تحميل قوالب المتطلبات
المخرجات:
--------
Dict[str, List[Dict[str, Any]]]
قوالب المتطلبات
"""
try:
file_path = 'data/templates/requirement_templates.json'
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
logger.warning(f"ملف قوالب المتطلبات غير موجود: {file_path}")
# إنشاء قوالب افتراضية
return self._create_default_requirement_templates()
except Exception as e:
logger.error(f"فشل في تحميل قوالب المتطلبات: {str(e)}")
return self._create_default_requirement_templates()
def _create_default_requirement_templates(self) -> Dict[str, List[Dict[str, Any]]]:
"""
إنشاء قوالب متطلبات افتراضية
المخرجات:
--------
Dict[str, List[Dict[str, Any]]]
قوالب المتطلبات الافتراضية
"""
templates = {
"technical": [
{
"title": "مؤهلات وخبرات الفريق الفني",
"description": "يجب توفير فريق فني مؤهل ذو خبرة لا تقل عن 5 سنوات في مجال المشروع، مع تقديم السير الذاتية للمهندسين الرئيسيين.",
"importance": "إلزامي",
"difficulty": 3
},
{
"title": "جودة المواد والمعدات",
"description": "يجب استخدام مواد ومعدات ذات جودة عالية ومطابقة للمواصفات القياسية السعودية والعالمية، مع تقديم شهادات الجودة والمنشأ.",
"importance": "إلزامي",
"difficulty": 4
},
{
"title": "خطة العمل والجدول الزمني",
"description": "تقديم خطة عمل تفصيلية وجدول زمني لتنفيذ المشروع، موضحاً المراحل الرئيسية والمدد الزمنية لكل مرحلة.",
"importance": "إلزامي",
"difficulty": 3
},
{
"title": "ضمان الأعمال",
"description": "تقديم ضمان للأعمال المنفذة لمدة لا تقل عن سنة من تاريخ الاستلام النهائي، مع الالتزام بإصلاح أي عيوب خلال فترة الضمان.",
"importance": "إلزامي",
"difficulty": 2
},
{
"title": "الصيانة الدورية",
"description": "يفضل تقديم برنامج للصيانة الدورية بعد انتهاء فترة الضمان، مع تحديد التكاليف والخدمات المشمولة.",
"importance": "ثانوي",
"difficulty": 2
}
],
"financial": [
{
"title": "العرض المالي",
"description": "تقديم عرض مالي مفصل يوضح تكاليف كل بند من بنود المشروع، مع بيان القيمة الإجمالية شاملة ضريبة القيمة المضافة.",
"importance": "إلزامي",
"difficulty": 3
},
{
"title": "الضمان الابتدائي",
"description": "تقديم ضمان ابتدائي بنسبة 2% من قيمة العرض، ساري المفعول لمدة 90 يوماً من تاريخ فتح المظاريف.",
"importance": "إلزامي",
"difficulty": 2
},
{
"title": "الضمان النهائي",
"description": "تقديم ضمان نهائي بنسبة 5% من قيمة العقد عند الترسية، ساري المفعول حتى انتهاء فترة الضمان.",
"importance": "إلزامي",
"difficulty": 2
},
{
"title": "شروط الدفع",
"description": "يتم الدفع على دفعات مرحلية حسب نسب الإنجاز، مع احتجاز نسبة 10% من كل دفعة كضمان حسن التنفيذ.",
"importance": "إلزامي",
"difficulty": 2
},
{
"title": "غرامات التأخير",
"description": "تفرض غرامة تأخير بنسبة 1% من قيمة العقد عن كل أسبوع تأخير، بحد أقصى 10% من القيمة الإجمالية للعقد.",
"importance": "إلزامي",
"difficulty": 3
}
],
"legal": [
{
"title": "السجل التجاري",
"description": "يجب أن يكون المتقدم حاصلاً على سجل تجاري ساري المفعول في نفس مجال المناقصة.",
"importance": "إلزامي",
"difficulty": 1
},
{
"title": "تصنيف المقاولين",
"description": "يجب أن يكون المقاول مصنفاً لدى وزارة الشؤون البلدية والقروية والإسكان في المجال المطلوب وبدرجة لا تقل عن الثالثة.",
"importance": "إلزامي",
"difficulty": 2
},
{
"title": "التأمينات",
"description": "تقديم شهادة من المؤسسة العامة للتأمينات الاجتماعية تثبت الوفاء بالالتزامات تجاه المؤسسة.",
"importance": "إلزامي",
"difficulty": 1
},
{
"title": "الزكاة والدخل",
"description": "تقديم شهادة من هيئة الزكاة والضريبة والجمارك تثبت سداد المستحقات.",
"importance": "إلزامي",
"difficulty": 1
},
{
"title": "نظام المنافسات والمشتريات",
"description": "الالتزام بأحكام نظام المنافسات والمشتريات الحكومية ولائحته التنفيذية.",
"importance": "إلزامي",
"difficulty": 2
}
]
}
return templates"""
محلل متطلبات المناقصات
يقوم باستخراج وتحليل وتصنيف متطلبات المناقصات
"""
import re
import logging
import json
import os
from typing import Dict, List, Any, Tuple, Optional, Union
import numpy as np
logger = logging.getLogger(__name__)
class RequirementAnalyzer:
"""
محلل متطلبات المناقصات
"""
def __init__(self, model_loader, arabic_nlp, config=None):
"""
تهيئة محلل المتطلبات
المعاملات:
----------
model_loader : ModelLoader
محمّل النماذج المستخدمة للتحليل
arabic_nlp : ArabicNLP
أدوات معالجة اللغة العربية
config : Dict, optional
إعدادات المحلل
"""
self.config = config or {}
self.model_loader = model_loader
self.arabic_nlp = arabic_nlp
# تحميل النماذج
self.ner_model = None
if hasattr(model_loader, 'get_ner_model'):
try:
self.ner_model = model_loader.get_ner_model()
logger.info("تم تحميل نموذج التعرف على الكيانات المسماة")
except Exception as e:
logger.warning(f"فشل في تحميل نموذج التعرف على الكيانات المسماة: {str(e)}")
# تحميل قوالب المتطلبات
self.requirement_templates = self._load_requirement_templates()
logger.info("تم تهيئة محلل المتطلبات")
def analyze(self, text: str) -> Dict[str, Any]:
"""
تحليل متطلبات المناقصة من النص
المعاملات:
----------
text : str
النص المستخرج من المناقصة
المخرجات:
--------
Dict[str, Any]
نتائج تحليل المتطلبات
"""
try:
logger.info("بدء تحليل متطلبات المناقصة")
# استخراج المتطلبات من النص
requirements = self._extract_requirements(text)
# تصنيف المتطلبات
categorized_requirements = self._categorize_requirements(requirements)
# تحليل الأهمية والصعوبة
analyzed_requirements = self._analyze_importance_difficulty(categorized_requirements)
# تحليل متطلبات المحتوى المحلي
local_content_requirements = self._extract_local_content_requirements(text)
# تحديد عدد المتطلبات الإلزامية
mandatory_count = sum(1 for req in requirements if req.get("importance", "") == "إلزامي")
# حساب متوسط صعوبة التنفيذ
difficulty_values = [req.get("difficulty", 3) for req in requirements if req.get("difficulty", 0) > 0]
avg_difficulty = np.mean(difficulty_values) if difficulty_values else 3.0
# حساب نسبة متطلبات المحتوى المحلي
total_count = len(requirements)
local_content_percentage = (len(local_content_requirements) / total_count * 100) if total_count > 0 else 0
# إعداد ملخص المتطلبات
summary = self._generate_requirements_summary(
analyzed_requirements, local_content_requirements, total_count, mandatory_count, avg_difficulty
)
# إعداد النتائج
results = {
"summary": summary,
"technical": analyzed_requirements.get("technical", []),
"financial": analyzed_requirements.get("financial", []),
"legal": analyzed_requirements.get("legal", []),
"local_content": local_content_requirements,
"total_count": total_count,
"mandatory_count": mandatory_count,
"avg_difficulty": avg_difficulty,
"local_content_percentage": local_content_percentage
}
logger.info(f"اكتمل تحليل المتطلبات: {total_count} متطلبات، {mandatory_count} إلزامية")
return results
except Exception as e:
logger.error(f"فشل في تحليل المتطلبات: {str(e)}")
return {
"summary": "حدث خطأ أثناء تحليل المتطلبات",
"technical": [],
"financial": [],
"legal": [],
"local_content": [],
"total_count": 0,
"mandatory_count": 0,
"avg_difficulty": 0,
"local_content_percentage": 0,
"error": str(e)
}
def _extract_requirements(self, text: str) -> List[Dict[str, Any]]:
"""
استخراج المتطلبات من النص
المعاملات:
----------
text : str
النص المستخرج من المناقصة
المخرجات:
--------
List[Dict[str, Any]]
قائمة المتطلبات المستخرجة
"""
requirements = []
# البحث عن قسم المتطلبات أو الشروط
requirement_sections = self._find_requirement_sections(text)
# إذا لم يتم العثور على أقسام للمتطلبات، استخدم النص كاملاً
if not requirement_sections:
requirement_sections = [text]
# معالجة كل قسم
for section in requirement_sections:
# استخراج المتطلبات من النص
section_requirements = self._parse_requirements_text(section)
# دمج المتطلبات المستخرجة
requirements.extend(section_requirements)
# إذا لم يتم العثور على متطلبات، استخدم القوالب
if not requirements:
requirements = self._generate_template_requirements()
# تنظيف وتوحيد المتطلبات
requirements = self._clean_requirements(requirements)
return requirements
def _find_requirement_sections(self, text: str) -> List[str]:
"""
البحث عن أقسام المتطلبات في النص
المعاملات:
----------
text : str
النص المستخرج من المناقصة
المخرجات:
--------
List[str]
أقسام المتطلبات المستخرجة
"""
sections = []
# الكلمات المفتاحية لأقسام المتطلبات
section_keywords = [
"المتطلبات", "الشروط", "المواصفات", "نطاق العمل",
"البنود", "المعايير", "الالتزامات", "المؤهلات",
"التأهيل", "الواجبات", "نطاق الأعمال", "الخدمات المطلوبة"
]
# البحث عن أقسام المتطلبات
for keyword in section_keywords:
pattern = rf"((?:{keyword}|{keyword.upper()})[:\s].*?)(?:^(?:{section_keywords[0]}|{section_keywords[0].upper()})[:\s]|\Z)"
matches = re.finditer(pattern, text, re.MULTILINE | re.DOTALL)
for match in matches:
section_text = match.group(1).strip()
if len(section_text) > 50: # تجاهل الأقسام القصيرة جدًا
sections.append(section_text)
# البحث عن قوائم البنود
bullet_lists = re.findall(r'(?:^|\n)(?:[•\-*]\s+.*(?:\n|$))+', text, re.MULTILINE)
for bullet_list in bullet_lists:
if len(bullet_list) > 100: # تجاهل القوائم القصيرة
sections.append(bullet_list)
# البحث عن قوائم مرقمة
numbered_lists = re.findall(r'(?:^|\n)(?:\d+[.)\s]+.*(?:\n|$))+', text, re.MULTILINE)
for numbered_list in numbered_lists:
if len(numbered_list) > 100: # تجاهل القوائم القصيرة
sections.append(numbered_list)
return sections
def _parse_requirements_text(self, text: str) -> List[Dict[str, Any]]:
"""
تحليل نص المتطلبات لاستخراج المتطلبات الفردية
المعاملات:
----------
text : str
نص قسم المتطلبات
المخرجات:
--------
List[Dict[str, Any]]
قائمة المتطلبات المستخرجة
"""
requirements = []
# استخراج المتطلبات من القوائم النقطية
bullet_items = re.findall(r'[•\-*]\s+(.*?)(?:\n[•\-*]|\n\n|\Z)', text, re.DOTALL)
for item in bullet_items:
item = item.strip()
if len(item) > 10: # تجاهل العناصر القصيرة جدًا
requirements.append({
"title": self._extract_requirement_title(item),
"description": item,
"source": "bullet_list"
})
# استخراج المتطلبات من القوائم المرقمة
numbered_items = re.findall(r'(\d+)[.)\s]+(.*?)(?:\n\d+[.)\s]|\n\n|\Z)', text, re.DOTALL)
for num, item in numbered_items:
item = item.strip()
if len(item) > 10: # تجاهل العناصر القصيرة جدًا
requirements.append({
"title": self._extract_requirement_title(item),
"description": item,
"source": "numbered_list",
"number": int(num)
})
# استخراج المتطلبات من الفقرات
if not requirements:
paragraphs = re.split(r'\n\s*\n', text)
for paragraph in paragraphs:
paragraph = paragraph.strip()
if len(paragraph) > 50 and len(paragraph) < 500: # تجاهل الفقرات القصيرة جدًا أو الطويلة جدًا
# تحقق مما إذا كانت الفقرة تحتوي على عبارات المتطلبات
if any(keyword in paragraph.lower() for keyword in ["يجب", "ضرورة", "إلزامي", "مطلوب", "يلتزم", "لا بد", "لابد", "شرط", "اشتراط"]):
requirements.append({
"title": self._extract_requirement_title(paragraph),
"description": paragraph,
"source": "paragraph"
})
return requirements
import re
def _extract_requirement_title(self, text: str) -> str:
"""
استخراج عنوان المتطلب من النص.
المعاملات:
----------
text : str
نص المتطلب.
المخرجات:
--------
str
عنوان المتطلب المستخرج.
"""
# تنظيف النص وإزالة أي فراغات زائدة
text = text.strip()
# محاولة استخراج العنوان من بداية النص باستخدام الجملة الأولى
first_sentence = re.split(r'[.!?،؛]', text)[0].strip()
# إذا كان النص قصيرًا جدًا، نعيده كما هو
if len(first_sentence) < 5:
return text
# التحقق من أن العنوان لا يحتوي على أرقام أو رموز غير مفهومة
if re.search(r'\d', first_sentence):
return text # إذا كان هناك أرقام، نعيد النص الأصلي
return first_sentence