Note Méthodologique : Stratégie de Dédoublonnage des Listes de Codes (DDI 3.3/6.0)

Published

October 22, 2026

Introduction

Cette note décrit la méthodologie implémentée dans le pipeline metadata-scrubber pour identifier les listes de codes (CodeList) redondantes au sein d’un fichier XML DDI (Data Documentation Initiative).

Scénario : un fichier de métadonnées peut contenir plusieurs définitions de CodeList qui, bien que nommées différemment ou légèrement variées, décrivent le même concept.

Référence : le fichier BTS.xml (réf. 4,7 Mo) — 156 CodeLists, 831 catégories, 503 références Variable → CodeList, 54 CodeLists redondantes dans 29 groupes.


1 Modèle de Données DDI et Extraction

1.1 Structure du fichier DDI

Un fichier DDI 3.3 est une suite d’objets enfermés dans des balises <Fragment> (~2 573 pour BTS.xml). Chaque Fragment contient exactement un objet identifiable, versionnable et maintenable :

  • CodeList : la liste de codes elle-même
  • Category : le libellé d’une valeur de code
  • Variable / RepresentedVariable : référence une CodeList

Ordre arbitraire : les CodeList peuvent apparaître avant les Category correspondantes → approche en deux passes obligatoire.

1.1.1 Structure XML : <Code>

Chaque <Code> ne contient pas le libellé directement :

<Code>
  <r:ID>4dd22a77-9078-4417-9710-48699af3298a</r:ID>
  <r:CategoryReference>
    <r:ID>d72b4a6c-b668-46c9-98d9-7ba4663e7fa0</r:ID>
  </r:CategoryReference>
  <r:Value>4</r:Value>
</Code>
  • r:Value : valeur brute
  • r:CategoryReference > r:ID : référence vers une Category

1.1.2 Structure XML : <Category>

Le libellé est stocké séparément :

<Category>
  <r:ID>d72b4a6c-b668-46c9-98d9-7ba4663e7fa0</r:ID>
  <r:Label><r:Content xml:lang="fr-FR">Autres organismes publics</r:Content></r:Label>
  <CategoryName>domempl-4</CategoryName>
</Category>

1.1.3 Structure XML : <Variable> référençant une CodeList

<Variable>
  <r:ID>7686be9c-7849-4941-bccc-62206f764a12</r:ID>
  <VariableName><r:String xml:lang="fr-FR">domempl</r:String></VariableName>
  <r:Label><r:Content xml:lang="fr-FR">Domaine d'emploi</r:Content></r:Label>
  <VariableRepresentation>
    <r:CodeRepresentation>
      <r:CodeListReference>
        <r:ID>36a67d27-866b-40c6-9e4d-286d1f3d31d1</r:ID>
      </r:CodeListReference>
    </r:CodeRepresentation>
  </VariableRepresentation>
</Variable>

1.2 Pipeline d’extraction (4 phases)

┌───────────────────────────────────────────────────────┐
│ 1. Lecture XML (lxml, namespaces ignorés)            │
│    filtrer par local(frag) == "Fragment"              │
├───────────────────────────────────────────────────────┤
│ 2. Passe 1 — Indexer les Catégories                  │
│    {r:ID → r:Label > r:Content.text}                  │
│    Résultat : 831 cat_id → libellé                    │
├───────────────────────────────────────────────────────┤
│ 3. Passe 2 — Extraire les CodeLists                   │
│    Pour chaque <Code> : extraire Value + CategoryRef  │
│    Résoudre libellé via index                          │
│    Résultat : 156 codelists avec codes résolus        │
├───────────────────────────────────────────────────────┤
│ 4. Passe 3 — Extraire les références Variables        │
│    Chercher CodeListReference au plus profond         │
│    Résultat : 503 références Variable → CodeList      │
└───────────────────────────────────────────────────────┘

2 L’Entonnoir de Détection

Principe : un entonnoir en trois niveaux, du plus simple au plus coûteux.

2.1 Phase 1 — Doublons Exact

Méthode :

  1. Normalisation : Unicode NFC, minuscule, espaces multiples → unique
  2. Signature : tuple trié des paires (valeur, libellé) normalisées
  3. Regroupement par signature identique

Nom et identifiant volontairement exclus de la signature → deux listes identiques nommées différemment sont détectées.

2.1.1 Exemple : 18 listes à 1 code

18 CodeLists nommées différentes (N-categorie_juridique, N-region, N-conv_coll, N_DEpartements, …).

Chacune contient un seul code :

Valeur Libellé
cf Voir dictionnaire des nomenclatures

Toutes 18 partagent exactement la même signature → doublons exacts. Probablement des codes génériques « consulter le dictionnaire » dupliqués par l’outil de modélisation (Colectica).

Limite connue : ce ne sont pas de vrais doublons conceptuels. Il s’agit d’un raccourci arbitraire du producteur, qui — au lieu de créer ou référencer les listes de codes spécifiques (existantes ou à créer) — a renvoyé vers un document statique unique censé toutes les regrouper. Cette pratique est désormais corrigée systématiquement à la validation de la documentation avant publication (référence directe vers la liste de codes spécifique). Le regroupement en doublon exact reste pertinent sur les données passées pour signaler l’anomalie, mais le choix d’une CodeList « maître » parmi les 18 est arbitraire/cosmétique — aucune n’est réellement correcte. La vraie correction (créer les 18 listes de codes spécifiques) reste un travail humain hors périmètre de l’outil.

2.1.2 Caractéristiques

Aspect Détail
Complexité \(O(N \cdot C)\) — linéaire, \(N\) = nb CodeLists, \(C\) = nb codes moyen
Réseau Aucun appel nécessaire
Précision 100 % — zéro faux-positif
Limite 1 différence dans une liste de 10 suffit pour passer

Sur BTS.xml : 29 groupes détectés, 54 CodeLists redondantes.

2.2 Phase 1 bis — Quasi-Doublons (Similarité Floue)

Méthode :

  1. À partir des uniques (une par groupe exact → ~102 pour BTS.xml), comparer toutes les paires de CodeLists
  2. Similarité des codes (_code_similarity) : chaque code de la première liste est apparié au meilleur code encore libre de la seconde (valeur puis libellé, via difflib.SequenceMatcher) ; un code est apparié si son score atteint 0.65. La similarité est le nombre de codes appariés rapporté à la taille de la plus grande des deux listes : elle est symétrique, et une liste incluse dans une plus grande n’atteint pas 100 % (13 codes inclus dans 22 → 0.59, paire écartée)
  3. Score hybride = 0.6 × similarité des codes + 0.4 × similarité des noms (les noms de moins de 3 caractères sont ignorés). Si les codes sont quasi identiques (≥ 0.95) mais les noms très différents (< 0.5), le score est relevé à 0.95
  4. Seuils : ≥ 0.90 = détecté ; ≥ 0.80 = flagged pour inspection ; similarité des codes minimale 0.75
  5. La confiance d’une détection non exacte est plafonnée à 0.99 : 100 % est réservé à la signature exacte

2.2.1 Exemples BTS.xml

Paire Score Différence
SOURCE ↔︎ SOURCE_1 0.9985 Identique (casse près)
N_tr_ca_23 ↔︎ TR_CA 0.9896 Majuscules/espace sur 2 codes
ETAT_ET ↔︎ ETAT_ET_1 0.9961 Quasi-identique
N_domempl_23 ↔︎ DOMEMPL 0.86 NON détectée (accent + ordre)

2.2.2 Caractéristiques

Aspect Détail
Complexité \(O(U^2 \cdot L)\), \(U\) ≈ 102 uniques
Faux négatifs Différence > ~10 % du texte
Faux positifs Deux listes de plages de valeurs similaires

Seuil MAX_CODES_FOR_FUZZY (défaut 100) : la comparaison code-à-code (_code_similarity) est en \(O(\text{len}(a.codes) \times \text{len}(b.codes))\), indépendamment du nombre de CodeLists. Une seule liste volumineuse (ex. la nomenclature des communes, ~35 000 codes) suffit à rendre une paire impraticable (plusieurs dizaines de minutes). Les CodeLists dépassant ce seuil sont donc exclues de la phase floue et listées explicitement dans les logs — un vrai doublon parmi elles serait de toute façon capté par la phase exacte (§ précédent).

2.3 Phase 2 — Doublons Sémantiques (Embeddings + LLM)

Les deux phases précédentes ne comprennent pas le sens : possible de “Sexe” vs “Genre” avec codes différents.

2.3.1 Embeddings de CodeLists

  1. Vectorisation : modèle qwen3-embedding-8b sur nom + label + codes
  2. Cosinus entre toutes les paires (102 vecteurs → 102×102)
  3. Filtrage : ≥ 0.90
  4. Juge LLM : gemma4-26b-moe sur les meilleures candidates

Troncature _TRUNCATE_CL (8000 caractères) : le texte concaténé (nom + label + codes) est tronqué avant l’appel d’embedding, pour rester sous la limite de contexte du modèle (qwen3-embedding-8b : 40 960 tokens). Sans cette troncature, une CodeList volumineuse (ex. la nomenclature des communes, ~35 000 codes) fait planter l’appel API. La troncature garde nom et label en entier (partie la plus discriminante) et un échantillon des premiers codes — la capacité de détection n’est donc pas perdue, contrairement à une exclusion complète comme pour le seuil MAX_CODES_FOR_FUZZY de la phase floue (§ précédent).

{
  "meme_concept": true,
  "confiance": 0.95,
  "raison": "Mêmes codes départementaux français ; seule la casse et les accents diffèrent."
}

Pas de barème : il n’existe pas de grille de notation ou de critères pondérés en aval. confiance est une valeur continue entre 0 et 1 auto-évaluée par le LLM lui-même (température 0, un seul appel par paire), accompagnée d’une justification en texte libre. Seules les 20 meilleures paires (triées par score cosinus) sont soumises au juge.

2.3.2 Embeddings de Variables (usage)

Approche inversée : on compare les variables qui référencent les CodeLists.

Liste Variables var_sig
N_domempl_23 domempl, domempl_empl ("domempl", "domempl_empl")
DOMEMPL domempl, domempl_empl, DOMEMPL, DOMEMPL_EMPL ("DOMEMPL", "DOMEMPL_EMPL", "domempl", "domempl_empl")

Même sémantique des 2 premières variables → mêmes concept métier. Particulièrement efficace quand toutes les CodeLists sont référencées (BTS.xml : 156/156).

2.3.3 Caractéristiques

Aspect Détail
Complexité \(O(U^2 \cdot D)\) pour embeddings, \(D\) = 8192
Coût Appels réseau ; dépend de OPENAI_BASE_URL + OPENAI_API_KEY
Précision La plus élevée (validation LLM)
Limite Coût calcul, dépendance réseau

3 Signaux de Renforcement : var_sig

Une CodeList est en principe toujours référencée par au moins une variable — c’est une hypothèse de modélisation, pas une contrainte garantie par DDI ni imposée par Colectica ; des CodeLists orphelines (0 variable référente) existent en pratique dans les données historiques. Deux listes utilisées par les mêmes variables sont quasi-certainement identiques.

Sans risque sur les orphelines : find_usage_groups exclut explicitement les CodeLists dont le var_sig est vide du regroupement par usage (signals.py). Les listes sans variable référente ne peuvent donc pas être faussement regroupées entre elles par ce seul signal.

var_sig(CL) = tuple(sorted( noms_des_variables_qui_la_referencent ))

3.1 Types de recoupement

Signal Signification
✓ même usage Ensembles de variables identiques → fort indicateur de redondance
~ usage partiel Intersection significative → variation contextuelle possible
✗ usages disjoints Aucune variable commune → probablement distinctes ou besoin de consolidation

3.2 Exemples BTS.xml

3.2.1 Groupe A : var_sig identique + contenu identique → doublon exact confirmé

CodeList var_sig
N_saison_23 ("saison",)
N_SAISON_Validité 2022 ("saison",)

3.2.2 Groupe B : var_sig identique + contenu différent → à vérifier

CodeList var_sig
N_champ_23 ("champ",)
N_CHAMP_2022 ("champ",)

3.2.3 Groupe B bis : var_sig identique + contenu différent → cas à rejeter

CodeList var_sig Codes
N_annee_23 ("annee",) 2019, 2020, 2021, 2022, 2023
N_annee_24 ("annee",) 2019, 2020, 2021, 2022, 2023, 2024

Cas fréquent avec les nomenclatures d’années d’enquête : chaque millésime ajoute le code de l’année écoulée à la liste précédente, sous la même variable référente. Le var_sig identique ne signale ici aucune redondance — les deux listes sont des versions légitimement distinctes du même référentiel. Ce contre-exemple confirme que var_sig identique + contenu différent (Groupe B) reste structurellement ambigu : ni un rapprochement automatique, ni un rejet automatique ne seraient corrects dans tous les cas — l’arbitrage humain reste nécessaire.

3.2.4 Groupe C : var_sig disjoints + contenu identique → à consolider

CodeList var_sig Codes
SOURCE ("source",) 6 codes identiques
SOURCE_1 ("source_1",) 6 codes identiques

4 Le Flag --registry : Dédoublonnage Transversal Itératif

4.1 Pourquoi --registry ?

Un seul run du pipeline identifie les redondances au sein d’un unique fichier traité. Mais les mêmes CodeLists peuvent apparaître dans plusieurs opérations (BTS, BPE, RP…). Sans registre partagé, chaque nouvel run redécouvre les paires déjà validées dans les opérations précédentes.

Le registre cleaned_codelists.json est un catalogue transversal de vérité : il s’alimente au fil des opérations et alimente la détection du run suivant, y compris les appariements cross-opération.

4.2 Flux transversal

Opération 1 : BTS_demo.xml (sans --registry)
  → codelist_duplicates.json → 4 paires détectées (exact + flou + sémantique)
  → Interface web : examiner / approuver / rejeter
  → /api/registry/save → cleaned_codelists.json (mères BTS)

Opération 2 : BPE_demo.xml avec --registry cleaned_codelists.json
  → masters BTS injectés dans la détection
  → CodeLists BTS doublons exclues (déjà validées)
  → codelist_duplicates.json → paires BPE intra + BPE↔BTS (cross-fichier)
  → /api/registry/save → cleaned_codelists.json mis à jour (BTS + BPE)

Opération N : fichier suivant avec --registry cleaned_codelists.json
  → registry = tous les masters validés précédemment
  → détection = intra-fichier + cross-opération sur les masters cumulés
  → registre s'enrichit, les paires résiduelles diminuent à chaque étape

Plus le registre grossit, plus la détection évite les réitérations
et capture les appariements inter-opérations.

4.3 Ce que fait --registry dans le pipeline (main.py, §2 bis)

Lors du lancement avec --registry cleaned_codelists.json :

  1. Lecture du registre nettoyé (schéma v2, migration automatique v1 → v2)
  2. Injection des entrées comme masters prioritaires (prépended à la liste des CodeLists)
  3. Exclusion des CodeLists dont l’ID figure dans replaces[].id (déjà traitées)
  4. Continuation : la détection exacte/floue/sémantique ne voit plus que les nouveautés
# Simplifié depuis main.py (lignes 141-155) :
registry_cls, replaced_ids = load_cleaned_codelists(registry_path)
codelists = [
    cl for cl in codelists
    if cl.id not in registry_ids and cl.id not in replaced_ids
]
codelists = registry_cls + codelists  # masters d'abord

À discuter — versioning : replaced_ids est aujourd’hui un simple ensemble d’IDs bruts, sans notion de version. Si une nouvelle version d’un objet déjà remplacé apparaît dans un fichier ultérieur, elle est silencieusement exclue de la détection (même comportement qu’un doublon déjà traité). Reste à trancher en équipe : faut-il ré-examiner un ID lors d’un changement de version (au risque de redétecter des paires déjà validées), ou continuer à l’exclure systématiquement (au risque de manquer un contenu qui a changé) ? Une piste : n’ajouter un couple (id, version) à replaces[] que si le modèle DDI expose une version exploitable sur le Fragment source.

4.4 Schéma du registre nettoyé (v2)

{
  "version": 2,
  "generated_at": "2025-xx-xxTxx:xx:xx+00:00",
  "source_registry": "s3://…/codelist_duplicates.json",
  "codelists": {
    "uuid-master": {
      "id": "uuid-master",
      "name": "Nom_master",
      "label": "Libellé master",
      "codes": [["1","Valeur 1"],["2","Valeur 2"]],
      "codes_count": 2,
      "vars": ["var1", "var2"],
      "replaces": [
        {
          "id": "uuid-dup",
          "name": "Nom_dup",
          "detection_types": ["exact", "usage"],
          "confidence": 1.0
        }
      ],
      "first_added_at": "2025-xx-xx...",
      "updated_at": "2025-xx-xx..."
    }
  }
}

4.5 Sync incrémental (sync_cleaned_registry)

Lors de /api/registry/save :

  • Décision approuvée → ajout au replaces du parent (création de l’entrée parente si inexistante)
  • Décision dé-approuvée → retrait du replaces (uniquement si l’ID existe toujours dans les duplicates du parent courant)
  • replaces vide → entrée supprimée uniquement si c’est le sync qui l’a vidée (les entrées ajoutées à la main survivent)
  • Name/label/codes/vars ne sont JAMAIS modifiés par le sync (éditions manuelles préservées)

5 Walkthrough : Exemples avec les Datasets Demo

Le dossier demo/ contient des fichiers XML de petite taille, générés depuis les sources S3, avec des paires connues pour illustrer et tester le pipeline.

Générer les fichiers : uv run demo/make_demo_files.py.

5.1 demo/BTS_demo.xml — Run 1 (sans registry)

Contexte : 10 CodeLists issues de BTS.xml : 4 paires connues + 2 listes uniques.

5.1.1 Paire 1 — Exact : N_NAF_6 ↔︎ N-ACTIVITE-FRANCAISE-6-POSTES

Valeur N_NAF_6 N-ACTIVITE-FRANCAISE-6-POSTES
AZ Agriculture, sylviculture et pêche Agriculture, sylviculture et pêche
BE Industrie manufacturière, ind. extractives et autres Industrie manufacturière, ind. extractives et autres
FZ Construction Construction
GI Commerce de gros et de détail, transports, hébergement et restauration Commerce de gros et de détail, transports, hébergement et restauration
JU Services divers Services divers
OQ Administration publique, enseignement, santé et action sociale Administration publique, enseignement, santé et action sociale
Vide Non renseigné Non renseigné

Détection : exact (signature (valeur, libellé) identique sur les 7 codes). Signal d’usage : mêmes variables référentes dans les deux listes. Décision expert : N_NAF_6 retenue comme maître → N-ACTIVITE-FRANCAISE-6-POSTES placée dans replaces[] de N_NAF_6.

5.1.2 Paire 2 — Fuzzy : N_nat_etab_23 ↔︎ NAT_ETAB

3 codes partagés (CCP, COM, STD) — libellés identiques mais noms de CodeList différents et description légèrement différente.

Détection : SequenceMatcher ≥ 0.90.

5.1.3 Paire 3 — Fuzzy : N_champ_23 ↔︎ N_CHAMP_2022

5 codes partagés (DADS, DADS-PUBLIC, DSN, DSN-PUBLIC, PE).

5.1.4 Paire 4 — Sémantique (LLM) : N_etat_23 ↔︎ Etat_2020

Pas de chevauchement direct de codes — détectée uniquement par embeddings + juge LLM.

Résultat run 1 : codelist_duplicates.json contient 4 paires. Sauvegarde dans cleaned_codelists.json (v1) : 4 entrées de maître avec leurs replaces[].


5.2 demo/BPE_demo.xml — Run 2 (avec --registry cleaned_codelists.json)

Contexte : 9 CodeLists issues de BPE.xml — 6 listes BPE natives, 2 copies de BTS avec IDs neufs, 1 unique à ajouter manuellement.

5.2.1 Paires intra-BPE (indépendantes du registre)

Paire 5 — L_VAR_SPECIF_2023 ↔︎ L_VAR_SPECIF_2021

Même concept, variantes annuelles différentes (codes 0/1/_U/_Z pour 2023 vs OUI/NON/IND/NC pour 2021).

Paire 6 — L_AN_BPE_EVOL_2023 ↔︎ L_AN_BPE_EVOL_2022

Codes : 2013/2017/2018/2023 pour 2023 vs 2017/2022 pour 2022 — même concept, années différentes.

5.2.2 Paires cross-fichier (détectées GRÂCE AU REGISTRY)

Paire 7 — NAF_6_POSTES_BPE ↔︎ registre N_NAF_6 (exact, cross-fichier)

NAF_6_POSTES_BPE est une copie exacte de N_NAF_6 avec des UUIDs entièrement neufs. Sans le registre, le pipeline ne détecterait qu’une similarité de contenu brute. Avec le registre, N_NAF_6 est injectée comme master prioritaire et servira de candidat à la comparaison — la détection exacte la reconnaît immédiatement.

Paire 8 — ETAT_UNITE_BPE ↔︎ registre N_etat_23 (fuzzy, cross-fichier)

ETAT_UNITE_BPE est une copie de N_etat_23 avec des tweaks sur les libellés (ex. Unité active → Unité en activité, Non renseigné → Non renseignée). La détection par signature exacte ne la capturera pas, mais la signature floue (SequenceMatcher ≥ 0.90) plus le registre qui place N_etat_23 comme master prioritaire permettent sa détection.


5.3 Registre après opération 2 (BPE)

cleaned_codelists.json (v2) contient maintenant 6 entrées de maître :

Maître Origine replaces[] contient
N_NAF_6 BTS_demo N-ACTIVITE-FRANCAISE-6-POSTES (run 1) + NAF_6_POSTES_BPE (run 2)
N_nat_etab_23 BTS_demo NAT_ETAB (run 1)
N_champ_23 BTS_demo N_CHAMP_2022 (run 1)
N_etat_23 BTS_demo Etat_2020 (run 1) + ETAT_UNITE_BPE (run 2)
L_VAR_SPECIF_2023 BPE_demo L_VAR_SPECIF_2021 (run 2)
L_AN_BPE_EVOL_2023 BPE_demo L_AN_BPE_EVOL_2022 (run 2)

5.4 Ajout manuel : L_QUALITE_ZONAGE_2023

Cette CodeList est unique (aucun doublon intra ou cross-fichier détecté). L’expert peut l’ajouter manuellement au registre (bouton “Ajouter au registre” dans l’interface), sans validation de paire. Elle sera présente comme master dans le registre pour les opérations futures.

À discuter — paquet partagé vs local au domaine producteur (RMéS) : l’ajout manuel ne distingue pas aujourd’hui une CodeList à verser dans le paquet de ressources partagées commun d’une CodeList qui doit rester locale au domaine du producteur. Critères candidats à trancher en équipe : réutilisation avérée par ≥ 2 domaines/opérations, correspondance à une nomenclature officiellement référencée, validation explicite d’un référent qualité. Il faudra aussi un mécanisme pour reverser a posteriori une CodeList locale dans le paquet partagé (ex. marquage « candidate à la promotion » + revue périodique), plutôt que de tout verser automatiquement — l’objectif étant d’éviter l’effet « premier-arrivé-premier-servi » qui imposerait les choix de nommage d’un seul producteur à tous les autres.

5.4.1 Amorçage du registre avec les nomenclatures officielles partagées

Le mécanisme --registry (§ ci-dessus) fait déjà gagner la priorité « maître » aux entrées du registre même sans aucune variable référente : main.py préfixe les entrées du registre nettoyé à la liste des CodeLists avant tout regroupement, et detect_exact_duplicates / find_usage_groups retiennent toujours le premier élément du groupe comme maître — donc les entrées du registre l’emportent structurellement dès qu’un doublon apparaît, indépendamment du nombre de variables qui les référencent au moment de l’injection.

A étudier : amorcer cleaned_codelists.json avec un export du paquet de ressources dédié aux nomenclatures officielles (codes renseignés, vars: [] au départ), en dehors du cycle de sync normal des décisions. Une entrée ajoutée ainsi « à la main » survit aux syncs suivants même avec un replaces vide (sync_cleaned_registry ne supprime que les entrées vidées par le sync lui-même). Aucune modification de code n’est donc nécessaire pour que ces nomenclatures soient toujours reconnues comme maîtres — seule une procédure d’amorçage (export + import initial) reste à mettre en place.


6 Tableau Récapitulatif — Dédoublonnage BTS.xml

Méthode Seuil Complexité Output BTS.xml (156 CLs)
Signature exacte — \(O(N \cdot C)\) 29 groupes, 54 CLs redondantes
Similarité floue ≥ 0.90 \(O(U^2 \cdot L)\) ~10 paires
Embeddings de liste ≥ 0.90 + LLM \(O(U^2 \cdot D)\) Dépend de config OpenAI
Embeddings de variable ≥ 0.92 \(O(V^2 \cdot D)\) Dépend de config OpenAI
var_sig Identique \(O(N)\) 17 groupes de même usage
  • \(N\) = nb CodeLists (156), \(C\) = nb codes moyen (10)
  • \(U\) = nb uniques après dédup exact (102)
  • \(V\) = nb variables (287), \(D\) = dimension embeddings (8192)
  • \(L\) = longueur moyenne du texte comparé (nom + paires valeur:libellé concaténées) par difflib.SequenceMatcher en phase floue

7 Perspectives

7.1 Performance à Grande Échelle

  • MSA / MinHash pour remplacer la comparaison par paires \(O(n^2)\)
  • Parallélisation du pipeline sur Kubernetes via Argo Workflows

7.2 Robustesse

  • Détection de sous-ensembles : une CodeList peut être incluse dans une autre
  • Embeddings spécialisés sur le vocabulaire statistique Insee
  • Inclusion par référence : le modèle DDI la permet, mais ni l’outil ni Colectica ne l’implémentent (crainte de références circulaires côté Colectica). Les listes plates issues d’un seul niveau d’une nomenclature hiérarchique officielle (ex. NAF) restent donc des CodeLists à part entière, correctement traitées comme distinctes par la détection de contenu.
  • Limite connue (déjà documentée dans le code, semantic.py, docstring de detect_semantic_userid) : l’heuristique de préfixe commun (même projet Colectica) peut signaler à tort deux niveaux différents d’une même nomenclature (ex. NAF 6 postes vs 17 postes) comme candidats — aucun garde-fou automatique aujourd’hui, surveillance manuelle nécessaire.

7.3 Pipeline Reproductible

  • Transformation vers pipeline targets (R) ou Python équivalent
  • Intégration CI/CD : déclencher le dédoublonnage à chaque commit

7.4 Analyse Fine de l’Usage

  • Hiérarchie des variables (groupes, familles d’enquêtes)
  • Pondération par importance (métrique de centralité)
  • Contextes d’enquête : une CodeList valide dans un contexte mais pas un autre

Conclusion

Un entonnoir en trois niveaux — exact → flou → sémantique — complété par un signal d’usage (var_sig) et un registre transversal itératif (--registry) permet de couvrir toute la palette des redondances : copies conformes, variantes mineures, synonymes conceptuels, et doublons cross-opération.

Les datasets demo/ illustrent ce workflow complet :

  1. BTS_demo (run 1) : 4 paires intra-fichier détectées (exact, fuzzy, sémantique)
  2. BPE_demo (run 2, avec registre) : 2 paires intra-BPE + 2 paires cross-fichier (NAF_6_POSTES_BPE ↔︎ N_NAF_6, ETAT_UNITE_BPE ↔︎ N_etat_23)
  3. Le registre cleaned_codelists.json accumule 6 maîtres validés couvrant les deux opérations