Niveau : junior · Durée estimée : ~8 heures · Stack : dbt-core, dbt-duckdb, sqlfluff, GitHub Actions
Tu viens d'arriver dans une boîte dont le projet dbt a zéro test et zéro CI. Les analystes cassent les modèles à chaque review, personne ne voit rien, et on découvre la régression quand un dashboard renvoie n'importe quoi le lundi matin.
Ta mission : sortir cette équipe de l'amateurisme. Tu pars d'un mini projet dbt (staging + marts) sur un jeu de données fourni, tu ajoutes les tests qui comptent, et tu câbles une CI qui attrape trois classes de régression avant qu'elles atteignent la prod :
- Erreur de syntaxe SQL (via
sqlfluff) - Dérive de schéma (via contrats dbt)
- Défaut qualité donnée (via tests dbt génériques + singuliers)
Pour que tu te concentres sur ce qui compte pédagogiquement, le template fournit :
- Un projet dbt fonctionnel (
dbt_project.yml,profiles.ymlpointant vers DuckDB local) - Les deux seeds :
seeds/raw_customers.csv(50 clients) +seeds/raw_orders.csv(200 commandes) - Deux staging models déjà écrits (
stg_customers,stg_orders) — reference pour le pattern - Une config
sqlfluffpour DuckDB - Une rubrique d'évaluation CI (
.github/workflows/iamdataeng-evaluate.yml) — ne la modifie pas, c'est elle qui détermine si ta soumission passe
dim_customers.sql — dimension client, une ligne par customer_id. Colonnes attendues :
| colonne | type | description |
|---|---|---|
customer_id |
varchar | Clé primaire, unique, non-null |
email |
varchar | Email lowercase |
name |
varchar | Nom complet |
country |
varchar | Code pays ISO-2 |
plan |
varchar | free / pro / enterprise |
signed_up_at |
date | Date d'inscription |
total_orders |
integer | Nombre de commandes non-cancelled |
total_spend_eur |
decimal(18,2) | Somme amount_eur des commandes non-cancelled |
first_order_date |
date | Nullable si aucune commande |
fct_orders.sql — fait commandes, une ligne par order_id. Colonnes attendues :
| colonne | type | description |
|---|---|---|
order_id |
varchar | Clé primaire |
customer_id |
varchar | FK vers dim_customers.customer_id |
order_date |
date | |
amount_cents |
integer | |
amount_eur |
decimal(18,2) | |
status |
varchar | Un de placed, paid, cancelled, refunded |
is_revenue |
boolean | true si status = 'paid' |
customer_country |
varchar | Dénormalisé depuis dim_customers.country |
Minimum 5 tests répartis sur les deux modèles. Utilise les tests génériques dbt :
not_nullsur les clés et colonnes critiquesuniquesur les clés primairesrelationshipspour la FKfct_orders.customer_id→dim_customers.customer_idaccepted_valuessurfct_orders.status
Un test singulier est une requête qui retourne des lignes quand une règle métier est violée. Exemple pertinent : "les commandes doivent avoir un amount_cents > 0 sauf si statut cancelled/refunded".
Dans _marts.yml, ajoute config.contract.enforced: true sur dim_customers, et déclare toutes les colonnes avec leur data_type. Le job CI contract-breakage simule un changement de schéma côté source (rename de colonne) — ton contrat doit faire échouer dbt build avec une erreur explicite.
# 1. Dépendances Python
make install
# 2. Packages dbt (dbt_utils)
make deps
# 3. Charger les seeds
make seed
# 4. Build complet (seeds + modèles + tests)
make build
# 5. Lint SQL
make lintEn alternative zéro-setup : ouvre le projet dans GitHub Codespaces via le bouton "Commencer" sur iamdataeng.vercel.app — tout sera installé automatiquement dans le devcontainer.
À chaque push sur main ou sur une branche, le workflow .github/workflows/iamdataeng-evaluate.yml tourne et vérifie :
| Check | Ce qui est testé | Si ça fail |
|---|---|---|
| dbt_build_passes | dbt build complet exit 0 |
Un modèle plante ou un test échoue. Regarde les logs dbt — la première ligne rouge donne la cause. |
| minimum_test_count ≥ 5 | Au moins 5 tests exécutés (génériques + singuliers combinés) | Tu as moins de 5 tests. Ajoute not_null sur les PKs, unique sur les business keys, relationships sur les FKs. |
| sqlfluff_passes | sqlfluff lint models/ exit 0 |
Violations de style SQL. sqlfluff fix models/ localement peut en corriger la plupart automatiquement. |
| contract_breakage_caught | Après rename d'une colonne dans un seed, dbt build DOIT échouer |
Ton contrat n'enforce pas réellement le schéma. Déclare toutes les colonnes avec data_type et enforced: true. |
Tu peux push autant de fois que tu veux pour itérer. Aucune limite de soumission.
{{ ref('stg_customers') }}dans la FROM mais pas déclaré comme dépendance en amont → erreur de compilation dbt.sqlfluffconfiguré pour PostgreSQL alors que le projet cible DuckDB → faux positifs. Le.sqlflufffourni est déjà bon, ne le change pas.- Tests lancés via
dbt testseulement au lieu dedbt build→ tu rates les modèles qui plantent. Toujoursdbt build. - Contrat déclaré mais
enforced: false(ou oublié) → le contrat est cosmétique, pas un garde-fou. data_typemis en majuscules (VARCHAR) alors que DuckDB renvoie minuscules → le contrat échoue pour la mauvaise raison. Écris les types en minuscules.