Spaces:
Paused
Paused
| """ | |
| خدمة تحليل المواصفات من المستندات | |
| """ | |
| 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 |