Introduction : Pourquoi automatiser vos flux CSV avec l'IA ?

Chaque entreprise manipule aujourd'hui des montagnes de données CSV non structurées : exports CRM, fichiers logs, rapports Excel bruts. Le problème ? Ces données nécessitent un nettoyage fastidieux avant toute utilisation. classification automatique, extraction d'entités, détection d'anomalies — autant de tâches qui prennent des heures en traitement manuel.

HolySheep AI propose une alternative aux API OpenAI et Anthropic avec une latence inférieure à 50 millisecondes et des tarifs jusqu'à 85% inférieurs. Découvrez comment construire un pipeline ETL complet qui автоматически nettoie, transforme et enrichit vos fichiers CSV grâce à l'intelligence artificielle.

Architecture du pipeline Tardis ETL

Notre architecture s'articule autour de 4 modules distincts :

Installation et dépendances

# Installation des dépendances
pip install pandas openai python-dotenv psycopg2-binary
pip install sqlalchemy pydantic httpx

Configuration de l'environnement

cat > .env << 'EOF' HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1 DATABASE_URL=postgresql://user:pass@localhost:5432/tardis_db EOF

Module 1 : Extracteur CSV haute performance

Notre extracteur gère les fichiers volumineux par chunking pour éviter les problèmes de mémoire. Il supporte l'encodage UTF-8, Latin-1 et détecte automatiquement le délimiteur.

import pandas as pd
import chardet
from pathlib import Path
from typing import Iterator, Optional
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class TardisExtractor:
    """Extracteur CSV avec détection automatique d'encodage et chunking."""
    
    def __init__(self, chunk_size: int = 10000):
        self.chunk_size = chunk_size
    
    def detect_encoding(self, file_path: str) -> str:
        """Détecte l'encodage du fichier."""
        with open(file_path, 'rb') as f:
            raw_data = f.read(100000)
            result = chardet.detect(raw_data)
            logger.info(f"Encodage détecté: {result}")
            return result['encoding'] or 'utf-8'
    
    def extract(self, file_path: str, 
                delimiter: Optional[str] = None,
                encoding: Optional[str] = None) -> Iterator[pd.DataFrame]:
        """
        Extrait les données CSV par chunks pour les fichiers volumineux.
        
        Args:
            file_path: Chemin vers le fichier CSV
            delimiter: Délimiteur (détection auto si None)
            encoding: Encodage (détection auto si None)
        
        Yields:
            DataFrame par chunks de taille chunk_size
        """
        if encoding is None:
            encoding = self.detect_encoding(file_path)
        
        # Lecture en chunks pour éviter les MemoryError
        logger.info(f"Début extraction: {file_path}")
        
        try:
            for chunk_idx, chunk in enumerate(pd.read_csv(
                file_path,
                chunksize=self.chunk_size,
                encoding=encoding,
                delimiter=delimiter,
                on_bad_lines='skip',  # Ignore les lignes corrompues
                low_memory=False
            )):
                logger.info(f"Chunk {chunk_idx}: {len(chunk)} lignes traitées")
                yield chunk
                
        except Exception as e:
            logger.error(f"Erreur extraction: {e}")
            raise

Utilisation

extractor = TardisExtractor(chunk_size=5000) for chunk in extractor.extract("data/clients_export.csv"): # Traitement de chaque chunk print(f"Traitement de {len(chunk)} lignes...")

Module 2 : Nettoyeur de données intelligent

Le module de nettoyage applique des règles métier : suppression des doublons, normalisation des chaînes, conversion des types, gestion des valeurs manquantes avec stratégie d'imputation.

import pandas as pd
import numpy as np
from pydantic import BaseModel, Field, validator
from typing import Dict, Any, List, Optional
from datetime import datetime
import re

class TardisCleaner:
    """Nettoyeur de données avec validation et transformation."""
    
    def __init__(self, 
                 drop_duplicates: bool = True,
                 strip_whitespace: bool = True,
                 lowercase_columns: bool = True):
        self.drop_duplicates = drop_duplicates
        self.strip_whitespace = strip_whitespace
        self.lowercase_columns = lowercase_columns
    
    def clean(self, df: pd.DataFrame) -> pd.DataFrame:
        """Applique toutes les règles de nettoyage."""
        df = df.copy()
        
        # Normalisation des noms de colonnes
        if self.lowercase_columns:
            df.columns = [col.lower().strip().replace(' ', '_') 
                         for col in df.columns]
        
        # Suppression des espaces superflus
        if self.strip_whitespace:
            df = df.apply(lambda x: x.str.strip() if x.dtype == "object" else x)
        
        # Suppression des doublons
        if self.drop_duplicates:
            before = len(df)
            df = df.drop_duplicates()
            logger.info(f"Doublons supprimés: {before - len(df)}")
        
        # Conversion des types dates
        date_columns = [col for col in df.columns if 'date' in col.lower()]
        for col in date_columns:
            df[col] = pd.to_datetime(df[col], errors='coerce')
        
        # Remplacement des valeurs nulles vides par NaN
        df = df.replace(r'^\s*$', np.nan, regex=True)
        
        return df
    
    def impute_numeric(self, df: pd.DataFrame, 
                       strategy: str = 'median') -> pd.DataFrame:
        """Impute les valeurs numériques manquantes."""
        numeric_cols = df.select_dtypes(include=[np.number]).columns
        
        for col in numeric_cols:
            if df[col].isna().sum() > 0:
                if strategy == 'median':
                    df[col].fillna(df[col].median(), inplace=True)
                elif strategy == 'mean':
                    df[col].fillna(df[col].mean(), inplace=True)
                elif strategy == 'zero':
                    df[col].fillna(0, inplace=True)
        
        return df
    
    def clean_text_field(self, text: str) -> str:
        """Nettoie un champ texte individuel."""
        if pd.isna(text):
            return ""
        
        text = str(text).strip()
        text = re.sub(r'\s+', ' ', text)  # Esp