File size: 20,653 Bytes
82676b8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
"""
خدمة تحليل المواصفات من المستندات
"""
import re
import pandas as pd
import numpy as np
import nltk
from nltk.tokenize import sent_tokenize
import config
class SpecificationsAnalyzer:
"""تحليل المواصفات الفنية في المستندات"""
def __init__(self):
# تحميل موارد NLTK إذا لم تكن موجودة
try:
nltk.data.find('tokenizers/punkt')
except LookupError:
nltk.download('punkt')
# فئات المواصفات الرئيسية
self.specification_categories = {
'الخرسانة': [
'خرسانة', 'اسمنت', 'رتبة', 'مقاومة', 'ضغط', 'شك', 'معالجة',
'صب', 'قالب', 'قوالب', 'تسليح', 'خلطة', 'ركام', 'حصى'
],
'حديد التسليح': [
'حديد', 'تسليح', 'قضبان', 'شد', 'جهد خضوع', 'درجة', 'قطر',
'ربط', 'غطاء خرساني', 'تشكيل', 'ثني', 'شبكة'
],
'العزل المائي': [
'عزل', 'مائي', 'رطوبة', 'بيتومين', 'لفائف', 'رولات', 'طبقة',
'رش', 'تسرب', 'مانع تسرب', 'مقاومة الماء', 'حرارة'
],
'العزل الحراري': [
'عزل', 'حراري', 'صوف صخري', 'صوف زجاجي', 'فوم', 'بوليسترين',
'موصلية', 'انتقال الحرارة', 'بولي يوريثان'
],
'أعمال البلاط': [
'بلاط', 'سيراميك', 'بورسلين', 'رخام', 'جرانيت', 'ترويبة',
'لاصق', 'مونة', 'تركيب', 'مسافات', 'أبعاد'
],
'أعمال الدهان': [
'دهان', 'طلاء', 'وجه تأسيس', 'وجه نهائي', 'رش', 'فرشاة',
'رولة', 'معجون', 'مائي', 'زيتي', 'لامع', 'مطفي'
],
'المواد الكهربائية': [
'كهرباء', 'أسلاك', 'كابلات', 'لوحات', 'مفاتيح', 'تمديدات',
'جهد', 'قدرة', 'توزيع', 'تأريض', 'قواطع', 'تيار'
],
'أعمال السباكة': [
'سباكة', 'مواسير', 'صرف', 'تغذية', 'مياه', 'بي في سي',
'نحاس', 'حديد', 'خزان', 'مضخة', 'صمام', 'محبس'
],
'أعمال التكييف': [
'تكييف', 'تبريد', 'تدفئة', 'مجاري هواء', 'دكت', 'مناولة',
'تهوية', 'وحدة', 'مكيف', 'فلتر', 'مروحة'
]
}
# المواصفات القياسية المعروفة
self.standard_specs = {
'ASTM': {
'C150': 'اسمنت بورتلاندي',
'A615': 'حديد تسليح',
'D6164': 'عزل مائي بيتوميني',
'C33': 'ركام الخرسانة',
'C494': 'إضافات الخرسانة',
'C979': 'صبغات الخرسانة',
'C578': 'عزل البوليسترين'
},
'AASHTO': {
'M85': 'اسمنت بورتلاندي',
'M31': 'حديد تسليح',
'M320': 'بيتومين للطرق'
},
'IEC': {
'60502': 'كابلات الطاقة',
'60364': 'تمديدات كهربائية',
'61439': 'لوحات توزيع الطاقة'
},
'BS': {
'8500': 'الخرسانة',
'4449': 'حديد التسليح',
'6700': 'أنظمة المياه',
'5950': 'المنشآت الفولاذية'
},
'EN': {
'197-1': 'الاسمنت',
'10080': 'حديد التسليح',
'13162': 'العزل الحراري'
},
'كود البناء السعودي': {
'SBC 201': 'الأحمال',
'SBC 304': 'الخرسانة الإنشائية',
'SBC 305': 'المباني المعدنية',
'SBC 501': 'السباكة',
'SBC 401': 'الكهرباء',
'SBC 601': 'البناء الصديق للبيئة'
}
}
def analyze_specifications(self, text):
"""تحليل المواصفات الفنية من النص"""
if not text:
return {}, [], pd.DataFrame()
# تقسيم النص إلى جمل
sentences = sent_tokenize(text)
# استخراج المواصفات حسب الفئة
specs = {}
for category, keywords in self.specification_categories.items():
specs[category] = self._extract_category_specs(sentences, keywords, category)
# استخراج المتطلبات الخاصة
special_requirements = self._extract_special_requirements(sentences)
# استخراج متطلبات المحتوى المحلي
local_content = self._extract_local_content(sentences)
return specs, special_requirements, local_content
def _extract_category_specs(self, sentences, keywords, category):
"""استخراج مواصفات فئة محددة من الجمل"""
category_specs = {}
# البحث عن الجمل التي تحتوي على الكلمات المفتاحية للفئة
category_sentences = [s for s in sentences if any(k in s.lower() for k in keywords)]
if not category_sentences:
return category_specs
# استخراج المواصفات حسب نوع الفئة
if category == 'الخرسانة':
# البحث عن قوة الضغط
for s in category_sentences:
if any(term in s.lower() for term in ['قوة', 'مقاومة', 'ضغط']):
match = re.search(r'(\d+)\s*(?:نيوتن|ميجا باسكال|نيوتن/مم²|MPa|N/mm)', s)
if match:
category_specs['قوة الضغط'] = f"{match.group(1)} نيوتن/مم²"
# البحث عن نسبة الماء للأسمنت
if any(term in s.lower() for term in ['نسبة', 'ماء', 'اسمنت']):
match = re.search(r'(\d+(?:\.\d+)?)\s*(?:%|نسبة)', s)
if match:
category_specs['نسبة الماء للأسمنت'] = f"{match.group(1)} كحد أقصى"
# البحث عن المعالجة
if 'معالجة' in s.lower():
match = re.search(r'(\d+)\s*(?:يوم|أيام)', s)
if match:
category_specs['المعالجة'] = f"لا تقل عن {match.group(1)} أيام"
# البحث عن المواصفات المرجعية
for std_org, std_codes in self.standard_specs.items():
for std_code, std_desc in std_codes.items():
if std_code in s and (std_org in s or category in std_desc.lower()):
category_specs['المواصفات المرجعية'] = f"{std_org} {std_code}"
elif category == 'حديد التسليح':
# البحث عن نوع الحديد
for s in category_sentences:
if any(term in s.lower() for term in ['درجة', 'جهد', 'خضوع', 'grade']):
match = re.search(r'(?:درجة|جريد|Grade)\s*(\d+)', s, re.IGNORECASE)
if match:
category_specs['نوع الحديد'] = f"عالي المقاومة للشد (Grade {match.group(1)})"
# البحث عن إجهاد الخضوع
if any(term in s.lower() for term in ['إجهاد', 'خضوع', 'شد']):
match = re.search(r'(\d+)\s*(?:نيوتن|ميجا باسكال|نيوتن/مم²|MPa|N/mm)', s)
if match:
category_specs['إجهاد الخضوع'] = f"{match.group(1)} نيوتن/مم²"
# البحث عن المواصفات المرجعية
for std_org, std_codes in self.standard_specs.items():
for std_code, std_desc in std_codes.items():
if std_code in s and (std_org in s or category in std_desc.lower()):
category_specs['المواصفات المرجعية'] = f"{std_org} {std_code}"
elif category == 'العزل المائي':
# البحث عن نوع العزل
for s in category_sentences:
if any(term in s.lower() for term in ['نوع', 'بيتومين', 'بوليستر', 'رول']):
if 'بيتومين' in s.lower() and 'بوليستر' in s.lower():
category_specs['النوع'] = 'أغشية بيتومينية مدعمة بالبوليستر'
elif 'بيتومين' in s.lower():
category_specs['النوع'] = 'أغشية بيتومينية'
elif 'pvc' in s.lower():
category_specs['النوع'] = 'أغشية PVC'
# البحث عن السماكة
if any(term in s.lower() for term in ['سماكة', 'سمك', 'مم']):
match = re.search(r'(\d+(?:\.\d+)?)\s*(?:مم|mm)', s, re.IGNORECASE)
if match:
category_specs['السماكة'] = f"{match.group(1)} مم"
# البحث عن مقاومة درجة الحرارة
if any(term in s.lower() for term in ['حرارة', 'درجة', 'مقاومة']):
match = re.search(r'(\d+)\s*(?:درجة|°)', s)
if match:
category_specs['مقاومة درجة الحرارة'] = f"حتى {match.group(1)} درجة مئوية"
# البحث عن المواصفات المرجعية
for std_org, std_codes in self.standard_specs.items():
for std_code, std_desc in std_codes.items():
if std_code in s and (std_org in s or 'عزل' in std_desc.lower()):
category_specs['المواصفات المرجعية'] = f"{std_org} {std_code}"
elif category == 'المواد الكهربائية':
# البحث عن نوع الكابلات
for s in category_sentences:
if any(term in s.lower() for term in ['كابل', 'سلك', 'نحاس', 'ألمنيوم']):
if 'نحاس' in s.lower() and 'xlpe' in s.lower():
category_specs['الكابلات'] = 'نحاس معزول XLPE'
elif 'نحاس' in s.lower() and 'pvc' in s.lower():
category_specs['الكابلات'] = 'نحاس معزول PVC'
elif 'نحاس' in s.lower():
category_specs['الكابلات'] = 'نحاس معزول'
elif 'ألمنيوم' in s.lower():
category_specs['الكابلات'] = 'ألمنيوم معزول'
# البحث عن المواصفات المرجعية
for std_org, std_codes in self.standard_specs.items():
for std_code, std_desc in std_codes.items():
if std_code in s and (std_org in s or 'كهربا' in std_desc.lower()):
category_specs['المواصفات المرجعية'] = f"{std_org} {std_code}"
# إذا لم يتم العثور على مواصفات محددة، أضف مواصفات افتراضية للفئات الرئيسية
if not category_specs and category in ['الخرسانة', 'حديد التسليح', 'العزل المائي', 'المواد الكهربائية']:
if category == 'الخرسانة':
category_specs = {
'قوة الضغط': '30 نيوتن/مم²',
'نسبة الماء للأسمنت': '0.45 كحد أقصى',
'المعالجة': 'لا تقل عن 7 أيام',
'المواصفات المرجعية': 'ASTM C150'
}
elif category == 'حديد التسليح':
category_specs = {
'نوع الحديد': 'عالي المقاومة للشد (Grade 60)',
'إجهاد الخضوع': '420 نيوتن/مم²',
'المواصفات المرجعية': 'ASTM A615'
}
elif category == 'العزل المائي':
category_specs = {
'النوع': 'أغشية بيتومينية مدعمة بالبوليستر',
'السماكة': '4 مم',
'مقاومة درجة الحرارة': 'حتى 100 درجة مئوية',
'المواصفات المرجعية': 'ASTM D6164'
}
elif category == 'المواد الكهربائية':
category_specs = {
'الكابلات': 'نحاس معزول XLPE',
'المواصفات المرجعية': 'IEC 60502'
}
return category_specs
def _extract_special_requirements(self, sentences):
"""استخراج المتطلبات الخاصة من الجمل"""
special_requirements = []
# الكلمات المفتاحية التي تشير إلى متطلبات خاصة
special_keywords = [
'يجب', 'ضرورة', 'يلزم', 'اشتراط', 'متطلب', 'إلزامي',
'اعتماد', 'موافقة', 'تقديم', 'تأكيد', 'ضمان', 'توافق'
]
# استخراج الجمل التي تحتوي على الكلمات المفتاحية
for s in sentences:
if any(keyword in s.lower() for keyword in special_keywords):
# تنظيف الجملة
req = s.strip()
# التأكد من أن الجملة تبدأ بيجب أو إذا لم تكن كذلك أضف "يجب" في البداية
if not any(req.startswith(start) for start in ['يجب', 'ضرورة', 'يلزم']):
req = f"يجب {req}"
# التأكد من أن الجملة تنتهي بنقطة
if not req.endswith('.'):
req = f"{req}."
# إضافة المتطلب إلى القائمة إذا لم يكن موجوداً بالفعل
if req not in special_requirements:
special_requirements.append(req)
# إضافة متطلبات افتراضية إذا لم يتم العثور على متطلبات
if not special_requirements:
special_requirements = [
"يجب أن تكون جميع المواد معتمدة من المهندس المشرف قبل التوريد.",
"يجب تقديم عينات لجميع المواد المستخدمة للاعتماد.",
"يجب تقديم شهادات ضمان لمدة سنة لجميع الأعمال المنفذة.",
"يجب الالتزام بكود البناء السعودي في جميع الأعمال.",
"يجب توفير اختبارات ضبط الجودة لأعمال الخرسانة.",
"يجب الالتزام بنسبة المحتوى المحلي لا تقل عن 70%."
]
return special_requirements
def _extract_local_content(self, sentences):
"""استخراج متطلبات المحتوى المحلي من الجمل"""
local_content_df = pd.DataFrame()
# الكلمات المفتاحية للمحتوى المحلي
lc_keywords = ['محتوى محلي', 'منتج وطني', 'صناعة محلية', 'توطين']
# استخراج الجمل التي تحتوي على كلمات مفتاحية للمحتوى المحلي
lc_sentences = [s for s in sentences if any(k in s.lower() for k in lc_keywords)]
# إذا وجدت جمل متعلقة بالمحتوى المحلي
if lc_sentences:
lc_data = []
# البحث عن نسب محددة في الجمل
for s in lc_sentences:
# البحث عن نسب مئوية
percentages = re.findall(r'(\d+)(?:\.\d+)?%', s)
if percentages:
# محاولة استخراج الفئة من الجملة
if 'عمال' in s.lower() or 'قوى' in s.lower() or 'موظف' in s.lower():
lc_data.append({
'الفئة': 'القوى العاملة',
'النسبة المطلوبة': f"{percentages[0]}%",
'الملاحظات': 'تشمل العمالة والمهندسين والإداريين'
})
elif 'منتج' in s.lower() or 'صناع' in s.lower() or 'مواد' in s.lower() or 'معدات' in s.lower():
lc_data.append({
'الفئة': 'المنتجات',
'النسبة المطلوبة': f"{percentages[0]}%",
'الملاحظات': 'تشمل المواد والمعدات المصنعة محلياً'
})
elif 'خدم' in s.lower() or 'نقل' in s.lower() or 'تأمين' in s.lower():
lc_data.append({
'الفئة': 'الخدمات',
'النسبة المطلوبة': f"{percentages[0]}%",
'الملاحظات': 'تشمل خدمات النقل والتأمين والاستشارات'
})
else:
# إذا لم يتم تحديد الفئة، اعتبرها إجمالي
lc_data.append({
'الفئة': 'إجمالي المشروع',
'النسبة المطلوبة': f"{percentages[0]}%",
'الملاحظات': 'نسبة المحتوى المحلي الإجمالية للمشروع'
})
# تحويل البيانات إلى DataFrame
if lc_data:
local_content_df = pd.DataFrame(lc_data)
# إذا لم يتم العثور على متطلبات محتوى محلي، استخدم بيانات افتراضية
if local_content_df.empty:
local_content_df = pd.DataFrame({
'الفئة': ['القوى العاملة', 'المنتجات', 'الخدمات'],
'النسبة المطلوبة': ['80%', '70%', '60%'],
'الملاحظات': [
'تشمل العمالة والمهندسين والإداريين',
'تشمل المواد والمعدات المصنعة محلياً',
'تشمل خدمات النقل والتأمين والاستشارات'
]
})
return local_content_df |