EGYADMIN commited on
Commit
78ccd47
·
verified ·
1 Parent(s): 5011401

Upload 5 files

Browse files
modules/ai_assistant/ai_app.py CHANGED
The diff for this file is too large to render. See raw diff
 
modules/ai_assistant/contract_analyzer.py CHANGED
The diff for this file is too large to render. See raw diff
 
modules/ai_assistant/document_analyzer.py CHANGED
@@ -40,468 +40,468 @@ except ImportError:
40
  logger.warning("لم يتم العثور على مكتبة pdf2image. لن يمكن تحويل ملفات PDF إلى صور.")
41
 
42
 
43
- class TextExtractor:
44
- """فئة استخراج النصوص من المستندات"""
45
-
46
- def __init__(self, config=None):
47
- """تهيئة مستخرج النصوص"""
48
- self.config = config or {}
49
-
50
- def extract_from_pdf(self, file_path):
51
- """استخراج النص من ملف PDF"""
52
- try:
53
- # محاكاة استخراج النص من PDF
54
- # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل PyPDF2 أو pdfplumber
55
- return f"تم استخراج النص من ملف PDF: {file_path}"
56
- except Exception as e:
57
- logger.error(f"خطأ في استخراج النص من PDF: {str(e)}")
58
- return f"حدث خطأ أثناء استخراج النص: {str(e)}"
59
-
60
- def extract_from_docx(self, file_path):
61
- """استخراج النص من ملف DOCX"""
62
- try:
63
- # محاكاة استخراج النص من DOCX
64
- # في التطبيق الحقيقي، يمكن استخدام مكتبة python-docx
65
- return f"تم استخراج النص من ملف DOCX: {file_path}"
66
- except Exception as e:
67
- logger.error(f"خطأ في استخراج النص من DOCX: {str(e)}")
68
- return f"حدث خطأ أثناء استخراج النص: {str(e)}"
69
-
70
- def extract_from_image(self, file_path):
71
- """استخراج النص من صورة باستخدام OCR"""
72
- try:
73
- # محاكاة استخراج النص من صورة
74
- # في التطبيق الحقيقي، يمكن استخدام مكتبة pytesseract
75
- return f"تم استخراج النص من صورة: {file_path}"
76
- except Exception as e:
77
- logger.error(f"خطأ في استخراج النص من صورة: {str(e)}")
78
- return f"حدث خطأ أثناء استخراج النص: {str(e)}"
79
-
80
- def extract(self, file_path):
81
- """استخراج النص من ملف بناءً على نوعه"""
82
- _, ext = os.path.splitext(file_path)
83
- ext = ext.lower()
84
-
85
- if ext == '.pdf':
86
- return self.extract_from_pdf(file_path)
87
- elif ext in ('.doc', '.docx'):
88
- return self.extract_from_docx(file_path)
89
- elif ext in ('.jpg', '.jpeg', '.png'):
90
- return self.extract_from_image(file_path)
91
- else:
92
- return "نوع ملف غير مدعوم"
93
-
94
-
95
- class ItemExtractor:
96
- """فئة استخراج العناصر من المستندات"""
97
-
98
- def __init__(self, config=None):
99
- """تهيئة مستخرج العناصر"""
100
- self.config = config or {}
101
-
102
- def extract_tables(self, document):
103
- """استخراج الجداول من المستند"""
104
- try:
105
- # محاكاة استخراج الجداول
106
- # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل camelot-py أو tabula-py
107
- return [
108
- {
109
- "عنوان": "جدول البنود والكميات",
110
- "بيانات": [
111
- {"البند": "أعمال الحفر", "الكمية": 1000, "الوحدة": "م³", "السعر": 50, "الإجمالي": 50000},
112
- {"البند": "أعمال الخرسانة", "الكمية": 500, "الوحدة": "م³", "السعر": 300, "الإجمالي": 150000},
113
- {"البند": "أعمال التشطيبات", "الكمية": 2000, "الوحدة": "م²", "السعر": 100, "الإجمالي": 200000}
114
- ]
115
- },
116
- {
117
- "عنوان": "جدول الجدول الزمني",
118
- "بيانات": [
119
- {"المرحلة": "التصميم", "المدة": "30 يوم", "تاريخ البدء": "2025-04-01", "تاريخ الانتهاء": "2025-04-30"},
120
- {"المرحلة": "الإنشاء", "المدة": "180 يوم", "تاريخ البدء": "2025-05-01", "تاريخ الانتهاء": "2025-10-31"},
121
- {"المرحلة": "التسليم", "المدة": "30 يوم", "تاريخ البدء": "2025-11-01", "تاريخ الانتهاء": "2025-11-30"}
122
- ]
123
- }
124
- ]
125
- except Exception as e:
126
- logger.error(f"خطأ في استخراج الجداول: {str(e)}")
127
- return []
128
-
129
- def extract_items(self, file_path):
130
- """استخراج البنود من المستند"""
131
- try:
132
- # محاكاة استخراج البنود
133
- return [
134
- {"بند": "أعمال الحفر والردم", "قيمة": 250000, "نسبة": "10%"},
135
- {"بند": "أعمال الخرسانة المسلحة", "قيمة": 750000, "نسبة": "30%"},
136
- {"بند": "أعمال التشطيبات", "قيمة": 500000, "نسبة": "20%"},
137
- {"بند": "أعمال الكهرباء", "قيمة": 350000, "نسبة": "14%"},
138
- {"بند": "أعمال السباكة", "قيمة": 300000, "نسبة": "12%"},
139
- {"بند": "أعمال التكييف", "قيمة": 350000, "نسبة": "14%"}
140
- ]
141
- except Exception as e:
142
- logger.error(f"خطأ في استخراج البنود: {str(e)}")
143
- return []
144
-
145
- def extract(self, file_path):
146
- """استخراج جميع العناصر من المستند"""
147
- return {
148
- "بنود": self.extract_items(file_path),
149
- "جداول": self.extract_tables(file_path)
150
- }
151
-
152
-
153
- class DocumentParser:
154
- """فئة تحليل المستندات"""
155
-
156
- def __init__(self, config=None):
157
- """تهيئة محلل المستندات"""
158
- self.config = config or {}
159
- self.text_extractor = TextExtractor(config)
160
- self.item_extractor = ItemExtractor(config)
161
-
162
- def parse_contract(self, file_path):
163
- """تحليل مستند عقد"""
164
- try:
165
- # محاكاة تحليل عقد
166
- return {
167
- "نوع المستند": "عقد",
168
- "معلومات العقد": {
169
- "رقم العقد": "CT-2025-001",
170
- "تاريخ العقد": "2025-03-15",
171
- "قيمة العقد": "2,500,000 ريال",
172
- "مدة العقد": "12 شهر",
173
- "تاريخ البدء": "2025-04-01",
174
- "تاريخ الانتهاء": "2026-03-31"
175
- },
176
- "أطراف العقد": {
177
- "الطرف الأول": "وزارة الإسكان",
178
- "الطرف الثاني": "شركة الإنشاءات المتطورة"
179
- },
180
- "بنود العقد": [
181
- "يلتزم الطرف الثاني بتنفيذ المشروع وفقاً للمواصفات والشروط المرفقة",
182
- "مدة تنفيذ المشروع 12 شهراً من تاريخ استلام الموقع",
183
- "قيمة العقد 2,500,000 ريال شاملة جميع الضرائب والرسوم",
184
- "يتم الدفع على دفعات شهرية حسب نسبة الإنجاز",
185
- "غرامة التأخير 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%"
186
- ],
187
- "المرفقات": [
188
- "جدول الكميات",
189
- "المواصفات الفنية",
190
- "الجدول الزمني",
191
- "الضمانات والتأمينات"
192
- ],
193
- "درجة الثقة": "95%"
194
- }
195
- except Exception as e:
196
- logger.error(f"خطأ في تحليل العقد: {str(e)}")
197
- return {"error": f"حدث خطأ أثناء تحليل العقد: {str(e)}"}
198
-
199
- def parse_tender(self, file_path):
200
- """تحليل مستند مناقصة"""
201
- try:
202
- # محاكاة تحليل مناقصة
203
- return {
204
- "نوع المستند": "مناقصة",
205
- "معلومات المناقصة": {
206
- "رقم المناقصة": "T-2025-002",
207
- "اسم المشروع": "إنشاء مبنى إداري",
208
- "الجهة المالكة": "وزارة المالية",
209
- "تاريخ الطرح": "2025-03-01",
210
- "تاريخ الإقفال": "2025-04-15",
211
- "القيمة التقديرية": "3,000,000 ريال"
212
- },
213
- "شروط المناقصة": [
214
- "تصنيف المقاول: الدرجة الأولى في مجال المباني",
215
- "خبرة سابقة: 5 مشاريع مماثلة خلال الـ 10 سنوات الماضية",
216
- "الضمان الابتدائي: 2% من قيمة العطاء",
217
- "الضمان النهائي: 5% من قيمة العقد",
218
- "مدة تنفيذ المشروع: 18 شهراً"
219
- ],
220
- "المستندات المطلوبة": [
221
- "شهادة التصنيف",
222
- "السجل التجاري",
223
- "شهادة الزكاة والدخل",
224
- "شهادة التأمينات الاجتماعية",
225
- "قائمة المشاريع المماثلة"
226
- ],
227
- "معايير التقييم": [
228
- {"المعيار": "السعر", "الوزن": "50%"},
229
- {"المع��ار": "الخبرة الفنية", "الوزن": "25%"},
230
- {"المعيار": "الجدول الزمني", "الوزن": "15%"},
231
- {"المعيار": "فريق العمل", "الوزن": "10%"}
232
- ],
233
- "درجة الثقة": "92%"
234
- }
235
- except Exception as e:
236
- logger.error(f"خطأ في تحليل المناقصة: {str(e)}")
237
- return {"error": f"حدث خطأ أثناء تحليل المناقصة: {str(e)}"}
238
-
239
- def parse_specifications(self, file_path):
240
- """تحليل كراسة الشروط والمواصفات"""
241
- try:
242
- # محاكاة تحليل كراسة الشروط والمواصفات
243
- return {
244
- "نوع المستند": "كراسة شروط ومواصفات",
245
- "معلومات المشروع": {
246
- "اسم المشروع": "إنشاء مبنى إداري",
247
- "الموقع": "الرياض - حي العليا",
248
- "المساحة": "5000 متر مربع",
249
- "عدد الطوابق": "5 طوابق"
250
- },
251
- "المواصفات الفنية": {
252
- "الهيكل الإنشائي": "خرسانة مسلحة",
253
- "الواجهات": "زجاج عاكس وحجر طبيعي",
254
- "التشطيبات الداخلية": "رخام للأرضيات، جبس للأسقف، دهانات عالية الجودة للجدران",
255
- "أنظمة الكهرباء": "نظام إنارة LED موفر للطاقة، نظام تحكم ذكي",
256
- "أنظمة التكييف": "نظام تكييف مركزي مع تحكم منفصل لكل منطقة",
257
- "أنظمة السلامة": "نظام إنذار وإطفاء حريق آلي، كاميرات مراقبة، نظام تحكم في الدخول"
258
- },
259
- "الشروط العامة": [
260
- "الالتزام بكود البناء السعودي",
261
- "الالتزام بمتطلبات الدفاع المدني",
262
- "الالتزام بمتطلبات الاستدامة وكفاءة الطاقة",
263
- "تقديم مخططات تنفيذية معتمدة قبل البدء في التنفيذ",
264
- "تقديم عينات للمواد للاعتماد قبل التوريد"
265
- ],
266
- "المرفقات": [
267
- "المخططات المعمارية",
268
- "المخططات الإنشائية",
269
- "مخططات الكهرباء",
270
- "مخططات التكييف",
271
- "مخططات السباكة",
272
- "جدول الكميات"
273
- ],
274
- "درجة الثقة": "90%"
275
- }
276
- except Exception as e:
277
- logger.error(f"خطأ في تحليل كراسة الشروط والمواصفات: {str(e)}")
278
- return {"error": f"حدث خطأ أثناء تحليل كراسة الشروط والمواصفات: {str(e)}"}
279
-
280
- def parse_dwg(self, file_path):
281
- """تحليل ملف DWG"""
282
- try:
283
- # محاكاة تحليل ملف DWG
284
- return {
285
- "نوع المستند": "ملف DWG",
286
- "معلومات الملف": {
287
- "اسم الملف": os.path.basename(file_path),
288
- "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
289
- "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
290
- },
291
- "محتويات الملف": {
292
- "عدد الطبقات": 15,
293
- "عدد الكائنات": 1250,
294
- "أبعاد الرسم": "50م × 30م"
295
- },
296
- "تحليل المساحات": {
297
- "المساحة الإجمالية": "4,500 م²",
298
- "مساحة البناء": "3,200 م²",
299
- "مساحة الخدمات": "800 م²",
300
- "مساحة الممرات": "500 م²"
301
- },
302
- "تحليل العناصر": {
303
- "عدد الغرف": 25,
304
- "عدد الأبواب": 40,
305
- "عدد النوافذ": 30,
306
- "عدد الأعمدة": 20
307
- },
308
- "ملاحظات": [
309
- "تصميم يتوافق مع متطلبات كود البناء السعودي",
310
- "توزيع جيد للمساحات",
311
- "تصميم يراعي متطلبات ذوي الاحتياجات الخاصة",
312
- "تصميم يراعي متطلبات السلامة والإخلاء"
313
- ],
314
- "درجة الثقة": "85%"
315
- }
316
- except Exception as e:
317
- logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
318
- return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
319
-
320
- def parse(self, file_path):
321
- """تحليل المستند بناءً على نوعه"""
322
- try:
323
- _, ext = os.path.splitext(file_path)
324
- ext = ext.lower()
325
-
326
- # تحديد نوع المستند بناءً على محتواه (محاكاة)
327
- file_name = os.path.basename(file_path).lower()
328
-
329
- if ext == '.dwg':
330
- return self.parse_dwg(file_path)
331
- elif 'contract' in file_name or 'عقد' in file_name:
332
- return self.parse_contract(file_path)
333
- elif 'tender' in file_name or 'مناقصة' in file_name:
334
- return self.parse_tender(file_path)
335
- elif 'spec' in file_name or 'شروط' in file_name or 'مواصفات' in file_name:
336
- return self.parse_specifications(file_path)
337
- else:
338
- # تحليل عام للمستند
339
- return {
340
- "نوع المستند": "مستند عام",
341
- "معلومات الملف": {
342
- "اسم الملف": os.path.basename(file_path),
343
- "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
344
- "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
345
- },
346
- "محتوى المستند": {
347
- "نص": self.text_extractor.extract(file_path),
348
- "عناصر": self.item_extractor.extract(file_path)
349
- },
350
- "درجة الثقة": "75%"
351
- }
352
- except Exception as e:
353
- logger.error(f"خطأ في تحليل المستند: {str(e)}")
354
- return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
355
-
356
-
357
- class AIDocumentAnalyzer:
358
- """فئة تحليل المستندات باستخدام الذكاء الاصطناعي"""
359
-
360
- def __init__(self):
361
- """تهيئة محلل المستندات الذكي"""
362
- self.document_parser = DocumentParser()
363
- self.api_keys = {}
364
-
365
- def set_api_key(self, provider, key):
366
- """تعيين مفتاح API لمزود خدمة الذكاء الاصطناعي"""
367
- self.api_keys[provider] = key
368
-
369
- def get_api_key(self, provider):
370
- """الحصول على مفتاح API لمزود خدمة الذكاء الاصطناعي"""
371
- return self.api_keys.get(provider)
372
-
373
- def analyze_document(self, file_path, provider="local"):
374
- """تحليل المستند باستخدام الذكاء الاصطناعي"""
375
- try:
376
- # تحليل محلي للمستند
377
- local_analysis = self.document_parser.parse(file_path)
378
-
379
- if provider == "local":
380
- return local_analysis
381
-
382
- # تحليل باستخدام خدمات الذكاء الاصطناعي السحابية
383
- if provider == "openai":
384
- # محاكاة تحليل باستخدام OpenAI
385
- enhanced_analysis = self._enhance_with_openai(local_analysis)
386
- return enhanced_analysis
387
- elif provider == "claude":
388
- # محاكاة تحليل باستخدام Claude
389
- enhanced_analysis = self._enhance_with_claude(local_analysis)
390
- return enhanced_analysis
391
- else:
392
- return local_analysis
393
- except Exception as e:
394
- logger.error(f"خطأ في تحليل المستند: {str(e)}")
395
- return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
396
-
397
- def _enhance_with_openai(self, analysis):
398
- """تحسين التحليل باستخدام OpenAI"""
399
- # محاكاة تحسين التحليل باستخدام OpenAI
400
- analysis["مصدر التحليل"] = "OpenAI"
401
- analysis["درجة الثقة"] = "98%"
402
-
403
- # إضافة تحليل المخاطر
404
- if "تحليل المخاطر" not in analysis:
405
- analysis["تحليل المخاطر"] = [
406
- {"المخاطرة": "تأخر التوريدات", "الاحتمالية": "متوسطة", "التأثير": "عالي", "استراتيجية التخفيف": "وضع خطة توريدات بديلة"},
407
- {"المخاطرة": "زيادة أسعار المواد", "الاحتمالية": "عالية", "التأثير": "عالي", "استراتيجية التخفيف": "تثبيت أسعار المواد الرئيسية مع الموردين"},
408
- {"المخاطرة": "نقص العمالة الماهرة", "الاحتمالية": "متوسطة", "التأثير": "متوسط", "استراتيجية التخفيف": "التعاقد المسبق مع مقاولي الباطن"},
409
- {"المخاطرة": "تغيير نطاق العمل", "الاحتمالية": "منخفضة", "التأثير": "عالي", "استراتيجية التخفيف": "توثيق نطاق العمل بدقة وإدارة التغيير"}
410
- ]
411
-
412
- # إضافة توصيات
413
- if "التوصيات" not in analysis:
414
- analysis["التوصيات"] = [
415
- "مراجعة بنود العقد بدقة قبل التوقيع",
416
- "التأكد من وضوح نطاق العمل وعدم وجود غموض",
417
- "التحقق من توافق المواصفات الفنية مع المعايير المحلية",
418
- "وضع خطة إدارة مخاطر شاملة للمشروع",
419
- "تخصيص احتياطي للطوارئ بنسبة 10-15% من قيمة المشروع"
420
- ]
421
-
422
- return analysis
423
-
424
- def _enhance_with_claude(self, analysis):
425
- """تحسين التحليل باستخدام Claude"""
426
- # محاكاة تحسين التحليل باستخدام Claude
427
- analysis["مصدر التحليل"] = "Claude"
428
- analysis["درجة الثقة"] = "97%"
429
-
430
- # إضافة تحليل الفرص
431
- if "تحليل الفرص" not in analysis:
432
- analysis["تحليل الفرص"] = [
433
- {"الفرصة": "تحسين التصميم", "الفائدة": "تقليل التكلفة بنسبة 5-10%", "المتطلبات": "مراجعة هندسية شاملة"},
434
- {"الفرصة": "استخدام مواد بديلة", "الفائدة": "تقليل وقت التنفيذ", "المتطلبات": "اعتماد المواصفات الجديدة"},
435
- {"الفرصة": "زيادة المحتوى المحلي", "الفائدة": "تحسين التصنيف في برنامج القيمة المضافة", "المتطلبات": "تحديد الموردين المحليين"},
436
- {"الفرصة": "تطبيق تقنيات البناء الحديثة", "الفائدة": "تحسين الجودة وتقليل الهدر", "المتطلبات": "تدريب فريق العمل"}
437
- ]
438
-
439
- # إضافة ملخص تنفيذي
440
- if "الملخص التنفيذي" not in analysis:
441
- analysis["الملخص التنفيذي"] = """
442
- يتضمن هذا المستند تفاصيل مشروع إنشاء مبنى إداري بمساحة إجمالية 5000 متر مربع.
443
- المشروع يتكون من 5 طوابق ويتضمن مواصفات فنية عالية الجودة.
444
- تقدر تكلفة المشروع بحوالي 3 مليون ريال ومدة التنفيذ 18 شهراً.
445
- يتميز المشروع بتصميم يراعي متطلبات الاستدامة وكفاءة الطاقة.
446
- تم تحديد عدة مخاطر محتملة للمشروع مع استراتيجيات التخفيف المناسبة.
447
- كما تم تحديد عدة فرص لتحسين المشروع من حيث التكلفة والجودة ووقت التنفيذ.
448
- """
449
-
450
- return analysis
451
-
452
- def analyze_dwg(self, file_path, provider="local"):
453
- """تحليل ملف DWG باستخدام الذكاء الاصطناعي"""
454
- try:
455
- # تحليل محلي لملف DWG
456
- local_analysis = self.document_parser.parse_dwg(file_path)
457
-
458
- if provider == "local":
459
- return local_analysis
460
-
461
- # تحسين التحليل باستخدام خدمات الذكاء الاصطناعي
462
- if provider == "openai" or provider == "claude":
463
- # إضافة تحليل متقدم
464
- local_analysis["تحليل متقدم"] = {
465
- "تقييم التصميم": "جيد جداً",
466
- "كفاءة استخدام المساحة": "90%",
467
- "توافق مع المعايير": "متوافق مع كود البناء السعودي",
468
- "اقتراحات التحسين": [
469
- "تحسين توزيع المساحات لزيادة كفاءة استخدام المساحة",
470
- "تحسين تصميم الممرات لتسهيل الحركة",
471
- "إضافة عناصر تصميم مستدامة لتقليل استهلاك الطاقة",
472
- "تحسين تصميم الواجهات لزيادة الإضاءة الطبيعية"
473
- ]
474
- }
475
-
476
- # إضافة تقدير التكلفة
477
- local_analysis["تقدير التكلفة"] = {
478
- "التكلفة الإجمالية التقديرية": "3,200,000 ريال",
479
- "تكلفة المتر المرب��": "800 ريال",
480
- "توزيع التكلفة": [
481
- {"البند": "الهيكل الإنشائي", "النسبة": "35%", "القيمة": "1,120,000 ريال"},
482
- {"البند": "التشطيبات", "النسبة": "25%", "القيمة": "800,000 ريال"},
483
- {"البند": "الأنظمة الكهربائية", "النسبة": "15%", "القيمة": "480,000 ريال"},
484
- {"البند": "الأنظمة الميكانيكية", "النسبة": "15%", "القيمة": "480,000 ريال"},
485
- {"البند": "الأعمال الخارجية", "النسبة": "10%", "القيمة": "320,000 ريال"}
486
- ]
487
- }
488
-
489
- # إضافة الجدول الزمني
490
- local_analysis["الجدول الزمني التقديري"] = {
491
- "المدة الإجمالية": "18 شهر",
492
- "المراحل": [
493
- {"المرحلة": "أعمال الحفر والأساسات", "المدة": "3 أشهر", "النسبة": "15%"},
494
- {"المرحلة": "الهيكل الإنشائي", "المدة": "6 أشهر", "النسبة": "35%"},
495
- {"المرحلة": "التشطيبات الداخلية", "المدة": "5 أشهر", "النسبة": "25%"},
496
- {"المرحلة": "الأنظمة الكهربائية والميكانيكية", "المدة": "3 أشهر", "النسبة": "15%"},
497
- {"المرحلة": "الأعمال الخارجية والتسليم", "المدة": "1 شهر", "النسبة": "10%"}
498
- ]
499
- }
500
-
501
- local_analysis["مصدر التحليل"] = provider.capitalize()
502
- local_analysis["درجة الثقة"] = "95%"
503
-
504
- return local_analysis
505
- except Exception as e:
506
- logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
507
  return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
 
40
  logger.warning("لم يتم العثور على مكتبة pdf2image. لن يمكن تحويل ملفات PDF إلى صور.")
41
 
42
 
43
+ class TextExtractor:
44
+ """فئة استخراج النصوص من المستندات"""
45
+
46
+ def __init__(self, config=None):
47
+ """تهيئة مستخرج النصوص"""
48
+ self.config = config or {}
49
+
50
+ def extract_from_pdf(self, file_path):
51
+ """استخراج النص من ملف PDF"""
52
+ try:
53
+ # محاكاة استخراج النص من PDF
54
+ # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل PyPDF2 أو pdfplumber
55
+ return f"تم استخراج النص من ملف PDF: {file_path}"
56
+ except Exception as e:
57
+ logger.error(f"خطأ في استخراج النص من PDF: {str(e)}")
58
+ return f"حدث خطأ أثناء استخراج النص: {str(e)}"
59
+
60
+ def extract_from_docx(self, file_path):
61
+ """استخراج النص من ملف DOCX"""
62
+ try:
63
+ # محاكاة استخراج النص من DOCX
64
+ # في التطبيق الحقيقي، يمكن استخدام مكتبة python-docx
65
+ return f"تم استخراج النص من ملف DOCX: {file_path}"
66
+ except Exception as e:
67
+ logger.error(f"خطأ في استخراج النص من DOCX: {str(e)}")
68
+ return f"حدث خطأ أثناء استخراج النص: {str(e)}"
69
+
70
+ def extract_from_image(self, file_path):
71
+ """استخراج النص من صورة باستخدام OCR"""
72
+ try:
73
+ # محاكاة استخراج النص من صورة
74
+ # في التطبيق الحقيقي، يمكن استخدام مكتبة pytesseract
75
+ return f"تم استخراج النص من صورة: {file_path}"
76
+ except Exception as e:
77
+ logger.error(f"خطأ في استخراج النص من صورة: {str(e)}")
78
+ return f"حدث خطأ أثناء استخراج النص: {str(e)}"
79
+
80
+ def extract(self, file_path):
81
+ """استخراج النص من ملف بناءً على نوعه"""
82
+ _, ext = os.path.splitext(file_path)
83
+ ext = ext.lower()
84
+
85
+ if ext == '.pdf':
86
+ return self.extract_from_pdf(file_path)
87
+ elif ext in ('.doc', '.docx'):
88
+ return self.extract_from_docx(file_path)
89
+ elif ext in ('.jpg', '.jpeg', '.png'):
90
+ return self.extract_from_image(file_path)
91
+ else:
92
+ return "نوع ملف غير مدعوم"
93
+
94
+
95
+ class ItemExtractor:
96
+ """فئة استخراج العناصر من المستندات"""
97
+
98
+ def __init__(self, config=None):
99
+ """تهيئة مستخرج العناصر"""
100
+ self.config = config or {}
101
+
102
+ def extract_tables(self, document):
103
+ """استخراج الجداول من المستند"""
104
+ try:
105
+ # محاكاة استخراج ا��جداول
106
+ # في التطبيق الحقيقي، يمكن استخدام مكتبات مثل camelot-py أو tabula-py
107
+ return [
108
+ {
109
+ "عنوان": "جدول البنود والكميات",
110
+ "بيانات": [
111
+ {"البند": "أعمال الحفر", "الكمية": 1000, "الوحدة": "م³", "السعر": 50, "الإجمالي": 50000},
112
+ {"البند": "أعمال الخرسانة", "الكمية": 500, "الوحدة": "م³", "السعر": 300, "الإجمالي": 150000},
113
+ {"البند": "أعمال التشطيبات", "الكمية": 2000, "الوحدة": "م²", "السعر": 100, "الإجمالي": 200000}
114
+ ]
115
+ },
116
+ {
117
+ "عنوان": "جدول الجدول الزمني",
118
+ "بيانات": [
119
+ {"المرحلة": "التصميم", "المدة": "30 يوم", "تاريخ البدء": "2025-04-01", "تاريخ الانتهاء": "2025-04-30"},
120
+ {"المرحلة": "الإنشاء", "المدة": "180 يوم", "تاريخ البدء": "2025-05-01", "تاريخ الانتهاء": "2025-10-31"},
121
+ {"المرحلة": "التسليم", "المدة": "30 يوم", "تاريخ البدء": "2025-11-01", "تاريخ الانتهاء": "2025-11-30"}
122
+ ]
123
+ }
124
+ ]
125
+ except Exception as e:
126
+ logger.error(f"خطأ في استخراج الجداول: {str(e)}")
127
+ return []
128
+
129
+ def extract_items(self, file_path):
130
+ """استخراج البنود من المستند"""
131
+ try:
132
+ # محاكاة استخراج البنود
133
+ return [
134
+ {"بند": "أعمال الحفر والردم", "قيمة": 250000, "نسبة": "10%"},
135
+ {"بند": "أعمال الخرسانة المسلحة", "قيمة": 750000, "نسبة": "30%"},
136
+ {"بند": "أعمال التشطيبات", "قيمة": 500000, "نسبة": "20%"},
137
+ {"بند": "أعمال الكهرباء", "قيمة": 350000, "نسبة": "14%"},
138
+ {"بند": "أعمال السباكة", "قيمة": 300000, "نسبة": "12%"},
139
+ {"بند": "أعمال التكييف", "قيمة": 350000, "نسبة": "14%"}
140
+ ]
141
+ except Exception as e:
142
+ logger.error(f"خطأ في استخراج البنود: {str(e)}")
143
+ return []
144
+
145
+ def extract(self, file_path):
146
+ """استخراج جميع العناصر من المستند"""
147
+ return {
148
+ "بنود": self.extract_items(file_path),
149
+ "جداول": self.extract_tables(file_path)
150
+ }
151
+
152
+
153
+ class DocumentParser:
154
+ """فئة تحليل المستندات"""
155
+
156
+ def __init__(self, config=None):
157
+ """تهيئة محلل المستندات"""
158
+ self.config = config or {}
159
+ self.text_extractor = TextExtractor(config)
160
+ self.item_extractor = ItemExtractor(config)
161
+
162
+ def parse_contract(self, file_path):
163
+ """تحليل مستند عقد"""
164
+ try:
165
+ # محاكاة تحليل عقد
166
+ return {
167
+ "نوع المستند": "عقد",
168
+ "معلومات العقد": {
169
+ "رقم العقد": "CT-2025-001",
170
+ "تاريخ العقد": "2025-03-15",
171
+ "قيمة العقد": "2,500,000 ريال",
172
+ "مدة العقد": "12 شهر",
173
+ "تاريخ البدء": "2025-04-01",
174
+ "تاريخ الانتهاء": "2026-03-31"
175
+ },
176
+ "أطراف العقد": {
177
+ "الطرف الأول": "وزارة الإسكان",
178
+ "الطرف الثاني": "شركة الإنشاءات المتطورة"
179
+ },
180
+ "بنود العقد": [
181
+ "يلتزم الطرف الثاني بتنفيذ المشروع وفقاً للمواصفات والشروط المرفقة",
182
+ "مدة تنفيذ المشروع 12 شهراً من تاريخ استلام الموقع",
183
+ "قيمة العقد 2,500,000 ريال شاملة جميع الضرائب والرسوم",
184
+ "يتم الدفع على دفعات شهرية حسب نسبة الإنجاز",
185
+ "غرامة التأخير 1% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%"
186
+ ],
187
+ "المرفقات": [
188
+ "جدول الكميات",
189
+ "المواصفات الفنية",
190
+ "الجدول الزمني",
191
+ "الضمانات والتأمينات"
192
+ ],
193
+ "درجة الثقة": "95%"
194
+ }
195
+ except Exception as e:
196
+ logger.error(f"خطأ في تحليل العقد: {str(e)}")
197
+ return {"error": f"حدث خطأ أثناء تحليل العقد: {str(e)}"}
198
+
199
+ def parse_tender(self, file_path):
200
+ """تحليل مستند مناقصة"""
201
+ try:
202
+ # محاكاة تحليل مناقصة
203
+ return {
204
+ "نوع المستند": "مناقصة",
205
+ "معلومات المناقصة": {
206
+ "رقم المناقصة": "T-2025-002",
207
+ "اسم المشروع": "إنشاء مبنى إداري",
208
+ "الجهة المالكة": "وزارة المالية",
209
+ "تاريخ الطرح": "2025-03-01",
210
+ "تاريخ الإقفال": "2025-04-15",
211
+ "القيمة التقديرية": "3,000,000 ريال"
212
+ },
213
+ "شروط المناقصة": [
214
+ "تصنيف المقاول: الدرجة الأولى في مجال المباني",
215
+ "خبرة سابقة: 5 مشاريع مماثلة خلال الـ 10 سنوات الماضية",
216
+ "الضمان الابتدائي: 2% من قيمة العطاء",
217
+ "الضمان النهائي: 5% من قيمة العقد",
218
+ "مدة تنفيذ المشروع: 18 شهراً"
219
+ ],
220
+ "المستندات المطلوبة": [
221
+ "شهادة التصنيف",
222
+ "السجل التجاري",
223
+ "شهادة الزكاة والدخل",
224
+ "شهادة التأمينات الاجتماعية",
225
+ "قائمة المشاريع المماثلة"
226
+ ],
227
+ "معايير التقييم": [
228
+ {"المعيار": "السعر", "الوزن": "50%"},
229
+ {"المعيار": "الخبرة الفنية", "الوزن": "25%"},
230
+ {"المعيار": "الجدول الزمني", "الوزن": "15%"},
231
+ {"المعيار": "فريق العمل", "الوزن": "10%"}
232
+ ],
233
+ "درجة الثقة": "92%"
234
+ }
235
+ except Exception as e:
236
+ logger.error(f"خطأ في تحليل المناقصة: {str(e)}")
237
+ return {"error": f"حدث خطأ أثناء تحليل المناقصة: {str(e)}"}
238
+
239
+ def parse_specifications(self, file_path):
240
+ """تحليل كراسة الشروط والمواصفات"""
241
+ try:
242
+ # محاكاة تحليل كراسة الشروط والمواصفات
243
+ return {
244
+ "نوع المستند": "كراسة شروط ومواصفات",
245
+ "معلومات المشروع": {
246
+ "اسم المشروع": "إنشاء مبنى إداري",
247
+ "الموقع": "الرياض - حي العليا",
248
+ "المساحة": "5000 متر مربع",
249
+ "عدد الطوابق": "5 طوابق"
250
+ },
251
+ "المواصفات الفنية": {
252
+ "الهيكل الإنشائي": "خرسانة مسلحة",
253
+ "الواجهات": "زجاج عاكس وحجر طبيعي",
254
+ "التشطيبات الداخلية": "رخام للأرضيات، جبس للأسقف، دهانات عالية الجودة للجدران",
255
+ "أنظمة الكهرباء": "نظام إنارة LED موفر للطاقة، نظام تحكم ذكي",
256
+ "أنظمة التكييف": "نظام تكييف مركزي مع تحكم منفصل لكل منطقة",
257
+ "أنظمة السلامة": "نظام إنذار وإطفاء حريق آلي، كاميرات مراقبة، نظام تحكم في الدخول"
258
+ },
259
+ "الشروط العامة": [
260
+ "الالتزام بكود البناء السعودي",
261
+ "الالتزام بمتطلبات الدفاع المدني",
262
+ "الالتزام بمتطلبات الاستدامة وكفاءة الطاقة",
263
+ "تقديم مخططات تنفيذية معتمدة قبل البدء في التنفيذ",
264
+ "تقديم عينات للمواد للاعتماد قبل التوريد"
265
+ ],
266
+ "المرفقات": [
267
+ "المخططات المعمارية",
268
+ "المخططات الإنشائية",
269
+ "مخططات الكهرباء",
270
+ "مخططات التكييف",
271
+ "مخططات السباكة",
272
+ "جدول الكميات"
273
+ ],
274
+ "درجة الثقة": "90%"
275
+ }
276
+ except Exception as e:
277
+ logger.error(f"خطأ في تحليل كراسة الشروط والمواصفات: {str(e)}")
278
+ return {"error": f"حدث خطأ أثناء تحليل كراسة الشروط والمواصفات: {str(e)}"}
279
+
280
+ def parse_dwg(self, file_path):
281
+ """تحليل ملف DWG"""
282
+ try:
283
+ # محاكاة تحليل ملف DWG
284
+ return {
285
+ "نوع المستند": "ملف DWG",
286
+ "معلومات الملف": {
287
+ "اسم الملف": os.path.basename(file_path),
288
+ "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
289
+ "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
290
+ },
291
+ "محتويات الملف": {
292
+ "عدد الطبقات": 15,
293
+ "عدد الكائنات": 1250,
294
+ "أبعاد الرسم": "50م × 30م"
295
+ },
296
+ "تحليل المساحات": {
297
+ "المساحة الإجمالية": "4,500 م²",
298
+ "مساحة البناء": "3,200 م²",
299
+ "مساحة الخدمات": "800 م²",
300
+ "مساحة الممرات": "500 م²"
301
+ },
302
+ "تحليل العناصر": {
303
+ "عدد الغرف": 25,
304
+ "عدد الأبواب": 40,
305
+ "عدد النوافذ": 30,
306
+ "عدد الأعمدة": 20
307
+ },
308
+ "ملاحظات": [
309
+ "تصميم يتوافق مع متطلبات كود البناء السعودي",
310
+ "توزيع جيد للمساحات",
311
+ "تصميم يراعي متطلبات ذوي الاحتياجات الخاصة",
312
+ "تصميم يراعي متطلبات السلامة والإخلاء"
313
+ ],
314
+ "درجة الثقة": "85%"
315
+ }
316
+ except Exception as e:
317
+ logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
318
+ return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}
319
+
320
+ def parse(self, file_path):
321
+ """تحليل المستند بناءً على نوعه"""
322
+ try:
323
+ _, ext = os.path.splitext(file_path)
324
+ ext = ext.lower()
325
+
326
+ # تحديد نوع المستند بناءً على محتواه (محاكاة)
327
+ file_name = os.path.basename(file_path).lower()
328
+
329
+ if ext == '.dwg':
330
+ return self.parse_dwg(file_path)
331
+ elif 'contract' in file_name or 'عقد' in file_name:
332
+ return self.parse_contract(file_path)
333
+ elif 'tender' in file_name or 'مناقصة' in file_name:
334
+ return self.parse_tender(file_path)
335
+ elif 'spec' in file_name or 'شروط' in file_name or 'مواصفات' in file_name:
336
+ return self.parse_specifications(file_path)
337
+ else:
338
+ # تحليل عام للمستند
339
+ return {
340
+ "نوع المستند": "مستند عام",
341
+ "معلومات الملف": {
342
+ "اسم الملف": os.path.basename(file_path),
343
+ "حجم الملف": f"{os.path.getsize(file_path) / (1024*1024):.2f} ميجابايت",
344
+ "تاريخ التعديل": time.ctime(os.path.getmtime(file_path))
345
+ },
346
+ "محتوى المستند": {
347
+ "نص": self.text_extractor.extract(file_path),
348
+ "عناصر": self.item_extractor.extract(file_path)
349
+ },
350
+ "درجة الثقة": "75%"
351
+ }
352
+ except Exception as e:
353
+ logger.error(f"خطأ في تحليل المستند: {str(e)}")
354
+ return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
355
+
356
+
357
+ class AIDocumentAnalyzer:
358
+ """فئة تحليل المستندات باستخدام الذكاء الاصطناعي"""
359
+
360
+ def __init__(self):
361
+ """تهيئة محلل المستندات الذكي"""
362
+ self.document_parser = DocumentParser()
363
+ self.api_keys = {}
364
+
365
+ def set_api_key(self, provider, key):
366
+ """تعيين مفتاح API لمزود خدمة الذكاء الاصطناعي"""
367
+ self.api_keys[provider] = key
368
+
369
+ def get_api_key(self, provider):
370
+ """الحصول على مفتاح API لمزود خدمة الذكاء الاصطناعي"""
371
+ return self.api_keys.get(provider)
372
+
373
+ def analyze_document(self, file_path, provider="local"):
374
+ """تحليل المستند باستخدام الذكاء الاصطناعي"""
375
+ try:
376
+ # تحليل محلي للمستند
377
+ local_analysis = self.document_parser.parse(file_path)
378
+
379
+ if provider == "local":
380
+ return local_analysis
381
+
382
+ # تحليل باستخدام خدمات الذكاء الاصطناعي السحابية
383
+ if provider == "openai":
384
+ # محاكاة تحليل باستخدام OpenAI
385
+ enhanced_analysis = self._enhance_with_openai(local_analysis)
386
+ return enhanced_analysis
387
+ elif provider == "claude":
388
+ # محاكاة تحليل باستخدام Claude
389
+ enhanced_analysis = self._enhance_with_claude(local_analysis)
390
+ return enhanced_analysis
391
+ else:
392
+ return local_analysis
393
+ except Exception as e:
394
+ logger.error(f"خطأ في تحليل المستند: {str(e)}")
395
+ return {"error": f"حدث خطأ أثناء تحليل المستند: {str(e)}"}
396
+
397
+ def _enhance_with_openai(self, analysis):
398
+ """تحسين التحليل باستخدام OpenAI"""
399
+ # محاكاة تحسين التحليل باستخدام OpenAI
400
+ analysis["مصدر التحليل"] = "OpenAI"
401
+ analysis["درجة الثقة"] = "98%"
402
+
403
+ # إضافة تحليل المخاطر
404
+ if "تحليل المخاطر" not in analysis:
405
+ analysis["تحليل المخاطر"] = [
406
+ {"المخاطرة": "تأخر التوريدات", "الاحتمالية": "متوسطة", "التأثير": "عالي", "استراتيجية التخفيف": "وضع خطة توريدات بديلة"},
407
+ {"المخاطرة": "زيادة أسعار المواد", "الاحتمالية": "عالية", "التأثير": "عالي", "استراتيجية التخفيف": "تثبيت أسعار المواد الرئيسية مع الموردين"},
408
+ {"المخاطرة": "نقص العمالة الماهرة", "الاحتمالية": "متوسطة", "التأثير": "متوسط", "استراتيجية التخفيف": "التعاقد المسبق مع مقاولي الباطن"},
409
+ {"المخاطرة": "تغيير نطاق العمل", "الاحتمالية": "منخفضة", "التأثير": "عالي", "استراتيجية التخفيف": "توثيق نطاق العمل بدقة وإدارة التغيير"}
410
+ ]
411
+
412
+ # إضافة توصيات
413
+ if "التوصيات" not in analysis:
414
+ analysis["التوصيات"] = [
415
+ "مراجعة بنود العقد بدقة قبل التوقيع",
416
+ "التأكد من وضوح نطاق العمل وعدم وجود غموض",
417
+ "التحقق من توافق المواصفات الفنية مع المعايير المحلية",
418
+ "وضع خطة إدارة مخاطر شاملة للمشروع",
419
+ "تخصيص احتياطي للطوارئ بنسبة 10-15% من قيمة المشروع"
420
+ ]
421
+
422
+ return analysis
423
+
424
+ def _enhance_with_claude(self, analysis):
425
+ """تحسين التحليل باستخدام Claude"""
426
+ # محاكاة تحسين التحليل باستخدام Claude
427
+ analysis["مصدر التحليل"] = "Claude"
428
+ analysis["درجة الثقة"] = "97%"
429
+
430
+ # إضافة تحليل الفرص
431
+ if "تحليل الفرص" not in analysis:
432
+ analysis["تحليل الفرص"] = [
433
+ {"الفرصة": "تحسين التصميم", "الفائدة": "تقليل التكلفة بنسبة 5-10%", "المتطلبات": "مراجعة هندسية شاملة"},
434
+ {"الفرصة": "استخدام مواد بديلة", "الفائدة": "تقليل وقت التنفيذ", "المتطلبات": "اعتماد المواصفات الجديدة"},
435
+ {"الفرصة": "زيادة المحتوى المحلي", "الفائدة": "تحسين التصنيف في برنامج القيمة المضافة", "المتطلبات": "تحديد الموردين المحليين"},
436
+ {"الفرصة": "تطبيق تقنيات البناء الحديثة", "الفائدة": "تحسين الجودة وتقليل الهدر", "المتطلبات": "تدريب فريق العمل"}
437
+ ]
438
+
439
+ # إضافة ملخص تنفيذي
440
+ if "الملخص التنفيذي" not in analysis:
441
+ analysis["الملخص التنفيذي"] = """
442
+ يتضمن هذا المستند تفاصيل مشروع إنشاء مبنى إداري بمساحة إجمالية 5000 متر مربع.
443
+ المشروع يتكون من 5 طوابق ويتضمن مواصفات فنية عالية الجودة.
444
+ تقدر تكلفة المشروع بحوالي 3 مليون ريال ومدة التنفيذ 18 شهراً.
445
+ يتميز المشروع بتصميم يراعي متطلبات الاستدامة وكفاءة الطاقة.
446
+ تم تحديد عدة مخاطر محتملة للمشروع مع استراتيجيات التخفيف المناسبة.
447
+ كما تم تحديد عدة فرص لتحسين المشروع من حيث التكلفة والجودة ووقت التنفيذ.
448
+ """
449
+
450
+ return analysis
451
+
452
+ def analyze_dwg(self, file_path, provider="local"):
453
+ """تحليل ملف DWG باستخدام الذكاء الاصطناعي"""
454
+ try:
455
+ # تحليل محلي لملف DWG
456
+ local_analysis = self.document_parser.parse_dwg(file_path)
457
+
458
+ if provider == "local":
459
+ return local_analysis
460
+
461
+ # تحسين التحليل باستخدام خدمات الذكاء الاصطناعي
462
+ if provider == "openai" or provider == "claude":
463
+ # إضافة تحليل متقدم
464
+ local_analysis["تحليل متقدم"] = {
465
+ "تقييم التصميم": "جيد جداً",
466
+ "كفاءة استخدام المساحة": "90%",
467
+ "توافق مع المعايير": "متوافق مع كود البناء السعودي",
468
+ "اقتراحات التحسين": [
469
+ "تحسين توزيع المساحات لزيادة كفاءة استخدام المساحة",
470
+ "تحسين تصميم الممرات لتسهيل الحركة",
471
+ "إضافة عناصر تصميم مستدامة لتقليل استهلاك الطاقة",
472
+ "تحسين تصميم الواجهات لزيادة الإضاءة الطبيعية"
473
+ ]
474
+ }
475
+
476
+ # إضافة تقدير التكلفة
477
+ local_analysis["تقدير التكلفة"] = {
478
+ "التكلفة الإجمالية التقديرية": "3,200,000 ريال",
479
+ "تكلفة المتر المربع": "800 ريال",
480
+ "توزيع التكلفة": [
481
+ {"البند": "الهيكل الإنشائي", "النسبة": "35%", "القيمة": "1,120,000 ريال"},
482
+ {"البند": "التشطيبات", "النسبة": "25%", "القيمة": "800,000 ريال"},
483
+ {"البند": "الأنظمة الكهربائية", "النسبة": "15%", "القيمة": "480,000 ريال"},
484
+ {"البند": "الأنظمة الميكانيكية", "النسبة": "15%", "القيمة": "480,000 ريال"},
485
+ {"البند": "الأعمال الخارجية", "النسبة": "10%", "القيمة": "320,000 ريال"}
486
+ ]
487
+ }
488
+
489
+ # إضافة الجدول الزمني
490
+ local_analysis["الجدول الزمني التقديري"] = {
491
+ "المدة الإجمالية": "18 شهر",
492
+ "المراحل": [
493
+ {"المرحلة": "أعمال الحفر والأساسات", "المدة": "3 أشهر", "النسبة": "15%"},
494
+ {"المرحلة": "الهيكل الإنشائي", "المدة": "6 أشهر", "النسبة": "35%"},
495
+ {"المرحلة": "التشطيبات الداخلية", "المدة": "5 أشهر", "النسبة": "25%"},
496
+ {"المرحلة": "الأنظمة الكهربائية والميكانيكية", "المدة": "3 أشهر", "النسبة": "15%"},
497
+ {"المرحلة": "الأعمال الخارجية والتسليم", "المدة": "1 شهر", "النسبة": "10%"}
498
+ ]
499
+ }
500
+
501
+ local_analysis["مصدر التحليل"] = provider.capitalize()
502
+ local_analysis["درجة الثقة"] = "95%"
503
+
504
+ return local_analysis
505
+ except Exception as e:
506
+ logger.error(f"خطأ في تحليل ملف DWG: {str(e)}")
507
  return {"error": f"حدث خطأ أثناء تحليل ملف DWG: {str(e)}"}