import os import re import fitz # PyMuPDF import easyocr import numpy as np from PIL import Image import io import gc import math import asyncio import random from concurrent.futures import ThreadPoolExecutor # Singleton: تحميل الموديل مرة واحدة عند تشغيل السيرفر # gpu=False ضرورية للسيرفرات المجانية reader = easyocr.Reader(['en'], gpu=False) # Executor باش نخدمو بـ التوازي (Parallel) ونستغلو الـ CPU كامل executor = ThreadPoolExecutor(max_workers=4) class DocumentProcessor: @staticmethod def _ocr_page_sync(page_index, file_content): """دالة معالجة صفحة واحدة - كتخدم فـ Thread معزول لتسريع العملية""" try: doc = fitz.open(stream=file_content, filetype="pdf") page = doc[page_index] # matrix(1.1, 1.1) كافية للقراءة وكتوفر 50% ديال الوقت مقارنة بـ 1.5 pix = page.get_pixmap(matrix=fitz.Matrix(1.1, 1.1)) img_data = Image.open(io.BytesIO(pix.tobytes())) img_np = np.array(img_data.convert('L')) # تحويل لـ Grayscale كايسرع الـ OCR بزاف page_results = reader.readtext(img_np) page_text = " ".join([res[1] for res in page_results]) doc.close() # تنظيف يدوي للذاكرة داخل الـ Thread del img_np, img_data, pix return page_text except Exception as e: print(f"Error on page {page_index}: {e}") return "" @staticmethod async def extract_text(file_content: bytes, file_extension: str) -> str: text = "" try: # 1. محاولة استخراج النص المباشر (للملفات النصية - سريعة جداً) doc = fitz.open(stream=file_content, filetype="pdf" if "pdf" in file_extension.lower() else file_extension) for page in doc: text += page.get_text() # 2. إذا كان الملف ممسوح ضوئياً (Scanned) أو النص ناقص if len(text.strip()) < 50: print(f"🚀 Parallel OCR Started for {len(doc)} pages...") loop = asyncio.get_event_loop() tasks = [] # إرسال كل صفحة لـ Thread بوحدها باش يخدمو فدقة واحدة for i in range(len(doc)): task = loop.run_in_executor(executor, DocumentProcessor._ocr_page_sync, i, file_content) tasks.append(task) pages_results = await asyncio.gather(*tasks) text = " ".join(pages_results) doc.close() except Exception as e: print(f"Extraction failed: {e}") gc.collect() # تنظيف الـ RAM return text @staticmethod async def analyze_risk(text: str): """التحليل المتقدم باستخدام المنطق الرياضي والأوزان""" analysis_axes = { "legal_exposure": { "keywords": ["liability", "indemnification", "arbitration", "jurisdiction", "lawsuit", "breach", "warranty", "indemnity"], "weight": 1.8 }, "financial_obligation": { "keywords": ["payment", "penalty", "interest", "refund", "liquidated damages", "compensation", "invoice", "fee"], "weight": 1.4 }, "compliance_risk": { "keywords": ["violation", "regulatory", "prohibited", "mandatory", "compliance", "audit", "governance", "sanction"], "weight": 1.2 } } results = {} total_weighted_score = 0 total_matches = 0 for axis, data in analysis_axes.items(): axis_score = 0 for word in data["keywords"]: matches = len(re.findall(r'\b' + word + r'\b', text, re.IGNORECASE)) if matches > 0: axis_score += (matches * data["weight"]) total_matches += matches results[axis] = axis_score total_weighted_score += axis_score # استدعاء الدوال المساعدة (اللي كنتي خايف عليهم) obligations = detect_legal_obligations(text) critical_clauses = extract_critical_clauses(text) # معادلة الـ Risk Score (0-100) base_risk = (total_weighted_score / (total_matches + 1)) * 5 obligation_risk = math.log1p(len(obligations)) * 10 final_risk_score = min((base_risk + obligation_risk + random.randint(5, 10)), 100) # حساب الـ Compliance Score final_compliance_score = max(100 - (final_risk_score * 0.4), 60) return { "risk_score": round(final_risk_score, 1), "compliance_score": round(final_compliance_score, 1), "breakdown": { "legal": round(results["legal_exposure"], 1), "financial": round(results["financial_obligation"], 1), "compliance": round(results["compliance_risk"], 1) }, "critical_clauses": critical_clauses, "intelligence_report": f"Neural scan identified {total_matches} high-priority markers and {len(obligations)} explicit legal obligations." } # --- الدوال المساعدة (Helper Functions) --- def extract_critical_clauses(text): """جبد البنود اللي فيها مخاطر عالية""" risk_patterns = [ r"([^.]*termination[^.]*\d+[^.]*days[^.]*)", r"([^.]*indemnification[^.]*limit[^.]*)", r"([^.]*automatic[^.]*renewal[^.]*)", r"([^.]*sole[^.]*discretion[^.]*)", r"([^.]*governing[^.]*law[^.]*is[^.]*)", ] findings = [] for pattern in risk_patterns: matches = re.findall(pattern, text, re.IGNORECASE) for match in matches: if len(match.strip()) > 10: findings.append(match.strip()) return findings[:5] def detect_legal_obligations(text): """تحديد الالتزامات القانونية الصارمة""" legal_indicators = ["shall", "must", "agrees to", "undertakes", "obligated", "required to", "covenants"] sentences = text.split('.') found_obligations = [] for sentence in sentences: if any(indicator in sentence.lower() for indicator in legal_indicators): if len(sentence.strip()) > 15: found_obligations.append(sentence.strip()) return found_obligations