
- Corrections mineures dans les pipelines - Optimisations de l'API complète - Améliorations de la documentation - Finalisation du système
193 lines
7.0 KiB
Python
193 lines
7.0 KiB
Python
"""
|
|
Pipeline de pré-traitement des documents
|
|
"""
|
|
|
|
import os
|
|
import tempfile
|
|
import hashlib
|
|
from pathlib import Path
|
|
from typing import Dict, Any
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def run(doc_id: str, ctx: Dict[str, Any]) -> None:
|
|
"""
|
|
Pipeline de pré-traitement des documents
|
|
|
|
Args:
|
|
doc_id: Identifiant du document
|
|
ctx: Contexte de traitement partagé entre les pipelines
|
|
"""
|
|
logger.info(f"🔧 Début du pré-traitement pour le document {doc_id}")
|
|
|
|
try:
|
|
# 1. Récupération du document depuis le stockage
|
|
document_path = _get_document_path(doc_id)
|
|
if not document_path or not os.path.exists(document_path):
|
|
raise FileNotFoundError(f"Document {doc_id} non trouvé")
|
|
|
|
# 2. Validation du fichier
|
|
file_info = _validate_file(document_path)
|
|
ctx["file_info"] = file_info
|
|
|
|
# 3. Calcul du hash pour l'intégrité
|
|
file_hash = _calculate_hash(document_path)
|
|
ctx["file_hash"] = file_hash
|
|
|
|
# 4. Préparation des répertoires de travail
|
|
work_dir = _prepare_work_directory(doc_id)
|
|
ctx["work_dir"] = work_dir
|
|
|
|
# 5. Conversion si nécessaire (HEIC -> JPEG, etc.)
|
|
processed_path = _convert_if_needed(document_path, work_dir)
|
|
ctx["processed_path"] = processed_path
|
|
|
|
# 6. Extraction des métadonnées
|
|
metadata = _extract_metadata(processed_path)
|
|
ctx["metadata"] = metadata
|
|
|
|
# 7. Détection du type de document
|
|
doc_type = _detect_document_type(processed_path)
|
|
ctx["detected_type"] = doc_type
|
|
|
|
logger.info(f"✅ Pré-traitement terminé pour {doc_id}")
|
|
logger.info(f" - Type détecté: {doc_type}")
|
|
logger.info(f" - Taille: {file_info['size']} bytes")
|
|
logger.info(f" - Hash: {file_hash[:16]}...")
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ Erreur lors du pré-traitement de {doc_id}: {e}")
|
|
ctx["preprocess_error"] = str(e)
|
|
raise
|
|
|
|
def _get_document_path(doc_id: str) -> str:
|
|
"""Récupère le chemin du document depuis le stockage"""
|
|
# Pour l'instant, simulation - sera remplacé par MinIO
|
|
storage_path = os.getenv("STORAGE_PATH", "/tmp/documents")
|
|
return os.path.join(storage_path, f"{doc_id}.pdf")
|
|
|
|
def _validate_file(file_path: str) -> Dict[str, Any]:
|
|
"""Valide le fichier et retourne ses informations"""
|
|
if not os.path.exists(file_path):
|
|
raise FileNotFoundError(f"Fichier non trouvé: {file_path}")
|
|
|
|
stat = os.stat(file_path)
|
|
file_info = {
|
|
"path": file_path,
|
|
"size": stat.st_size,
|
|
"modified": stat.st_mtime,
|
|
"extension": Path(file_path).suffix.lower()
|
|
}
|
|
|
|
# Validation de la taille (max 50MB)
|
|
if file_info["size"] > 50 * 1024 * 1024:
|
|
raise ValueError("Fichier trop volumineux (>50MB)")
|
|
|
|
# Validation de l'extension
|
|
allowed_extensions = ['.pdf', '.jpg', '.jpeg', '.png', '.tiff', '.heic']
|
|
if file_info["extension"] not in allowed_extensions:
|
|
raise ValueError(f"Format non supporté: {file_info['extension']}")
|
|
|
|
return file_info
|
|
|
|
def _calculate_hash(file_path: str) -> str:
|
|
"""Calcule le hash SHA-256 du fichier"""
|
|
sha256_hash = hashlib.sha256()
|
|
with open(file_path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
sha256_hash.update(chunk)
|
|
return sha256_hash.hexdigest()
|
|
|
|
def _prepare_work_directory(doc_id: str) -> str:
|
|
"""Prépare le répertoire de travail pour le document"""
|
|
work_base = os.getenv("WORK_DIR", "/tmp/processing")
|
|
work_dir = os.path.join(work_base, doc_id)
|
|
|
|
os.makedirs(work_dir, exist_ok=True)
|
|
|
|
# Création des sous-répertoires
|
|
subdirs = ["input", "output", "temp", "artifacts"]
|
|
for subdir in subdirs:
|
|
os.makedirs(os.path.join(work_dir, subdir), exist_ok=True)
|
|
|
|
return work_dir
|
|
|
|
def _convert_if_needed(file_path: str, work_dir: str) -> str:
|
|
"""Convertit le fichier si nécessaire (HEIC -> JPEG, etc.)"""
|
|
file_ext = Path(file_path).suffix.lower()
|
|
|
|
if file_ext == '.heic':
|
|
# Conversion HEIC vers JPEG
|
|
output_path = os.path.join(work_dir, "input", "converted.jpg")
|
|
# Ici on utiliserait une bibliothèque comme pillow-heif
|
|
# Pour l'instant, on copie le fichier original
|
|
import shutil
|
|
shutil.copy2(file_path, output_path)
|
|
return output_path
|
|
|
|
# Pour les autres formats, on copie dans le répertoire de travail
|
|
output_path = os.path.join(work_dir, "input", f"original{file_ext}")
|
|
import shutil
|
|
shutil.copy2(file_path, output_path)
|
|
return output_path
|
|
|
|
def _extract_metadata(file_path: str) -> Dict[str, Any]:
|
|
"""Extrait les métadonnées du fichier"""
|
|
metadata = {
|
|
"filename": os.path.basename(file_path),
|
|
"extension": Path(file_path).suffix.lower(),
|
|
"size": os.path.getsize(file_path)
|
|
}
|
|
|
|
# Métadonnées spécifiques selon le type
|
|
if metadata["extension"] == '.pdf':
|
|
try:
|
|
import PyPDF2
|
|
with open(file_path, 'rb') as f:
|
|
pdf_reader = PyPDF2.PdfReader(f)
|
|
metadata.update({
|
|
"pages": len(pdf_reader.pages),
|
|
"title": pdf_reader.metadata.get('/Title', '') if pdf_reader.metadata else '',
|
|
"author": pdf_reader.metadata.get('/Author', '') if pdf_reader.metadata else '',
|
|
"creation_date": pdf_reader.metadata.get('/CreationDate', '') if pdf_reader.metadata else ''
|
|
})
|
|
except ImportError:
|
|
logger.warning("PyPDF2 non disponible, métadonnées PDF limitées")
|
|
except Exception as e:
|
|
logger.warning(f"Erreur lors de l'extraction des métadonnées PDF: {e}")
|
|
|
|
elif metadata["extension"] in ['.jpg', '.jpeg', '.png', '.tiff']:
|
|
try:
|
|
from PIL import Image
|
|
with Image.open(file_path) as img:
|
|
metadata.update({
|
|
"width": img.width,
|
|
"height": img.height,
|
|
"mode": img.mode,
|
|
"format": img.format
|
|
})
|
|
except ImportError:
|
|
logger.warning("PIL non disponible, métadonnées image limitées")
|
|
except Exception as e:
|
|
logger.warning(f"Erreur lors de l'extraction des métadonnées image: {e}")
|
|
|
|
return metadata
|
|
|
|
def _detect_document_type(file_path: str) -> str:
|
|
"""Détecte le type de document basé sur le nom et les métadonnées"""
|
|
filename = os.path.basename(file_path).lower()
|
|
|
|
# Détection basée sur le nom de fichier
|
|
if any(keyword in filename for keyword in ['acte', 'vente', 'achat']):
|
|
return 'acte_vente'
|
|
elif any(keyword in filename for keyword in ['donation', 'don']):
|
|
return 'acte_donation'
|
|
elif any(keyword in filename for keyword in ['succession', 'heritage']):
|
|
return 'acte_succession'
|
|
elif any(keyword in filename for keyword in ['cni', 'identite', 'passeport']):
|
|
return 'cni'
|
|
elif any(keyword in filename for keyword in ['contrat', 'bail', 'location']):
|
|
return 'contrat'
|
|
else:
|
|
return 'unknown' |