Skip to content

RaphaelRIVIERE/ml-deployment-api

Repository files navigation

title Attrition Api
emoji 🤖
colorFrom blue
colorTo green
sdk docker
pinned false

Coverage

API de prédiction d'attrition RH

Description

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.

Structure du projet

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

Installation

Prérequis

  • Python 3.12+
  • Docker (pour PostgreSQL via docker-compose)

Étapes

  1. Cloner le repo
git clone git@github.com:RaphaelRIVIERE/ml-deployment-api.git
cd ml-deployment-api
  1. Créer et activer le venv
python3 -m venv venv
source venv/bin/activate
  1. Installer les dépendances
pip install -r requirements.txt
  1. Lancer PostgreSQL
docker-compose up -d
  1. Créer les tables
python scripts/create_db.py
  1. 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.csv

Configuration

Un fichier .env.example est fourni à la racine du projet. Il suffit de le copier et de renseigner ta clé API :

cp .env.example .env

Le 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).

Base de données

ORM et connexion

  • ORM : SQLAlchemy avec le style declarative_base()
  • Driver : psycopg2-binary pour la connexion PostgreSQL
  • Lancement : PostgreSQL via Docker (docker-compose up -d)

Schéma des tables

employees — dataset RH

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).

predictions — historique des appels API

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.

logs — traces HTTP

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 predictions et employees sont indépendantes : les prédictions API ne référencent pas employees car les employés soumis à prédiction ne sont pas forcément dans le dataset.

Diagramme UML

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"
Loading

Contraintes notables

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

Utilisation

Lancer l'API

uvicorn app.main:app --reload

L'API est accessible sur http://localhost:8000. La documentation interactive Swagger est disponible sur http://localhost:8000/docs.

Authentification

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_KEY est vide ou absent du .env
  • Les secrets ne transitent jamais dans l'URL (header uniquement)

Endpoints

GET / — Accueil de l'API

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"
}

GET /health — Health check

Vérifie que l'API est opérationnelle. Pas d'authentification requise.

curl http://localhost:8000/health

Réponse :

{ "status": "ok", "message": "API opérationnelle" }

GET /model/info — Informations sur le modèle

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)"
}

POST /predict — Prédiction du risque de départ

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)

GET /predictions — Historique des prédictions

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"

Analyse des données & tableau de bord

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.

Requêtes analytiques disponibles

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.py

Cas d'usage analytiques

La 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) ?

Exemple de requête SQL directe

-- 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.

Tests

Lancer les tests

pytest tests/ -v

Rapport de couverture

Affichage dans le terminal :

pytest tests/ --cov=app --cov-report=term-missing

Rapport HTML navigable (généré dans htmlcov/) :

pytest tests/ --cov=app --cov-report=term-missing --cov-report=html

Résultat de couverture

Derniè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)

Structure des tests

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é)

Déploiement

Environnements déployés

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)

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

Pipeline CI/CD

Le déploiement est automatisé via GitHub Actions (.github/workflows/ci_cd.yml).

Cartographie des environnements

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 :

  1. Test — installation des dépendances + exécution de pytest
  2. 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

Sécurité

Gestion des secrets

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.

Bonnes pratiques en production

  • 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)

Limitations actuelles (POC)

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_digest pour é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

Processus de stockage des données

Flux complet d'un appel /predict

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 à /predict est 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.

Performances du modèle

Métriques d'évaluation

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é).

Justification du seuil 0.40

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.

Protocole de mise à jour du modèle

Le modèle est actuellement statique (fichier ml_model/pipeline.pkl versionné dans Git).

Pour le réentraîner :

  1. Exporter les nouvelles données depuis la table predictions
  2. Réentraîner le pipeline scikit-learn sur les données enrichies
  3. Sauvegarder le nouveau pipeline.pkl
  4. Ouvrir une PR sur main → le CI/CD redéploie automatiquement le modèle

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors