OmniFile-Processor / docs /DEVELOPER_GUIDE.md
Dr. Abdulmalek
deploy: OmniFile AI Processor v4.3.0
900df0b
# دليل المطور - 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`