# دليل المطور - OmniFile AI Processor v2.1 # Developer Guide - OmniFile AI Processor v2.1 > **دليل شامل للمطورين لبناء وتوسيع النظام** > **Comprehensive developer guide for building and extending the system** --- ## المحتويات / Table of Contents 1. [هيكل المشروع / Project Structure](#1-هيكل-المشروع--project-structure) 2. [البنية المعمارية / Architecture](#2-البنية-المعمارية--architecture) 3. [إضافة ميزة جديدة / Adding a New Feature](#3-إضافة-ميزة-جديدة--adding-a-new-feature) 4. [إضافة لغة جديدة / Adding a New Language](#4-إضافة-لغة-جديدة--adding-a-new-language) 5. [إضافة محرك OCR جديد / Adding a New OCR Engine](#5-إضافة-محرك-ocr-جديد--adding-a-new-ocr-engine) 6. [الاختبارات / Testing](#6-الاختبارات--testing) 7. [النشر / Deployment](#7-النشر--deployment) 8. [معايير الكود / Code Standards](#8-معايير-الكود--code-standards) 9. [GitHub Actions CI/CD](#9-github-actions-cicd) --- ## 1. هيكل المشروع / Project Structure ``` OmniFile_Processor/ | |-- app.py # واجهة Streamlit الرئيسية / Main Streamlit UI |-- main.py # نقطة الدخول / Entry point (CLI arguments) |-- config.py # الإعدادات المركزية / Central config (OmniFileConfig dataclass) |-- database.py # قاعدة بيانات SQLite / SQLite database (OmniFileDB) |-- tasks.py # مهام Celery / Celery async tasks |-- requirements.txt # التبعيات / Dependencies |-- Dockerfile # Docker للنشر / Docker for deployment |-- LICENSE # الترخيص / License |-- __init__.py # إصدار المشروع / Project version | |-- modules/ # الوحدات الفرعية / Sub-modules | |-- __init__.py | | | |-- nlp/ # معالجة اللغة الطبيعية / NLP | | |-- __init__.py | | |-- spell_corrector.py # المصحح الإملائي / Spell corrector (EN/AR/DE) | | |-- translator.py # المترجم التقني / Technical translator | | |-- summarizer.py # ملخص النصوص / Text summarizer (BART) | | |-- entity_extractor.py # استخراج الكيانات / NER entity extraction | | |-- text_classifier.py # تصنيف النصوص / Text classification | | |-- language_detector.py # كشف اللغة / Language detection | | |-- correction_dict.json # قاموس التصحيحات المُتعلمة / Learned corrections | | | |-- vision/ # الرؤية الحاسوبية / Computer Vision | | |-- __init__.py | | |-- ocr_engine.py # محرك OCR المتكامل / Integrated OCR engine | | |-- pdf_processor.py # معالج PDF / PDF processor | | |-- image_preprocessor.py # معالجة الصور المسبقة / Image preprocessing | | |-- text_reconstructor.py # إعادة تجميع النصوص / Text reconstruction | | | |-- security/ # الأمان والحماية / Security | |-- __init__.py | |-- sensitive_data_scanner.py # فحص البيانات الحساسة / Sensitive data scanner | |-- file_scanner.py # فحص الملفات / File scanner | |-- file_organizer.py # تنظيم الملفات / File organizer | |-- backup_manager.py # النسخ الاحتياطي / Backup manager | |-- archive_handler.py # معالجة الأرشيفات / Archive handler | |-- code_protector.py # حماية الكود / Code protection | |-- src/ # محرك HandwrittenOCR المتقدم / Advanced HandwrittenOCR | |-- __init__.py | |-- main.py # نقطة دخول المحرك / Engine entry point | |-- gradio_ui.py # واجهة Gradio المتقدمة / Advanced Gradio UI | |-- recognition.py # التعرف المتقدم / Advanced recognition | |-- preprocessing.py # المعالجة المسبقة المتقدمة / Advanced preprocessing | |-- reconstruction.py # إعادة البناء المتقدمة / Advanced reconstruction | |-- correction.py # التصحيح المتقدم / Advanced correction | |-- export.py # التصدير / Export | |-- pdf_processor.py # معالج PDF متقدم / Advanced PDF processor | |-- review_ui.py # واجهة المراجعة / Review UI | |-- study_guide.py # دليل الدراسة / Study guide | |-- finetuning.py # التدريب الدقيق / Fine-tuning | |-- metrics.py # مقاييس الأداء / Performance metrics | |-- database.py # قاعدة بيانات المحرك / Engine database | |-- migration.py # ترحيل البيانات / Data migration | |-- sync.py # المزامنة / Synchronization | |-- logger.py # نظام التسجيل / Logging system | |-- tests/ # الاختبارات / Tests | |-- __init__.py | |-- conftest.py # Fixtures المشتركة / Shared fixtures | |-- test_spell_corrector.py # اختبارات المصحح / Spell corrector tests | |-- test_summarizer.py # اختبارات التلخيص / Summarizer tests | |-- test_sensitive_scanner.py # اختبارات فحص البيانات / Sensitive data tests | |-- notebooks/ | # دفاتر Jupyter / Jupyter notebooks | |-- OmniFile_Complete.ipynb # دفتر شامل / Complete notebook | |-- HandwrittenOCR_Ultimate.ipynb | |-- HandwrittenOCR_Colab.ipynb | |-- data_seed/ | # بيانات أولية / Seed data | |-- correction_dict_seed.json # بذرة قاموس التصحيح / Corrections seed | |-- artifacts/ | # ملفات مُنتجة / Generated artifacts | |-- correction_dict.json | |-- database/ | # ملفات قاعدة البيانات / Database files |-- data/ | # بيانات المستخدم / User data | |-- raw/ | # ملفات خام / Raw files | | |-- pdfs/ | | |-- images/ | | |-- archives/ | |-- processed/ | # ملفات معالجة / Processed files | |-- exports/ | # ملفات مُصدّرة / Exported files |-- models_cache/ | # تخزين النماذج / Model cache |-- backups/ | # نسخ احتياطية / Backups |-- logs/ | # سجلات / Logs |-- docs/ | # التوثيق / Documentation ``` --- ## 2. البنية المعمارية / Architecture ### 2.1 نظرة عامة / Overview يعتمد النظام على بنية **وحدات (Modular)** مع **تحميل بطيء (Lazy Loading)** و**انحطاط سلس (Graceful Degradation)**: ``` ┌─────────────────────────────────────────────────────────────┐ │ واجهة المستخدم / UI │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ Streamlit │ │ Gradio │ │ CLI │ │ │ │ (app.py) │ │ (gradio_ui) │ │ (main.py) │ │ │ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │ └─────────┼─────────────────┼───────────────────┼─────────────┘ │ │ │ v v v ┌─────────────────────────────────────────────────────────────┐ │ الإعدادات / Config │ │ OmniFileConfig (config.py) │ │ إعدادات OCR | NLP | الأمان | النشر | اللغات │ └──────────────────────────┬──────────────────────────────────┘ │ ┌────────────────┼────────────────┐ v v v ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │ modules/ │ │ modules/ │ │ modules/ │ │ vision/ │ │ nlp/ │ │ security/ │ │ │ │ │ │ │ │ OCR Engine │ │ SpellCheck │ │ SensitiveScan│ │ PDF Process │ │ Translator │ │ FileOrganize │ │ ImgProcess │ │ Summarizer │ │ BackupMgr │ │ TextRecon │ │ NER │ │ CodeProtect │ │ │ │ Classify │ │ FileScan │ │ │ │ LangDetect │ │ ArchiveHand │ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ v v v ┌─────────────────────────────────────────────────────────────┐ │ قاعدة البيانات / Database │ │ OmniFileDB (database.py) │ │ documents | ocr_results | translations | entities │ │ corrections_log | processing_history │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.2 الوحدات الأساسية / Core Modules #### `config.py` - الإعدادات المركزية / Central Configuration فئة `OmniFileConfig` هي Dataclass مركزية تحتوي جميع إعدادات النظام: The `OmniFileConfig` class is a central dataclass containing all system settings: ```python from config import OmniFileConfig # إنشاء إعدادات لبيئة مختلفة / Create config for different environments cfg_local = OmniFileConfig.from_local(project_root="~/OmniFile_AI") cfg_colab = OmniFileConfig.from_colab_drive() cfg_custom = OmniFileConfig( use_gpu=True, enable_trocr=True, trocr_model_variant="small", ) # حفظ وتحميل الإعدادات / Save and load config cfg.save("config/settings.json") cfg_loaded = OmniFileConfig.load("config/settings.json") # إعداد البيئة / Setup environment cfg.setup_environment() # ينشئ المجلدات ويضبط المتغيرات # خصائص مفيدة / Useful properties cfg.db_path # مسار قاعدة البيانات cfg.data_raw_dir # مجلد الملفات الخام cfg.models_cache_dir # مجلد تخزين النماذج cfg.is_colab # كشف بيئة Colab ``` #### `database.py` - قاعدة البيانات / Database فئة `OmniFileDB` تدير قاعدة بيانات SQLite بوضع WAL: The `OmniFileDB` class manages an SQLite database in WAL mode: ```python from database import OmniFileDB db = OmniFileDB("omnifile_data.db") db.create_tables() # إدراج مستند / Insert document doc_id = db.insert_document({ "file_name": "test.pdf", "file_type": "pdf", "raw_text": "Hello World", "language": "en", }) # تحديث مستند / Update document db.update_document(doc_id, {"corrected_text": "Hello World!", "is_reviewed": True}) # جلب مستند / Get document doc = db.get_document(doc_id) # بحث / Search results = db.search_documents("hello") # إحصائيات / Statistics stats = db.get_stats() # تصدير / Export db.export_to_json("backup.json") ``` **الجداول / Tables:** | الجدول / Table | الوصف / Description | |---|---| | `documents` | المستندات الأساسية (file_name, raw_text, corrected_text, category, ...) | | `ocr_results` | نتائج OCR التفصيلية (word_text, confidence, model_source, x, y, w, h) | | `translations` | الترجمات (source_text, translated_text, source_lang, target_lang) | | `entities` | الكيانات المسماة (entity_text, entity_type, confidence) | | `corrections_log` | سجل التصحيحات (original_text, corrected_text, auto_or_manual) | | `processing_history` | سجل المعالجة (action, target, status, duration_sec) | #### `app.py` - واجهة Streamlit / Streamlit UI الواجهة الرئيسية مبنية بتبويبات Streamlit: The main interface is built with Streamlit tabs: ```python # app.py يستخدم OmniFileConfig و OmniFileDB # app.py uses OmniFileConfig and OmniFileDB # الهيكل العام / General structure: # - شريط جانبي للإعدادات / Sidebar for settings # - تبويبات للميزات / Tabs for features # - رفع الملفات / File upload # - عرض النتائج / Results display ``` ### 2.3 الوحدات الفرعية / Sub-modules #### `modules/nlp/` - معالجة اللغة الطبيعية / NLP | الملف / File | الفئة / Class | الوصف / Description | |---|---|---| | `spell_corrector.py` | `SpellCorrector` | تصحيح إملائي متعدد اللغات (EN/AR/DE) مع حماية المصطلحات | | `translator.py` | `TechnicalTranslator` | ترجمة تقنية مع حماية الكود وقاموس مدمج | | `summarizer.py` | `TextSummarizer` | تلخيص بـ BART مع كشف لغة تلقائي وكاش | | `entity_extractor.py` | - | استخراج الكيانات المسماة (NER) بـ AraBERT | | `text_classifier.py` | `TextClassifier` | تصنيف المستندات بـ AraBERTv2 | | `language_detector.py` | `LanguageDetector` | كشف لغة النص تلقائياً | #### `modules/vision/` - الرؤية الحاسوبية / Computer Vision | الملف / File | الفئة / Class | الوصف / Description | |---|---|---| | `ocr_engine.py` | `OCREngine` | محرك متكامل (TrOCR + EasyOCR + Tesseract) مع كاش و ONNX | | `pdf_processor.py` | `PDFProcessor` | معالجة PDF (تحويل صفحات لصور، استخراج نص) | | `image_preprocessor.py` | `ImagePreprocessor` | معالجة مسبقة (CLAHE, denoise, deskew, binarize) | | `text_reconstructor.py` | `TextReconstructor` | إعادة تجميع النص من مربعات الكلمات | #### `modules/security/` - الأمان والحماية / Security | الملف / File | الفئة / Class | الوصف / Description | |---|---|---| | `sensitive_data_scanner.py` | `SensitiveDataScanner` | فحص بيانات حساسة (Presidio + Regex) وإخفائها | | `file_scanner.py` | - | فحص أمني للملفات (امتدادات، أنماط) | | `file_organizer.py` | `FileOrganizer` | فرز تلقائي حسب النوع | | `backup_manager.py` | `BackupManager` | نسخ احتياطي واستعادة | | `archive_handler.py` | - | معالجة الأرشيفات (zip, tar.gz) | | `code_protector.py` | - | حماية مقاطع الكود من المعالجة | #### `src/` - محرك HandwrittenOCR المتقدم / Advanced HandwrittenOCR Engine محرك متقدم للخط اليدوي مع واجهة Gradio: Advanced handwriting engine with Gradio interface: | الملف / File | الوصف / Description | |---|---| | `main.py` | نقطة دخول المحرك | | `gradio_ui.py` | واجهة Gradio المتقدمة | | `recognition.py` | التعرف المتقدم بالدفعات | | `preprocessing.py` | معالجة مسبقة متقدمة | | `reconstruction.py` | إعادة بناء النص | | `correction.py` | التصحيح المتقدم | | `finetuning.py` | التدريب الدقيق بـ LoRA | | `metrics.py` | حساب CER/WER | | `study_guide.py` | إنشاء أدلة دراسة | --- ## 3. إضافة ميزة جديدة / Adding a New Feature ### مثال عملي: إضافة ميزة Question Answering / Practical Example: Adding QA Feature ### الخطوة 1: إنشاء ملف الوحدة / Step 1: Create the Module File ```python # modules/nlp/qa_engine.py """ محرك الأسئلة والأجوبة (Question Answering Engine) v1.0 ========================================================== إجابة على الأسئلة بناءً على سياق نصي. المؤلف: Your Name الإصدار: 1.0.0 """ import logging from typing import Optional logger = logging.getLogger(__name__) class QAEngine: """ محرك الأسئلة والأجوبة — يجيب على أسئلة بناءً على سياق نصي. الميزات: - تحميل بطيء (Lazy Loading) - كشف GPU تلقائي - دعم اللغتين العربية والإنجليزية - انحطاط سلس عند الفشل """ # النماذج المدعومة حسب اللغة MODELS_BY_LANG = { "en": "deepset/roberta-base-squad2", "ar": "deepset/roberta-base-squad2", # يمكن تغييره لنموذج عربي } def __init__( self, model_name: Optional[str] = None, device: Optional[str] = None, max_answer_length: int = 50, ) -> None: """ تهيئة محرك QA. المعاملات: model_name: اسم النموذج (إذا None، يُختار حسب اللغة) device: الجهاز ('cuda' أو 'cpu' أو None لتلقائي) max_answer_length: أقصى طول للإجابة """ self.model_name = model_name self._device = device or self._detect_device() self.max_answer_length = max_answer_length # النموذج - تُحمّل بشكل بطيء self._pipeline = None self._loaded_model_name = None # فحص توفر المكتبات self._has_transformers = self._check_library("transformers") self._has_torch = self._check_library("torch") @staticmethod def _detect_device() -> str: """كشف أفضل جهاز متاح.""" try: import torch if torch.cuda.is_available(): return "cuda" except (ImportError, Exception): pass return "cpu" @staticmethod def _check_library(import_name: str) -> bool: """التحقق من توفر مكتبة.""" try: __import__(import_name) return True except ImportError: return False def _load_pipeline(self, model_name: str) -> bool: """تحميل نموذج QA (يتم مرة واحدة).""" if self._loaded_model_name == model_name and self._pipeline is not None: return True if not (self._has_transformers and self._has_torch): logger.warning( "مكتبات transformers/torch غير مثبتة. " "pip install transformers torch" ) return False try: from transformers import pipeline logger.info("جارٍ تحميل نموذج QA: %s على %s...", model_name, self._device) self._pipeline = pipeline( "question-answering", model=model_name, device=self._device, ) self._loaded_model_name = model_name logger.info("تم تحميل نموذج QA بنجاح") return True except Exception as e: logger.error("فشل تحميل نموذج QA '%s': %s", model_name, e) return False def answer( self, question: str, context: str, language: Optional[str] = None, ) -> dict: """ الإجابة على سؤال بناءً على سياق. المعاملات: question: السؤال context: النص المرجعي language: لغة النص (إذا None، يُكشف تلقائياً) العائد: قاموس يحتوي: - answer: الإجابة - score: مستوى الثقة - start: بداية الإجابة في السياق - end: نهاية الإجابة في السياق - model: النموذج المستخدم """ if not question or not context: return { "answer": "", "score": 0.0, "start": 0, "end": 0, "model": "none", "error": "question_or_context_empty", } # اختيار النموذج lang = language or "en" model_name = self.model_name or self.MODELS_BY_LANG.get(lang, self.MODELS_BY_LANG["en"]) # تحميل النموذج if not self._load_pipeline(model_name): return { "answer": "النموذج غير متاح", "score": 0.0, "start": 0, "end": 0, "model": "none", "error": "model_not_loaded", } # الإجابة try: result = self._pipeline( question=question, context=context, max_answer_length=self.max_answer_length, ) return { "answer": result.get("answer", ""), "score": result.get("score", 0.0), "start": result.get("start", 0), "end": result.get("end", 0), "model": self._loaded_model_name, } except Exception as e: logger.error("فشل الإجابة: %s", e) return { "answer": "", "score": 0.0, "start": 0, "end": 0, "model": self._loaded_model_name, "error": str(e), } def is_available(self) -> bool: """هل المحرك متاح؟""" return self._has_transformers and self._has_torch ``` ### الخطوة 2: التسجيل في `__init__.py` / Step 2: Register in `__init__.py` ```python # modules/nlp/__init__.py from .qa_engine import QAEngine __all__ = [ "QAEngine", # ... existing exports ] ``` ### الخطوة 3: إضافة التبويب في `app.py` / Step 3: Add Tab in `app.py` ```python # app.py import streamlit as st from modules.nlp.qa_engine import QAEngine def render_qa_tab(cfg, db): """تبويب الأسئلة والأجوبة.""" st.header("❓ الأسئلة والأجوبة / Question Answering") # Initialize engine if "qa_engine" not in st.session_state: st.session_state.qa_engine = QAEngine( device="cuda" if cfg.use_gpu else "cpu" ) qa = st.session_state.qa_engine # Input question = st.text_input("السؤال / Question", placeholder="e.g., What is machine learning?") context = st.text_area( "السياق / Context", placeholder="Paste the reference text here...", height=200, ) if st.button("إجابة / Answer") and question and context: with st.spinner("جارٍ البحث عن الإجابة..."): result = qa.answer(question, context) if result.get("answer"): st.success(f"**الإجابة / Answer:** {result['answer']}") st.info(f"الثقة / Confidence: {result['score']:.1%}") else: st.error(f"لم يتم العثور على إجابة: {result.get('error', '')}") ``` ### الخطوة 4: تحديث `requirements.txt` / Step 4: Update Requirements ```txt # NLP - Question Answering datasets>=2.15.0 # (transformers و torch موجودان بالفعل) ``` ### الخطوة 5: كتابة الاختبارات / Step 5: Write Tests ```python # tests/test_qa_engine.py """ اختبارات محرك الأسئلة والأجوبة / QA Engine Tests """ import pytest from modules.nlp.qa_engine import QAEngine class TestQAEngine: """اختبارات فئة QAEngine.""" def test_init(self): """اختبار التهيئة.""" qa = QAEngine() assert qa._device in ("cpu", "cuda") assert qa._pipeline is None # لا يُحمّل تلقائياً def test_init_with_model(self): """اختبار التهيئة بنموذج محدد.""" qa = QAEngine(model_name="deepset/roberta-base-squad2") assert qa.model_name == "deepset/roberta-base-squad2" def test_answer_empty_question(self): """اختبار: سؤال فارغ.""" qa = QAEngine() result = qa.answer("", "Some context") assert result["answer"] == "" assert "error" in result def test_answer_empty_context(self): """اختبار: سياق فارغ.""" qa = QAEngine() result = qa.answer("What?", "") assert result["answer"] == "" assert "error" in result def test_answer_returns_expected_keys(self): """اختبار: مفاتيح النتيجة.""" qa = QAEngine() result = qa.answer("Q", "C") expected_keys = {"answer", "score", "start", "end", "model"} assert expected_keys.issubset(result.keys()) def test_is_available(self): """اختبار: توفر المحرك.""" qa = QAEngine() # يجب أن يعيد True إذا كانت المكتبات مثبتة result = qa.is_available() assert isinstance(result, bool) ``` ### الخطوة 6: إضافة الإعداد في `config.py` / Step 6: Add Config Setting ```python # config.py - في OmniFileConfig dataclass # QA Engine enable_qa: bool = True qa_model_name: str = "deepset/roberta-base-squad2" qa_max_answer_length: int = 50 ``` --- ## 4. إضافة لغة جديدة / Adding a New Language ### مثال: إضافة اللغة الفرنسية (FR) / Example: Adding French (FR) ### الخطوة 1: تحديث `config.py` / Step 1: Update Config ```python # config.py @dataclass class OmniFileConfig: # ... supported_languages: list = field( default_factory=lambda: ["en", "ar", "de", "fr"] # أضف "fr" ) ``` ### الخطوة 2: إضافة مصحح في `spell_corrector.py` / Step 2: Add Corrector ```python # modules/nlp/spell_corrector.py class SpellCorrector: def __init__(self, ...): # ... self.supported_languages = ["en", "ar", "de", "fr"] # أضف "fr" # محاولة تحميل المصحح الفرنسي self._fr_corrector = None self._fr_available = False self._try_load_french_corrector() def _try_load_french_corrector(self) -> None: """محاولة تحميل مصحح الفرنسية (pyspellchecker).""" try: from spellchecker import SpellChecker self._fr_corrector = SpellChecker(language="fr") self._fr_available = True logger.info("تم تحميل مصحح الفرنسية (pyspellchecker) بنجاح") except ImportError: logger.warning("مكتبة pyspellchecker غير مثبتة. pip install pyspellchecker") except Exception as e: logger.warning("فشل تحميل مصحح الفرنسية: %s", e) @staticmethod def _is_french_word(word: str) -> bool: """هل الكلمة فرنسية؟""" french_chars = sum(1 for c in word if c in "àâäéèêëïîôùûüÿçœæÀÂÄÉÈÊËÏÎÔÙÛÜŸÇŒÆ") if french_chars > 0: return True latin_chars = sum(1 for c in word if c.isalpha() and c.isascii()) arabic_chars = sum(1 for c in word if "\u0600" <= c <= "\u06FF") return latin_chars > len(word) * 0.5 and arabic_chars == 0 def _correct_french_word(self, word: str) -> Optional[str]: """تصحيح كلمة فرنسية.""" if word in self._protected_terms or word.lower() in self._protected_terms: return None learned = self._get_learned_correction(word) if learned: return learned if self._fr_available and self._fr_corrector: try: if len(word) <= 2: return None if word.lower() in self._fr_corrector.word_frequency: return None candidates = self._fr_corrector.correction(word) if candidates and candidates.lower() != word.lower(): if abs(len(candidates) - len(word)) <= 3: return candidates except Exception as e: logger.debug("خطأ في تصحيح فرنسي '%s': %s", word, e) return None def correct_word(self, word: str) -> str: """تصحيح كلمة واحدة (محدّث لدعم الفرنسية).""" if self._should_skip_word(word): return word if self._is_arabic_word(word): correction = self._correct_arabic_word(word) elif self._is_french_word(word): # <-- أضف هذا الشرط قبل الإنجليزية correction = self._correct_french_word(word) elif self._is_german_word(word): correction = self._correct_german_word(word) elif self._is_english_word(word): correction = self._correct_english_word(word) else: return word return correction if correction else word def is_available(self) -> dict: """فحص توفر المصححات (محدّث).""" return { "english": self._en_available, "arabic": self._ar_available, "german": self._de_available, "french": self._fr_available, # <-- أضف هذا "learned": len(self._learned_corrections) > 0, } ``` ### الخطوة 3: إضافة نموذج ترجمة في `translator.py` / Step 3: Add Translation Model ```python # modules/nlp/translator.py class TechnicalTranslator: TRANSLATION_MODELS: dict[str, str] = { # ... existing models ... # أضف الاتجاهات الجديدة: "en-fr": "Helsinki-NLP/opus-mt-en-fr", "fr-en": "Helsinki-NLP/opus-mt-fr-en", "fr-ar": "Helsinki-NLP/opus-mt-fr-ar", "ar-fr": "Helsinki-NLP/opus-mt-ar-fr", "fr-de": "Helsinki-NLP/opus-mt-fr-de", "de-fr": "Helsinki-NLP/opus-mt-de-fr", } SUPPORTED_LANGUAGES = ["en", "ar", "de", "fr"] # أضف "fr" ``` ### الخطوة 4: إضافة نموذج تلخيص (اختياري) / Step 4: Add Summarization Model (Optional) ```python # modules/nlp/summarizer.py class TextSummarizer: MODELS_BY_LANG = { # ... existing models ... "fr": [ "mrm8488/camembert2camembert_shared-french-summarization", ], } FALLBACK_MODELS = { # ... existing models ... "fr": "mrm8488/camembert2camembert_shared-french-summarization", } ``` ### الخطوة 5: تحديث كشف اللغة / Step 5: Update Language Detection ```python # modules/nlp/summarizer.py - _detect_language() @staticmethod def _detect_language(text: str) -> str: """كشف لغة النص.""" arabic_chars = sum(1 for c in text if "\u0600" <= c <= "\u06FF") german_chars = sum(1 for c in text if c in "äöüÄÖÜß") french_chars = sum(1 for c in text if c in "àâäéèêëïîôùûüÿçœæÀÂÄÉÈÊËÏÎÔÙÛÜŸÇŒÆ") total_alpha = sum(1 for c in text if c.isalpha()) if total_alpha == 0: return "en" if arabic_chars / total_alpha > 0.3: return "ar" if french_chars / total_alpha > 0.05: # <-- أضف هذا قبل الألمانية return "fr" if german_chars / total_alpha > 0.05: return "de" return "en" ``` ### الخطوة 6: اختبارات اللغة الجديدة / Step 6: Tests for New Language ```python # tests/test_spell_corrector.py class TestFrenchCorrection: """اختبارات التصحيح الفرنسي.""" def test_correct_french_word(self): """اختبار تصحيح كلمة فرنسية.""" corrector = SpellCorrector() if corrector.is_available().get("french"): result = corrector.correct_word("bonjor") # خطأ إملائي assert result == "bonjour" def test_protect_french_code(self): """اختبار حماية الكود مع فرنسية.""" corrector = SpellCorrector() result = corrector.correct_word("numpy") # لا يُصحح assert result == "numpy" ``` --- ## 5. إضافة محرك OCR جديد / Adding a New OCR Engine ### واجهة المحرك / Engine Interface كل محرك OCR جديد يجب أن يوفر الواجهة التالية: Every new OCR engine must provide the following interface: ```python from typing import Optional, Union import numpy as np from PIL import Image class NewOCREngine: """ واجهة محرك OCR جديد. يجب أن يوفر: recognize, recognize_batch, recognize_pdf, get_available_engines, unload_models """ def __init__(self, **kwargs): """تهيئة المحرك.""" self._model = None self._loaded = False def recognize( self, image: Union[np.ndarray, Image.Image], languages: Optional[list[str]] = None, ) -> dict: """ التعرف على النص في صورة واحدة. المعاملات: image: صورة PIL أو numpy array languages: لغات مطلوبة العائد: dict: { "text": str, # النص المستخرج "confidence": float, # مستوى الثقة (0-1) "source": str, # اسم المحرك "word_count": int, # عدد الكلمات "words": list[dict], # تفاصيل الكلمات مع الإحداثيات "processing_time": float, "details": dict, } """ raise NotImplementedError def recognize_batch( self, images: list[Union[np.ndarray, Image.Image]], languages: Optional[list[str]] = None, ) -> list[dict]: """التعرف على مجموعة صور.""" return [self.recognize(img, languages) for img in images] def recognize_pdf( self, pdf_path: str, pages: Optional[list[int]] = None, languages: Optional[list[str]] = None, ) -> list[dict]: """استخراج النص من PDF.""" raise NotImplementedError def get_available_engines(self) -> list[dict]: """قائمة المحركات المتاحة.""" return [{"name": "NewOCR", "available": True, "enabled": True}] def unload_models(self) -> None: """تفريغ النماذج من الذاكرة.""" self._model = None self._loaded = False ``` ### التسجيل في `OCREngine` / Register in OCREngine ```python # modules/vision/ocr_engine.py class OCREngine: def __init__(self, ...): # ... existing engines ... self._new_ocr_reader = None self._new_ocr_loaded = False def _recognize_new_ocr(self, image): """التعرف باستخدام المحرك الجديد.""" # ... implementation ... pass def recognize(self, image, languages=None): # ... existing code ... # أضف المحرك الجديد في سلسلة المحركات: if self.enable_new_ocr: new_result = self._recognize_new_ocr(pil_image) if new_result: results.append(new_result) # ... rest of method ... ``` --- ## 6. الاختبارات / Testing ### تشغيل الاختبارات / Running Tests ```bash # تشغيل جميع الاختبارات / Run all tests pytest tests/ -v # تشغيل اختبار محدد / Run specific test pytest tests/test_spell_corrector.py -v # تشغيل مع تغطية الكود / Run with coverage pytest tests/ --cov=modules --cov-report=html # تشغيل اختبارات وحدة محددة / Run specific test class pytest tests/test_spell_corrector.py::TestSpellCorrector::test_correct_word -v # عرض الإخراج المطبوع / Show print output pytest tests/ -v -s ``` ### كتابة اختبار جديد / Writing a New Test ```python # tests/test_qa_engine.py """ اختبارات محرك QA / QA Engine Tests النمط المتبع: Arrange - Act - Assert """ import pytest class TestQAEngine: """اختبارات فئة QAEngine.""" def setup_method(self): """إعداد قبل كل اختبار (Arrange).""" from modules.nlp.qa_engine import QAEngine self.qa = QAEngine() # === اختبارات الحالة السعيدة / Happy Path Tests === def test_answer_returns_dict(self): """اختبار: answer() يعيد dict.""" result = self.qa.answer("What?", "Context text") assert isinstance(result, dict) def test_answer_has_required_keys(self): """اختبار: النتيجة تحتوي المفاتيح المطلوبة.""" result = self.qa.answer("Q", "C") for key in ["answer", "score", "start", "end", "model"]: assert key in result, f"Missing key: {key}" def test_answer_score_is_float(self): """اختبار: score هو رقم بين 0 و 1.""" result = self.qa.answer("Q", "C") assert isinstance(result["score"], (int, float)) assert 0 <= result["score"] <= 1 # === اختبارات الحالات الخطأ / Error Case Tests === def test_answer_empty_question(self): """اختبار: سؤال فارغ يعيد خطأ.""" result = self.qa.answer("", "Context") assert result["answer"] == "" assert "error" in result def test_answer_empty_context(self): """اختبار: سياق فارغ يعيد خطأ.""" result = self.qa.answer("Question", "") assert result["answer"] == "" assert "error" in result def test_answer_none_inputs(self): """اختبار: مدخلات None لا تسبب crash.""" result = self.qa.answer(None, None) assert isinstance(result, dict) # === اختبارات التهيئة / Initialization Tests === def test_lazy_loading(self): """اختبار: النموذج لا يُحمّل تلقائياً.""" qa = QAEngine() assert qa._pipeline is None def test_custom_model_name(self): """اختبار: تمرير اسم نموذج مخصص.""" qa = QAEngine(model_name="custom-model") assert qa.model_name == "custom-model" ``` ### Fixtures المشتركة / Shared Fixtures ```python # tests/conftest.py """ Fixtures المشتركة لجميع الاختبارات. """ import pytest import numpy as np from PIL import Image @pytest.fixture def sample_text_en(): """نص إنجليزي بسيط.""" return "This is a sample text for testing." @pytest.fixture def sample_text_ar(): """نص عربي بسيط.""" return "هذا نص عربي للاختبار." @pytest.fixture def sample_image(): """صورة اختبار بسيطة.""" return Image.new("RGB", (200, 100), color="white") @pytest.fixture def sample_pdf_path(tmp_path): """مسار PDF اختبار مؤقت.""" pdf_path = tmp_path / "test.pdf" return str(pdf_path) @pytest.fixture def sample_config(): """إعدادات اختبار.""" from config import OmniFileConfig return OmniFileConfig( use_gpu=False, enable_trocr=False, enable_easyocr=False, enable_tesseract=False, ) @pytest.fixture def sample_db(tmp_path): """قاعدة بيانات اختبار مؤقتة.""" from database import OmniFileDB db_path = str(tmp_path / "test.db") db = OmniFileDB(db_path) db.create_tables() yield db # cleanup ``` --- ## 7. النشر / Deployment ### 7.1 HuggingFace Spaces (Docker) / HuggingFace Spaces المشروع يحتوي `Dockerfile` جاهز. للنشر: The project includes a ready `Dockerfile`. To deploy: ```dockerfile # Dockerfile (موجود بالفعل / already exists) FROM python:3.11-slim WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ poppler-utils \ tesseract-ocr \ tesseract-ocr-ara \ tesseract-ocr-eng \ && rm -rf /var/lib/apt/lists/* # Copy requirements and install Python packages COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy all project files COPY . . # Expose Streamlit default port EXPOSE 7860 # Health check HEALTHCHECK CMD curl -f http://localhost:7860/_stcore/health || exit 1 # Run Streamlit CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"] ``` ### 7.2 Docker Compose (مع Redis) / Docker Compose (with Redis) ```yaml # docker-compose.yml version: '3.8' services: omnifile: build: . ports: - "7860:7860" environment: - ENABLE_CELERY=true - CELERY_BROKER_URL=redis://redis:6379/0 volumes: - ./data:/app/data - ./models_cache:/app/models_cache - ./database:/app/database depends_on: - redis deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data celery-worker: build: . command: celery -A tasks worker --loglevel=info environment: - ENABLE_CELERY=true - CELERY_BROKER_URL=redis://redis:6379/0 volumes: - ./data:/app/data - ./models_cache:/app/models_cache depends_on: - redis volumes: redis_data: ``` ```bash # تشغيل / Run docker-compose up -d # مشاهدة السجلات / View logs docker-compose logs -f omnifile # إيقاف / Stop docker-compose down ``` ### 7.3 Google Colab / Google Colab ```python # main.py --colab # أو: from config import OmniFileConfig cfg = OmniFileConfig.from_colab_drive() cfg.setup_environment() # ثم: !streamlit run app.py --server.port 7860 ``` ### 7.4 AWS / Azure / GCP ```bash # باستخدام Docker / Using Docker # AWS ECS: docker build -t omnifile . aws ecs register-task-definition --family omnifile --container-definitions ... # Azure Container Apps: az containerapp create --image omnifile ... # GCP Cloud Run: gcloud run deploy omnifile --source . --port 7860 ``` ### 7.5 Celery Workers (المعالجة غير المتزامنة) / Celery Workers ```bash # 1. تشغيل Redis redis-server & # 2. تشغيل Worker celery -A tasks worker --loglevel=info --concurrency=4 # 3. تشغيل Worker مع مراقبة celery -A tasks worker --loglevel=info --concurrency=2 \ --max-tasks-per-child=1000 \ --time-limit=300 \ --soft-time-limit=240 # 4. مراقبة المهام celery -A tasks inspect active celery -A tasks inspect reserved celery -A tasks events --dump ``` --- ## 8. معايير الكود / Code Standards ### 8.1 التنسيق / Formatting ```python # 1. Python 3.8+ type hints def process_text(text: str, language: str = "en") -> dict: ... # 2. توثيق عربي + إنجليزي class SpellCorrector: """ مصحح إملائي ذكي — يدعم العربية والإنجليزية مع حماية المصطلحات البرمجية. Smart spell corrector — supports Arabic and English with code term protection. الميزات / Features: - تصحيح متعدد اللغات - حماية المصطلحات التقنية - تعلم من المستخدم """ def correct_word(self, word: str) -> str: """ تصحيح كلمة واحدة. Correct a single word. المعاملات: word: الكلمة المراد تصحيحها / The word to correct العائد: الكلمة المصححة / The corrected word """ ... ``` ### 8.2 التسجيل / Logging ```python # استخدم logging بدلاً من print / Use logging instead of print import logging logger = logging.getLogger(__name__) # الصحيح / Correct: logger.info("تم تحميل النموذج: %s", model_name) logger.warning("فشل التحميل: %s", error) logger.error("خطأ حرج: %s", error, exc_info=True) logger.debug("قيمة متغيرة: %s", value) # الخاطئ / Wrong: print("تم التحميل") # لا تستخدم print في الإنتاج print(f"Error: {error}") # لا تستخدم print ``` ### 8.3 معالجة الأخطاء / Error Handling ```python # 1. Graceful Degradation - انحطاط سلس try: from presidio_analyzer import AnalyzerEngine self._analyzer = AnalyzerEngine() self._presidio_available = True except ImportError: logger.warning("presidio غير مثبت. سيتم استخدام Regex فقط.") self._presidio_available = False except Exception as e: logger.warning("فشل تحميل presidio: %s", e) self._presidio_available = False # 2. لا تستخدم except: فارغ / Never use bare except: try: ... except: # خاطئ / Wrong pass # 3. حدد نوع الاستثناء / Specify exception type try: ... except (ValueError, TypeError) as e: # صحيح / Correct logger.error("خطأ: %s", e) ``` ### 8.4 تحميل بطيء / Lazy Loading ```python # النماذج الثقيلة لا تُحمّل في __init__ / Heavy models don't load in __init__ class MyEngine: def __init__(self): self._model = None # لا تحميل هنا / Don't load here self._loaded = False def _load_model(self) -> bool: """تحميل النموذج عند أول استخدام / Load model on first use.""" if self._loaded: return True try: self._model = load_heavy_model() self._loaded = True return True except Exception as e: logger.error("فشل التحميل: %s", e) return False def process(self, data): if not self._load_model(): # تحميل عند الحاجة / Load when needed return self._fallback(data) return self._model.process(data) ``` ### 8.5 إدارة الذاكرة / Memory Management ```python # 1. تنظيف الذاكرة بعد المعالجة import gc import torch def process_batch(images): results = [] for img in images: result = process(img) results.append(result) # تنظيف / Cleanup torch.cuda.empty_cache() gc.collect() return results # 2. تفريغ النماذج عند الحاجة def unload_models(self): """تفريغ النماذج من الذاكرة.""" self._model = None self._loaded = False torch.cuda.empty_cache() gc.collect() ``` ### 8.6 دعم اللغات / Language Support ```python # كل نص مرئي للمستخدم يجب أن يكون ثنائي اللغة # All user-visible text must be bilingual # أسماء المتغيرات والدوال: إنجليزية / Variable and function names: English def correct_text(text: str) -> dict: # الرسائل والتوثيق: عربي + إنجليزي / Messages and docs: Arabic + English logger.info("تم تحميل المصحح بنجاح / Spell corrector loaded successfully") st.success("تمت المعالجة بنجاح / Processing completed successfully") # أخطاء: عربي + إنجليزي / Errors: Arabic + English raise ValueError("حقل file_name مطلوب / file_name field is required") ``` --- ## 9. GitHub Actions CI/CD ### 9.1 اختبارات تلقائية / Automated Tests ```yaml # .github/workflows/tests.yml name: Tests on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y tesseract-ocr tesseract-ocr-ara - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-cov - name: Run tests run: | pytest tests/ -v --cov=modules --cov-report=xml - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.xml lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - run: pip install ruff - run: ruff check modules/ src/ tests/ ``` ### 9.2 بناء Docker / Docker Build ```yaml # .github/workflows/docker.yml name: Docker Build on: push: tags: ['v*'] workflow_dispatch: jobs: build-and-push: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to DockerHub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ secrets.DOCKER_USERNAME }}/omnifile-processor:latest ${{ secrets.DOCKER_USERNAME }}/omnifile-processor:${{ github.ref_name }} cache-from: type=gha cache-to: type=gha,mode=max ``` ### 9.3 نشر على HuggingFace Spaces / Deploy to HuggingFace Spaces ```yaml # .github/workflows/deploy.yml name: Deploy to HuggingFace on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Push to HuggingFace env: HF_TOKEN: ${{ secrets.HF_TOKEN }} run: | git clone https://DrAbdulmalek:$HF_TOKEN@huggingface.co/spaces/DrAbdulmalek/OmniFile_Processor hf_space rsync -av --delete --exclude='.git' ./ hf_space/ cd hf_space git add . git commit -m "Deploy from GitHub Actions" git push ``` --- > **المؤلف / Author:** Dr Abdulmalek Tamer Al-husseini > **📍 الموقع / Location:** Homs, Syria > **📧 البريد / Email:** Abdulmalek.husseini@gmail.com > **الإصدار / Version:** 2.1 > **الترخيص / License:** راجع ملف `LICENSE` > **المساهمة / Contributing:** Pull Requests مرحب بها على branch `develop`