Aller au contenu

Modèle de données

Architecture médaillon

L'entrepôt (warehouse) est découpé en trois schémas, chacun avec une responsabilité unique et non négociable.

flowchart LR
    S1[Générateur<br/>synthétique] --> B
    S2[API ODRÉ] --> B
    B["🥉 bronze<br/><small>brut, horodaté</small>"] --> S["🥈 silver<br/><small>typé, conformé</small>"]
    S --> G["🥇 gold<br/><small>modèle en étoile</small>"]
    G --> M[Metabase]
Schéma Contenu Matérialisation Règle
bronze Données telles qu'arrivées de la source Tables Aucune transformation. Colonne _ingested_at obligatoire
silver Typé, nettoyé, dédoublonné, enrichi des règles métier Vues Un modèle par source, sans jointure entre domaines
gold Faits et dimensions Tables Ce que Metabase interroge, et rien d'autre

Conserver le bronze intact n'est pas un principe abstrait : c'est ce qui permet de reconstruire tout l'aval quand une règle métier change, sans redemander les données à la source.

Convention de nommage : médaillon en base, dbt dans le code

Les schémas physiques portent les noms médaillon (bronze, silver, gold), mais l'arborescence dbt suit la convention idiomatique de l'outil :

dbt/models/                        →  schéma PostgreSQL
  staging/       stg_*.sql         →  silver
  intermediate/  int_*.sql         →  silver (éphémère, rien en base)
  marts/         dim_*/fct_*.sql   →  gold
  (_sources.yml pointe sur         ←  bronze)

Pourquoi les deux. Médaillon se raconte en vingt secondes devant un décideur ; la convention dbt guide précisément l'implémentation et est comprise de quiconque a déjà fait du dbt. Le pont coûte une ligne de configuration (+schema: dans dbt_project.yml) et une macro.

La macro indispensable

Par défaut, dbt préfixe les schémas personnalisés par le schéma cible : +schema: silver produirait public_silver. Ce préfixage sert à isoler les développeurs sur un entrepôt partagé — ce n'est pas notre cas. La macro macros/generate_schema_name.sql le neutralise.

Le modèle en étoile

erDiagram
    dim_customer ||--o{ fct_orders : "customer_id"
    dim_customer ||--o{ fct_order_lines : "customer_id"
    dim_customer ||--o{ fct_subscriptions : "customer_id"
    dim_customer ||--o{ fct_support_tickets : "customer_id"
    dim_product  ||--o{ fct_order_lines : "product_id"
    dim_date     ||--o{ fct_orders : "date_key"
    dim_date     ||--o{ fct_order_lines : "date_key"
    dim_date     ||--o{ fct_energy_regional_daily : "date_key"
    dim_date     ||--o{ fct_finance_mensuel : "date_key"
    dim_region   ||--o{ dim_customer : "code_insee_region"
    dim_region   ||--o{ fct_energy_regional_daily : "code_insee_region"
Table Grain Lignes
dim_customer un client 5 000
dim_product un produit 120
dim_date un jour ~780
fct_orders une commande ~22 000
fct_order_lines une ligne de commande ~71 000
fct_subscriptions un abonnement 1 500
fct_support_tickets un ticket 14 000
fct_energy_regional_daily une région × un jour ~540
fct_finance_mensuel un mois clos 24
dim_region une région française 13

Les clés étrangères n'existent pas en base

dbt crée des tables, pas des contraintes. PostgreSQL ne connaît donc aucune relation entre ces tables. Sans action, Metabase verrait huit tables indépendantes et il serait impossible de croiser un chiffre d'affaires avec un segment client sans écrire une jointure à la main.

Les relations sont déclarées dans les métadonnées Metabase, par scripts/metabase_provision.py. C'est ce qui rend le modèle navigable en quelques clics et active le drill-through.

Deux modèles à connaître

fct_finance_mensuel consolide le compte de résultat au grain mensuel : chiffre d'affaires brut et net, remises consenties, coût des ventes, marge, revenu récurrent, panier moyen. Sans lui, chaque analyse reconstruirait ces agrégats à sa façon — et les chiffres divergeraient.

Le mois en cours est exclu

Un mois partiel produirait une chute finale sur toutes les courbes, lue à tort comme un effondrement. Un compte de résultat porte sur des périodes closes.

dim_region porte les 13 régions métropolitaines avec leurs centroïdes. Elle sert les cartes de l'application sur mesure et relie les clients comme les mesures d'énergie à une géographie commune.

Cohérence du jeu synthétique

Le générateur (dags/lib/synthetic.py) est déterministe : graine fixe, deux exécutions produisent le même jeu. Cela rend les démonstrations reproductibles et les tests dbt stables.

Surtout, le comportement d'achat dépend du segment client. Une première version tirait le client uniformément : le segment le plus nombreux (les TPE) dominait mécaniquement le chiffre d'affaires, produisant des TPE pesant plus que des grands comptes. Un prospect repère cette incohérence en trois secondes.

Chaque segment porte donc un profil : fréquence de commande, quantités, nombre de lignes, taux de remise, taux d'attrition, nombre de postes souscrits.

Deux autres corrections ont été nécessaires pour que la série temporelle soit présentable :

  • Deux tiers du parc client préexistent à la fenêtre observée. Sans cela le chiffre d'affaires part de zéro au premier mois : aucune entreprise ne démarre sans client.
  • La saisonnalité est tirée au sort, pas appliquée après coup. Une première version décalait 25 % des commandes de fin d'année vers une date ultérieure — ce qui concentrait mécaniquement la masse en fin de période et faisait paraître le chiffre d'affaires multiplié par quarante en deux ans. Le mois est désormais choisi par tirage pondéré (coefficient saisonnier × croissance de 1,4 %/mois).

Résultat obtenu :

Indicateur Valeur
Chiffre d'affaires produit 52,7 M€/an
Revenu récurrent 32,7 M€/an
Taux de marge 53 %
Panier moyen 4 745 € (médiane 2 934 €)
Grands comptes 49,5 % du CA pour 6 % des clients
TPE 3,0 % du CA pour 42 % des clients
Attrition 14,2 %

Qualité des données

58 tests s'exécutent à chaque dbt build : unicité et non-nullité des clés, intégrité référentielle entre faits et dimensions, valeurs autorisées, plages numériques, unicité de combinaisons.

dbt build respecte le graphe de dépendances : un modèle dont un test échoue ne propage pas ses données en aval. Les modèles avals sont sautés plutôt que construits sur une base douteuse.