Update modules/document_analysis/analyzer.py
Browse files
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
|