| title | Attrition Api |
|---|---|
| emoji | 🤖 |
| colorFrom | blue |
| colorTo | green |
| sdk | docker |
| pinned | false |
API REST de prédiction du risque d'attrition RH développée avec FastAPI. Elle expose un modèle de Régression Logistique entraîné sur le dataset IBM HR Attrition (1470 employés), prédit la probabilité de départ d'un employé, et persiste chaque appel en base de données PostgreSQL pour des analyses ultérieures.
ml-deployment-api/
│
├── app/ # Code source de l'API FastAPI
│ ├── main.py # Point d'entrée FastAPI, config et middlewares
│ ├── routes/
│ │ └── predict.py # Endpoints /predict et /predictions
│ ├── middleware/
│ │ └── logging.py # Middleware de logging HTTP (table logs)
│ ├── schemas/
│ │ └── prediction.py # Modèles Pydantic input/output
│ ├── db/
│ │ ├── models.py # Modèles SQLAlchemy (ORM)
│ │ ├── session.py # Connexion PostgreSQL
│ │ └── crud.py # Opérations CRUD
│
├── ml_model/ # Pipeline ML sérialisé et logique d'inférence
│ ├── pipeline.pkl
│ ├── preprocessing.py
│ └── loader.py # Chargement du pipeline et inférence
│
├── scripts/ # Scripts utilitaires (BDD, données)
│ ├── create_db.py # Création des tables
│ ├── insert_data.py # Insère les employés (fixtures par défaut, ou dataset complet en argument)
│ └── query_db.py # Requêtes analytiques
│
├── tests/ # Tests unitaires et d'intégration
│ ├── conftest.py # Fixtures partagées
│ ├── test_api.py
│ ├── test_model.py
│ └── test_db.py
│
├── docs/ # Documentation et diagrammes
│ └── uml.png
│
├── fixtures/ # Données d'exemple pour le développement
│ └── employees.csv # 20 employés représentatifs (structure table `employees`)
│
├── .github/workflows/ # Pipeline CI/CD GitHub Actions
│ └── ci_cd.yml
├── Dockerfile
├── docker-compose.yaml
├── requirements.txt
├── .env.example # Template de configuration (variables d'environnement)
├── .gitignore
└── README.md
- Python 3.12+
- Docker (pour PostgreSQL via docker-compose)
- Cloner le repo
git clone git@github.com:RaphaelRIVIERE/ml-deployment-api.git
cd ml-deployment-api- Créer et activer le venv
python3 -m venv venv
source venv/bin/activate- Installer les dépendances
pip install -r requirements.txt- Lancer PostgreSQL
docker-compose up -d- Créer les tables
python scripts/create_db.py- Insérer les données dans la table
employees
# Fixtures d'exemple par défaut (20 employés représentatifs)
python scripts/insert_data.py
# Ou avec le dataset complet (~1470 employés)
python scripts/insert_data.py data/dataset.csvUn fichier .env.example est fourni à la racine du projet. Il suffit de le copier et de renseigner ta clé API :
cp .env.example .envLe fichier .env.example contient les variables suivantes :
| Variable | Description | Valeur par défaut |
|---|---|---|
API_KEY |
Clé d'authentification de l'API | — |
DB_HOST |
Hôte PostgreSQL | localhost |
DB_PORT |
Port PostgreSQL | 5432 |
DB_USER |
Utilisateur PostgreSQL | — |
DB_PASSWORD |
Mot de passe PostgreSQL | — |
DB_NAME |
Nom de la base de données | attrition_db |
Sécurité : Ne jamais committer
.env(déjà listé dans.gitignore). Générer une clé robuste avec :python -c "import secrets; print(secrets.token_hex(32))"En CI/CD, injecter les secrets via GitHub Actions Secrets (Settings → Secrets and variables → Actions).
- ORM : SQLAlchemy avec le style
declarative_base() - Driver :
psycopg2-binarypour la connexion PostgreSQL - Lancement : PostgreSQL via Docker (
docker-compose up -d)
Stocke les employés. La colonne a_quitte_l_entreprise est convertie de "Oui"/"Non" en BOOLEAN.
Peuplée via scripts/insert_data.py (fixtures de 20 employés par défaut, ou dataset complet en argument).
Enregistre chaque appel à POST /predict avec les inputs envoyés, les outputs retournés et un timestamp automatique (created_at TIMESTAMPTZ).
Pas de fixtures pour cette table : elle se peuple automatiquement à chaque appel à
POST /predict. Cela garantit que les données reflètent de vraies interactions avec le modèle.
Enregistre chaque requête HTTP reçue par l'API (endpoint, méthode, code retour, temps de réponse). Pour les appels à /predict, la colonne prediction_id référence la prédiction associée.
Pas de fixtures pour cette table : elle se peuple automatiquement via le middleware à chaque requête entrante.
Les tables
predictionsetemployeessont indépendantes : les prédictions API ne référencent pasemployeescar les employés soumis à prédiction ne sont pas forcément dans le dataset.
erDiagram
EMPLOYEES {
int id PK
timestamptz created_at
int age
varchar genre
varchar statut_marital
int revenu_mensuel
varchar departement
varchar poste
smallint niveau_hierarchique_poste
varchar domaine_etude
smallint niveau_education
int nombre_experiences_precedentes
int annee_experience_totale
int annees_dans_l_entreprise
int annees_dans_le_poste_actuel
int annees_sous_responsable_actuel
int annees_depuis_la_derniere_promotion
smallint note_evaluation_actuelle
smallint note_evaluation_precedente
int augmentation_salaire_precedente
int nb_formations_suivies
int nombre_participation_pee
smallint satisfaction_employee_environnement
smallint satisfaction_employee_nature_travail
smallint satisfaction_employee_equipe
smallint satisfaction_employee_equilibre_pro_perso
varchar heure_supplementaires
varchar frequence_deplacement
int distance_domicile_travail
boolean a_quitte_l_entreprise
}
PREDICTIONS {
int id PK
timestamptz created_at
int age
varchar genre
varchar statut_marital
varchar poste
varchar domaine_etude
smallint niveau_education
varchar departement
smallint niveau_hierarchique_poste
int nombre_experiences_precedentes
int annee_experience_totale
int annees_dans_l_entreprise
int annees_dans_le_poste_actuel
int annees_sous_responsable_actuel
int annees_depuis_la_derniere_promotion
smallint note_evaluation_actuelle
smallint note_evaluation_precedente
int augmentation_salaire_precedente
int nb_formations_suivies
int nombre_participation_pee
smallint satisfaction_employee_environnement
smallint satisfaction_employee_nature_travail
smallint satisfaction_employee_equipe
smallint satisfaction_employee_equilibre_pro_perso
varchar heure_supplementaires
varchar frequence_deplacement
int distance_domicile_travail
int revenu_mensuel
smallint prediction
float probabilite
}
LOGS {
int id PK
timestamptz timestamp
varchar endpoint
varchar method
int status_code
float response_time_ms
int prediction_id FK
}
PREDICTIONS ||--o{ LOGS : "prediction_id"
| Colonne | Contrainte |
|---|---|
genre |
VARCHAR — "M" = Homme, "F" = Femme |
heure_supplementaires |
VARCHAR — "Oui" ou "Non" |
frequence_deplacement |
VARCHAR — "Aucun", "Occasionnel" ou "Fréquent" |
a_quitte_l_entreprise |
BOOLEAN — converti depuis "Oui"/"Non" du CSV |
created_at |
TIMESTAMPTZ — posé par PostgreSQL (server_default=func.now()) |
| Scores satisfaction / évaluation | SMALLINT — valeurs entre 0 et 5 |
uvicorn app.main:app --reloadL'API est accessible sur http://localhost:8000.
La documentation interactive Swagger est disponible sur http://localhost:8000/docs.
Tous les endpoints (sauf / et /health) nécessitent une clé API passée dans le header HTTP :
X-API-Key: ta_clé_secrète
- Une requête sans clé ou avec une clé invalide retourne
401 Unauthorized - L'API refuse de démarrer si
API_KEYest vide ou absent du.env - Les secrets ne transitent jamais dans l'URL (header uniquement)
Retourne les informations générales de l'API. Pas d'authentification requise.
curl http://localhost:8000/Réponse :
{
"name": "Futurisys HR Churn API",
"version": "1.3.0",
"documentation": "http://localhost:8000/docs",
"health": "http://localhost:8000/health"
}Vérifie que l'API est opérationnelle. Pas d'authentification requise.
curl http://localhost:8000/healthRéponse :
{ "status": "ok", "message": "API opérationnelle" }Retourne les métadonnées du modèle déployé.
curl http://localhost:8000/model/info \
-H "X-API-Key: ta_clé_secrète"Réponse :
{
"algorithme": "Régression Logistique",
"seuil": 0.4,
"description": "Classification binaire — risque de départ RH (0 = Reste, 1 = Quitte)"
}Envoie les features RH d'un employé et reçoit une prédiction de départ.
curl -X POST http://localhost:8000/predict \
-H "X-API-Key: ta_clé_secrète" \
-H "Content-Type: application/json" \
-d '{
"age": 35,
"genre": "M",
"statut_marital": "Marié(e)",
"poste": "Consultant",
"domaine_etude": "Infra & Cloud",
"niveau_education": 3,
"departement": "Ventes",
"niveau_hierarchique_poste": 2,
"nombre_experiences_precedentes": 2,
"annee_experience_totale": 10,
"annees_dans_l_entreprise": 5,
"annees_dans_le_poste_actuel": 2,
"annees_sous_responsable_actuel": 3,
"annees_depuis_la_derniere_promotion": 1,
"note_evaluation_actuelle": 3,
"note_evaluation_precedente": 3,
"augmentation_salaire_precedente": 15,
"nb_formations_suivies": 2,
"nombre_participation_pee": 1,
"satisfaction_employee_environnement": 3,
"satisfaction_employee_nature_travail": 4,
"satisfaction_employee_equipe": 3,
"satisfaction_employee_equilibre_pro_perso": 2,
"heure_supplementaires": "Non",
"frequence_deplacement": "Occasionnel",
"distance_domicile_travail": 10,
"revenu_mensuel": 5000
}'Réponse :
{
"prediction": 0,
"label": "Reste",
"probabilite": 0.2341
}Champs de la réponse :
| Champ | Type | Description |
|---|---|---|
prediction |
int | 0 = Reste, 1 = Quitte |
label |
string | "Reste" ou "Quitte" |
probabilite |
float | Probabilité de départ (entre 0 et 1) |
Retourne la liste des prédictions enregistrées en base, triées de la plus récente à la plus ancienne. Authentification requise.
Paramètres query (optionnels) :
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
skip |
int | 0 | Décalage (pagination) |
limit |
int | 100 | Nombre max de résultats |
curl http://localhost:8000/predictions?limit=10 \
-H "X-API-Key: ta_clé_secrète"La table predictions constitue un journal structuré de toutes les interactions
avec le modèle. Elle peut être exploitée pour du reporting RH en temps réel.
Le script scripts/query_db.py expose les requêtes suivantes :
| Fonction | Description |
|---|---|
stats_employees(session) |
Nombre d'employés dans le dataset, taux de churn réel |
stats_predictions(session) |
Nombre de prédictions loggées, date de la dernière |
predict_from_db(session, id) |
Charge un employé, prédit et compare au label réel |
apercu_employees(session, n) |
Affiche les N premiers employés du dataset |
Lancer l'analyse :
python scripts/query_db.pyLa table predictions permet de répondre aux questions métier suivantes :
- Taux de risque global : quelle proportion d'employés soumis au modèle sont prédits "à risque" ?
- Évolution dans le temps : le taux de prédictions "Quitte" augmente-t-il sur les dernières semaines ?
- Profils à risque dominants : quels postes ou départements concentrent
le plus de prédictions
1? - Distribution des probabilités : combien de prédictions sont proches du seuil 0.40 (zone d'incertitude) ?
-- Taux de risque prédit par département
SELECT departement,
COUNT(*) AS nb_predictions,
SUM(prediction) AS nb_a_risque,
ROUND(100.0 * SUM(prediction) / COUNT(*), 1) AS taux_risque_pct
FROM predictions
GROUP BY departement
ORDER BY taux_risque_pct DESC;Ces analyses peuvent alimenter un tableau de bord BI (Metabase, Superset, Power BI) connecté directement à la base PostgreSQL.
pytest tests/ -vAffichage dans le terminal :
pytest tests/ --cov=app --cov-report=term-missingRapport HTML navigable (généré dans htmlcov/) :
pytest tests/ --cov=app --cov-report=term-missing --cov-report=htmlDernière mesure : 96% (18 tests, 247 instructions)
| Fichier | Couverture |
|---|---|
app/db/crud.py |
100% |
app/db/models.py |
100% |
app/middleware/__init__.py |
100% |
app/schemas/prediction.py |
98% |
app/main.py |
97% |
app/middleware/logging.py |
94% |
app/routes/predict.py |
93% |
app/db/session.py |
50% (infrastructure PostgreSQL, non testée en isolation) |
| Fichier | Contenu |
|---|---|
tests/conftest.py |
Fixtures partagées (client de test, payload valide) |
tests/test_api.py |
Tests des endpoints HTTP (health, predict, auth) |
tests/test_model.py |
Tests du pipeline ML (chargement, prédiction, seuil) |
tests/test_db.py |
Tests de la couche base de données (CRUD, contraintes d'intégrité) |
| Environnement | Branche | URL | Base de données |
|---|---|---|---|
| Production | main |
https://rriviere-attrition-api.hf.space/docs | PostgreSQL Neon (instance prod) |
| Développement | dev |
https://rriviere-attrition-api-dev.hf.space/docs | PostgreSQL Neon (instance dev) |
- Documentation Swagger (prod) : https://rriviere-attrition-api.hf.space/docs
En production et développement, PostgreSQL est hébergé sur Neon (serverless). Les credentials sont injectés via les variables/secrets HF Spaces. Les deux environnements disposent d'instances isolées
Le déploiement est automatisé via GitHub Actions (.github/workflows/ci_cd.yml).
| Environnement | Déclencheur | Infrastructure |
|---|---|---|
| CI / Tests unitaires (éphémère) | Push/PR sur main ou dev |
CI runner GitHub Actions (VM Ubuntu, détruite après les tests) |
| Développement | Push sur dev |
Hugging Face Space dev + PostgreSQL Neon (instance dev) |
| Production | Push sur main |
Hugging Face Space prod + PostgreSQL Neon (instance prod) |
Étapes :
- Test — installation des dépendances + exécution de
pytest - Deploy — si les tests passent, push automatique vers Hugging Face Spaces qui rebuild l'image Docker
Secrets (Settings → Secrets → Actions) :
| Secret | Description |
|---|---|
HF_TOKEN |
Token Hugging Face avec droits Write |
API_KEY |
Clé API injectée dans HF Spaces |
DB_PASSWORD |
Mot de passe PostgreSQL injecté dans HF Spaces |
Variables (Settings → Variables → Actions) :
| Variable | Description |
|---|---|
HF_SPACE_PROD |
Nom du Space HF de production (ex : username/space-name) |
HF_SPACE_DEV |
Nom du Space HF de développement |
| Contexte | Mécanisme |
|---|---|
| Développement local | Fichier .env (non commité, voir .env.example) |
| GitHub Actions (CI/CD) | GitHub Secrets (Settings → Secrets → Actions) |
| Hugging Face Spaces | HF Variables / Secrets (Settings → Variables) |
Les secrets injectés au runtime sont : API_KEY, HF_TOKEN, DB_PASSWORD.
- Toujours servir l'API derrière HTTPS (assuré par Hugging Face Spaces)
- Générer une clé robuste :
python -c "import secrets; print(secrets.token_hex(32))" - Rotation régulière de la clé API recommandée (re-déploiement nécessaire)
- Ne jamais committer
.env(déjà listé dans.gitignore)
Cette implémentation est acceptable pour un POC / projet démonstratif.
- La clé API est statique et hachée (SHA-256) : la comparaison se fait via
hmac.compare_digestpour éviter les timing attacks - Pas de mécanisme de révocation ou d'expiration de token
- Pas de rate limiting (protection contre le bruteforce absente)
- En production réelle, préférer OAuth2 / JWT avec expiration
Requête HTTP (JSON)
↓
Validation Pydantic (PredictionInput)
→ Champs typés, valeurs bornées (ex : age entre 18 et 65)
→ Rejet 422 si données invalides
↓
Conversion en DataFrame pandas
→ Mise en forme compatible avec le pipeline scikit-learn
↓
Pipeline ML (predict_proba)
→ Retourne une probabilité entre 0 et 1
→ Seuil 0.40 appliqué : proba ≥ 0.40 → prediction = 1 (Quitte)
↓
Log obligatoire en base de données (table predictions)
→ Inputs + outputs + timestamp enregistrés systématiquement
↓
Réponse JSON (PredictionOutput)
Important : chaque appel à
/predictest systématiquement loggé en base de données avant le renvoi de la réponse. Il n'est pas possible d'obtenir une prédiction sans persistance.
Le modèle est une Régression Logistique entraîné sur le dataset IBM HR Attrition.
| Métrique | Valeur |
|---|---|
| Recall (classe "Quitte") | 79% |
| Précision (classe "Quitte") | 36% |
| F1-score (classe "Quitte") | 49% |
| PR-AUC | 59% |
| ROC-AUC | 83% |
Métriques évaluées sur le jeu de test (20% du dataset, stratifié).
Le seuil de décision par défaut d'une régression logistique est 0.50. Il est ici abaissé à 0.40 pour :
- Maximiser la sensibilité (recall) : dans un contexte RH, il est préférable d'identifier trop d'employés à risque plutôt que d'en manquer (le coût de remplacement d'un employé est élevé)
- Réduire les faux négatifs : un employé prédit "Reste" qui part réellement est plus coûteux qu'une alerte inutile
Ce choix est un compromis volontaire entre précision et recall, justifié par le domaine métier.
Le modèle est actuellement statique (fichier ml_model/pipeline.pkl versionné dans Git).
Pour le réentraîner :
- Exporter les nouvelles données depuis la table
predictions - Réentraîner le pipeline scikit-learn sur les données enrichies
- Sauvegarder le nouveau
pipeline.pkl - Ouvrir une PR sur
main→ le CI/CD redéploie automatiquement le modèle