2. Codif regex

Rôle de l’étape

codif-regex applique une liste de règles d’expressions régulières au libellé normalisé. C’est un premier filtre rapide et déterministe pour les libellés standardisés : lignes techniques (carte bancaire, prélèvement, remise…) et quelques catégories génériques (« courses », « restaurant »…).

  • Une règle qui matche → le code est attribué immédiatement, méthode "REGEX", et la ligne n’est plus traitée par les modèles.

  • Une ligne sans match → transmise telle quelle à LCS, RAG et TTC.

  • Code : regex-codif/ — entrée regex-codif/src/main.py

  • Règles : regex-codif/config/rules.yaml

Entrées

Les chemins dépendent du mode (sélectionné par --input-file, cf. Généralités) :

Variable config Mode Chemin S3
paths.test_set évaluation …/{run}/preprocessing/raw_test.parquet
paths.train_set évaluation …/{run}/preprocessing/raw_train.parquet
paths.test_set_prod production …/{run}/preprocessing/observations.parquet (lignes à coder)
paths.train_set_prod production …/{run}/preprocessing/annotations_full.parquet (KB complète)

C’est aussi ici qu’a lieu l’échantillonnage centralisé du pipeline : si sample-annotations (éval) ou sample-observations (prod) est renseigné, le jeu à coder est échantillonné une seule fois (random_state=42) et tous les classifieurs aval héritent des mêmes lignes via raw_test_without_regex.parquet.

Traitement

Les règles

Chaque règle associe un motif (sur le libellé) à un code COICOP, dans rules.yaml :

rules:
  - pattern: "^\\s*boulangerie\\s*$"     # libellé = "boulangerie"
    code: "01.1.1.3"
  - pattern: "^restaurant$"
    code: "11.1.1"
  - pattern: "\\b(divers\\s+)?courses?\\b"  # contient "course(s)" / "divers courses"
    code: "98.1"
  - pattern: "^carte bancaire$"
    code: "98.3"
  - pattern: "^prelevement$"
    code: "98.4"
  - pattern: "^illisible$"
    code: "98.4"
  - pattern: "\\bcantine\\b"
    code: "11.1.2"

Le fichier contient 43 règles. Quelques codes spéciaux apparaissent : "Reprise manuelle" (à recoder à la main) et les codes techniques 98.x / 99.x.

Le cas « Reprise manuelle »

Deux des 43 règles ne renvoient pas un code COICOP mais le littéral "Reprise manuelle" : des libellés reconnus comme réels mais impossibles à coder automatiquement de façon fiable. Ce littéral se comporte ensuite comme n’importe quel code regex :

  • la ligne est considérée codée (elle ne passe pas par les modèles ni par le LLM) ;
  • la valeur traverse le pipeline telle quelle jusqu’à la colonne predicted_code du fichier livré, avec prediction_source = "regex" (voir le tableau des valeurs possibles) ;
  • côté aval, il faut donc filtrer predicted_code == "Reprise manuelle" pour constituer la pile de recodage humain.

Application

apply_regex() (regex-codif/src/data/apply_regex.py) applique les règles sur la colonne s_pr_product (normalisation forte), puis sépare le jeu en deux :

df["predict_code"] = apply_regex_rules(df["s_pr_product"], rules)

df_regex_predicted = df[df["predict_code"].notna()]      # matché → prédiction finale
df_regex_predicted["method"] = "REGEX"

df_without_regex = df[df["predict_code"].isna()].drop(columns="predict_code")  # → modèles
AvertissementPortée volontairement étroite

La plupart des règles sont ancrées (^…$) : elles n’attrapent que des libellés exacts. Un libellé comme Ticket CB (normalisé ticket cb) ne correspond ni à ^carte bancaire$ ni à ^cb$ — il n’est donc pas codé par regex et part vers les modèles. C’est attendu : le regex ne traite que les cas non ambigus, le reste relève des modèles + arbitrage LLM.

Exemple sur le fil rouge

s_pr_product Règle déclenchée predict_code Suite
illisible ^illisible$ 98.4 prédiction finale (si non retiré au preprocessing)
ticket cb (aucune) → LCS / RAG / TTC
bagu tradition u ble bretagne (aucune) → LCS / RAG / TTC
max garden flowers balle surprise (aucune) → LCS / RAG / TTC

Sorties

{run} = …/workflow_runs/{run_date}/{run_id}/codif-regex

Fichier Contenu
{run}/REGEX_pred.parquet lignes codées par regex (predict_code, method="REGEX")
{run}/raw_test_without_regex.parquet lignes non codées → entrée des modèles
{run}/raw_train_without_regex.parquet idem côté train / suggester
{run}/error_regex_pred.parquet erreurs (si vérité terrain disponible)

➡️ Étape suivante : 3. Prune — troncature & élagage