Architecture & Documentation — Pipeline de codification regex

Enquête Budget de Famille — Documentation technique

Date de publication

15 avril 2026

Présentation du projet

Note

Ce pipeline assigne automatiquement un code COICOP à chaque libellé de dépense de l’Enquête Budget de Famille (BdF) à l’aide d’expressions régulières déterministes. Il constitue la première étape de la chaîne de codification : les libellés reconnus par les règles regex sont directement codifiés ; les autres sont transmis aux modèles de machine learning en aval.

Objectifs

Objectif Description
Précision ciblée Identifier avec certitude les libellés stéréotypés (libellés génériques, cas techniques) avant tout traitement ML
Réductibilité Alléger la charge des modèles aval en traitant les cas simples en amont
Traçabilité Maintenir un jeu de règles explicites, versionnées et auditables
Évolutivité Permettre l’ajout ou la suppression de règles sans retraîner de modèle

Nomenclature COICOP

La COICOP (Classification of Individual Consumption According to Purpose) est la nomenclature internationale de référence pour classifier les dépenses des ménages. Dans le cadre BdF, elle est complétée par des codes techniques (préfixe 98) pour les cas particuliers (libellés vides, saisies illisibles, remises…) et des codes hors champ (préfixe 99) pour les opérations non consommatoires.


Architecture technique

Structure du projet

regex_codif/
├── config/
│   ├── config.yaml          # Chemins S3 et paramètres d'export
│   └── rules.yaml           # Règles regex de codification COICOP
├── src/
│   ├── __init__.py
│   ├── main.py              # Point d'entrée du pipeline
│   └── utils/
│       ├── __init__.py
│       ├── load_config.py   # Chargement du fichier de configuration
│       ├── load_rules.py    # Chargement et application des règles regex
│       ├── logging.py       # Configuration du logger
│       └── init_duckdb.py   # Initialisation DuckDB avec secrets S3
├── index.qmd                # Documentation et architecture (ce document)
├── resultats.qmd            # Analyse des résultats
├── _quarto.yml              # Configuration du site Quarto
└── rapport_style.css        # Feuille de style CSS

Documentation des modules Python

src/main.py — Point d’entrée du pipeline

Orchestre l’ensemble du pipeline dans l’ordre suivant :

  1. Configuration — charge config.yaml via load_config() et initialise le logger
  2. DuckDB — ouvre une connexion en mémoire avec les secrets S3 via init_duckdb()
  3. Règles — charge rules.yaml via load_regex_rules()
  4. Données — lit le jeu de test Parquet depuis S3 via une requête DuckDB
  5. Classification — applique les règles via apply_regex_rules() sur la colonne s_pr_product
  6. Split — sépare les lignes classifiées (predict_code non nul) des non classifiées
  7. Métriques — calcule l’accuracy globale et par code, journalise les erreurs
  8. Export — exporte les trois fichiers de sortie vers S3 (si S3.export: true)
Astuce

Le flag S3.export dans config.yaml permet de désactiver l’export S3 pour les exécutions de test locales sans modifier le code.


src/utils/load_config.py — Chargement de la configuration

def load_config(config_path="config/config.yaml") -> dict:
    """Charge le fichier de configuration YAML et retourne un dictionnaire."""

Lecture simple d’un fichier YAML via yaml.safe_load(). Retourne un dictionnaire structuré avec les clés paths et S3.


src/utils/load_rules.py — Chargement et application des règles

Contient deux fonctions :

def load_regex_rules(yaml_path: str) -> list[dict]:
    """Charge les règles depuis rules.yaml.
    Retourne une liste de dicts {"pattern": str, "code": str}."""
def apply_regex_rules(series: pd.Series, rules: list[dict]) -> pd.Series:
    """Applique les règles sur une Series de libellés.

    Mécanisme :
    - Pré-compilation de tous les patterns avec re.IGNORECASE
    - Pour chaque libellé : itération ordonnée, premier match gagne
    - Retourne None si aucune règle ne correspond
    """
Important

L’ordre des règles est critique. Les règles sont appliquées séquentiellement ; la première règle qui correspond détermine le code prédit. Les règles les plus spécifiques (avec ancres ^…$) doivent impérativement précéder les règles génériques (sous-chaîne libre).


src/utils/init_duckdb.py — Initialisation DuckDB

def init_duckdb(config: dict) -> duckdb.DuckDBPyConnection:
    """Crée une connexion DuckDB en mémoire et configure les secrets S3.

    Les credentials sont lus depuis les variables d'environnement :
    AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_S3_ENDPOINT
    """

La connexion DuckDB est utilisée uniquement pour lire le fichier Parquet depuis S3. Le traitement des données est ensuite effectué avec Pandas.


src/utils/logging.py — Configuration du logger

def setup_logging() -> logging.Logger:
    """Configure et retourne un logger standard Python.
    Niveau INFO, format : [timestamp] [level] message."""

Configuration

config/config.yaml

paths:
  # Jeu de test annoté manuellement (vérité terrain)
  test_set:     "s3://bucket/path/raw_test.parquet"
  # Prédicitions des libellés classifiés par regex
  output_pred:  "s3://bucket/path/raw_test_REGEX_pred.parquet"
  # Erreurs de prédiction (code prédit ≠ code vrai)
  error_pred:   "s3://bucket/path/error_regex_pred.parquet"
  # Libellés non classifiés (à transmettre aux modèles ML)
  output_test_set: "s3://bucket/path/raw_test_without_regex.parquet"

S3:
  BUCKET: "nom-du-bucket"
  export: true    # false pour désactiver l'export S3 (mode test)

config/rules.yaml

Chaque règle est un objet YAML avec deux champs :

rules:
  - pattern: "expression régulière"   # Motif regex (syntaxe Python re)
    code: "XX.X.X"                    # Code COICOP à assigner si match

  # Exemple : libellé exact "restaurant"
  - pattern: "^\\s*restaurant\\s*$"
    code: "11.1.1"

  # Exemple : sous-chaîne "drive" entourée de mots
  - pattern: "\\bdrive\\b"
    code: "98.1"
Note

Les patterns sont appliqués avec re.IGNORECASE (insensible à la casse) et re.search() (recherche de sous-chaîne). Les ancres ^ et $ permettent de contraindre la correspondance à l’intégralité du libellé.