Nettoyage et réconciliation de données
Outils par défaut : DuckDB 2.0 SQL ou l'API paresseuse de Polars 2.x (scan_csv, scan_parquet) pour les fichiers locaux. Pandas uniquement lorsqu'une bibliothèque l'impose. Python via uv, lint avec ruff. Vérifiez tout détail d'API modifié dans Polars 2.0 ou DuckDB 2.0 par rapport à la documentation actuelle avant de vous y fier (voir references/landscape-2026-11.md).
Ne jamais corriger silencieusement
Chaque coercition, suppression, valeur par défaut ou fusion est comptée et signalée. Une étape de nettoyage est (lignes_entrée, lignes_sortie, lignes_modifiées, raison). Les échecs de conversion deviennent NULL plus un compteur, jamais une ligne supprimée ni une valeur devinée. Conservez les données brutes intactes ; écrivez la sortie nettoyée dans un nouveau chemin. Si les comptes d'une étape vous surprennent, arrêtez-vous et signalez-les au lieu de continuer.
Procédure
- Profilage (lecture seule). 2. Décider de la clé, du grain et des types. 3. Nettoyer dans un pipeline de petites étapes comptées. 4. Réconcilier si deux sources existent. 5. Émettre les exceptions plus un résumé d'exécution avec chaque compte.
1. Profilage avant de toucher
Lisez d'abord chaque colonne comme du texte (all_varchar=true dans DuckDB, infer_schema=False dans polars), car l'inférence sur un échantillon transforme silencieusement 00123 en 123 et les colonnes mixtes en type de la première ligne.
Ensuite, par colonne, calculez : nombre de lignes, taux de valeurs nulles/chaînes vides, nombre de valeurs distinctes, min/max (en texte et en analysé), longueur la plus courte/la plus longue, et part de valeurs correspondant à un modèle de type (entier, décimal, date ISO, autre).
SUMMARIZE de DuckDB fait la plupart de ces calculs en un appel.
Enregistrez avant le nettoyage : nombre total de lignes, nombre de clés candidates distinctes, lignes par clé (max), et les en-têtes exactement comme trouvés (espaces de fin, BOM, noms en double). Une clé candidate n'est une clé que si le nombre de valeurs distinctes est égal au nombre de lignes après normalisation ; testez-la, ne présumez pas.
Vérifiez : les doublons de lignes entières, les en-têtes répétés au milieu du fichier, les lignes de pied de page/total dans les données, les valeurs sentinelles ("N/A", "-", "0000-00-00", 9999-12-31), les encodages mixtes, et les colonnes numériques qui sont en réalité des identifiants.
2. Types et analyse syntaxique
- Les identifiants, les codes postaux/ZIP, les numéros de compte et de téléphone sont des chaînes, toujours. Stockez la largeur avec remplissage de zéros comme règle (
lpadà la largeur connue) uniquement lorsque la largeur est documentée ; sinon conservez-la telle quelle. - Dates : analysez avec un format explicite par colonne (
try_strptime/str.to_date(format, strict=False)). Ne laissez jamais le moteur deviner l'ordre jour/mois. Les valeurs ambiguës (03/04/2026) sans ordre documenté sont une exception, pas un pile ou face. Gardez une décision de fuseau horaire explicite. - Monnaie : analysez en unités mineures entières ou
DECIMAL(p,s), jamais en flottant. Supprimez les symboles monétaires et les séparateurs de milliers selon la règle de la locale :1.234,56et1,234.56sont des locales différentes, décidez donc par source et affirmez que chaque valeur correspond au modèle choisi. Les parenthèses et le moins final sont des négatifs. Enregistrez la devise par ligne ; ne faites pas la somme entre devises. - Booléens et énumérations : mappez à partir d'un tableau explicite ; les valeurs non mappées vont dans les exceptions.
- Après chaque conversion, comptez
non-nul avantmoinsnon-nul après. Ce nombre est le nombre d'échecs de coercition et figure dans le rapport.
3. Normalisation
Appliquez à une clé de correspondance dérivée, pas à la colonne d'origine, afin que la valeur brute reste disponible :
Unicode NFKC, coupe (trim), réduction des espaces internes, pliage de casse (casefold), suppression des espaces de largeur nulle et insécables.
NFKC est avec perte (ligatures, formes pleine largeur, exposants), ce qui est le but pour la correspondance et erroné pour l'affichage.
Pour les identifiants, supprimez également les séparateurs (-, /, espaces) et décidez explicitement du traitement des O/0 et I/1.
NFKC n'est pas dans le SQL de base de DuckDB ; faites-le en polars/Python ou enregistrez une UDF (vérifiez la prise en charge actuelle).
4. Dédoublonnage
- Doublons exacts : supprimez avec un compte ; conservez le numéro de ligne source de la ligne survivante.
- Doublons par clé : décidez d'abord s'il s'agit de vraies répétitions ou d'événements distincts (même numéro de facture, montant différent est un conflit, pas un doublon).
- Le choix du survivant doit être déterministe et énoncé :
ROW_NUMBER() OVER (PARTITION BY key ORDER BY updated_at DESC NULLS LAST, completeness DESC, source_row DESC) = 1(DuckDBQUALIFY). Terminez toujours le ORDER BY par un critère de départage unique tel que le numéro de ligne source, car les égalités sont sinon résolues par un ordre de parcours arbitraire. Dans polars, utilisezsort(...)puisunique(subset, keep="first", maintain_order=True). - Le dédoublonnage flou est un générateur de candidats, jamais une fusion automatique : regroupez sur une clé peu coûteuse (code postal normalisé, 3 premières lettres, année), notez au sein des groupes, n'acceptez automatiquement qu'au-dessus d'un seuil élevé avec un champ secondaire exact concordant, et envoyez la bande moyenne à une liste humaine. Rapportez le seuil et les comptes.
5. Audit de réconciliation
Source de vérité = A, dérivé = B (par exemple, export système vs documents OCRisés). Ne dites jamais "B a tort" avant d'avoir vérifié si l'extraction ou la clé est erronée.
- Totaux de contrôle d'abord. Nombre de lignes et somme de chaque colonne de monnaie/quantité dans A et B, plus les comptes de clés distinctes. Une divergence vous indique s'il faut s'attendre à des lignes manquantes, des lignes supplémentaires ou une dérive des montants. Des totaux qui concordent alors que les lignes diffèrent signifient des erreurs qui se compensent ; continuez.
- Correspondance des clés. Construisez la même clé normalisée des deux côtés (section 3). Faites d'abord une correspondance exacte, puis en passes explicites de sévérité décroissante (par exemple, clé uniquement, puis clé plus fenêtre de date), en étiquetant chaque correspondance avec la passe qui l'a produite. Ne mélangez jamais les passes non étiquetées.
- Anti-jointures dans les deux sens. A-non-dans-B (manquant dans le dérivé) et B-non-dans-A (inattendu dans le dérivé, souvent des fautes de frappe de clé OCR ou des doublons). Signalez les deux ; ne lire qu'un seul sens cache la moitié des erreurs. Avant de déclarer une ligne manquante, vérifiez si sa clé entre en collision avec des doublons d'un côté ou de l'autre (les jointures plusieurs-à-plusieurs gonflent les totaux).
- Comparaison des champs sur les paires appariées. Comparez par champ après la même normalisation.
Tolérance monétaire : comparez en unités mineures,
abs(a - b) <= toloù tol est déclarée à l'avance (0 pour les livres de comptes ; une tolérance d'arrondi telle que 1 unité mineure par ligne uniquement lorsque les sources arrondissent différemment, et jamais un pourcentage sur les petits montants). Dates : exactes, ou une fenêtre de jour déclarée. Texte : égalité normalisée, puis un score de similarité rapporté comme un score, pas comme une réussite. - Classez, ne vous contentez pas de différencier. Chaque exception reçoit un code de raison :
MISSING_IN_B,EXTRA_IN_B,DUP_KEY_A,DUP_KEY_B,AMOUNT_MISMATCH,FIELD_MISMATCH,UNPARSEABLE_A,UNPARSEABLE_B,AMBIGUOUS_MATCH. Les lignes non analysables sont des exceptions, jamais exclues silencieusement des totaux. - Bouclez la boucle. L'identité finale doit être vérifiée :
|A| = apparié + manquant_dans_B + excédent_doublon_A + non_analysable_A(et l'équivalent pour B). Si cela ne s'équilibre pas, l'audit n'est pas terminé.
Les modèles SQL pour les passes, les anti-jointures, les tolérances et la vérification d'équilibre se trouvent dans references/reconciliation-recipes.md.
Rapport d'exceptions
Une ligne par exception, lisible sans l'outillage :
code_raison, clé, fichier source A et numéro de ligne, fichier source B et numéro de ligne (et page pour les documents scannés), champ, valeur_a, valeur_b, delta, action_suggérée.
Triez par impact monétaire décroissant, puis par raison.
Placez un résumé d'exécution en haut : entrées avec comptes de lignes et sommes de contrôle, chaque étape de nettoyage avec comptes, totaux de contrôle des deux côtés, identité d'équilibre, tolérances utilisées.
Écrivez un CSV pour la machine et une courte vue markdown ou xlsx pour l'humain ; ne déversez pas des milliers de lignes dans le chat, résumez les comptes par raison et montrez les quelques premières.
Garde-fous
- Affirmez, ne vérifiez pas visuellement : nombres de lignes après jointures, unicité de la clé après dédoublonnage, pas de colonnes flottantes pour la monnaie.
- Les jointures à gauche sur des clés non uniques multiplient les lignes ; vérifiez le compte avant et après chaque jointure.
- N'écrasez pas A. Les corrections apportées à la source de vérité sont une décision humaine répertoriée dans le rapport.
- Les secrets pour les bases de données sont lus dans
/run/secrets/<name>, pas dans les variables d'environnement ou les lignes de commande. - Les vérifications déclaratives (soda-core, great_expectations) valent la peine d'être ajoutées une fois les règles stables ; le profil et les comptes ci-dessus passent en premier.
Les leçons tirées d'une utilisation réelle s'accumulent dans references/gotchas.md.