TL;DR
  • La qualité = assez fiable pour agir : périmètre clair, transparence sur les données douteuses.
  • Cadre en trois temps : tests à l'ingestion, monitoring des dérives, boucle de correction.
  • 10 tests suffisent pour attraper 90 % des incidents (fraîcheur, volumétrie, schéma, nulls, unicité, référentiel, plages, doublons, distribution, règles métier).
  • Python orchestre les tests, historise et calcule le score d'impact pour notifier sans spam.
  • Score d'impact : criticité, surface, fraîcheur et exposition ; publie uniquement si le score est acceptable.
  • Alertes utiles : dire quoi, l'impact et proposer une action.
  • Boucle de correction : détecter, classifier, corriger, ajouter le test manquant, apprendre.

On parle beaucoup de « qualité » comme d'une vertu. En pratique, c'est un produit. Et comme tous les produits, il faut des garde‑fous automatiques.

J'avais 25 ans quand un bug data m'a coûté une présentation devant le CFO. Depuis, je préfère recevoir une alerte à 3 h du matin plutôt que d'entendre « votre chiffre est faux » en plein board. Ce guide est le résultat de nuits à automatiser la qualité des données et à apprendre de mes erreurs.

1) La définition utile de la qualité

La qualité des données n'est pas « zéro erreur ». C'est : assez fiable pour agir sur un périmètre clair, avec une transparence sur ce qui est douteux. Si ton dataset est à 98 % correct mais que les 2 % restants touchent un KPI critique, tu n'as pas 98 % : tu as un incident.

2) Le cadre : tests, monitoring, puis correction

  • Tests : valider le dataset à l'ingestion et avant publication
  • Monitoring : détecter dérives et anomalies dans le temps
  • Correction : une boucle claire qui rend le système meilleur

3) Les 10 tests qui catchent 90 % des incidents

Tu n'as pas besoin d'une usine. Tu as besoin d'une shortlist.

  1. Fraîcheur : dernière date de refresh dans la fenêtre attendue
  2. Volumétrie : lignes dans un intervalle normal (min, max, ou z score)
  3. Schéma : colonnes attendues, types attendus, pas de colonnes fantômes
  4. Nulls : champs critiques non nuls (ex : date, montant, id)
  5. Unicité : clés uniques (ex : id_transaction)
  6. Référentiel : clés fact qui existent en dimension
  7. Plages : montants, taux, quantités dans des bornes plausibles
  8. Doublons : duplication anormale sur une combinaison de champs
  9. Distribution : dérive de distribution (ex : part canal, panier moyen)
  10. Règles métier : invariants (ex : marge = CA - coûts, pas de marge au dessus de 100 %)

4) Exemples SQL simples (et suffisants)

Exemple : test de nulls sur champs critiques.

SELECT COUNT(*) AS nb_nulls FROM fact_ventes WHERE date_commande IS NULL OR montant IS NULL OR id_client IS NULL;

Test d'unicité.

SELECT id_transaction, COUNT(*) AS c FROM fact_ventes GROUP BY id_transaction HAVING COUNT(*) > 1;

Test de référentiel.

SELECT COUNT(*) AS orphelins FROM fact_ventes f LEFT JOIN dim_client c ON f.id_client = c.id_client WHERE c.id_client IS NULL;

Ces tests produisent des résultats simples : 0 = OK, >0 = incident. Pas besoin de poésie.

5) Python pour orchestrer, scorer et notifier

SQL te dit si c'est cassé. Python te permet de :

  • centraliser les tests et leurs seuils
  • garder l'historique (pour détecter une dérive)
  • calculer un score d'impact
  • notifier au bon niveau (pas de spam)

Voici un squelette minimal en Python qui orchestre les tests, historise et notifie :


tests = {
    "fraicheur": "SELECT COUNT(*) FROM ventes WHERE date_commande < CURRENT_DATE - INTERVAL '1 day'",
    # ajoute tes autres requêtes...
}
history = []

def run_tests(conn, tests):
    incidents = []
    for name, sql in tests.items():
        count = execute_sql(conn, sql)  # ta fonction maison d'exécution SQL
        history.append((date.today(), name, count))
        if count > 0:
            incidents.append((name, count))
    return incidents

def compute_score(incidents):
    # ton propre calcul de criticité/surface/fraîcheur/exposition
    return sum(count for (_, count) in incidents)

# Exemple d'appel
incidents = run_tests(conn, tests)
score = compute_score(incidents)
if score > seuil:
    notifier(incidents, score)  # à toi de définir notifier()
          

6) Le score d'impact (la vraie valeur)

Le but n'est pas de tout corriger. Le but est de corriger ce qui détruit la décision. Donc tu scores un incident selon :

  • Criticité : est‑ce que ça touche un KPI de pilotage ou un KPI finance ?
  • Surface : combien de lignes, de clients ou de pays touchés ?
  • Fraîcheur : incident actuel ou historique ?
  • Exposition : est‑ce déjà publié dans un report consommé ?

Résultat : tu peux choisir de bloquer la publication seulement quand le score dépasse un seuil. C'est ça qui rend le système viable.

7) Alerting utile : moins de bruit, plus de résolution

Une alerte utile a trois propriétés :

  • Elle dit quoi : test, table, seuil, valeur observée
  • Elle dit l'impact : KPI, périmètre, score
  • Elle dit quoi faire : owner, action (rerun, rollback, patch source)

Une alerte qui ne propose pas d'action est juste un stress automatique.

8) La boucle de correction

La qualité progresse quand tu fermes la boucle :

  1. Incident détecté
  2. Classification (bug, changement source, exception métier)
  3. Correction source ou pipeline
  4. Ajout ou ajustement de test
  5. Post mortem court : ce qui change pour éviter le prochain

Checklist de démarrage (1 semaine)

  • Identifier 5 tables critiques (celles qui alimentent les KPI)
  • Écrire 10 tests (ceux listés plus haut)
  • Créer une table d'historique des tests (date, test, statut, valeur)
  • Définir un score d'impact simple et un seuil de blocage de publication
  • Définir qui reçoit quoi : dev, data owner, business owner

Si tu veux une implémentation courte (SQL + Python) adaptée à ta stack, je peux te sortir une suite de tests prête à brancher. Me contacter