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.