First commit

This commit is contained in:
2025-07-09 02:12:06 +02:00
parent 1a7946495c
commit a5e044d747
23 changed files with 1967 additions and 0 deletions

14
worker/app/__init__.py Normal file
View File

@@ -0,0 +1,14 @@
"""
Module d'initialisation du worker
"""
import logging
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("cheque_scanner_worker_module")
logger.info("Module worker initialisé")

View File

@@ -0,0 +1,14 @@
"""
Module proxy pour l'API
"""
import logging
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("app.proxy")
logger.info("Module proxy app initialisé")

View File

@@ -0,0 +1,37 @@
"""
Module proxy pour importer process_cheque_image depuis le module tasks parent
"""
import sys
import os
import logging
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("app.tasks.proxy")
# Importer la fonction process_cheque_image depuis le module tasks parent
logger.info("Initialisation du module proxy app.tasks")
# Ajouter le répertoire parent au path pour pouvoir importer tasks
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))
sys.path.insert(0, parent_dir)
try:
# Importer depuis le module tasks parent
from tasks import process_cheque_image
logger.info("Fonction process_cheque_image importée avec succès")
except ImportError as e:
logger.error(f"Erreur lors de l'importation de process_cheque_image: {str(e)}")
# Créer une fonction de secours qui enregistre l'erreur
def process_cheque_image(job_id, file_path):
logger.error(f"Appel à la fonction de secours process_cheque_image: {job_id}, {file_path}")
raise RuntimeError(f"Impossible d'importer la véritable fonction process_cheque_image: {str(e)}")
# Exporter la fonction pour qu'elle soit accessible via app.tasks.process_cheque_image
__all__ = ['process_cheque_image']
logger.info("Module proxy app.tasks initialisé")

16
worker/app/entrypoint.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
# Suppression de la vérification Redis qui nécessite redis-cli
echo "Démarrage du worker..."
# Créer les dossiers nécessaires s'ils n'existent pas
mkdir -p /app/data/uploads
mkdir -p /app/data/results
mkdir -p /app/data/tmp
# Définir les permissions
chmod -R 755 /app/data
# Exécuter la commande spécifiée
exec "$@"

View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
# Suppression de la vérification Redis qui nécessite redis-cli
echo "Démarrage du worker prioritaire..."
# Créer les dossiers nécessaires s'ils n'existent pas
mkdir -p /app/data/uploads
mkdir -p /app/data/results
mkdir -p /app/data/tmp
# Définir les permissions
chmod -R 755 /app/data
# Exécuter la commande spécifiée
exec "$@"

232
worker/app/tasks.py Normal file
View File

@@ -0,0 +1,232 @@
"""
Tâches de traitement pour le worker Cheque Scanner
"""
import os
import sys
import time
import json
import logging
import traceback
import redis
from datetime import datetime
from rq import get_current_job
# Ajouter le module d'extraction au path
sys.path.append('/app/shared')
# Importer les fonctions d'extraction
from extraction import (
extraire_infos_cheque,
get_tessdata_path
)
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("cheque_scanner_tasks")
# Variables d'environnement
REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
DEFAULT_OCR_LANGUAGE = os.getenv("DEFAULT_OCR_LANGUAGE", "eng")
ALTERNATIVE_OCR_LANGUAGE = os.getenv("ALTERNATIVE_OCR_LANGUAGE", "fra")
TESSERACT_DATA_PATH = os.getenv("TESSERACT_DATA_PATH", "/usr/share/tesseract-ocr/4.00/tessdata")
def update_job_status(job_id, status, message=None, progress=None, result=None, texte_brut=None, erreur=None, methode=None):
"""
Met à jour le statut d'une tâche dans Redis
"""
try:
# Connexion à Redis
redis_conn = redis.Redis.from_url(REDIS_URL)
# Préparer les données à mettre à jour
update_data = {
"status": status,
"updated_at": datetime.now().isoformat()
}
if message:
update_data["message"] = message
if progress:
update_data["progress"] = str(progress)
if result:
update_data["result"] = str(result)
if texte_brut:
update_data["texte_brut"] = texte_brut
if erreur:
update_data["erreur"] = erreur
if methode:
update_data["methode"] = methode
if status == "completed":
update_data["completed_at"] = datetime.now().isoformat()
# Mettre à jour les données dans Redis
redis_conn.hset(f"job:{job_id}", mapping=update_data)
logger.info(f"Statut de la tâche {job_id} mis à jour: {status}")
return True
except Exception as e:
logger.error(f"Erreur lors de la mise à jour du statut de la tâche {job_id}: {str(e)}")
return False
def process_cheque_image(file_path, job_id):
"""
Traite une image de chèque pour en extraire les informations
Args:
file_path (str): Chemin vers l'image à traiter
job_id (str): Identifiant de la tâche
Returns:
dict: Résultat de l'extraction
"""
job = get_current_job()
logger.info(f"Début du traitement de l'image: {file_path} (Tâche: {job_id})")
# Mettre à jour le statut
update_job_status(
job_id=job_id,
status="processing",
message="Traitement en cours",
progress=10
)
try:
# Vérifier que le fichier existe
if not os.path.exists(file_path):
raise FileNotFoundError(f"L'image {file_path} n'existe pas")
# Récupérer le chemin vers tessdata
tessdata_path = TESSERACT_DATA_PATH
if not os.path.exists(tessdata_path):
# Essayer de trouver automatiquement
tessdata_path = get_tessdata_path()
# Mise à jour intermédiaire
update_job_status(
job_id=job_id,
status="processing",
message="Extraction des informations en cours",
progress=30
)
# Première tentative avec la langue par défaut
try:
logger.info(f"Tentative d'extraction avec la langue: {DEFAULT_OCR_LANGUAGE}")
update_job_status(
job_id=job_id,
status="processing",
message=f"Extraction avec langue {DEFAULT_OCR_LANGUAGE}",
progress=50
)
infos, texte = extraire_infos_cheque(
chemin_image=file_path,
methode="ocr",
language=DEFAULT_OCR_LANGUAGE,
tessdata=tessdata_path
)
methode = "ocr"
except Exception as e:
logger.warning(f"Échec de la première tentative: {str(e)}")
# Deuxième tentative avec la langue alternative
try:
logger.info(f"Tentative d'extraction avec la langue: {ALTERNATIVE_OCR_LANGUAGE}")
update_job_status(
job_id=job_id,
status="processing",
message=f"Extraction avec langue {ALTERNATIVE_OCR_LANGUAGE}",
progress=60
)
infos, texte = extraire_infos_cheque(
chemin_image=file_path,
methode="ocr",
language=ALTERNATIVE_OCR_LANGUAGE,
tessdata=tessdata_path
)
methode = "ocr"
except Exception as e2:
logger.warning(f"Échec de la deuxième tentative: {str(e2)}")
# Troisième tentative avec la méthode CV
logger.info("Tentative d'extraction avec la méthode CV (sans OCR)")
update_job_status(
job_id=job_id,
status="processing",
message="Extraction par détection de zones (sans OCR)",
progress=70
)
infos, texte = extraire_infos_cheque(
chemin_image=file_path,
methode="cv"
)
methode = "cv"
# Mise à jour finale
update_job_status(
job_id=job_id,
status="processing",
message="Finalisation des résultats",
progress=90
)
# Sauvegarder les résultats dans Redis
update_job_status(
job_id=job_id,
status="completed",
message="Extraction terminée avec succès",
progress=100,
result=infos,
texte_brut=texte,
methode=methode
)
logger.info(f"Traitement terminé pour la tâche {job_id}")
return {
"job_id": job_id,
"status": "completed",
"result": infos,
"methode": methode
}
except Exception as e:
# Capturer l'erreur
error_trace = traceback.format_exc()
logger.error(f"Erreur lors du traitement de l'image: {str(e)}\n{error_trace}")
# Mettre à jour le statut avec l'erreur
update_job_status(
job_id=job_id,
status="failed",
message="Échec du traitement",
erreur=str(e)
)
# Retourner l'erreur
return {
"job_id": job_id,
"status": "failed",
"error": str(e)
}

142
worker/app/worker.py Normal file
View File

@@ -0,0 +1,142 @@
"""
Worker principal pour le traitement des images de chèques
"""
import os
import sys
import time
import redis
import logging
import signal
import argparse
import importlib
from rq import Connection, Worker, Queue
from rq.job import Job
from rq.worker import SimpleWorker
from dotenv import load_dotenv
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("cheque_scanner_worker")
# Ajouter les modules au path
sys.path.append('/app/shared')
sys.path.append('/app/api_app')
sys.path.append('/app')
# Importer notre module tasks local
try:
from tasks import process_cheque_image
logger.info("Fonction process_cheque_image importée avec succès")
except ImportError as e:
logger.error(f"Erreur lors de l'importation directe de process_cheque_image: {str(e)}")
# Charger les variables d'environnement
load_dotenv()
# Classe Worker personnalisée qui remplace l'importation de fonction
class CustomWorker(SimpleWorker):
def perform_job(self, job, queue):
"""
Remplace l'importation de app.tasks.process_cheque_image par notre fonction locale
"""
# Si la tâche est app.tasks.process_cheque_image, remplacer par notre fonction locale
if job.func_name == 'app.tasks.process_cheque_image' or job.func_name == 'process_cheque_image' or job.func_name == 'tasks.process_cheque_image':
job.func_name = 'tasks.process_cheque_image'
job._func = process_cheque_image
logger.info(f"Fonction remplacée pour la tâche {job.id}")
return super().perform_job(job, queue)
# Variables d'environnement
REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
QUEUE_NAME = os.getenv("QUEUE_NAME", "cheque_processing")
HIGH_PRIORITY_QUEUE_NAME = os.getenv("HIGH_PRIORITY_QUEUE_NAME", "cheque_processing_high")
WORKER_NAME = os.getenv("WORKER_NAME", f"worker-{os.getpid()}")
# Créer les dossiers nécessaires
UPLOAD_FOLDER = os.getenv("UPLOAD_FOLDER", "/app/data/uploads")
RESULT_FOLDER = os.getenv("RESULT_FOLDER", "/app/data/results")
TEMP_FOLDER = os.getenv("TEMP_FOLDER", "/app/data/tmp")
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(RESULT_FOLDER, exist_ok=True)
os.makedirs(TEMP_FOLDER, exist_ok=True)
def signal_handler(signum, frame):
"""
Gestionnaire de signal pour arrêter proprement le worker
"""
logger.info(f"Signal {signum} reçu, arrêt du worker...")
sys.exit(0)
def start_worker(queues, worker_name=None):
"""
Démarre un worker pour traiter les tâches des files d'attente spécifiées
Args:
queues (list): Liste des noms de files d'attente à traiter
worker_name (str, optional): Nom du worker. Si None, un nom sera généré
"""
if not worker_name:
worker_name = WORKER_NAME
logger.info(f"Démarrage du worker '{worker_name}' sur les files: {', '.join(queues)}")
# Connexion à Redis
redis_conn = redis.Redis.from_url(REDIS_URL)
try:
# Vérifier la connexion à Redis
redis_conn.ping()
logger.info("Connexion à Redis établie")
except redis.exceptions.ConnectionError:
logger.error(f"Impossible de se connecter à Redis: {REDIS_URL}")
sys.exit(1)
# Configurer le gestionnaire de signal
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
# Vérifier la connexion à Redis
logger.info("Vérification de la connexion à Redis")
# Démarrer le worker avec notre classe personnalisée
with Connection(redis_conn):
# Créer les queues
worker_queues = [Queue(name, connection=redis_conn) for name in queues]
# Créer le worker personnalisé
worker = CustomWorker(
worker_queues,
name=worker_name
)
logger.info(f"Worker '{worker_name}' prêt à traiter les tâches")
worker.work(with_scheduler=True)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Worker pour le traitement des images de chèques")
parser.add_argument(
"--queues",
nargs="+",
default=[HIGH_PRIORITY_QUEUE_NAME, QUEUE_NAME],
help="Liste des files d'attente à traiter (par ordre de priorité)"
)
parser.add_argument(
"--name",
type=str,
default=None,
help="Nom du worker"
)
args = parser.parse_args()
# Démarrer le worker
start_worker(args.queues, args.name)