Segmentation non supervisée des clients d'Olist afin de permettre à l'équipe marketing de personnaliser ses campagnes de communication selon le profil comportemental de chaque groupe de clients.
- Contexte business
- Architecture du projet
- Dataset Olist
- Installation pas à pas
- Lancement de l'environnement
- Description de chaque fichier
- Pipeline MLOps — ordre d'exécution
- Résultats
- Segmentation clients
- Tests unitaires
- CI/CD GitHub Actions
- API FastAPI
- Dashboard marketing
- Contrat de maintenance
- Auteur
Olist est une entreprise brésilienne fondée en 2015 qui connecte les petits marchands aux grandes marketplaces en ligne (Amazon, Mercado Livre, Americanas, etc.) via une solution SaaS. Valorisée à 1,5 milliard de dollars en 2021, elle gère plus de 90 000 clients actifs aux comportements très variés.
Problème : L'équipe marketing ne peut pas appliquer une stratégie de communication unique à tous les clients. Un client qui commande tous les mois à 300 BRL n'a pas le même profil qu'un acheteur unique à 50 BRL insatisfait.
Solution : Ce projet vise à identifier automatiquement des groupes de clients homogènes à partir de leurs données comportementales (historique d'achats, satisfaction, délais de livraison) pour permettre des campagnes ciblées et mesurables.
4 segments identifiés :
| Segment | Nom | Taille | Stratégie |
|---|---|---|---|
| 0 | Nouveaux satisfaits | 49% | Fidélisation |
| 1 | Clients dormants | 33% | Réactivation |
| 2 | Clients insatisfaits | 15% | Récupération |
| 3 | VIP multi-acheteurs | 3% | Programme VIP |
┌─────────────────────────────────────────────────────────┐
│ PIPELINE MLOPS │
│ │
│ data/raw/ → src/data/load_data.py │
│ (CSV Olist) Chargement des 5 tables │
│ ↓ │
│ src/features/ → build_features.py │
│ build_features RFM enrichi (récence, fréquence, │
│ montant, satisfaction, livraison) │
│ ↓ │
│ src/models/ → train.py │
│ train K-Means K=4 + MLflow logging │
│ ↓ │
│ models/ → kmeans_model.pkl + scaler.pkl │
│ artefacts Sauvegardés localement + MLflow │
│ ↓ │
│ src/api/ → main.py (FastAPI) │
│ main 3 endpoints REST + Swagger │
│ ↓ │
│ dashboard/ → index.html │
│ index.html Interface marketing │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ QUALITÉ & CI/CD │
│ │
│ tests/ → 49 tests unitaires Pytest │
│ couverture 100% logique métier │
│ ↓ │
│ .github/ → GitHub Actions │
│ workflows/ci.yml Black + Flake8 + Pytest │
│ exécutés à chaque git push │
└─────────────────────────────────────────────────────────┘
Les données comprennent l'historique des commandes de 2016 à 2018, avec 100 000 commandes réelles anonymisées, gracieusement fournies par Olist sous licence CC BY-NC-SA 4.0 et disponibles sur Kaggle.
| Fichier CSV | Lignes | Colonnes | Rôle dans le projet |
|---|---|---|---|
olist_orders_dataset.csv |
99 441 | 8 | Table centrale — statuts et dates |
olist_order_items_dataset.csv |
112 650 | 7 | Prix et frais de port par article |
olist_order_payments_dataset.csv |
103 886 | 5 | Mode et montant des paiements |
olist_order_reviews_dataset.csv |
99 224 | 7 | Notes et commentaires clients |
olist_customers_dataset.csv |
99 441 | 5 | Localisation des clients |
- Période couverte : septembre 2016 → août 2018
- Commandes livrées : 96 478 (97%)
- Clients uniques : 93 358 (après feature engineering)
- 97% des clients n'ont commandé qu'une seule fois
- Score satisfaction moyen : 4.09/5
- 92% des livraisons arrivent en avance (moyenne -12 jours)
- Montant moyen par commande : 159 BRL (médiane : 105 BRL)
Avant de commencer, vérifiez que ces outils sont installés :
python --version # >= 3.13 recommandé
git --version # >= 2.0
docker --version # >= 24.0
docker compose version # >= 2.0git clone https://github.com/thiernodaoudaly/olist-customer-segmentation.git
cd olist-customer-segmentation# Windows
py -3.13 -m venv venv
venv\Scripts\activate
# Linux / macOS
python3.13 -m venv venv
source venv/bin/activateVotre prompt doit afficher (venv) au début.
# Mise à jour pip en premier
python -m pip install --upgrade pip
# Installation de toutes les dépendances
pip install -r requirements.txt
# Installation du package src/ en mode editable
# Permet d'importer src.data, src.features, etc. dans les notebooks
pip install -e .python scripts/check_env.pyRésultat attendu : tous les packages marqués OK.
Téléchargez le dataset depuis Kaggle : https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce
Extrayez tous les fichiers CSV dans data/raw/ :
data/raw/
├── olist_orders_dataset.csv
├── olist_order_items_dataset.csv
├── olist_order_payments_dataset.csv
├── olist_order_reviews_dataset.csv
├── olist_customers_dataset.csv
├── olist_sellers_dataset.csv
├── olist_products_dataset.csv
├── olist_geolocation_dataset.csv
└── product_category_name_translation.csv
DVC (Data Version Control) versionne les fichiers de données trop lourds pour Git (CSV, modèles .pkl).
dvc init
dvc add data/raw
git add data/raw.dvc data/.gitignore .dvc/ .dvcignore
git commit -m "data: ajout dataset Olist via DVC"python src/models/train.pyCe script exécute tout le pipeline en une commande : chargement → features → scaling → K-Means → MLflow logging.
# Terminal 1 — API FastAPI
uvicorn src.api.main:app --reload --port 8000
# Terminal 2 — Interface MLflow (optionnel)
mlflow ui --port 5000Ouvrez dashboard/index.html dans votre navigateur.
L'API doit tourner sur localhost:8000 pour que le dashboard fonctionne.
Docker garantit la reproductibilité totale.
# Construire et lancer tous les services
docker-compose up --build| Service | URL | Description |
|---|---|---|
| API FastAPI | http://localhost:8000 | Endpoint de segmentation |
| Swagger UI | http://localhost:8000/docs | Documentation interactive |
| MLflow UI | http://localhost:5000 | Tracking des expériences |
Pour arrêter :
docker-compose down# Activer le venv
venv\Scripts\activate # Windows
source venv/bin/activate # Linux/macOS
# Lancer l'API
uvicorn src.api.main:app --reload --port 8000
# Lancer MLflow dans un terminal séparé
mlflow ui --port 5000DVC garantit que le pipeline est exécuté dans le bon ordre et ne rejoue que les étapes modifiées.
dvc reproRôle : Analyse exploratoire des données (EDA).
Ce notebook explore les 5 tables du dataset Olist pour comprendre la structure des données avant toute modélisation. Il répond aux questions suivantes : quelles sont les valeurs manquantes ? Quelles sont les distributions des variables clés ? Quelles transformations sont nécessaires avant le clustering ?
Ce qu'il produit :
- Distribution des statuts de commandes
- Analyse de la fréquence d'achat (97% acheteurs uniques)
- Distribution des montants (asymétrique → log-transformation)
- Score de satisfaction (bimodal : pics en 1 et 5)
- Délais de livraison (92% en avance, moyenne -12 jours)
- Décision finale : features RFM enrichies à construire
Pour l'exécuter :
jupyter notebook notebooks/segmentation_01_eda.ipynbRôle : Feature engineering, modélisation et tracking MLflow.
Ce notebook construit les features RFM enrichies, teste plusieurs valeurs de K avec K-Means, compare les résultats dans MLflow et produit le modèle final avec les 4 segments nommés.
Ce qu'il produit :
- Features engineerées pour 93 358 clients
- 9 runs MLflow (K=2 à K=10) + 1 run final K=4
- Graphiques Elbow et Silhouette Score
- 4 segments nommés avec stratégies marketing
- Fichier
data/processed/rfm_features.csv
Pour l'exécuter :
jupyter notebook notebooks/segmentation_02_modeling.ipynbRôle : Simulation de la stabilité des segments dans le temps.
Ce notebook découpe les données en 7 trimestres (2017Q1 → 2018Q3), entraîne K-Means sur chaque trimestre et mesure si les segments restent cohérents avec le temps via l'ARI et la variance du silhouette.
Ce qu'il produit :
- ARI entre trimestres consécutifs (label switching détecté)
- Variance du silhouette sur 7 trimestres (std = 0.022 → stable)
- Graphique d'évolution de la taille des clusters
- Contrat de maintenance : mise à jour trimestrielle recommandée
Pour l'exécuter :
jupyter notebook notebooks/segmentation_03_simulation.ipynbRôle : Chargement des tables CSV Olist en mémoire.
Ce module fournit deux fonctions :
-
load_olist(data_dir)— charge les 5 tables nécessaires dans un dictionnaire{nom_table: DataFrame}. Lève uneFileNotFoundErrorexplicite si un fichier manque, avec le lien Kaggle pour télécharger. -
get_merged_dataset(data_dir)— charge et merge toutes les tables en un seul DataFrame via des LEFT JOIN surorder_idetcustomer_id.
Pourquoi customer_unique_id et pas customer_id ?
Chaque commande a un customer_id unique — un même client a donc
plusieurs customer_id différents pour ses différentes commandes.
customer_unique_id identifie le vrai client physique.
Utilisation :
from src.data.load_data import load_olist
dfs = load_olist("data/raw")
orders = dfs["orders"] # 99 441 lignes × 8 colonnes
customers = dfs["customers"] # 99 441 lignes × 5 colonnesRôle : Construction des features RFM enrichies.
Ce module contient une fonction par feature (principe de responsabilité unique — chaque fonction est testable indépendamment) :
| Fonction | Feature produite | Transformation |
|---|---|---|
compute_recency() |
Jours depuis dernier achat | Aucune |
compute_frequency() |
Nb de commandes | log1p |
compute_monetary() |
Montant total (prix + frais de port) | log1p |
compute_satisfaction() |
Score moyen des avis | Médiane pour NaN |
compute_delivery() |
Délai réel vs estimé | clip(-30, +30) |
build_features() |
Assemble tout en un DataFrame | Toutes ci-dessus |
Pourquoi log1p sur frequency et monetary ? K-Means calcule des distances euclidiennes. Sans transformation, un montant de 13 664 BRL dominerait tous les calculs de distance. log1p(13664) = 9.52 vs log1p(105) = 4.65 — écart ×2 au lieu de ×130.
Pourquoi la médiane pour imputer review_score_mean ? La distribution des scores est bimodale (pics en 1 et 5). La moyenne (4.09) ne représente quasiment aucun client réel. La médiane (5.0) correspond au comportement majoritaire.
Utilisation :
from src.features.build_features import build_features
from src.data.load_data import load_olist
dfs = load_olist("data/raw")
df_features = build_features(dfs)
# Retourne 93 358 lignes × 8 colonnesRôle : Script standalone d'entraînement du modèle complet.
Ce script exécute le pipeline complet en une commande :
load_olist() → build_features() → StandardScaler →
KMeans → mlflow.log → sauvegarde .pkl.
Il accepte des arguments CLI pour être paramétrable :
# Entraînement par défaut (K=4)
python src/models/train.py
# Avec paramètres personnalisés
python src/models/train.py --n-clusters 5 --run-name mon_run
# Avec dossier de données différent
python src/models/train.py --data-dir data/raw --random-state 0Ce qu'il sauvegarde :
data/processed/rfm_features.csv— features de tous les clientsmodels/kmeans_model.pkl— modèle K-Means sérialisémodels/scaler.pkl— StandardScaler sérialisé- Run MLflow avec paramètres, métriques et artefacts
Rôle : Prédiction du segment d'un client individuel.
Ce module fournit predict_segment(client_features, kmeans_model, scaler)
qui prend les données brutes d'un client, applique les transformations
(log1p), scale avec le StandardScaler entraîné, et retourne le segment.
Pourquoi scaler.transform() et pas scaler.fit_transform() ?
fit_transform() recalculerait mean/std sur un seul client — résultat
absurde. transform() utilise les paramètres appris lors de l'entraînement
sur les 93 358 clients.
Utilisation :
from src.models.predict import predict_segment
import pickle
with open("models/kmeans_model.pkl", "rb") as f:
kmeans = pickle.load(f)
with open("models/scaler.pkl", "rb") as f:
scaler = pickle.load(f)
result = predict_segment(
client_features={
"recency": 135,
"frequency": 1,
"monetary": 147.0,
"review_score_mean": 4.5,
"delivery_delay_mean": -12.0,
},
kmeans_model=kmeans,
scaler=scaler,
)
# {"segment_id": 0, "segment_name": "Nouveaux satisfaits", "strategy": "..."}Rôle : API REST FastAPI exposant le modèle de segmentation.
L'API charge automatiquement le modèle K-Means et le scaler
depuis MLflow au démarrage (via le lifespan context manager).
3 endpoints disponibles :
| Méthode | Endpoint | Description |
|---|---|---|
| GET | / |
Health check — API et modèle opérationnels ? |
| GET | /segments |
Liste des 4 segments avec stratégies |
| POST | /predict |
Prédit le segment d'un client |
Validation automatique avec Pydantic : Si un champ manque ou a un mauvais type, FastAPI retourne une erreur 422 avec un message clair avant même d'appeler le modèle.
Pour lancer l'API :
uvicorn src.api.main:app --reload --port 8000Documentation interactive : http://localhost:8000/docs
Rôle : Vérifie que tous les packages du requirements.txt sont bien installés avec les bonnes versions.
python scripts/check_env.pyAffiche un tableau avec le statut (OK / MANQUANT) et la version de chaque package. Pratique après une nouvelle installation ou avant un déploiement.
Rôle : Téléchargement automatique du dataset Olist depuis Kaggle via l'API Kaggle officielle.
# Installation préalable
pip install kaggle
# Placer kaggle.json dans ~/.kaggle/kaggle.json
# Téléchargement
python scripts/load_data.py
python scripts/load_data.py --data-dir data/raw
python scripts/load_data.py --force # re-télécharge si déjà présentRôle : Tests de sanité — vérifie que l'environnement de test
fonctionne et que le package src/ est importable.
3 tests : Python fonctionne, packages installés, src/ importable.
Rôle : Tests unitaires pour src/data/load_data.py.
10 tests qui vérifient :
load_olist()retourne un dictionnaire de DataFrames- Les 5 tables attendues sont présentes
- Les noms de tables sont simplifiés (
orders, pasolist_orders_dataset) - Une
FileNotFoundErrorest levée si un fichier manque get_merged_dataset()retourne un DataFrame sans doublons
Technique : Les tests utilisent des fixtures Pytest avec
tmp_path (dossier temporaire automatiquement supprimé après)
et des mini-CSV de 3-5 lignes — pas les vrais CSV Olist.
Cela garantit des tests rapides (< 1s) et indépendants du filesystem.
Rôle : Tests unitaires pour src/features/build_features.py.
21 tests qui vérifient chaque fonction individuellement :
compute_recency()— récence basée sur la DERNIÈRE commandecompute_frequency()— client avec 2 commandes → frequency=2compute_monetary()— montant inclut les frais de portcompute_delivery()— délai clippé entre -30 et +30build_features()— aucun NaN, une ligne par client, log1p positif
Rôle : Tests unitaires pour src/models/predict.py.
15 tests qui vérifient la logique de predict_segment() :
- Retourne un dict avec les bonnes clés
segment_idest un entier entre 0 et 3log1pest bien appliqué sur frequency et monetary- Le scaler et le modèle sont appelés exactement une fois
- Les 4 segments sont tous atteignables
Technique : Utilise MagicMock de unittest.mock pour simuler
le modèle KMeans et le StandardScaler sans charger MLflow.
mock_kmeans.predict.return_value = np.array([0]) → retourne
toujours le cluster 0, quelle que soit l'entrée.
Liste de toutes les dépendances Python avec leurs versions minimales. Organisé par catégorie : données, visualisation, ML, MLOps, API, qualité de code, tests.
pip install -r requirements.txtRend le dossier src/ installable comme un package Python.
Sans ce fichier, from src.data.load_data import load_olist
échouerait avec ModuleNotFoundError.
pip install -e . # installe src/ en mode editableConfiguration centralisée des outils de qualité de code :
[tool.black]— formatage automatique, max 88 caractères par ligne[tool.pytest.ini_options]— dossier de tests, pattern de fichiers
Configuration de Flake8 (linter PEP8) :
max-line-length = 88— aligné sur Blackextend-ignore = E501— délègue la longueur de ligne à Blackexclude— ignore venv/, notebooks/, .git/
Image Docker pour l'API FastAPI.
FROM python:3.13-slim # image légère ~50Mo vs 900Mo pour python:3.13
WORKDIR /app # répertoire de travail dans le container
COPY requirements.txt . # copié AVANT le code → cache Docker préservé
RUN pip install ... # installation des dépendances
COPY . . # code source copié APRÈS les deps
RUN pip install -e . # installation de src/ comme package
EXPOSE 8000 # port exposé
CMD ["uvicorn", ...] # commande de démarrage
Orchestre deux services qui se voient sur un réseau Docker interne :
- api — FastAPI sur le port 8000
- mlflow — serveur MLflow sur le port 5000
Les volumes montent les dossiers locaux dans les containers pour
que les données et les runs MLflow persistent sur votre disque.
Définit le pipeline reproductible DVC :
stages:
train:
cmd: python src/models/train.py # commande à exécuter
deps: # fichiers dont dépend l'étape
- src/models/train.py
- data/raw
outs: # fichiers produits (trackés par DVC)
- data/processed/rfm_features.csv
- models/kmeans_model.pkldvc repro # exécute le pipeline, ne rejoue que les étapes modifiéesParamètres du pipeline DVC, modifiables sans toucher au code :
train:
n_clusters: 4
random_state: 42
data_dir: data/rawPour tester K=5 : modifier n_clusters: 5 puis dvc repro.
Pipeline CI/CD GitHub Actions exécuté à chaque git push :
- Checkout du code
- Installation Python 3.13
- Installation des dépendances
- Black — vérifie le formatage
- Flake8 — vérifie les règles PEP8
- Pytest — exécute les 49 tests avec couverture
Si une étape échoue, le badge CI/CD passe au rouge et le push est marqué comme défaillant.
Interface web pour l'équipe marketing.
Deux onglets :
- Segments — affiche les 4 segments chargés depuis l'API GET /segments
- Prédire un client — formulaire de saisie des features + appel POST /predict + affichage du résultat avec couleur par segment
4 profils types pré-chargés pour tester rapidement chaque segment.
Prérequis : uvicorn doit tourner sur localhost:8000.
Voici l'ordre exact pour reproduire le projet de zéro :
# 1. Cloner et installer
git clone https://github.com/thiernodaoudaly/olist-customer-segmentation.git
cd olist-customer-segmentation
py -3.13 -m venv venv
venv\Scripts\activate # Windows
pip install -r requirements.txt
pip install -e .
# 2. Vérifier l'installation
python scripts/check_env.py
# 3. Télécharger les données (manuellement depuis Kaggle)
# Placer les CSV dans data/raw/
# 4. Initialiser DVC
dvc init
dvc add data/raw
git add data/raw.dvc data/.gitignore
git commit -m "data: dataset Olist via DVC"
# 5. Lancer l'EDA (exploration)
jupyter notebook notebooks/segmentation_01_eda.ipynb
# 6. Lancer la modélisation
jupyter notebook notebooks/segmentation_02_modeling.ipynb
# 7. Lancer la simulation de stabilité
jupyter notebook notebooks/segmentation_03_simulation.ipynb
# 8. Entraîner le modèle final via script standalone
python src/models/train.py
# 9. Lancer les tests unitaires
pytest tests/ -v --cov=src --cov-report=term-missing
# 10. Vérifier la qualité du code
black --check src/ tests/
flake8 src/ tests/
# 11. Lancer l'API
uvicorn src.api.main:app --reload --port 8000
# 12. Ouvrir le dashboard
# Ouvrir dashboard/index.html dans le navigateur
# 13. Visualiser les expériences MLflow
mlflow ui --port 5000
# Ouvrir http://localhost:5000
# 14. Reproduire le pipeline avec DVC
dvc repro
# 15. Commiter et pusher (déclenche CI/CD)
git add .
git commit -m "feat: pipeline complet"
git push # → GitHub Actions exécute les tests automatiquement| Modèle | n_clusters | Silhouette Score | Davies-Bouldin | Retenu |
|---|---|---|---|---|
| K-Means | 2 | 0.5909 | 0.6453 | ❌ trop grossier |
| K-Means | 3 | 0.3471 | 1.1488 | ❌ chute brutale |
| K-Means | 4 | 0.2521 | 1.2179 | ✅ retenu |
| K-Means | 5 | 0.2384 | 1.1555 | ❌ |
| K-Means | 6 | 0.2469 | 1.1540 | ❌ |
| K-Means | 7 | 0.2376 | 1.1505 | ❌ |
| K-Means | 8 | 0.2250 | 1.1901 | ❌ |
| K-Means | 9 | 0.2214 | 1.2218 | ❌ |
| K-Means | 10 | 0.2189 | 1.1831 | ❌ |
Justification du choix K=4 : Le silhouette optimal est obtenu à K=2 (0.59) mais 2 segments ne permettent pas à l'équipe marketing de personnaliser ses campagnes de façon significative. K=4 est le premier K stable sur la courbe Elbow, avec 4 profils clients distincts et actionnables.
| Feature | Source | Transformation | Justification |
|---|---|---|---|
recency |
orders — purchase_date | Aucune | Jours depuis SNAPSHOT_DATE |
frequency_log |
orders — nb commandes | log1p | 97% des clients = 1 commande |
monetary_log |
order_items — prix+port | log1p | Distribution très asymétrique |
review_score_mean |
order_reviews | Médiane pour NaN | Score 1-5, bimodal |
delivery_delay_mean |
orders — réel vs estimé | clip(-30, 30) | Outliers extrêmes |
| Segment | Nom | Taille | Récence | Montant | Satisfaction | Délai |
|---|---|---|---|---|---|---|
| 0 | Nouveaux satisfaits | 45 997 (49%) | 135j | 147 BRL | 4.66/5 | -12.5j |
| 1 | Clients dormants | 30 556 (33%) | 404j | 168 BRL | 4.58/5 | -14.2j |
| 2 | Clients insatisfaits | 14 004 (15%) | 229j | 188 BRL | 1.60/5 | -3.7j |
| 3 | VIP multi-acheteurs | 2 801 (3%) | 222j | 309 BRL | 4.21/5 | -12.4j |
Segment 0 — Nouveaux satisfaits (49%) Ces clients ont commandé récemment (135 jours), sont très satisfaits (4.66/5) mais n'ont commandé qu'une seule fois. C'est le segment le plus large et le plus facile à convertir en clients fidèles. Stratégie : programme de fidélisation, offre exclusive pour le 2ème achat, email de suivi personnalisé.
Segment 1 — Clients dormants (33%) Ces clients n'ont pas commandé depuis plus d'un an (404 jours) mais étaient satisfaits lors de leur dernier achat (4.58/5). Ils ont un fort potentiel de réactivation. Stratégie : campagne "Vous nous manquez", offre de retour avec réduction, mise en avant des nouveautés depuis leur départ.
Segment 2 — Clients insatisfaits (15%) Ces clients ont un score de satisfaction de seulement 1.60/5, malgré des livraisons globalement correctes (-3.7 jours vs -12 jours pour les autres segments). Le problème vient du produit lui-même, pas de la logistique. Stratégie : enquête de satisfaction urgente, geste commercial ciblé (remboursement partiel, bon d'achat), amélioration de la qualité des produits concernés.
Segment 3 — VIP multi-acheteurs (3%) Seuls 3% des clients mais les plus précieux : 2.11 commandes en moyenne, 309 BRL de montant total (2x la moyenne), satisfaction correcte (4.21/5). Stratégie : programme VIP exclusif, accès anticipé aux nouveautés, service client prioritaire, invitations événements.
49 tests — 6.74s — 0 failures
Couverture logique métier : 100%
| Fichier de test | Tests | Ce qui est vérifié |
|---|---|---|
test_sanity.py |
3 | Environnement, imports, src/ |
test_load_data.py |
13 | Chargement CSV, gestion erreurs |
test_build_features.py |
21 | Chaque feature individuellement |
test_predict.py |
15 | Logique de prédiction, mock MLflow |
Fixtures Pytest — données minimales en mémoire (3-5 lignes) plutôt que les vrais CSV Olist. Garantit des tests rapides, indépendants du filesystem et exécutables en CI/CD.
MagicMock — simule KMeans et StandardScaler sans charger
MLflow. mock.predict.return_value = np.array([0]) retourne
toujours le cluster 0, permettant de tester la logique sans
dépendance externe.
tmp_path — fixture Pytest native qui crée un dossier
temporaire unique par test, supprimé automatiquement après.
# Lancer tous les tests
pytest tests/ -v
# Avec couverture
pytest tests/ -v --cov=src --cov-report=html
# Rapport HTML dans htmlcov/index.htmlLe pipeline .github/workflows/ci.yml s'exécute automatiquement
à chaque git push sur les branches main et develop.
1. actions/checkout@v4 Récupère le code
2. actions/setup-python@v5 Installe Python 3.13
3. pip install -r requirements Installe les dépendances
4. black --check src/ tests/ Vérifie le formatage
5. flake8 src/ tests/ Vérifie les règles PEP8
6. pytest tests/ --cov=src Exécute les 49 tests
7. upload-artifact Archive le rapport de couverture
Si une étape échoue → badge rouge + notification. Si tout passe → badge vert.
Health check — vérifie que l'API est opérationnelle et que le modèle est chargé.
{
"status": "ok",
"model_loaded": true,
"scaler_loaded": true,
"version": "1.0.0"
}Retourne la liste des 4 segments avec leurs stratégies marketing.
[
{
"segment_id": 0,
"segment_name": "Nouveaux satisfaits",
"strategy": "Fidélisation — offres exclusives pour 2ème achat"
},
...
]Prédit le segment d'un client à partir de ses features comportementales.
Corps de la requête :
{
"recency": 135,
"frequency": 1,
"monetary": 147.0,
"review_score_mean": 4.5,
"delivery_delay_mean": -12.0
}Réponse :
{
"segment_id": 0,
"segment_name": "Nouveaux satisfaits",
"strategy": "Fidélisation — offres exclusives pour 2ème achat",
"input_features": { ... }
}Documentation interactive : http://localhost:8000/docs
Interface web dashboard/index.html permettant à l'équipe marketing
de tester les endpoints sans connaître l'API ni le code.
Onglet Segments : affiche les 4 segments chargés dynamiquement depuis GET /segments avec leurs couleurs et stratégies.
Onglet Prédire un client : formulaire de saisie avec 5 champs, 4 boutons de profils types pré-remplis pour tester rapidement, résultat affiché avec badge coloré par segment.
Prérequis :
uvicorn src.api.main:app --reload --port 8000
# Puis ouvrir dashboard/index.html dans le navigateurSimulation sur 7 trimestres (2017Q1 → 2018Q3) :
| Indicateur | Valeur | Interprétation |
|---|---|---|
| Silhouette moyen | 0.271 | Qualité du clustering correcte |
| Silhouette std | 0.022 | Très faible variation — stable |
| ARI brut moyen | 0.02 | Non interprétable (label switching) |
| CV taille clusters | 0.76 | Variation naturelle liée au RFM |
Note méthodologique : L'ARI brut (0.02) est affecté par le label switching intrinsèque à K-Means — les labels changent entre trimestres même si les profils restent stables. L'analyse par variance de silhouette est plus robuste.
Fréquence : mise à jour trimestrielle (tous les 3 mois)
Déclencheur d'alerte : silhouette < 0.20 → mise à jour urgente
Procédure de mise à jour :
# 1. Recalculer les features sur les 12 derniers mois
python src/models/train.py --data-dir data/raw
# 2. Comparer les nouveaux segments (silhouette vs précédent)
mlflow ui --port 5000
# 3. Si silhouette >= 0.20 → déployer le nouveau modèle
# 4. Mettre à jour le dashboard et l'API (redémarrer uvicorn)
# 5. Documenter les changements dans le README| Catégorie | Technologie | Version | Rôle |
|---|---|---|---|
| Langage | Python | 3.13 | Langage principal |
| ML & Clustering | scikit-learn | 1.4+ | K-Means, StandardScaler, métriques |
| Feature engineering | NumPy + Pandas | 1.26 / 2.2 | Calcul RFM, transformations |
| Visualisation | Matplotlib + Seaborn | 3.8 / 0.13 | Graphiques EDA et clustering |
| Tracking expériences | MLflow | 2.14 | Logging paramètres, métriques, artefacts |
| Versioning données | DVC | 3.55 | Versioning CSV et modèles .pkl |
| API REST | FastAPI + Uvicorn | 0.111 / 0.29 | Endpoint de prédiction |
| Validation données | Pydantic | 2.7 | Validation des requêtes API |
| Tests unitaires | Pytest + pytest-cov | 8.2 / 5.0 | 49 tests, couverture 100% logique |
| Qualité de code | Black + Flake8 + isort | 24.4 / 7.0 / 5.13 | Formatage PEP8 automatique |
| CI/CD | GitHub Actions | — | Pipeline automatisé à chaque push |
| Containerisation | Docker + Compose | 29.3 / 5.1 | Reproductibilité totale |
| Interface marketing | HTML + CSS + JS | — | Dashboard sans framework |
| Notebooks | Jupyter | — | EDA, modélisation, simulation |
- Thierno Daouda LY
- Babacar Niang
- Mouhamed Sarr
Projet réalisé dans le cadre du cours MLOps — 2026.

