root 5d8ad901d1 Initial commit: Pipeline notarial complet
- Infrastructure complète de traitement de documents notariaux
- API FastAPI d'ingestion et d'orchestration
- Pipelines Celery pour le traitement asynchrone
- Support des formats PDF, JPEG, PNG, TIFF, HEIC
- OCR avec Tesseract et correction lexicale
- Classification automatique des documents avec Ollama
- Extraction de données structurées
- Indexation dans AnythingLLM et OpenSearch
- Système de vérifications et contrôles métier
- Base de données PostgreSQL pour le métier
- Stockage objet avec MinIO
- Base de données graphe Neo4j
- Recherche plein-texte avec OpenSearch
- Supervision avec Prometheus et Grafana
- Scripts d'installation pour Debian
- Documentation complète
- Tests unitaires et de performance
- Service systemd pour le déploiement
- Scripts de déploiement automatisés
2025-09-08 22:05:22 +02:00

188 lines
6.1 KiB
Python

"""
Worker Celery pour le pipeline de traitement des documents notariaux
"""
import os
import time
import logging
from celery import Celery
from celery.signals import task_prerun, task_postrun, task_failure
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from pipelines import preprocess, ocr, classify, extract, index, checks, finalize
from utils.database import Document, ProcessingLog, init_db
from utils.storage import get_document, store_artifact
# Configuration du logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configuration Celery
app = Celery(
'worker',
broker=os.getenv("REDIS_URL", "redis://localhost:6379/0"),
backend=os.getenv("REDIS_URL", "redis://localhost:6379/0")
)
# Configuration de la base de données
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+psycopg://notariat:notariat_pwd@localhost:5432/notariat")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@app.task(bind=True, name='pipeline.run')
def pipeline_run(self, doc_id: str):
"""
Pipeline principal de traitement d'un document
"""
db = SessionLocal()
ctx = {"doc_id": doc_id, "db": db}
try:
logger.info(f"Début du traitement du document {doc_id}")
# Mise à jour du statut
document = db.query(Document).filter(Document.id == doc_id).first()
if not document:
raise ValueError(f"Document {doc_id} non trouvé")
document.status = "processing"
db.commit()
# Exécution des étapes du pipeline
steps = [
("preprocess", preprocess.run),
("ocr", ocr.run),
("classify", classify.run),
("extract", extract.run),
("index", index.run),
("checks", checks.run),
("finalize", finalize.run)
]
for step_name, step_func in steps:
try:
logger.info(f"Exécution de l'étape {step_name} pour le document {doc_id}")
# Enregistrement du début de l'étape
log_entry = ProcessingLog(
document_id=doc_id,
step_name=step_name,
status="started"
)
db.add(log_entry)
db.commit()
start_time = time.time()
# Exécution de l'étape
step_func(doc_id, ctx)
# Enregistrement de la fin de l'étape
duration = int((time.time() - start_time) * 1000) # en millisecondes
log_entry.status = "completed"
log_entry.completed_at = time.time()
log_entry.duration = duration
db.commit()
logger.info(f"Étape {step_name} terminée pour le document {doc_id} en {duration}ms")
except Exception as e:
logger.error(f"Erreur dans l'étape {step_name} pour le document {doc_id}: {e}")
# Enregistrement de l'erreur
log_entry.status = "failed"
log_entry.completed_at = time.time()
log_entry.error_message = str(e)
db.commit()
# Ajout de l'erreur au document
if not document.errors:
document.errors = []
document.errors.append(f"{step_name}: {str(e)}")
document.status = "failed"
db.commit()
raise
# Succès complet
document.status = "completed"
db.commit()
logger.info(f"Traitement terminé avec succès pour le document {doc_id}")
return {
"doc_id": doc_id,
"status": "completed",
"processing_steps": ctx.get("processing_steps", {}),
"extracted_data": ctx.get("extracted_data", {})
}
except Exception as e:
logger.error(f"Erreur fatale lors du traitement du document {doc_id}: {e}")
# Mise à jour du statut d'erreur
document = db.query(Document).filter(Document.id == doc_id).first()
if document:
document.status = "failed"
if not document.errors:
document.errors = []
document.errors.append(f"Erreur fatale: {str(e)}")
db.commit()
raise
finally:
db.close()
@app.task(name='queue.process_imports')
def process_import_queue():
"""
Traitement de la queue d'import Redis
"""
import redis
import json
r = redis.Redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379/0"))
try:
# Récupération d'un élément de la queue
result = r.brpop("queue:import", timeout=1)
if result:
_, payload_str = result
payload = json.loads(payload_str)
doc_id = payload["doc_id"]
logger.info(f"Traitement du document {doc_id} depuis la queue")
# Lancement du pipeline
pipeline_run.delay(doc_id)
# Décrémentation du compteur
r.decr("stats:pending_tasks")
except Exception as e:
logger.error(f"Erreur lors du traitement de la queue d'import: {e}")
# Configuration des signaux Celery
@task_prerun.connect
def task_prerun_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, **kwds):
"""Handler avant exécution d'une tâche"""
logger.info(f"Début de la tâche {task.name} (ID: {task_id})")
@task_postrun.connect
def task_postrun_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, retval=None, state=None, **kwds):
"""Handler après exécution d'une tâche"""
logger.info(f"Fin de la tâche {task.name} (ID: {task_id}) - État: {state}")
@task_failure.connect
def task_failure_handler(sender=None, task_id=None, exception=None, traceback=None, einfo=None, **kwds):
"""Handler en cas d'échec d'une tâche"""
logger.error(f"Échec de la tâche {sender.name} (ID: {task_id}): {exception}")
if __name__ == '__main__':
# Initialisation de la base de données
init_db()
# Démarrage du worker
app.start()