""" 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'