EGYADMIN commited on
Commit
92688b8
·
verified ·
1 Parent(s): c9982ee

Update modules/document_analysis/analyzer.py

Browse files
Files changed (1) hide show
  1. modules/document_analysis/analyzer.py +1490 -87
modules/document_analysis/analyzer.py CHANGED
@@ -120,6 +120,10 @@ class DocumentAnalyzer:
120
 
121
  # تحليل متقدم للمستند
122
  analysis = {
 
 
 
 
123
  "file_info": {
124
  "name": os.path.basename(document_path),
125
  "type": "PDF",
@@ -169,6 +173,289 @@ class DocumentAnalyzer:
169
  except Exception as e:
170
  logger.error(f"خطأ في تحليل PDF: {str(e)}")
171
  raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  def _extract_text_from_pdf(self, document_path):
174
  """استخراج النص من ملف PDF"""
@@ -195,102 +482,1210 @@ class DocumentAnalyzer:
195
 
196
  def _analyze_financial_terms(self, text):
197
  """تحليل الجزء المالي"""
198
- # Implementation for financial term analysis is needed here. This is a placeholder.
199
- return "Placeholder financial terms analysis"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  def _analyze_legal_terms(self, text):
202
  """تحليل القانوني للعقد"""
203
- # Implementation for legal term analysis is needed here. This is a placeholder.
204
- return "Placeholder legal terms analysis"
 
 
 
 
 
 
 
 
 
 
 
 
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  def _analyze_risks(self, text):
207
  """تحليل المخاطر"""
208
- # Implementation for risk analysis is needed here. This is a placeholder.
209
- return "Placeholder risk analysis"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  def _analyze_conditions(self, text):
212
  """دراسة كراسة الشروط"""
213
- # Implementation for conditions analysis is needed here. This is a placeholder.
214
- return "Placeholder conditions analysis"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
  def _generate_summary(self, text):
217
  """توليد ملخص"""
218
- # Implementation for summary generation is needed here. This is a placeholder.
219
- return "Placeholder summary"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
  def _generate_recommendations(self, text):
222
  """توليد التوصيات"""
223
- # Implementation for recommendation generation is needed here. This is a placeholder.
224
- return "Placeholder recommendations"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
  def _analyze_docx(self, document_path, document_type):
229
  """تحليل مستند Word"""
230
  try:
231
- # محاكاة تحليل مستند Word
232
- logger.info(f"تحليل مستند Word: {document_path}")
233
-
234
- # في التطبيق الفعلي، سيتم استخدام مكتبة مثل python-docx
235
- # لاستخراج النص من ملف Word وتحليله
236
-
237
- # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
238
- # (مشابه لتحليل PDF)
239
- self.analysis_results["items"] = [
240
- {"id": 1, "name": "توريد معدات", "description": "توريد معدات المشروع", "unit": "مجموعة", "estimated_quantity": 10},
241
- {"id": 2, "name": "تركيب المعدات", "description": "تركيب وتشغيل المعدات", "unit": "مجموعة", "estimated_quantity": 10},
242
- {"id": 3, "name": "التدريب", "description": "تدريب الموظفين على استخدام المعدات", "unit": "يوم", "estimated_quantity": 20}
243
- ]
244
-
245
- # محاكاة استخراج الكيانات والتواريخ والمبالغ والمخاطر
246
- # (مشابه لتحليل PDF)
247
-
248
  except Exception as e:
249
  logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
250
  raise
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  def _analyze_xlsx(self, document_path, document_type):
253
  """تحليل مستند Excel"""
254
  try:
255
- # محاكا�� تحليل مستند Excel
256
- logger.info(f"تحليل مستند Excel: {document_path}")
257
-
258
- # في التطبيق الفعلي، سيتم استخدام مكتبة مثل pandas أو openpyxl
259
- # لاستخراج البيانات من ملف Excel وتحليلها
260
-
261
- # محاكاة استخراج البنود
262
- self.analysis_results["items"] = [
263
- {"id": 1, "name": "بند 1", "description": "وصف البند 1", "unit": "وحدة", "estimated_quantity": 100},
264
- {"id": 2, "name": "بند 2", "description": "وصف البند 2", "unit": "وحدة", "estimated_quantity": 200},
265
- {"id": 3, "name": "بند 3", "description": "وصف البند 3", "unit": "وحدة", "estimated_quantity": 300}
266
- ]
267
-
268
- # محاكاة استخراج المبالغ
269
- self.analysis_results["amounts"] = [
270
- {"type": "item_cost", "amount": 10000, "currency": "SAR", "description": "تكلفة البند 1"},
271
- {"type": "item_cost", "amount": 20000, "currency": "SAR", "description": "تكلفة البند 2"},
272
- {"type": "item_cost", "amount": 30000, "currency": "SAR", "description": "تكلفة البند 3"}
273
- ]
274
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  except Exception as e:
276
  logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
277
  raise
278
 
279
- def _analyze_txt(self, document_path, document_type):
280
- """تحليل مستند نصي"""
281
  try:
282
- # محاكاة تحليل مستند نصي
283
- logger.info(f"تحليل مستند نصي: {document_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
- # في التطبيق الفعلي، سيتم قراءة الملف النصي وتحليله
 
 
 
 
 
 
 
 
 
 
286
 
287
- # محاكاة استخراج البنود والكيانات والتواريخ والمبالغ والمخاطر
288
- # (مشابه لتحليلات أخرى)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  except Exception as e:
291
  logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
292
  raise
293
 
 
 
 
 
 
294
  def get_analysis_status(self):
295
  """الحصول على حالة التحليل الحالي"""
296
  if not self.analysis_in_progress:
@@ -411,8 +1806,14 @@ class DocumentAnalyzer:
411
  def _calculate_complexity(self, text):
412
  """حساب مستوى تعقيد النص"""
413
  words = text.split()
 
 
 
414
  avg_word_length = sum(len(word) for word in words) / len(words)
415
  sentences = text.split('.')
 
 
 
416
  avg_sentence_length = len(words) / len(sentences)
417
 
418
  # حساب درجة التعقيد (1-10)
@@ -450,39 +1851,41 @@ class DocumentAnalyzer:
450
  return related
451
 
452
  def process_image(self, image_path):
453
- from PIL import Image
454
- import io
455
-
456
- # فتح الصورة
457
- with Image.open(image_path) as img:
458
- # تحويل الصورة إلى RGB إذا كانت RGBA
459
- if img.mode == 'RGBA':
460
- img = img.convert('RGB')
461
-
462
- # البدء بجودة عالية وتقليلها تدريجياً حتى نصل للحجم المطلوب
463
- quality = 95
464
- max_size = (1200, 1200)
465
-
466
- while True:
467
- img.thumbnail(max_size, Image.Resampling.LANCZOS)
468
- buffer = io.BytesIO()
469
- img.save(buffer, format='JPEG', quality=quality, optimize=True)
470
- size = len(buffer.getvalue())
471
-
472
- # إذا كان الحجم أقل من 5 ميجابايت، نخرج من الحلقة
473
- if size <= 5000000:
474
- break
475
-
476
- # تقليل الجودة والحجم
477
- quality = max(quality - 10, 20) # لا نقلل الجودة عن 20
478
- max_size = (int(max_size[0] * 0.8), int(max_size[1] * 0.8))
479
 
480
- # إذا وصلنا للحد الأدنى من الجودة والحجم ولم نصل للحجم المطلوب
481
- if quality == 20 and max_size[0] < 400:
482
- raise ValueError("لا يمكن ضغط الصورة للحجم المطلوب")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
 
484
- # تحويل الصورة المضغوطة إلى base64
485
- return base64.b64encode(buffer.getvalue()).decode('utf-8')
 
 
 
486
 
487
  def convert_pdf_to_images(self, pdf_path):
488
  """تحويل PDF إلى صور"""
 
120
 
121
  # تحليل متقدم للمستند
122
  analysis = {
123
+ "document_path": document_path,
124
+ "document_type": document_type,
125
+ "analysis_start_time": self.analysis_results["analysis_start_time"],
126
+ "status": "جاري التحليل",
127
  "file_info": {
128
  "name": os.path.basename(document_path),
129
  "type": "PDF",
 
173
  except Exception as e:
174
  logger.error(f"خطأ في تحليل PDF: {str(e)}")
175
  raise
176
+
177
+ def extract_document_metadata(self, document_path):
178
+ """استخراج البيانات الوصفية للمستند"""
179
+ try:
180
+ # تحديد نوع المستند
181
+ file_extension = os.path.splitext(document_path)[1].lower()
182
+
183
+ metadata = {
184
+ "filename": os.path.basename(document_path),
185
+ "file_type": file_extension.replace('.', '').upper(),
186
+ "file_size": os.path.getsize(document_path),
187
+ "creation_date": "غير متوفر",
188
+ "modification_date": time.ctime(os.path.getmtime(document_path)),
189
+ "author": "غير متوفر",
190
+ "title": "غير متوفر"
191
+ }
192
+
193
+ # استخراج البيانات الوصفية حسب نوع المستند
194
+ if file_extension == '.pdf':
195
+ pdf_metadata = self._extract_pdf_metadata(document_path)
196
+ metadata.update(pdf_metadata)
197
+ elif file_extension == '.docx':
198
+ docx_metadata = self._extract_docx_metadata(document_path)
199
+ metadata.update(docx_metadata)
200
+ elif file_extension == '.xlsx':
201
+ xlsx_metadata = self._extract_xlsx_metadata(document_path)
202
+ metadata.update(xlsx_metadata)
203
+
204
+ return metadata
205
+
206
+ except Exception as e:
207
+ logger.error(f"خطأ في استخراج البيانات الوصفية: {str(e)}")
208
+ return None
209
+
210
+ def _extract_pdf_metadata(self, document_path):
211
+ """استخراج البيانات الوصفية من ملف PDF"""
212
+ try:
213
+ import PyPDF2
214
+
215
+ metadata = {}
216
+
217
+ with open(document_path, 'rb') as file:
218
+ reader = PyPDF2.PdfReader(file)
219
+
220
+ # استخراج البيانات الوصفية المتاحة
221
+ if reader.metadata:
222
+ if '/Title' in reader.metadata:
223
+ metadata["title"] = reader.metadata['/Title']
224
+ if '/Author' in reader.metadata:
225
+ metadata["author"] = reader.metadata['/Author']
226
+ if '/CreationDate' in reader.metadata:
227
+ metadata["creation_date"] = reader.metadata['/CreationDate']
228
+ if '/ModDate' in reader.metadata:
229
+ metadata["modification_date"] = reader.metadata['/ModDate']
230
+ if '/Producer' in reader.metadata:
231
+ metadata["producer"] = reader.metadata['/Producer']
232
+ if '/Creator' in reader.metadata:
233
+ metadata["creator"] = reader.metadata['/Creator']
234
+
235
+ # إضافة عدد الصفحات
236
+ metadata["pages"] = len(reader.pages)
237
+
238
+ return metadata
239
+
240
+ except Exception as e:
241
+ logger.error(f"خطأ في استخراج البيانات الوصفية من PDF: {str(e)}")
242
+ return {}
243
+
244
+ def compare_documents(self, document_path1, document_path2):
245
+ """مقارنة مستندين"""
246
+ try:
247
+ # تحليل المستندين
248
+ self.analyze_document(document_path1)
249
+ analysis1 = self.get_analysis_results()
250
+
251
+ self.analyze_document(document_path2)
252
+ analysis2 = self.get_analysis_results()
253
+
254
+ # مقارنة نتائج التحليل
255
+ comparison = {
256
+ "document1": {
257
+ "path": document_path1,
258
+ "file_info": analysis1.get("file_info", {})
259
+ },
260
+ "document2": {
261
+ "path": document_path2,
262
+ "file_info": analysis2.get("file_info", {})
263
+ },
264
+ "differences": self._find_document_differences(analysis1, analysis2),
265
+ "similarity_score": self._calculate_similarity_score(analysis1, analysis2)
266
+ }
267
+
268
+ return comparison
269
+
270
+ except Exception as e:
271
+ logger.error(f"خطأ في مقارنة المستندات: {str(e)}")
272
+ return None
273
+
274
+ def _find_document_differences(self, analysis1, analysis2):
275
+ """العثور على الاختلافات بين تحليلين"""
276
+ differences = {}
277
+
278
+ # مقارنة البنود
279
+ if "items" in analysis1 and "items" in analysis2:
280
+ items1 = set(item["name"] for item in analysis1["items"] if "name" in item)
281
+ items2 = set(item["name"] for item in analysis2["items"] if "name" in item)
282
+
283
+ differences["items"] = {
284
+ "only_in_doc1": list(items1 - items2),
285
+ "only_in_doc2": list(items2 - items1),
286
+ "common": list(items1.intersection(items2))
287
+ }
288
+
289
+ # مقارنة الكيانات
290
+ if "entities" in analysis1 and "entities" in analysis2:
291
+ entities1 = set(entity for entity in analysis1["entities"])
292
+ entities2 = set(entity for entity in analysis2["entities"])
293
+
294
+ differences["entities"] = {
295
+ "only_in_doc1": list(entities1 - entities2),
296
+ "only_in_doc2": list(entities2 - entities1),
297
+ "common": list(entities1.intersection(entities2))
298
+ }
299
+
300
+ # مقارنة التواريخ
301
+ if "dates" in analysis1 and "dates" in analysis2:
302
+ dates1 = set(date for date in analysis1["dates"])
303
+ dates2 = set(date for date in analysis2["dates"])
304
+
305
+ differences["dates"] = {
306
+ "only_in_doc1": list(dates1 - dates2),
307
+ "only_in_doc2": list(dates2 - dates1),
308
+ "common": list(dates1.intersection(dates2))
309
+ }
310
+
311
+ # مقارنة المبالغ
312
+ if "amounts" in analysis1 and "amounts" in analysis2:
313
+ amounts1 = set(amount["amount"] for amount in analysis1["amounts"] if "amount" in amount)
314
+ amounts2 = set(amount["amount"] for amount in analysis2["amounts"] if "amount" in amount)
315
+
316
+ differences["amounts"] = {
317
+ "only_in_doc1": list(amounts1 - amounts2),
318
+ "only_in_doc2": list(amounts2 - amounts1),
319
+ "common": list(amounts1.intersection(amounts2))
320
+ }
321
+
322
+ return differences
323
+
324
+ def _calculate_similarity_score(self, analysis1, analysis2):
325
+ """حساب درجة التشابه بين تحليلين"""
326
+ # محاكاة بسيطة لحساب درجة التشابه
327
+ similarity_score = 0
328
+ total_factors = 0
329
+
330
+ # التشابه في البنود
331
+ if "items" in analysis1 and "items" in analysis2:
332
+ items1 = set(item["name"] for item in analysis1["items"] if "name" in item)
333
+ items2 = set(item["name"] for item in analysis2["items"] if "name" in item)
334
+
335
+ if items1 or items2: # تجنب القسمة على صفر
336
+ similarity_score += len(items1.intersection(items2)) / max(len(items1.union(items2)), 1)
337
+ total_factors += 1
338
+
339
+ # التشابه في الكيانات
340
+ if "entities" in analysis1 and "entities" in analysis2:
341
+ entities1 = set(entity for entity in analysis1["entities"])
342
+ entities2 = set(entity for entity in analysis2["entities"])
343
+
344
+ if entities1 or entities2:
345
+ similarity_score += len(entities1.intersection(entities2)) / max(len(entities1.union(entities2)), 1)
346
+ total_factors += 1
347
+
348
+ # التشابه في التواريخ
349
+ if "dates" in analysis1 and "dates" in analysis2:
350
+ dates1 = set(date for date in analysis1["dates"])
351
+ dates2 = set(date for date in analysis2["dates"])
352
+
353
+ if dates1 or dates2:
354
+ similarity_score += len(dates1.intersection(dates2)) / max(len(dates1.union(dates2)), 1)
355
+ total_factors += 1
356
+
357
+ # التشابه في المبالغ
358
+ if "amounts" in analysis1 and "amounts" in analysis2:
359
+ amounts1 = set(amount["amount"] for amount in analysis1["amounts"] if "amount" in amount)
360
+ amounts2 = set(amount["amount"] for amount in analysis2["amounts"] if "amount" in amount)
361
+
362
+ if amounts1 or amounts2:
363
+ similarity_score += len(amounts1.intersection(amounts2)) / max(len(amounts1.union(amounts2)), 1)
364
+ total_factors += 1
365
+
366
+ # حساب المتوسط
367
+ if total_factors > 0:
368
+ similarity_percentage = (similarity_score / total_factors) * 100
369
+ return round(similarity_percentage, 2)
370
+ else:
371
+ return 0.0
372
+
373
+ def generate_report(self, analysis_results=None, report_format="html"):
374
+ """توليد تقرير من نتائج التحليل"""
375
+ try:
376
+ # استخدام نتائج التحليل الحالية إذا لم يتم توفير نتائج
377
+ if analysis_results is None:
378
+ analysis_results = self.analysis_results
379
+
380
+ if not analysis_results:
381
+ logger.warning("لا توجد نتائج تحليل لتوليد تقرير")
382
+ return None
383
+
384
+ # توليد التقرير حسب الصيغة المطلوبة
385
+ if report_format.lower() == "html":
386
+ return self._generate_html_report(analysis_results)
387
+ elif report_format.lower() == "pdf":
388
+ return self._
389
+
390
+ def _extract_docx_metadata(self, document_path):
391
+ """استخراج البيانات الوصفية من ملف Word"""
392
+ try:
393
+ import docx
394
+
395
+ metadata = {}
396
+
397
+ doc = docx.Document(document_path)
398
+
399
+ # استخراج البيانات الوصفية المتاحة
400
+ core_properties = doc.core_properties
401
+
402
+ if core_properties.title:
403
+ metadata["title"] = core_properties.title
404
+ if core_properties.author:
405
+ metadata["author"] = core_properties.author
406
+ if core_properties.created:
407
+ metadata["creation_date"] = str(core_properties.created)
408
+ if core_properties.modified:
409
+ metadata["modification_date"] = str(core_properties.modified)
410
+ if core_properties.last_modified_by:
411
+ metadata["last_modified_by"] = core_properties.last_modified_by
412
+ if core_properties.revision:
413
+ metadata["revision"] = core_properties.revision
414
+
415
+ # إضافة عدد الصفحات (تقريبي)
416
+ text_length = sum(len(paragraph.text) for paragraph in doc.paragraphs)
417
+ estimated_pages = max(1, text_length // 3000)
418
+ metadata["pages"] = estimated_pages
419
+
420
+ return metadata
421
+
422
+ except Exception as e:
423
+ logger.error(f"خطأ في استخراج البيانات الوصفية من Word: {str(e)}")
424
+ return {}
425
+
426
+ def _extract_xlsx_metadata(self, document_path):
427
+ """استخراج البيانات الوصفية من ملف Excel"""
428
+ try:
429
+ import openpyxl
430
+
431
+ metadata = {}
432
+
433
+ workbook = openpyxl.load_workbook(document_path, read_only=True)
434
+
435
+ # استخراج البيانات الوصفية المتاحة
436
+ if workbook.properties:
437
+ if workbook.properties.title:
438
+ metadata["title"] = workbook.properties.title
439
+ if workbook.properties.creator:
440
+ metadata["author"] = workbook.properties.creator
441
+ if workbook.properties.created:
442
+ metadata["creation_date"] = str(workbook.properties.created)
443
+ if workbook.properties.modified:
444
+ metadata["modification_date"] = str(workbook.properties.modified)
445
+ if workbook.properties.lastModifiedBy:
446
+ metadata["last_modified_by"] = workbook.properties.lastModifiedBy
447
+ if workbook.properties.revision:
448
+ metadata["revision"] = workbook.properties.revision
449
+
450
+ # إضافة عدد الأوراق
451
+ metadata["sheets"] = len(workbook.sheetnames)
452
+ metadata["sheet_names"] = workbook.sheetnames
453
+
454
+ return metadata
455
+
456
+ except Exception as e:
457
+ logger.error(f"خطأ في استخراج البيانات الوصفية من Excel: {str(e)}")
458
+ return {}
459
 
460
  def _extract_text_from_pdf(self, document_path):
461
  """استخراج النص من ملف PDF"""
 
482
 
483
  def _analyze_financial_terms(self, text):
484
  """تحليل الجزء المالي"""
485
+ financial_terms = []
486
+
487
+ # البحث عن الأقسام المالية
488
+ sections = text.split('\n\n')
489
+ for section in sections:
490
+ if any(keyword in section.lower() for keyword in ['مالي', 'تكلفة', 'سعر', 'ميزانية', 'دفع']):
491
+ financial_terms.append(section.strip())
492
+
493
+ # استخراج المبالغ المالية
494
+ amounts = self._extract_monetary_amounts(text)
495
+
496
+ return {
497
+ "sections": financial_terms,
498
+ "amounts": amounts,
499
+ "payment_terms": self._extract_payment_terms(text),
500
+ "budget_allocation": self._extract_budget_allocation(text)
501
+ }
502
+
503
+ def _extract_monetary_amounts(self, text):
504
+ """استخراج المبالغ المالية من النص"""
505
+ import re
506
+ # نمط للبحث عن المبالغ المالية بالريال السعودي والدولار الأمريكي
507
+ pattern = r'(\d{1,3}(?:,\d{3})*(?:\.\d+)?)\s*(?:ريال|دولار|SAR|USD|ر\.س|\$)'
508
+ matches = re.findall(pattern, text)
509
+ return [float(amount.replace(',', '')) for amount in matches]
510
+
511
+ def _extract_payment_terms(self, text):
512
+ """استخراج شروط الدفع"""
513
+ payment_terms = []
514
+ sections = text.split('\n\n')
515
+ for section in sections:
516
+ if any(keyword in section.lower() for keyword in ['دفع', 'سداد', 'أقساط', 'مستحقات']):
517
+ payment_terms.append(section.strip())
518
+ return payment_terms
519
+
520
+ def _extract_budget_allocation(self, text):
521
+ """استخراج تخصيص الميزانية"""
522
+ # هذه وظيفة بسيطة لاستخراج تخصيص الميزانية
523
+ # في التطبيق الحقيقي، قد تحتاج إلى تحليل أكثر تعقيدًا
524
+ budget_items = []
525
+ sections = text.split('\n\n')
526
+ for section in sections:
527
+ if any(keyword in section.lower() for keyword in ['ميزانية', 'تخصيص', 'تمويل']):
528
+ budget_items.append(section.strip())
529
+ return budget_items
530
 
531
  def _analyze_legal_terms(self, text):
532
  """تحليل القانوني للعقد"""
533
+ legal_terms = []
534
+
535
+ # البحث عن الأقسام القانونية
536
+ sections = text.split('\n\n')
537
+ for section in sections:
538
+ if any(keyword in section.lower() for keyword in ['قانون', 'تشريع', 'نظام', 'حكم', 'قضاء', 'محكمة']):
539
+ legal_terms.append(section.strip())
540
+
541
+ return {
542
+ "sections": legal_terms,
543
+ "liability_clauses": self._extract_liability_clauses(text),
544
+ "dispute_resolution": self._extract_dispute_resolution(text),
545
+ "legal_references": self._extract_legal_references(text)
546
+ }
547
 
548
+ def _extract_liability_clauses(self, text):
549
+ """استخراج بنود المسؤولية"""
550
+ liability_clauses = []
551
+ sections = text.split('\n\n')
552
+ for section in sections:
553
+ if any(keyword in section.lower() for keyword in ['مسؤولية', 'التزام', 'ضمان', 'تعويض']):
554
+ liability_clauses.append(section.strip())
555
+ return liability_clauses
556
+
557
+ def _extract_dispute_resolution(self, text):
558
+ """استخراج آلية فض النزاعات"""
559
+ dispute_clauses = []
560
+ sections = text.split('\n\n')
561
+ for section in sections:
562
+ if any(keyword in section.lower() for keyword in ['نزاع', 'خلاف', 'تحكيم', 'قضاء', 'تسوية']):
563
+ dispute_clauses.append(section.strip())
564
+ return dispute_clauses
565
+
566
+ def _extract_legal_references(self, text):
567
+ """استخراج المراجع القانونية"""
568
+ import re
569
+ # نمط للبحث عن المراجع القانونية مثل أرقام القوانين واللوائح
570
+ pattern = r'قانون رقم \d+|لائحة \d+|نظام \d+|مرسوم \d+'
571
+ return re.findall(pattern, text)
572
+
573
  def _analyze_risks(self, text):
574
  """تحليل المخاطر"""
575
+ risk_factors = []
576
+
577
+ # البحث عن الأقسام المتعلقة بالمخاطر
578
+ sections = text.split('\n\n')
579
+ for section in sections:
580
+ if any(keyword in section.lower() for keyword in ['مخاطر', 'خطر', 'تهديد', 'ضرر', 'إخلال']):
581
+ risk_factors.append(section.strip())
582
+
583
+ # تصنيف المخاطر
584
+ risk_categories = {
585
+ "financial_risks": self._extract_financial_risks(text),
586
+ "operational_risks": self._extract_operational_risks(text),
587
+ "legal_risks": self._extract_legal_risks(text),
588
+ "technical_risks": self._extract_technical_risks(text)
589
+ }
590
+
591
+ # تقييم شدة المخاطر
592
+ risk_severity = self._assess_risk_severity(risk_factors)
593
+
594
+ return {
595
+ "risk_factors": risk_factors,
596
+ "risk_categories": risk_categories,
597
+ "risk_severity": risk_severity,
598
+ "mitigation_suggestions": self._suggest_risk_mitigation(risk_factors)
599
+ }
600
+
601
+ def _extract_financial_risks(self, text):
602
+ """استخراج المخاطر المالية"""
603
+ financial_risks = []
604
+ sections = text.split('\n\n')
605
+ for section in sections:
606
+ if any(keyword in section.lower() for keyword in ['مخاطر مالية', 'خسارة', 'تكلفة إضافية', 'غرامة']):
607
+ financial_risks.append(section.strip())
608
+ return financial_risks
609
+
610
+ def _extract_operational_risks(self, text):
611
+ """استخراج المخاطر التشغيلية"""
612
+ operational_risks = []
613
+ sections = text.split('\n\n')
614
+ for section in sections:
615
+ if any(keyword in section.lower() for keyword in ['مخاطر تشغيلية', 'توقف', 'تعطل', 'تأخير']):
616
+ operational_risks.append(section.strip())
617
+ return operational_risks
618
+
619
+ def _extract_legal_risks(self, text):
620
+ """استخراج المخاطر القانونية"""
621
+ legal_risks = []
622
+ sections = text.split('\n\n')
623
+ for section in sections:
624
+ if any(keyword in section.lower() for keyword in ['مخاطر قانونية', 'نزاع', 'مخالفة', 'تقاضي']):
625
+ legal_risks.append(section.strip())
626
+ return legal_risks
627
+
628
+ def _extract_technical_risks(self, text):
629
+ """استخراج المخاطر الفنية"""
630
+ technical_risks = []
631
+ sections = text.split('\n\n')
632
+ for section in sections:
633
+ if any(keyword in section.lower() for keyword in ['مخاطر فنية', 'عطل', 'خلل', 'تقني']):
634
+ technical_risks.append(section.strip())
635
+ return technical_risks
636
+
637
+ def _assess_risk_severity(self, risk_factors):
638
+ """تقييم شدة المخاطر"""
639
+ # في التطبيق الحقيقي، ستحتاج إلى تحليل أكثر تعقيدًا
640
+ # هذه مجرد محاكاة بسيطة
641
+ severity_scores = []
642
+ for risk in risk_factors:
643
+ # تقييم بسيط بناءً على طول النص والكلمات الرئيسية
644
+ score = len(risk) / 100 # كلما كان النص أطول، كلما كانت المخاطر أكثر تفصيلاً
645
+
646
+ # زيادة الدرجة بناءً على كلمات مفتاحية تدل على شدة الخطر
647
+ severe_keywords = ['خطير', 'شديد', 'كبير', 'جسيم', 'عالي']
648
+ for keyword in severe_keywords:
649
+ if keyword in risk.lower():
650
+ score += 1
651
+
652
+ severity_scores.append(min(score, 10)) # تحديد سقف للدرجة
653
+
654
+ # متوسط درجة الشدة
655
+ average_severity = sum(severity_scores) / len(severity_scores) if severity_scores else 0
656
+
657
+ # تصنيف المخاطر بناءً على متوسط الشدة
658
+ if average_severity >= 7:
659
+ return "عالية"
660
+ elif average_severity >= 4:
661
+ return "متوسطة"
662
+ else:
663
+ return "منخفضة"
664
+
665
+ def _suggest_risk_mitigation(self, risk_factors):
666
+ """اقتراح آليات تخفيف المخاطر"""
667
+ mitigations = []
668
+
669
+ # في التطبيق الحقيقي، ستحتاج إلى محرك استدلال أكثر تعقيدًا
670
+ # هذه مجرد اقتراحات عامة
671
+
672
+ if any("مالي" in risk for risk in risk_factors):
673
+ mitigations.append("ضمانات مالية وتأمين لتغطية المخاطر المالية")
674
+
675
+ if any("تأخير" in risk for risk in risk_factors):
676
+ mitigations.append("وضع جداول زمنية مرنة وخطط بديلة للطوارئ")
677
+
678
+ if any("قانوني" in risk for risk in risk_factors):
679
+ mitigations.append("مراجعة قانونية شاملة للعقد وبنوده")
680
+
681
+ if any("فني" in risk for risk in risk_factors):
682
+ mitigations.append("اختبارات فنية مسبقة وضمانات للأداء الفني")
683
+
684
+ # إضافة توصيات عامة إذا لم يتم العثور على مخاطر محددة
685
+ if not mitigations:
686
+ mitigations = [
687
+ "وضع خطة إدارة مخاطر شاملة",
688
+ "تحديد مسؤوليات الأطراف بوضوح",
689
+ "وضع آليات للمتابعة والتقييم الدوري",
690
+ "توفير ضمانات مالية وفنية كافية"
691
+ ]
692
+
693
+ return mitigations
694
 
695
  def _analyze_conditions(self, text):
696
  """دراسة كراسة الشروط"""
697
+ conditions = []
698
+
699
+ # البحث عن الأقسام المتعلقة بالشروط
700
+ sections = text.split('\n\n')
701
+ for section in sections:
702
+ if any(keyword in section.lower() for keyword in ['شروط', 'متطلبات', 'معايير', 'مواصفات']):
703
+ conditions.append(section.strip())
704
+
705
+ # تصنيف الشروط
706
+ categorized_conditions = {
707
+ "general_conditions": self._extract_general_conditions(text),
708
+ "technical_conditions": self._extract_technical_conditions(text),
709
+ "administrative_conditions": self._extract_administrative_conditions(text),
710
+ "financial_conditions": self._extract_financial_conditions(text)
711
+ }
712
+
713
+ # تقييم مدى اكتمال الشروط ووضوحها
714
+ completeness_score = self._assess_conditions_completeness(conditions)
715
+ clarity_score = self._assess_conditions_clarity(conditions)
716
+
717
+ return {
718
+ "conditions_list": conditions,
719
+ "categorized_conditions": categorized_conditions,
720
+ "completeness_score": completeness_score,
721
+ "clarity_score": clarity_score,
722
+ "improvement_suggestions": self._suggest_conditions_improvements(conditions)
723
+ }
724
+
725
+ def _extract_general_conditions(self, text):
726
+ """استخراج الشروط العامة"""
727
+ general_conditions = []
728
+ sections = text.split('\n\n')
729
+ for section in sections:
730
+ if any(keyword in section.lower() for keyword in ['شروط عامة', 'أحكام عامة']):
731
+ general_conditions.append(section.strip())
732
+ return general_conditions
733
+
734
+ def _extract_technical_conditions(self, text):
735
+ """استخراج الشروط الفنية"""
736
+ technical_conditions = []
737
+ sections = text.split('\n\n')
738
+ for section in sections:
739
+ if any(keyword in section.lower() for keyword in ['شروط فنية', 'مواصفات فنية', 'متطلبات فنية']):
740
+ technical_conditions.append(section.strip())
741
+ return technical_conditions
742
+
743
+ def _extract_administrative_conditions(self, text):
744
+ """استخراج الشروط الإدارية"""
745
+ admin_conditions = []
746
+ sections = text.split('\n\n')
747
+ for section in sections:
748
+ if any(keyword in section.lower() for keyword in ['شروط إدارية', 'متطلبات إدارية']):
749
+ admin_conditions.append(section.strip())
750
+ return admin_conditions
751
+
752
+ def _extract_financial_conditions(self, text):
753
+ """استخراج الشروط المالية"""
754
+ financial_conditions = []
755
+ sections = text.split('\n\n')
756
+ for section in sections:
757
+ if any(keyword in section.lower() for keyword in ['شروط مالية', 'متطلبات مالية']):
758
+ financial_conditions.append(section.strip())
759
+ return financial_conditions
760
+
761
+ def _assess_conditions_completeness(self, conditions):
762
+ """تقييم اكتمال الشروط"""
763
+ # تحقق من وجود جميع أنواع الشروط الرئيسية
764
+ required_categories = ['عامة', 'فنية', 'إدارية', 'مالية']
765
+
766
+ coverage = 0
767
+ for category in required_categories:
768
+ if any(category in condition.lower() for condition in conditions):
769
+ coverage += 1
770
+
771
+ # حساب نسبة التغطية
772
+ completeness_score = (coverage / len(required_categories)) * 10
773
+
774
+ return min(round(completeness_score, 1), 10) # تحديد سقف للدرجة
775
+
776
+ def _assess_conditions_clarity(self, conditions):
777
+ """تقييم وضوح الشروط"""
778
+ # في التطبيق الحقيقي، ستحتاج إلى تحليل لغوي أكثر تعقيدًا
779
+ # هذه مجرد محاكاة بسيطة
780
+
781
+ clarity_scores = []
782
+ for condition in conditions:
783
+ # تقييم بسيط بناءً على وضوح النص
784
+ score = 10 # نبدأ بدرجة كاملة
785
+
786
+ # تقليل الدرجة بناءً على كلمات غامضة
787
+ ambiguous_terms = ['ربما', 'قد', 'يمكن', 'محتمل', 'حسب الاقتضاء', 'في بعض الحالات']
788
+ for term in ambiguous_terms:
789
+ if term in condition.lower():
790
+ score -= 1
791
+
792
+ # تقليل الدرجة إذا كان النص طويلًا جدًا
793
+ if len(condition) > 500:
794
+ score -= 2
795
+
796
+ clarity_scores.append(max(score, 1)) # الحد الأدنى للدرجة هو 1
797
+
798
+ # متوسط درجة الوضوح
799
+ average_clarity = sum(clarity_scores) / len(clarity_scores) if clarity_scores else 0
800
+
801
+ return round(average_clarity, 1)
802
+
803
+ def _suggest_conditions_improvements(self, conditions):
804
+ """اقتراح تحسينات للشروط"""
805
+ suggestions = []
806
+
807
+ # اقتراحات عامة لتحسين الشروط
808
+ if not any('عامة' in condition.lower() for condition in conditions):
809
+ suggestions.append("إضافة قسم للشروط العامة يوضح نطاق العمل والمسؤوليات العامة")
810
+
811
+ if not any('فنية' in condition.lower() for condition in conditions):
812
+ suggestions.append("إضافة قسم للشروط الفنية يحدد المواصفات والمتطلبات الفنية بدقة")
813
+
814
+ if not any('إدارية' in condition.lower() for condition in conditions):
815
+ suggestions.append("إضافة قسم للشروط الإدارية يوضح الإجراءات والمتطلبات الإدارية")
816
+
817
+ if not any('مالية' in condition.lower() for condition in conditions):
818
+ suggestions.append("إضافة قسم للشروط المالية يحدد الالتزامات المالية وآليات الدفع")
819
+
820
+ # اقتراحات للشروط الموجودة
821
+ ambiguous_conditions = []
822
+ for condition in conditions:
823
+ if any(term in condition.lower() for term in ['ربما', 'قد', 'يمكن', 'محتمل']):
824
+ ambiguous_conditions.append(condition)
825
+
826
+ if ambiguous_conditions:
827
+ suggestions.append("توضيح الشروط الغامضة وتحديد المتطلبات بدقة أكبر")
828
+
829
+ # إضافة توصيات عامة إذا لم يتم العثور على مشاكل محددة
830
+ if not suggestions:
831
+ suggestions = [
832
+ "تنظيم الشروط في أقسام منفصلة وواضحة",
833
+ "استخدام لغة بسيطة ومباشرة في صياغة الشروط",
834
+ "تحديد المعايير الكمية والنوعية بدقة",
835
+ "تضمين آليات لحل النزاعات في حالة الاختلاف حول تفسير الشروط"
836
+ ]
837
+
838
+ return suggestions
839
 
840
  def _generate_summary(self, text):
841
  """توليد ملخص"""
842
+ # في التطبيق الحقيقي، ستحتاج إلى استخدام تقنيات معالجة اللغة الطبيعية
843
+ # لتلخيص النص بشكل ذكي. هذه مجرد محاكاة بسيطة.
844
+
845
+ # استخراج الجمل المهمة من النص
846
+ important_sentences = []
847
+ sentences = text.split('.')
848
+
849
+ # البحث عن جمل مهمة بناءً على كلمات مفتاحية
850
+ key_terms = ['شروط', 'بنود', 'التزامات', 'متطلبات', 'تكلفة', 'مدة', 'ضمان', 'غرامة']
851
+ for sentence in sentences:
852
+ if any(term in sentence.lower() for term in key_terms):
853
+ important_sentences.append(sentence.strip())
854
+
855
+ # اختيار عدد محدود من الجمل للملخص
856
+ max_sentences = min(10, len(important_sentences))
857
+ summary_sentences = important_sentences[:max_sentences]
858
+
859
+ # دمج الجمل في ملخص
860
+ summary = '. '.join(summary_sentences)
861
+
862
+ # إضافة خاتمة موجزة
863
+ summary += f"\n\nيتكون المستند من {len(sentences)} جملة وتم تلخيصه في {len(summary_sentences)} جمل رئيسية."
864
+
865
+ return summary
866
 
867
  def _generate_recommendations(self, text):
868
  """توليد التوصيات"""
869
+ # في التطبيق الحقيقي، ستحتاج إلى تحليل أكثر تعقيدًا
870
+ # هذه مجرد توصيات عامة بناءً على المحتوى
871
+
872
+ recommendations = []
873
+
874
+ # توصيات بناءً على وجود أو غياب أقسام معينة
875
+ if 'شروط' not in text.lower():
876
+ recommendations.append("إضافة قسم واضح للشروط العامة والخاصة")
877
+
878
+ if 'مواصفات فنية' not in text.lower():
879
+ recommendations.append("توضيح المواصفات الفنية المطلوبة بشكل مفصل")
880
+
881
+ if 'غرامات' not in text.lower():
882
+ recommendations.append("تحديد الغرامات والجزاءات بوضوح في حالة عدم الالتزام")
883
+
884
+ if 'ضمان' not in text.lower():
885
+ recommendations.append("تضمين بنود الضمان والصيانة بشكل واضح")
886
+
887
+ # توصيات بناءً على تحليل المخاطر
888
+ risks = self._analyze_risks(text)
889
+ if risks["risk_severity"] == "عالية":
890
+ recommendations.append("مراجعة بنود العقد للتقليل من المخاطر العالية المحددة في التحليل")
891
+
892
+ # توصيات بناءً على تحليل الشروط
893
+ conditions = self._analyze_conditions(text)
894
+ if conditions["clarity_score"] < 7:
895
+ recommendations.append("تحسين صياغة الشروط لزيادة الوضوح وتقليل الغموض")
896
+
897
+ # توصيات عامة
898
+ general_recommendations = [
899
+ "مراجعة العقد من قبل مستشار قانوني متخصص",
900
+ "التأكد من توافق البنود مع الأنظمة واللوائح الحالية",
901
+ "تضمين آليات واضحة لحل النزاعات",
902
+ "تحديد مسؤوليات كل طرف بشكل صريح",
903
+ "وضع جداول زمنية واضحة للتنفيذ ومؤشرات للأداء"
904
+ ]
905
+
906
+ # دمج التوصيات
907
+ recommendations.extend(general_recommendations)
908
+
909
+ return recommendations
910
+
911
+ def _analyze_tender_specifics(self, text):
912
+ """تحليل خاص بالمناقصات"""
913
+ return {
914
+ "eligibility_criteria": self._extract_eligibility_criteria(text),
915
+ "submission_requirements": self._extract_submission_requirements(text),
916
+ "evaluation_criteria": self._extract_evaluation_criteria(text),
917
+ "timeline": self._extract_tender_timeline(text)
918
+ }
919
+
920
+ def _extract_eligibility_criteria(self, text):
921
+ """استخراج معايير الأهلية"""
922
+ criteria = []
923
+ sections = text.split('\n\n')
924
+ for section in sections:
925
+ if any(keyword in section.lower() for keyword in ['أهلية', 'شروط المشاركة', 'متطلبات التأهيل']):
926
+ criteria.append(section.strip())
927
+ return criteria
928
+
929
+ def _extract_submission_requirements(self, text):
930
+ """استخراج متطلبات تقديم العروض"""
931
+ requirements = []
932
+ sections = text.split('\n\n')
933
+ for section in sections:
934
+ if any(keyword in section.lower() for keyword in ['تقديم العروض', 'متطلبات العرض', 'مستندات']):
935
+ requirements.append(section.strip())
936
+ return requirements
937
+
938
+ def _extract_evaluation_criteria(self, text):
939
+ """استخراج معايير التقييم"""
940
+ criteria = []
941
+ sections = text.split('\n\n')
942
+ for section in sections:
943
+ if any(keyword in section.lower() for keyword in ['معايير التقييم', 'آلية التقييم', 'ترسية']):
944
+ criteria.append(section.strip())
945
+ return criteria
946
+
947
+ def _extract_tender_timeline(self, text):
948
+ """استخراج الجدول الزمني للمناقصة"""
949
+ import re
950
+
951
+ timeline = {}
952
+
953
+ # البحث عن تواريخ محددة مثل تاريخ الإعلان، تاريخ الإغلاق، إلخ.
954
+ date_pattern = r'(\d{1,2}[-/]\d{1,2}[-/]\d{2,4})'
955
+
956
+ # تاريخ الإعلان
957
+ announcement_match = re.search(r'تاريخ الإعلان\s*[:؛]\s*' + date_pattern, text)
958
+ if announcement_match:
959
+ timeline["announcement_date"] = announcement_match.group(1)
960
+
961
+ # تاريخ بدء استلام العروض
962
+ start_submission_match = re.search(r'بدء استلام العروض\s*[:؛]\s*' + date_pattern, text)
963
+ if start_submission_match:
964
+ timeline["submission_start_date"] = start_submission_match.group(1)
965
+
966
+ # تاريخ إغلاق استلام العروض
967
+ end_submission_match = re.search(r'إغلاق استلام العروض\s*[:؛]\s*' + date_pattern, text)
968
+ if end_submission_match:
969
+ timeline["submission_end_date"] = end_submission_match.group(1)
970
+
971
+ # تاريخ فتح المظاريف
972
+ opening_match = re.search(r'فتح المظاريف\s*[:؛]\s*' + date_pattern, text)
973
+ if opening_match:
974
+ timeline["opening_date"] = opening_match.group(1)
975
+
976
+ # تاريخ التقييم
977
+ evaluation_match = re.search(r'تاريخ التقييم\s*[:؛]\s*' + date_pattern, text)
978
+ if evaluation_match:
979
+ timeline["evaluation_date"] = evaluation_match.group(1)
980
+
981
+ # تاريخ الترسية
982
+ award_match = re.search(r'تاريخ الترسية\s*[:؛]\s*' + date_pattern, text)
983
+ if award_match:
984
+ timeline["award_date"] = award_match.group(1)
985
+
986
+ return timeline
987
 
988
+ def _analyze_contract_specifics(self, text):
989
+ """تحليل خاص بالعقود"""
990
+ return {
991
+ "parties": self._extract_contract_parties(text),
992
+ "duration": self._extract_contract_duration(text),
993
+ "termination_conditions": self._extract_termination_conditions(text),
994
+ "penalties": self._extract_penalties(text),
995
+ "warranties": self._extract_warranties(text)
996
+ }
997
+
998
+ def _extract_contract_parties(self, text):
999
+ """استخراج أطراف العقد"""
1000
+ parties = {}
1001
+
1002
+ # البحث عن الطرف الأول
1003
+ first_party_match = re.search(r'الطرف الأول\s*[:؛]\s*([^\n]+)', text)
1004
+ if first_party_match:
1005
+ parties["first_party"] = first_party_match.group(1).strip()
1006
+
1007
+ # البحث عن الطرف الثاني
1008
+ second_party_match = re.search(r'الطرف الثاني\s*[:؛]\s*([^\n]+)', text)
1009
+ if second_party_match:
1010
+ parties["second_party"] = second_party_match.group(1).strip()
1011
+
1012
+ return parties
1013
+
1014
+ def _extract_contract_duration(self, text):
1015
+ """استخراج مدة العقد"""
1016
+ duration = {}
1017
+
1018
+ # البحث عن مدة العقد
1019
+ duration_match = re.search(r'مدة العقد\s*[:؛]\s*([^\n]+)', text)
1020
+ if duration_match:
1021
+ duration["text"] = duration_match.group(1).strip()
1022
+
1023
+ # البحث عن تاريخ بداية العقد
1024
+ start_date_match = re.search(r'تاريخ بداية العقد\s*[:؛]\s*(\d{1,2}[-/]\d{1,2}[-/]\d{2,4})', text)
1025
+ if start_date_match:
1026
+ duration["start_date"] = start_date_match.group(1)
1027
+
1028
+ # البحث عن تاريخ نهاية العقد
1029
+ end_date_match = re.search(r'تاريخ نهاية العقد\s*[:؛]\s*(\d{1,2}[-/]\d{1,2}[-/]\d{2,4})', text)
1030
+ if end_date_match:
1031
+ duration["end_date"] = end_date_match.group(1)
1032
+
1033
+ return duration
1034
+
1035
+ def _extract_termination_conditions(self, text):
1036
+ """استخراج شروط إنهاء العقد"""
1037
+ conditions = []
1038
+ sections = text.split('\n\n')
1039
+ for section in sections:
1040
+ if any(keyword in section.lower() for keyword in ['إنهاء العقد', 'فسخ العقد', 'إلغاء العقد']):
1041
+ conditions.append(section.strip())
1042
+ return conditions
1043
+
1044
+ def _extract_penalties(self, text):
1045
+ """استخراج الغرامات والجزاءات"""
1046
+ penalties = []
1047
+ sections = text.split('\n\n')
1048
+ for section in sections:
1049
+ if any(keyword in section.lower() for keyword in ['غرامة', 'جزاء', 'عقوبة', 'تعويض']):
1050
+ penalties.append(section.strip())
1051
+ return penalties
1052
+
1053
+ def _extract_warranties(self, text):
1054
+ """استخراج الضمانات"""
1055
+ warranties = []
1056
+ sections = text.split('\n\n')
1057
+ for section in sections:
1058
+ if any(keyword in section.lower() for keyword in ['ضمان', 'كفالة', 'تأمين']):
1059
+ warranties.append(section.strip())
1060
+ return warranties
1061
 
1062
+ def _analyze_technical_specifics(self, text):
1063
+ """تحليل خاص بالمستندات الفنية"""
1064
+ return {
1065
+ "specifications": self._extract_technical_specifications(text),
1066
+ "standards": self._extract_technical_standards(text),
1067
+ "testing_procedures": self._extract_testing_procedures(text),
1068
+ "quality_requirements": self._extract_quality_requirements(text)
1069
+ }
1070
+
1071
+ def _extract_technical_specifications(self, text):
1072
+ """استخراج المواصفات الفنية"""
1073
+ specifications = []
1074
+ sections = text.split('\n\n')
1075
+ for section in sections:
1076
+ if any(keyword in section.lower() for keyword in ['مواصفات فنية', 'خصائص', 'متطلبات فنية']):
1077
+ specifications.append(section.strip())
1078
+ return specifications
1079
+
1080
+ def _extract_technical_standards(self, text):
1081
+ """استخراج المعايير الفنية"""
1082
+ standards = []
1083
+ sections = text.split('\n\n')
1084
+ for section in sections:
1085
+ if any(keyword in section.lower() for keyword in ['معايير', 'مقاييس', 'مواصفات قياسية']):
1086
+ standards.append(section.strip())
1087
+ return standards
1088
+
1089
+ def _extract_testing_procedures(self, text):
1090
+ """استخراج إجراءات الاختبار"""
1091
+ procedures = []
1092
+ sections = text.split('\n\n')
1093
+ for section in sections:
1094
+ if any(keyword in section.lower() for keyword in ['اختبار', 'فحص', 'تجربة']):
1095
+ procedures.append(section.strip())
1096
+ return procedures
1097
+
1098
+ def _extract_quality_requirements(self, text):
1099
+ """استخراج متطلبات الجودة"""
1100
+ requirements = []
1101
+ sections = text.split('\n\n')
1102
+ for section in sections:
1103
+ if any(keyword in section.lower() for keyword in ['جودة', 'ضمان الجودة', 'رقابة']):
1104
+ requirements.append(section.strip())
1105
+ return requirements
1106
+
1107
+ def _extract_entities(self, text):
1108
+ """استخراج الكيانات من النص"""
1109
+ entities = {
1110
+ "organizations": self._extract_organizations(text),
1111
+ "people": self._extract_people(text),
1112
+ "locations": self._extract_locations(text)
1113
+ }
1114
+ return entities
1115
+
1116
+ def _extract_organizations(self, text):
1117
+ """استخراج المنظمات والشركات"""
1118
+ import re
1119
+ # نمط بسيط للبحث عن المنظمات والشركات
1120
+ org_pattern = r'شركة [\u0600-\u06FF\s]+|مؤسسة [\u0600-\u06FF\s]+|وزارة [\u0600-\u06FF\s]+|هيئة [\u0600-\u06FF\s]+'
1121
+ return list(set(re.findall(org_pattern, text)))
1122
+
1123
+ def _extract_people(self, text):
1124
+ """استخراج أسماء الأشخاص"""
1125
+ # في التطبيق الحقيقي، ستحتاج إلى استخدام تقنيات التعرف على الكيانات
1126
+ # هذه مجرد محاكاة بسيطة
1127
+ return []
1128
+
1129
+ def _extract_locations(self, text):
1130
+ """استخراج المواقع"""
1131
+ import re
1132
+ # نمط بسيط للبحث عن المواقع
1133
+ location_pattern = r'مدينة [\u0600-\u06FF\s]+|منطقة [\u0600-\u06FF\s]+|محافظة [\u0600-\u06FF\s]+'
1134
+ return list(set(re.findall(location_pattern, text)))
1135
+
1136
+ def _extract_materials(self, text):
1137
+ """استخراج المواد"""
1138
+ materials = []
1139
+ # في التطبيق الحقيقي، ستحتاج إلى قائمة بالمواد الشائعة للبحث عنها
1140
+ common_materials = ['حديد', 'خشب', 'زجاج', 'ألمنيوم', 'نحاس', 'بلاستيك', 'خرسانة']
1141
+ for material in common_materials:
1142
+ if material in text.lower():
1143
+ # البحث عن السياق المحيط بالمادة
1144
+ pattern = r'[^.]*\b' + material + r'\b[^.]*\.'
1145
+ material_contexts = re.findall(pattern, text)
1146
+ for context in material_contexts:
1147
+ materials.append(context.strip())
1148
+ return materials
1149
+
1150
+ def _extract_measurements(self, text):
1151
+ """استخراج القياسات"""
1152
+ import re
1153
+ # البحث عن القياسات مثل الطول والعرض والوزن وغيرها
1154
+ measurement_pattern = r'\d+(?:\.\d+)?\s*(?:متر|سم|مم|كجم|طن|لتر|مل)'
1155
+ return re.findall(measurement_pattern, text)
1156
+
1157
+ def _extract_standards(self, text):
1158
+ """استخراج المعايير"""
1159
+ standards = []
1160
+ # معايير شائعة للبحث عنها
1161
+ common_standards = ['ISO', 'SASO', 'ASTM', 'BS', 'DIN', 'IEC']
1162
+ for standard in common_standards:
1163
+ if standard in text:
1164
+ # البحث عن المعيار مع رقمه
1165
+ pattern = r'\b' + standard + r'\s*\d+\b'
1166
+ standard_matches = re.findall(pattern, text)
1167
+ standards.extend(standard_matches)
1168
+ return standards
1169
+
1170
+ def _analyze_topics(self, text):
1171
+ """تحليل المواضيع الرئيسية"""
1172
+ # في التطبيق الحقيقي، ستحتاج إلى استخدام تقنيات تحليل المواضيع مثل LDA
1173
+ # هذه مجرد محاكاة بسيطة
1174
+
1175
+ topics = {}
1176
+
1177
+ # مواضيع شائعة في المناقصات والعقود
1178
+ common_topics = {
1179
+ "financial": ['سعر', 'تكلفة', 'ميزانية', 'دفع', 'مالي'],
1180
+ "technical": ['فني', 'مواصفات', 'معايير', 'تقني'],
1181
+ "legal": ['قانوني', 'شرط', 'بند', 'التزام', 'حق'],
1182
+ "administrative": ['إداري', 'إجراء', 'تنظيم', 'إشراف'],
1183
+ "time": ['مدة', 'فترة', 'موعد', 'تاريخ', 'جدول زمني']
1184
+ }
1185
+
1186
+ # حساب تكرار كلمات كل موضوع في النص
1187
+ word_count = len(text.split())
1188
+
1189
+ for topic, keywords in common_topics.items():
1190
+ topic_count = 0
1191
+ for keyword in keywords:
1192
+ # عدد مرات ظهور الكلمة المفتاحية في النص
1193
+ topic_count += len(re.findall(r'\b' + keyword + r'\w*\b', text))
1194
+
1195
+ # حساب النسبة المئوية للموضوع
1196
+ if word_count > 0:
1197
+ topic_percentage = (topic_count / word_count) * 100
1198
+ topics[topic] = round(topic_percentage, 2)
1199
+ else:
1200
+ topics[topic] = 0
1201
+
1202
+ return topics
1203
+
1204
+ def _check_required_terms(self, text):
1205
+ """التحقق من وجود المصطلحات المطلوبة"""
1206
+ required_terms = {
1207
+ "general": ['نطاق العمل', 'مدة التنفيذ', 'الشروط العامة'],
1208
+ "financial": ['قيمة العقد', 'طريقة الدفع', 'الضمان المالي'],
1209
+ "legal": ['حل النزاعات', 'الإنهاء', 'التعويضات'],
1210
+ "technical": ['المواصفات الفنية', 'ضمان الجودة', 'معايير القبول']
1211
+ }
1212
+
1213
+ found_terms = {}
1214
+
1215
+ for category, terms in required_terms.items():
1216
+ found_in_category = []
1217
+ for term in terms:
1218
+ if term in text:
1219
+ found_in_category.append(term)
1220
+
1221
+ found_terms[category] = found_in_category
1222
+
1223
+ return found_terms
1224
+
1225
+ def _calculate_compliance_score(self, text):
1226
+ """حساب درجة الامتثال"""
1227
+ # التحقق من وجود الأقسام المطلوبة
1228
+ missing_sections = self._check_missing_sections(text)
1229
+ required_terms = self._check_required_terms(text)
1230
+
1231
+ # حساب درجة الامتثال
1232
+ total_required_terms = sum(len(terms) for terms in required_terms.values())
1233
+ found_terms = sum(len(found) for found in required_terms.values())
1234
+
1235
+ if total_required_terms > 0:
1236
+ compliance_percentage = (found_terms / total_required_terms) * 100
1237
+
1238
+ # تقليل الدرجة بناءً على الأقسام المفقودة
1239
+ compliance_percentage -= len(missing_sections) * 5
1240
+
1241
+ # ضمان أن الدرجة في النطاق المناسب
1242
+ compliance_percentage = max(0, min(100, compliance_percentage))
1243
+
1244
+ return round(compliance_percentage, 1)
1245
+ else:
1246
+ return 0
1247
+
1248
+ def _get_version_info(self, document_path):
1249
+ """الحصول على معلومات الإصدار"""
1250
+ # في التطبيق الحقيقي، قد تحتاج لاستخراج معلومات الإصدار من الملف
1251
+ version_info = {
1252
+ "filename": os.path.basename(document_path),
1253
+ "last_modified": time.ctime(os.path.getmtime(document_path))
1254
+ }
1255
+
1256
+ # البحث عن رقم الإصدار في اسم الملف
1257
+ match = re.search(r'[vV](\d+(?:\.\d+)*)', os.path.basename(document_path))
1258
+ if match:
1259
+ version_info["version_number"] = match.group(1)
1260
+ else:
1261
+ version_info["version_number"] = "غير محدد"
1262
+
1263
+ return version_info
1264
 
1265
  def _analyze_docx(self, document_path, document_type):
1266
  """تحليل مستند Word"""
1267
  try:
1268
+ # استخراج النص من ملف Word
1269
+ text = self._extract_text_from_docx(document_path)
1270
+
1271
+ # استخدام نفس آلية تحليل PDF للتحليل
1272
+ analysis = self._analyze_pdf(document_path, document_type)
1273
+
1274
+ # تحديث نوع الملف
1275
+ analysis["file_info"]["type"] = "DOCX"
1276
+
1277
+ return analysis
1278
+
 
 
 
 
 
 
1279
  except Exception as e:
1280
  logger.error(f"خطأ في تحليل مستند Word: {str(e)}")
1281
  raise
1282
 
1283
+ def _extract_text_from_docx(self, document_path):
1284
+ """استخراج النص من ملف Word"""
1285
+ try:
1286
+ import docx
1287
+
1288
+ doc = docx.Document(document_path)
1289
+ text = ""
1290
+
1291
+ for paragraph in doc.paragraphs:
1292
+ text += paragraph.text + "\n"
1293
+
1294
+ for table in doc.tables:
1295
+ for row in table.rows:
1296
+ for cell in row.cells:
1297
+ text += cell.text + " "
1298
+ text += "\n"
1299
+
1300
+ return text
1301
+
1302
+ except Exception as e:
1303
+ logger.error(f"خطأ في استخراج النص من Word: {str(e)}")
1304
+ raise
1305
+
1306
  def _analyze_xlsx(self, document_path, document_type):
1307
  """تحليل مستند Excel"""
1308
  try:
1309
+ # استخراج البيانات من ملف Excel
1310
+ data = self._extract_data_from_xlsx(document_path)
1311
+
1312
+ # إنشاء تحليل مخصص لملفات Excel
1313
+ analysis = {
1314
+ "document_path": document_path,
1315
+ "document_type": document_type,
1316
+ "analysis_start_time": self.analysis_results["analysis_start_time"],
1317
+ "status": "جاري التحليل",
1318
+ "file_info": {
1319
+ "name": os.path.basename(document_path),
1320
+ "type": "XLSX",
1321
+ "size": os.path.getsize(document_path),
1322
+ "sheets": self._count_sheets(document_path),
1323
+ "create_date": "غير متوفر",
1324
+ "modify_date": time.ctime(os.path.getmtime(document_path))
1325
+ },
1326
+ "data_analysis": {
1327
+ "sheet_summary": data["sheet_summary"],
1328
+ "total_rows": data["total_rows"],
1329
+ "total_columns": data["total_columns"],
1330
+ "numeric_columns": data["numeric_columns"],
1331
+ "text_columns": data["text_columns"],
1332
+ "date_columns": data["date_columns"]
1333
+ }
1334
+ }
1335
+
1336
+ # إضافة تحليلات إضافية حسب نوع المستند
1337
+ if document_type == "tender":
1338
+ analysis["tender_analysis"] = self._analyze_excel_tender(data)
1339
+ elif document_type == "financial":
1340
+ analysis["financial_analysis"] = self._analyze_excel_financial(data)
1341
+
1342
+ return analysis
1343
+
1344
  except Exception as e:
1345
  logger.error(f"خطأ في تحليل مستند Excel: {str(e)}")
1346
  raise
1347
 
1348
+ def _extract_data_from_xlsx(self, document_path):
1349
+ """استخراج البيانات من ملف Excel"""
1350
  try:
1351
+ import pandas as pd
1352
+
1353
+ # قراءة جميع الأوراق في الملف
1354
+ excel_file = pd.ExcelFile(document_path)
1355
+ sheet_names = excel_file.sheet_names
1356
+
1357
+ data = {
1358
+ "sheet_summary": {},
1359
+ "total_rows": 0,
1360
+ "total_columns": 0,
1361
+ "numeric_columns": 0,
1362
+ "text_columns": 0,
1363
+ "date_columns": 0,
1364
+ "sheets": {}
1365
+ }
1366
+
1367
+ for sheet_name in sheet_names:
1368
+ # قراءة الورقة إلى DataFrame
1369
+ df = pd.read_excel(excel_file, sheet_name=sheet_name)
1370
+
1371
+ # تحليل أنواع البيانات في الأعمدة
1372
+ column_types = {}
1373
+ numeric_columns = 0
1374
+ text_columns = 0
1375
+ date_columns = 0
1376
+
1377
+ for column in df.columns:
1378
+ if pd.api.types.is_numeric_dtype(df[column]):
1379
+ column_types[column] = "numeric"
1380
+ numeric_columns += 1
1381
+ elif pd.api.types.is_datetime64_dtype(df[column]):
1382
+ column_types[column] = "date"
1383
+ date_columns += 1
1384
+ else:
1385
+ column_types[column] = "text"
1386
+ text_columns += 1
1387
+
1388
+ # تحديث ملخص الورقة
1389
+ data["sheet_summary"][sheet_name] = {
1390
+ "rows": len(df),
1391
+ "columns": len(df.columns),
1392
+ "column_types": column_types
1393
+ }
1394
+
1395
+ # تحديث الإحصائيات الإجمالية
1396
+ data["total_rows"] += len(df)
1397
+ data["total_columns"] += len(df.columns)
1398
+ data["numeric_columns"] += numeric_columns
1399
+ data["text_columns"] += text_columns
1400
+ data["date_columns"] += date_columns
1401
+
1402
+ # تخزين البيانات (مع حد أقصى للصفوف للتحكم في الحجم)
1403
+ max_rows = 100
1404
+ data["sheets"][sheet_name] = df.head(max_rows).to_dict(orient="records")
1405
+
1406
+ return data
1407
+
1408
+ except Exception as e:
1409
+ logger.error(f"خطأ في استخراج البيانات من Excel: {str(e)}")
1410
+ raise
1411
 
1412
+ def _count_sheets(self, document_path):
1413
+ """حساب عدد الأوراق في ملف Excel"""
1414
+ try:
1415
+ import pandas as pd
1416
+
1417
+ excel_file = pd.ExcelFile(document_path)
1418
+ return len(excel_file.sheet_names)
1419
+
1420
+ except Exception as e:
1421
+ logger.error(f"خطأ في حساب عدد الأوراق: {str(e)}")
1422
+ return 0
1423
 
1424
+ def _analyze_excel_tender(self, data):
1425
+ """تحليل بيانات المناقصة من ملف Excel"""
1426
+ # تحليل بسيط لملف Excel خاص بالمناقصة
1427
+ analysis = {
1428
+ "items": self._extract_tender_items(data),
1429
+ "quantities": self._extract_tender_quantities(data),
1430
+ "pricing": self._extract_tender_pricing(data)
1431
+ }
1432
+
1433
+ return analysis
1434
+
1435
+ def _extract_tender_items(self, data):
1436
+ """استخراج البنود من بيانات المناقصة"""
1437
+ items = []
1438
+
1439
+ # البحث عن الأوراق التي تحتوي على بنود المناقصة
1440
+ for sheet_name, sheet_data in data["sheets"].items():
1441
+ if not sheet_data:
1442
+ continue
1443
+
1444
+ # البحث عن الأعمدة التي قد تحتوي على أسماء البنود
1445
+ possible_item_columns = ["البند", "الوصف", "المادة", "البيان", "item", "description"]
1446
+
1447
+ for row in sheet_data:
1448
+ item_found = False
1449
+
1450
+ # البحث عن أسماء البنود
1451
+ for column in possible_item_columns:
1452
+ if column in row and row[column]:
1453
+ # تحقق من وجود أعمدة الكمية والوحدة
1454
+ quantity = None
1455
+ unit = None
1456
+
1457
+ for qty_col in ["الكمية", "العدد", "quantity", "qty"]:
1458
+ if qty_col in row and row[qty_col]:
1459
+ quantity = row[qty_col]
1460
+ break
1461
+
1462
+ for unit_col in ["الوحدة", "unit", "uom"]:
1463
+ if unit_col in row and row[unit_col]:
1464
+ unit = row[unit_col]
1465
+ break
1466
+
1467
+ # إضافة البند إلى القائمة
1468
+ items.append({
1469
+ "name": row[column],
1470
+ "quantity": quantity,
1471
+ "unit": unit
1472
+ })
1473
+
1474
+ item_found = True
1475
+ break
1476
+
1477
+ if item_found:
1478
+ break
1479
+
1480
+ return items
1481
+
1482
+ def _extract_tender_quantities(self, data):
1483
+ """استخراج الكميات من بيانات المناقصة"""
1484
+ quantities = {}
1485
+
1486
+ # البحث عن الأوراق التي تحتوي على كميات المناقصة
1487
+ for sheet_name, sheet_data in data["sheets"].items():
1488
+ if not sheet_data:
1489
+ continue
1490
+
1491
+ # البحث عن الأعمدة التي قد تحتوي على الكميات
1492
+ quantity_columns = ["الكمية", "العدد", "quantity", "qty"]
1493
+ item_columns = ["البند", "الوصف", "المادة", "البيان", "item", "description"]
1494
+
1495
+ for row in sheet_data:
1496
+ item_name = None
1497
+ quantity = None
1498
+
1499
+ # البحث عن اسم البند
1500
+ for col in item_columns:
1501
+ if col in row and row[col]:
1502
+ item_name = row[col]
1503
+ break
1504
+
1505
+ # البحث عن الكمية
1506
+ for col in quantity_columns:
1507
+ if col in row and row[col]:
1508
+ quantity = row[col]
1509
+ break
1510
+
1511
+ # تخزين الكمية إذا وجدت
1512
+ if item_name and quantity:
1513
+ quantities[item_name] = quantity
1514
+
1515
+ return quantities
1516
+
1517
+ def _extract_tender_pricing(self, data):
1518
+ """استخراج الأسعار من بيانات المناقصة"""
1519
+ pricing = {}
1520
+
1521
+ # البحث عن الأوراق التي تحتوي على أسعار المناقصة
1522
+ for sheet_name, sheet_data in data["sheets"].items():
1523
+ if not sheet_data:
1524
+ continue
1525
+
1526
+ # البحث عن الأعمدة التي قد تحتوي على الأسعار
1527
+ price_columns = ["السعر", "التكلفة", "المبلغ", "price", "cost", "amount"]
1528
+ item_columns = ["البند", "الوصف", "المادة", "البيان", "item", "description"]
1529
+
1530
+ for row in sheet_data:
1531
+ item_name = None
1532
+ price = None
1533
+
1534
+ # البحث عن اسم البند
1535
+ for col in item_columns:
1536
+ if col in row and row[col]:
1537
+ item_name = row[col]
1538
+ break
1539
+
1540
+ # البحث عن السعر
1541
+ for col in price_columns:
1542
+ if col in row and row[col]:
1543
+ price = row[col]
1544
+ break
1545
+
1546
+ # تخزين السعر إذا وجد
1547
+ if item_name and price:
1548
+ pricing[item_name] = price
1549
+
1550
+ return pricing
1551
+
1552
+ def _analyze_excel_financial(self, data):
1553
+ """تحليل البيانات المالية من ملف Excel"""
1554
+ # تحليل بسيط لملف Excel مالي
1555
+ analysis = {
1556
+ "total_amount": self._calculate_total_amount(data),
1557
+ "budget_breakdown": self._extract_budget_breakdown(data),
1558
+ "payment_schedule": self._extract_payment_schedule(data)
1559
+ }
1560
+
1561
+ return analysis
1562
+
1563
+ def _calculate_total_amount(self, data):
1564
+ """حساب المبلغ الإجمالي من البيانات المالية"""
1565
+ total = 0
1566
+
1567
+ # البحث عن الأوراق التي تحتوي على بيانات مالية
1568
+ for sheet_name, sheet_data in data["sheets"].items():
1569
+ if not sheet_data:
1570
+ continue
1571
+
1572
+ # البحث عن الأعمدة التي قد تحتوي على مبالغ
1573
+ amount_columns = ["المبلغ", "الإجمالي", "المجموع", "amount", "total", "sum"]
1574
+
1575
+ for row in sheet_data:
1576
+ for col in amount_columns:
1577
+ if col in row and row[col] and isinstance(row[col], (int, float)):
1578
+ total += row[col]
1579
+
1580
+ return total
1581
+
1582
+ def _extract_budget_breakdown(self, data):
1583
+ """استخراج تفاصيل الميزانية من البيانات المالية"""
1584
+ breakdown = {}
1585
+
1586
+ # البحث عن الأوراق التي تحتوي على تفاصيل الميزانية
1587
+ for sheet_name, sheet_data in data["sheets"].items():
1588
+ if not sheet_data:
1589
+ continue
1590
+
1591
+ # البحث عن الأعمدة التي قد تحتوي على بنود الميزانية
1592
+ category_columns = ["البند", "الفئة", "القسم", "category", "item"]
1593
+ amount_columns = ["المبلغ", "التكلفة", "القيمة", "amount", "cost", "value"]
1594
+
1595
+ for row in sheet_data:
1596
+ category = None
1597
+ amount = None
1598
+
1599
+ # البحث عن فئة الميزانية
1600
+ for col in category_columns:
1601
+ if col in row and row[col]:
1602
+ category = row[col]
1603
+ break
1604
+
1605
+ # البحث عن المبلغ
1606
+ for col in amount_columns:
1607
+ if col in row and row[col] and isinstance(row[col], (int, float)):
1608
+ amount = row[col]
1609
+ break
1610
+
1611
+ # تخزين بند الميزانية إذا وجد
1612
+ if category and amount:
1613
+ breakdown[category] = amount
1614
+
1615
+ return breakdown
1616
+
1617
+ def _extract_payment_schedule(self, data):
1618
+ """استخراج جدول الدفعات من البيانات المالية"""
1619
+ schedule = []
1620
+
1621
+ # البحث عن الأوراق التي تحتوي على جدول الدفعات
1622
+ for sheet_name, sheet_data in data["sheets"].items():
1623
+ if not sheet_data:
1624
+ continue
1625
+
1626
+ # البحث عن الأعمدة التي قد تحتوي على معلومات الدفعات
1627
+ date_columns = ["التاريخ", "الموعد", "date", "schedule"]
1628
+ amount_columns = ["المبلغ", "الدفعة", "القيمة", "amount", "payment", "value"]
1629
+ description_columns = ["الوصف", "البيان", "description", "details"]
1630
+
1631
+ for row in sheet_data:
1632
+ date = None
1633
+ amount = None
1634
+ description = None
1635
+
1636
+ # البحث عن تاريخ الدفعة
1637
+ for col in date_columns:
1638
+ if col in row and row[col]:
1639
+ date = row[col]
1640
+ break
1641
+
1642
+ # البحث عن مبلغ الدفعة
1643
+ for col in amount_columns:
1644
+ if col in row and row[col]:
1645
+ amount = row[col]
1646
+ break
1647
+
1648
+ # البحث عن وصف الدفعة
1649
+ for col in description_columns:
1650
+ if col in row and row[col]:
1651
+ description = row[col]
1652
+ break
1653
+
1654
+ # تخزين الدفعة إذا وجدت
1655
+ if date and amount:
1656
+ schedule.append({
1657
+ "date": date,
1658
+ "amount": amount,
1659
+ "description": description
1660
+ })
1661
+
1662
+ return schedule
1663
 
1664
+ def _analyze_txt(self, document_path, document_type):
1665
+ """تحليل مستند نصي"""
1666
+ try:
1667
+ # قراءة محتوى الملف النصي
1668
+ with open(document_path, 'r', encoding='utf-8') as file:
1669
+ text = file.read()
1670
+
1671
+ # استخدام نفس آلية تحليل PDF
1672
+ analysis = self._analyze_pdf(document_path, document_type)
1673
+
1674
+ # تحديث نوع الملف
1675
+ analysis["file_info"]["type"] = "TXT"
1676
+ analysis["file_info"]["pages"] = self._estimate_pages(text)
1677
+
1678
+ return analysis
1679
+
1680
  except Exception as e:
1681
  logger.error(f"خطأ في تحليل مستند نصي: {str(e)}")
1682
  raise
1683
 
1684
+ def _estimate_pages(self, text):
1685
+ """تقدير عدد الصفحات في النص"""
1686
+ # تقدير بسيط: كل 3000 حرف تعادل صفحة واحدة تقريبًا
1687
+ return max(1, len(text) // 3000)
1688
+
1689
  def get_analysis_status(self):
1690
  """الحصول على حالة التحليل الحالي"""
1691
  if not self.analysis_in_progress:
 
1806
  def _calculate_complexity(self, text):
1807
  """حساب مستوى تعقيد النص"""
1808
  words = text.split()
1809
+ if not words:
1810
+ return 0
1811
+
1812
  avg_word_length = sum(len(word) for word in words) / len(words)
1813
  sentences = text.split('.')
1814
+ if not sentences:
1815
+ return 0
1816
+
1817
  avg_sentence_length = len(words) / len(sentences)
1818
 
1819
  # حساب درجة التعقيد (1-10)
 
1851
  return related
1852
 
1853
  def process_image(self, image_path):
1854
+ """معالجة وضغط ا��صورة"""
1855
+ try:
1856
+ # فتح الصورة
1857
+ with Image.open(image_path) as img:
1858
+ # تحويل الصورة إلى RGB إذا كانت RGBA
1859
+ if img.mode == 'RGBA':
1860
+ img = img.convert('RGB')
1861
+
1862
+ # البدء بجودة عالية وتقليلها تدريجياً حتى نصل للحجم المطلوب
1863
+ quality = 95
1864
+ max_size = (1200, 1200)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1865
 
1866
+ while True:
1867
+ img.thumbnail(max_size, Image.Resampling.LANCZOS)
1868
+ buffer = io.BytesIO()
1869
+ img.save(buffer, format='JPEG', quality=quality, optimize=True)
1870
+ size = len(buffer.getvalue())
1871
+
1872
+ # إذا كان الحجم أقل من 5 ميجابايت، نخرج من الحلقة
1873
+ if size <= 5000000:
1874
+ break
1875
+
1876
+ # تقليل الجودة والحجم
1877
+ quality = max(quality - 10, 20) # لا نقلل الجودة عن 20
1878
+ max_size = (int(max_size[0] * 0.8), int(max_size[1] * 0.8))
1879
+
1880
+ # إذا وصلنا للحد الأدنى من الجودة والحجم ولم نصل للحجم المطلوب
1881
+ if quality == 20 and max_size[0] < 400:
1882
+ raise ValueError("لا يمكن ضغط الصورة للحجم المطلوب")
1883
 
1884
+ # تحويل الصورة المضغوطة إلى base64
1885
+ return base64.b64encode(buffer.getvalue()).decode('utf-8')
1886
+ except Exception as e:
1887
+ logger.error(f"خطأ في معالجة الصورة: {str(e)}")
1888
+ raise
1889
 
1890
  def convert_pdf_to_images(self, pdf_path):
1891
  """تحويل PDF إلى صور"""