EGYADMIN commited on
Commit
ea61973
·
verified ·
1 Parent(s): d21d576

Update modules/document_analysis/analyzer.py

Browse files
Files changed (1) hide show
  1. modules/document_analysis/analyzer.py +92 -50
modules/document_analysis/analyzer.py CHANGED
@@ -9,6 +9,10 @@ import threading
9
  from pathlib import Path
10
  import datetime
11
  import json
 
 
 
 
12
 
13
  # تهيئة السجل
14
  logging.basicConfig(
@@ -19,33 +23,33 @@ logger = logging.getLogger('document_analysis')
19
 
20
  class DocumentAnalyzer:
21
  """فئة تحليل المستندات"""
22
-
23
  def __init__(self, config=None):
24
  """تهيئة محلل المستندات"""
25
  self.config = config
26
  self.analysis_in_progress = False
27
  self.current_document = None
28
  self.analysis_results = {}
29
-
30
  # إنشاء مجلد المستندات إذا لم يكن موجوداً
31
  if config and hasattr(config, 'DOCUMENTS_PATH'):
32
  self.documents_path = Path(config.DOCUMENTS_PATH)
33
  else:
34
  self.documents_path = Path('data/documents')
35
-
36
  if not self.documents_path.exists():
37
  self.documents_path.mkdir(parents=True, exist_ok=True)
38
-
39
  def analyze_document(self, document_path, document_type="tender", callback=None):
40
  """تحليل مستند"""
41
  if self.analysis_in_progress:
42
  logger.warning("هناك عملية تحليل جارية بالفعل")
43
  return False
44
-
45
  if not os.path.exists(document_path):
46
  logger.error(f"المستند غير موجود: {document_path}")
47
  return False
48
-
49
  self.analysis_in_progress = True
50
  self.current_document = document_path
51
  self.analysis_results = {
@@ -59,7 +63,7 @@ class DocumentAnalyzer:
59
  "amounts": [],
60
  "risks": []
61
  }
62
-
63
  # بدء التحليل في خيط منفصل
64
  thread = threading.Thread(
65
  target=self._analyze_document_thread,
@@ -67,15 +71,15 @@ class DocumentAnalyzer:
67
  )
68
  thread.daemon = True
69
  thread.start()
70
-
71
  return True
72
-
73
  def _analyze_document_thread(self, document_path, document_type, callback):
74
  """خيط تحليل المستند"""
75
  try:
76
  # تحديد نوع المستند
77
  file_extension = os.path.splitext(document_path)[1].lower()
78
-
79
  if file_extension == '.pdf':
80
  self.analysis_results = self._analyze_pdf(document_path, document_type)
81
  elif file_extension == '.docx':
@@ -88,32 +92,32 @@ class DocumentAnalyzer:
88
  logger.error(f"نوع المستند غير مدعوم: {file_extension}")
89
  self.analysis_results["status"] = "فشل التحليل"
90
  self.analysis_results["error"] = "نوع المستند غير مدعوم"
91
-
92
  # تحديث حالة التحليل
93
  if self.analysis_results["status"] != "فشل التحليل":
94
  self.analysis_results["status"] = "اكتمل التحليل"
95
  self.analysis_results["analysis_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
96
-
97
  logger.info(f"اكتمل تحليل المستند: {document_path}")
98
-
99
  except Exception as e:
100
  logger.error(f"خطأ في تحليل المستند: {str(e)}")
101
  self.analysis_results["status"] = "فشل التحليل"
102
  self.analysis_results["error"] = str(e)
103
-
104
  finally:
105
  self.analysis_in_progress = False
106
-
107
  # استدعاء دالة الاستجابة إذا تم توفيرها
108
  if callback and callable(callback):
109
  callback(self.analysis_results)
110
-
111
  def _analyze_pdf(self, document_path, document_type):
112
  """تحليل مستند PDF باستخدام الذكاء الاصطناعي"""
113
  try:
114
  # استخراج النص من PDF
115
  text = self._extract_text_from_pdf(document_path)
116
-
117
  # تحليل متقدم للمستند
118
  analysis = {
119
  "file_info": {
@@ -214,10 +218,10 @@ class DocumentAnalyzer:
214
  try:
215
  # محاكاة تحليل مستند Word
216
  logger.info(f"تحليل مستند Word: {document_path}")
217
-
218
  # في التطبيق الفعلي، سيتم استخدام مكتبة مثل python-docx
219
  # لاستخراج النص من ملف Word وتحليله
220
-
221
  # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
222
  # (مشابه لتحليل PDF)
223
  self.analysis_results["items"] = [
@@ -225,56 +229,56 @@ class DocumentAnalyzer:
225
  {"id": 2, "name": "تركيب المعدات", "description": "تركيب وتشغيل المعدات", "unit": "مجموعة", "estimated_quantity": 10},
226
  {"id": 3, "name": "التدريب", "description": "تدريب الموظفين على استخدام المعدات", "unit": "يوم", "estimated_quantity": 20}
227
  ]
228
-
229
  # محاكاة استخراج الكيانات والتواريخ والمبالغ والمخاطر
230
  # (مشابه لتحليل PDF)
231
-
232
  except Exception as e:
233
  logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
234
  raise
235
-
236
  def _analyze_xlsx(self, document_path, document_type):
237
  """تحليل مستند Excel"""
238
  try:
239
  # محاكاة تحليل مستند Excel
240
  logger.info(f"تحليل مستند Excel: {document_path}")
241
-
242
  # في التطبيق الفعلي، سيتم استخدام مكتبة مثل pandas أو openpyxl
243
  # لاستخراج البيانات من ملف Excel وتحليلها
244
-
245
  # محاكاة استخراج البنود
246
  self.analysis_results["items"] = [
247
  {"id": 1, "name": "بند 1", "description": "وصف البند 1", "unit": "وحدة", "estimated_quantity": 100},
248
  {"id": 2, "name": "بند 2", "description": "وصف البند 2", "unit": "وحدة", "estimated_quantity": 200},
249
  {"id": 3, "name": "بند 3", "description": "وصف البند 3", "unit": "وحدة", "estimated_quantity": 300}
250
  ]
251
-
252
  # محاكاة استخراج المبالغ
253
  self.analysis_results["amounts"] = [
254
  {"type": "item_cost", "amount": 10000, "currency": "SAR", "description": "تكلفة البند 1"},
255
  {"type": "item_cost", "amount": 20000, "currency": "SAR", "description": "تكلفة البند 2"},
256
  {"type": "item_cost", "amount": 30000, "currency": "SAR", "description": "تكلفة البند 3"}
257
  ]
258
-
259
  except Exception as e:
260
  logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
261
  raise
262
-
263
  def _analyze_txt(self, document_path, document_type):
264
  """تحليل مستند نصي"""
265
  try:
266
  # محاكاة تحليل مستند نصي
267
  logger.info(f"تحليل مستند نصي: {document_path}")
268
-
269
  # في التطبيق الفعلي، سيتم قراءة الملف النصي وتحليله
270
-
271
  # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
272
- # (مشابه للتحليلات الأخرى)
273
-
274
  except Exception as e:
275
  logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
276
  raise
277
-
278
  def get_analysis_status(self):
279
  """الحصول على حالة التحليل الحالي"""
280
  if not self.analysis_in_progress:
@@ -282,53 +286,53 @@ class DocumentAnalyzer:
282
  return {"status": "لا يوجد تحليل جارٍ"}
283
  else:
284
  return {"status": self.analysis_results.get("status", "غير معروف")}
285
-
286
  return {
287
  "status": "جاري التحليل",
288
  "document_path": self.current_document,
289
  "start_time": self.analysis_results.get("analysis_start_time")
290
  }
291
-
292
  def get_analysis_results(self):
293
  """الحصول على نتائج التحليل"""
294
  return self.analysis_results
295
-
296
  def export_analysis_results(self, output_path=None):
297
  """تصدير نتائج التحليل إلى ملف JSON"""
298
  if not self.analysis_results:
299
  logger.warning("لا توجد نتائج تحليل للتصدير")
300
  return None
301
-
302
  if not output_path:
303
  # إنشاء اسم ملف افتراضي
304
  timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
305
  filename = f"analysis_results_{timestamp}.json"
306
  output_path = os.path.join(self.documents_path, filename)
307
-
308
  try:
309
  with open(output_path, 'w', encoding='utf-8') as f:
310
  json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
311
-
312
  logger.info(f"تم تصدير نتائج التحليل إلى: {output_path}")
313
  return output_path
314
-
315
  except Exception as e:
316
  logger.error(f"خطأ في تصدير نتائج التحليل: {str(e)}")
317
  return None
318
-
319
  def import_analysis_results(self, input_path):
320
  """استيراد نتائج التحليل من ملف JSON"""
321
  if not os.path.exists(input_path):
322
  logger.error(f"ملف نتائج التحليل غير موجود: {input_path}")
323
  return False
324
-
325
  try:
326
  with open(input_path, 'r', encoding='utf-8') as f:
327
  self.analysis_results = json.load(f)
328
-
329
  logger.info(f"تم استيراد نتائج التحليل من: {input_path}")
330
  return True
331
-
332
  except Exception as e:
333
  logger.error(f"خطأ في استيراد نتائج التحليل: {str(e)}")
334
  return False
@@ -377,11 +381,11 @@ class DocumentAnalyzer:
377
  # البحث عن القيم النقدية
378
  currency_pattern = r'[\d,]+\.?\d*\s*(?:ريال|دولار|SAR|USD)'
379
  currencies = re.findall(currency_pattern, text)
380
-
381
  # البحث عن النسب المئوية
382
  percentage_pattern = r'\d+\.?\d*\s*%'
383
  percentages = re.findall(percentage_pattern, text)
384
-
385
  return {
386
  "currencies": currencies,
387
  "percentages": percentages
@@ -398,7 +402,7 @@ class DocumentAnalyzer:
398
  avg_word_length = sum(len(word) for word in words) / len(words)
399
  sentences = text.split('.')
400
  avg_sentence_length = len(words) / len(sentences)
401
-
402
  # حساب درجة التعقيد (1-10)
403
  complexity = min((avg_word_length * 0.5 + avg_sentence_length * 0.2), 10)
404
  return round(complexity, 2)
@@ -413,12 +417,12 @@ class DocumentAnalyzer:
413
  "الغرامات",
414
  "شروط الدفع"
415
  ]
416
-
417
  missing = []
418
  for section in required_sections:
419
  if section not in text:
420
  missing.append(section)
421
-
422
  return missing
423
 
424
  def _find_related_documents(self, document_path):
@@ -426,9 +430,47 @@ class DocumentAnalyzer:
426
  directory = os.path.dirname(document_path)
427
  base_name = os.path.basename(document_path)
428
  related = []
429
-
430
  for file in os.listdir(directory):
431
  if file != base_name and file.startswith(base_name.split('_')[0]):
432
  related.append(file)
433
-
434
  return related
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  from pathlib import Path
10
  import datetime
11
  import json
12
+ import base64
13
+ import time
14
+ from PIL import Image
15
+ import io
16
 
17
  # تهيئة السجل
18
  logging.basicConfig(
 
23
 
24
  class DocumentAnalyzer:
25
  """فئة تحليل المستندات"""
26
+
27
  def __init__(self, config=None):
28
  """تهيئة محلل المستندات"""
29
  self.config = config
30
  self.analysis_in_progress = False
31
  self.current_document = None
32
  self.analysis_results = {}
33
+
34
  # إنشاء مجلد المستندات إذا لم يكن موجوداً
35
  if config and hasattr(config, 'DOCUMENTS_PATH'):
36
  self.documents_path = Path(config.DOCUMENTS_PATH)
37
  else:
38
  self.documents_path = Path('data/documents')
39
+
40
  if not self.documents_path.exists():
41
  self.documents_path.mkdir(parents=True, exist_ok=True)
42
+
43
  def analyze_document(self, document_path, document_type="tender", callback=None):
44
  """تحليل مستند"""
45
  if self.analysis_in_progress:
46
  logger.warning("هناك عملية تحليل جارية بالفعل")
47
  return False
48
+
49
  if not os.path.exists(document_path):
50
  logger.error(f"المستند غير موجود: {document_path}")
51
  return False
52
+
53
  self.analysis_in_progress = True
54
  self.current_document = document_path
55
  self.analysis_results = {
 
63
  "amounts": [],
64
  "risks": []
65
  }
66
+
67
  # بدء التحليل في خيط منفصل
68
  thread = threading.Thread(
69
  target=self._analyze_document_thread,
 
71
  )
72
  thread.daemon = True
73
  thread.start()
74
+
75
  return True
76
+
77
  def _analyze_document_thread(self, document_path, document_type, callback):
78
  """خيط تحليل المستند"""
79
  try:
80
  # تحديد نوع المستند
81
  file_extension = os.path.splitext(document_path)[1].lower()
82
+
83
  if file_extension == '.pdf':
84
  self.analysis_results = self._analyze_pdf(document_path, document_type)
85
  elif file_extension == '.docx':
 
92
  logger.error(f"نوع المستند غير مدعوم: {file_extension}")
93
  self.analysis_results["status"] = "فشل التحليل"
94
  self.analysis_results["error"] = "نوع المستند غير مدعوم"
95
+
96
  # تحديث حالة التحليل
97
  if self.analysis_results["status"] != "فشل التحليل":
98
  self.analysis_results["status"] = "اكتمل التحليل"
99
  self.analysis_results["analysis_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
100
+
101
  logger.info(f"اكتمل تحليل المستند: {document_path}")
102
+
103
  except Exception as e:
104
  logger.error(f"خطأ في تحليل المستند: {str(e)}")
105
  self.analysis_results["status"] = "فشل التحليل"
106
  self.analysis_results["error"] = str(e)
107
+
108
  finally:
109
  self.analysis_in_progress = False
110
+
111
  # استدعاء دالة الاستجابة إذا تم توفيرها
112
  if callback and callable(callback):
113
  callback(self.analysis_results)
114
+
115
  def _analyze_pdf(self, document_path, document_type):
116
  """تحليل مستند PDF باستخدام الذكاء الاصطناعي"""
117
  try:
118
  # استخراج النص من PDF
119
  text = self._extract_text_from_pdf(document_path)
120
+
121
  # تحليل متقدم للمستند
122
  analysis = {
123
  "file_info": {
 
218
  try:
219
  # محاكاة تحليل مستند Word
220
  logger.info(f"تحليل مستند Word: {document_path}")
221
+
222
  # في التطبيق الفعلي، سيتم استخدام مكتبة مثل python-docx
223
  # لاستخراج النص من ملف Word وتحليله
224
+
225
  # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
226
  # (مشابه لتحليل PDF)
227
  self.analysis_results["items"] = [
 
229
  {"id": 2, "name": "تركيب المعدات", "description": "تركيب وتشغيل المعدات", "unit": "مجموعة", "estimated_quantity": 10},
230
  {"id": 3, "name": "التدريب", "description": "تدريب الموظفين على استخدام المعدات", "unit": "يوم", "estimated_quantity": 20}
231
  ]
232
+
233
  # محاكاة استخراج الكيانات والتواريخ والمبالغ والمخاطر
234
  # (مشابه لتحليل PDF)
235
+
236
  except Exception as e:
237
  logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
238
  raise
239
+
240
  def _analyze_xlsx(self, document_path, document_type):
241
  """تحليل مستند Excel"""
242
  try:
243
  # محاكاة تحليل مستند Excel
244
  logger.info(f"تحليل مستند Excel: {document_path}")
245
+
246
  # في التطبيق الفعلي، سيتم استخدام مكتبة مثل pandas أو openpyxl
247
  # لاستخراج البيانات من ملف Excel وتحليلها
248
+
249
  # محاكاة استخراج البنود
250
  self.analysis_results["items"] = [
251
  {"id": 1, "name": "بند 1", "description": "وصف البند 1", "unit": "وحدة", "estimated_quantity": 100},
252
  {"id": 2, "name": "بند 2", "description": "وصف البند 2", "unit": "وحدة", "estimated_quantity": 200},
253
  {"id": 3, "name": "بند 3", "description": "وصف البند 3", "unit": "وحدة", "estimated_quantity": 300}
254
  ]
255
+
256
  # محاكاة استخراج المبالغ
257
  self.analysis_results["amounts"] = [
258
  {"type": "item_cost", "amount": 10000, "currency": "SAR", "description": "تكلفة البند 1"},
259
  {"type": "item_cost", "amount": 20000, "currency": "SAR", "description": "تكلفة البند 2"},
260
  {"type": "item_cost", "amount": 30000, "currency": "SAR", "description": "تكلفة البند 3"}
261
  ]
262
+
263
  except Exception as e:
264
  logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
265
  raise
266
+
267
  def _analyze_txt(self, document_path, document_type):
268
  """تحليل مستند نصي"""
269
  try:
270
  # محاكاة تحليل مستند نصي
271
  logger.info(f"تحليل مستند نصي: {document_path}")
272
+
273
  # في التطبيق الفعلي، سيتم قراءة الملف النصي وتحليله
274
+
275
  # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
276
+ # (مشابه لتحليلات أخرى)
277
+
278
  except Exception as e:
279
  logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
280
  raise
281
+
282
  def get_analysis_status(self):
283
  """الحصول على حالة التحليل الحالي"""
284
  if not self.analysis_in_progress:
 
286
  return {"status": "لا يوجد تحليل جارٍ"}
287
  else:
288
  return {"status": self.analysis_results.get("status", "غير معروف")}
289
+
290
  return {
291
  "status": "جاري التحليل",
292
  "document_path": self.current_document,
293
  "start_time": self.analysis_results.get("analysis_start_time")
294
  }
295
+
296
  def get_analysis_results(self):
297
  """الحصول على نتائج التحليل"""
298
  return self.analysis_results
299
+
300
  def export_analysis_results(self, output_path=None):
301
  """تصدير نتائج التحليل إلى ملف JSON"""
302
  if not self.analysis_results:
303
  logger.warning("لا توجد نتائج تحليل للتصدير")
304
  return None
305
+
306
  if not output_path:
307
  # إنشاء اسم ملف افتراضي
308
  timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
309
  filename = f"analysis_results_{timestamp}.json"
310
  output_path = os.path.join(self.documents_path, filename)
311
+
312
  try:
313
  with open(output_path, 'w', encoding='utf-8') as f:
314
  json.dump(self.analysis_results, f, ensure_ascii=False, indent=4)
315
+
316
  logger.info(f"تم تصدير نتائج التحليل إلى: {output_path}")
317
  return output_path
318
+
319
  except Exception as e:
320
  logger.error(f"خطأ في تصدير نتائج التحليل: {str(e)}")
321
  return None
322
+
323
  def import_analysis_results(self, input_path):
324
  """استيراد نتائج التحليل من ملف JSON"""
325
  if not os.path.exists(input_path):
326
  logger.error(f"ملف نتائج التحليل غير موجود: {input_path}")
327
  return False
328
+
329
  try:
330
  with open(input_path, 'r', encoding='utf-8') as f:
331
  self.analysis_results = json.load(f)
332
+
333
  logger.info(f"تم استيراد نتائج التحليل من: {input_path}")
334
  return True
335
+
336
  except Exception as e:
337
  logger.error(f"خطأ في استيراد نتائج التحليل: {str(e)}")
338
  return False
 
381
  # البحث عن القيم النقدية
382
  currency_pattern = r'[\d,]+\.?\d*\s*(?:ريال|دولار|SAR|USD)'
383
  currencies = re.findall(currency_pattern, text)
384
+
385
  # البحث عن النسب المئوية
386
  percentage_pattern = r'\d+\.?\d*\s*%'
387
  percentages = re.findall(percentage_pattern, text)
388
+
389
  return {
390
  "currencies": currencies,
391
  "percentages": percentages
 
402
  avg_word_length = sum(len(word) for word in words) / len(words)
403
  sentences = text.split('.')
404
  avg_sentence_length = len(words) / len(sentences)
405
+
406
  # حساب درجة التعقيد (1-10)
407
  complexity = min((avg_word_length * 0.5 + avg_sentence_length * 0.2), 10)
408
  return round(complexity, 2)
 
417
  "الغرامات",
418
  "شروط الدفع"
419
  ]
420
+
421
  missing = []
422
  for section in required_sections:
423
  if section not in text:
424
  missing.append(section)
425
+
426
  return missing
427
 
428
  def _find_related_documents(self, document_path):
 
430
  directory = os.path.dirname(document_path)
431
  base_name = os.path.basename(document_path)
432
  related = []
433
+
434
  for file in os.listdir(directory):
435
  if file != base_name and file.startswith(base_name.split('_')[0]):
436
  related.append(file)
437
+
438
  return related
439
+
440
+ def process_image(self, image_path):
441
+ from PIL import Image
442
+ import io
443
+
444
+ # فتح الصورة
445
+ with Image.open(image_path) as img:
446
+ # تحويل الصورة إلى RGB إذا كانت RGBA
447
+ if img.mode == 'RGBA':
448
+ img = img.convert('RGB')
449
+
450
+ # تحديد الحجم الأقصى وضغط أكبر
451
+ max_size = (1200, 1200)
452
+ img.thumbnail(max_size, Image.Resampling.LANCZOS)
453
+
454
+ # ضغط الصورة بجودة أقل للحصول على حجم أصغر
455
+ buffer = io.BytesIO()
456
+ img.save(buffer, format='JPEG', quality=60, optimize=True)
457
+
458
+ # التحقق من الحجم والضغط أكثر إذا لزم الأمر
459
+ if len(buffer.getvalue()) > 5000000: # 5MB
460
+ # محاولة ضغط إضافية
461
+ img.thumbnail((800, 800), Image.Resampling.LANCZOS)
462
+ buffer = io.BytesIO()
463
+ img.save(buffer, format='JPEG', quality=40, optimize=True)
464
+
465
+ # تحويل الصورة المضغوطة إلى base64
466
+ return base64.b64encode(buffer.getvalue()).decode('utf-8')
467
+
468
+ def convert_pdf_to_images(self, pdf_path):
469
+ """تحويل PDF إلى صور"""
470
+ try:
471
+ from pdf2image import convert_from_path
472
+ images = convert_from_path(pdf_path)
473
+ return images
474
+ except Exception as e:
475
+ logger.error(f"فشل في تحويل ملف PDF إلى صورة: {str(e)}")
476
+ raise