rriviere commited on
Commit
be68e43
·
verified ·
1 Parent(s): 74e067a

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +44 -97
  2. app/main.py +1 -1
  3. scripts/query_db.py +3 -1
README.md CHANGED
@@ -6,7 +6,7 @@ colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
9
- ![Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen)
10
 
11
  # API de prédiction d'attrition RH
12
 
@@ -18,48 +18,47 @@ API REST de prédiction du risque d'attrition RH développée avec FastAPI. Elle
18
  ```
19
  ml-deployment-api/
20
  │
21
- ├── app/
22
- │ ├── main.py
23
  │ ├── routes/
24
- │ │ └── predict.py
 
 
25
  │ ├── schemas/
26
- │ │ └── prediction.py
27
  │ ├── db/
28
- │ │ ├── models.py
29
- │ │ ├── session.py
30
- │ │ └── crud.py
31
  │
32
- ├── ml_model/
33
  │ ├── pipeline.pkl
34
  │ ├── preprocessing.py
35
- │ └── loader.py
36
  │
37
- ├── scripts/
38
- │ ├── create_db.py
39
- │ ├── insert_data.py # Insère les employés (fixtures par défaut, ou dataset complet en argument)
40
- │ └── query_db.py
41
  │
42
- ├── tests/
43
- │ ├── conftest.py
44
  │ ├── test_api.py
45
  │ ├── test_model.py
46
  │ └── test_db.py
47
  │
48
- ├── docs/
49
  │ └── uml.png
50
  │
51
- ├── data/
52
- │ └── dataset.csv
53
  │
54
- ├── fixtures/
55
- │ └── employees.csv # 20 employés représentatifs (structure table `employees`)
56
- │
57
- ├── .github/workflows/
58
  │ └── ci_cd.yml
59
  ├── Dockerfile
60
  ├── docker-compose.yaml
61
  ├── requirements.txt
62
- ├── .env.example
63
  ├── .gitignore
64
  └── README.md
65
  ```
@@ -258,8 +257,6 @@ erDiagram
258
  | `created_at` | `TIMESTAMPTZ` — posé par PostgreSQL (`server_default=func.now()`) |
259
  | Scores satisfaction / évaluation | `SMALLINT` — valeurs entre `0` et `5` |
260
 
261
- ---
262
-
263
  ## Utilisation
264
 
265
  ### Lancer l'API
@@ -283,8 +280,6 @@ X-API-Key: ta_clé_secrète
283
  - L'API refuse de démarrer si `API_KEY` est vide ou absent du `.env`
284
  - Les secrets ne transitent jamais dans l'URL (header uniquement)
285
 
286
- ---
287
-
288
  ### Endpoints
289
 
290
  #### `GET /` — Accueil de l'API
@@ -299,14 +294,12 @@ Réponse :
299
  ```json
300
  {
301
  "name": "Futurisys HR Churn API",
302
- "version": "1.0.0",
303
  "documentation": "http://localhost:8000/docs",
304
  "health": "http://localhost:8000/health"
305
  }
306
  ```
307
 
308
- ---
309
-
310
  #### `GET /health` — Health check
311
 
312
  Vérifie que l'API est opérationnelle. Pas d'authentification requise.
@@ -320,8 +313,6 @@ Réponse :
320
  { "status": "ok", "message": "API opérationnelle" }
321
  ```
322
 
323
- ---
324
-
325
  #### `GET /model/info` — Informations sur le modèle
326
 
327
  Retourne les métadonnées du modèle déployé.
@@ -340,8 +331,6 @@ Réponse :
340
  }
341
  ```
342
 
343
- ---
344
-
345
  #### `POST /predict` — Prédiction du risque de départ
346
 
347
  Envoie les features RH d'un employé et reçoit une prédiction de départ.
@@ -397,7 +386,6 @@ Réponse :
397
  | `label` | string | `"Reste"` ou `"Quitte"` |
398
  | `probabilite` | float | Probabilité de départ (entre 0 et 1) |
399
 
400
- ---
401
 
402
  #### `GET /predictions` — Historique des prédictions
403
 
@@ -414,8 +402,6 @@ curl http://localhost:8000/predictions?limit=10 \
414
  -H "X-API-Key: ta_clé_secrète"
415
  ```
416
 
417
- ---
418
-
419
  ## Analyse des données & tableau de bord
420
 
421
  La table `predictions` constitue un journal structuré de toutes les interactions
@@ -463,6 +449,8 @@ GROUP BY departement
463
  ORDER BY taux_risque_pct DESC;
464
  ```
465
 
 
 
466
  ## Tests
467
 
468
  ### Lancer les tests
@@ -485,16 +473,18 @@ pytest tests/ --cov=app --cov-report=term-missing --cov-report=html
485
 
486
  ### Résultat de couverture
487
 
488
- Dernière mesure : **95%** (13 tests, 193 instructions)
489
 
490
  | Fichier | Couverture |
491
  |---|---|
492
  | `app/db/crud.py` | 100% |
493
  | `app/db/models.py` | 100% |
494
- | `app/schemas/prediction.py` | 100% |
495
- | `app/routes/predict.py` | 98% |
496
- | `app/main.py` | 96% |
497
- | `app/db/session.py` | 56% *(infrastructure PostgreSQL, non testée en isolation)* |
 
 
498
 
499
 
500
  ### Structure des tests
@@ -513,8 +503,8 @@ Dernière mesure : **95%** (13 tests, 193 instructions)
513
 
514
  | Environnement | Branche | URL | Base de données |
515
  |---|---|---|---|
516
- | Production | `main` | https://rriviere-attrition-api.hf.space | PostgreSQL Neon (instance prod) |
517
- | Développement | `dev` | https://rriviere-attrition-api-dev.hf.space | PostgreSQL Neon (instance dev) |
518
 
519
 
520
  - **Documentation Swagger (prod)** : https://rriviere-attrition-api.hf.space/docs
@@ -525,9 +515,13 @@ En production et développement, PostgreSQL est hébergé sur Neon (serverless).
525
 
526
  Le déploiement est automatisé via GitHub Actions (`.github/workflows/ci_cd.yml`).
527
 
528
- **Déclenchement** :
529
- - Push sur `main` → tests + déploiement en **production**
530
- - Push sur `dev` → tests + déploiement en **développement**
 
 
 
 
531
 
532
  **Étapes :**
533
  1. **Test** — installation des dépendances + exécution de `pytest`
@@ -546,25 +540,8 @@ Le déploiement est automatisé via GitHub Actions (`.github/workflows/ci_cd.yml
546
  | `HF_SPACE_PROD` | Nom du Space HF de production (ex : `username/space-name`) |
547
  | `HF_SPACE_DEV` | Nom du Space HF de développement |
548
 
549
- ---
550
-
551
  ## Sécurité
552
 
553
- ### Mécanisme d'authentification
554
-
555
- L'API utilise une authentification par clé API transmise dans le header HTTP `X-API-Key` :
556
-
557
- ```bash
558
- curl http://localhost:8000/predict \
559
- -H "X-API-Key: ta_clé_secrète" \
560
- -H "Content-Type: application/json" \
561
- ...
562
- ```
563
-
564
- - Seul l'endpoint `GET /health` est public (pas d'authentification requise)
565
- - Une clé absente ou incorrecte retourne `401 Unauthorized`
566
- - La clé ne transite jamais dans l'URL pour éviter les fuites dans les logs serveur
567
-
568
  ### Gestion des secrets
569
 
570
  | Contexte | Mécanisme |
@@ -586,12 +563,11 @@ Les secrets injectés au runtime sont : `API_KEY`, `HF_TOKEN`, `DB_PASSWORD`.
586
 
587
  > Cette implémentation est acceptable pour un POC / projet démonstratif.
588
 
589
- - La clé API est **statique et non hachée** : elle est comparée en clair en mémoire
590
  - Pas de mécanisme de révocation ou d'expiration de token
591
  - Pas de rate limiting (protection contre le bruteforce absente)
592
  - En production réelle, préférer OAuth2 / JWT avec expiration
593
 
594
- ---
595
 
596
  ## Processus de stockage des données
597
 
@@ -619,17 +595,6 @@ Réponse JSON (PredictionOutput)
619
 
620
  > **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.
621
 
622
- ### Rôle des deux tables
623
-
624
- | Table | Contenu | Usage |
625
- |---|---|---|
626
- | `employees` | Dataset RH complet (1470 lignes) | Référence statique, utilisée pour l'entraînement initial du modèle |
627
- | `predictions` | Log de chaque appel à `POST /predict` | Traçabilité, analyse des prédictions, tableau de bord |
628
- | `logs` | Log de chaque requête HTTP | Monitoring, traçabilité complète, jointure avec `predictions` |
629
-
630
- `employees` est indépendante des deux autres : un employé soumis à prédiction n'a pas à exister dans `employees`. `logs` est liée à `predictions` via `prediction_id` (nullable) : seuls les appels à `POST /predict` ont une FK renseignée.
631
-
632
- ---
633
 
634
  ## Performances du modèle
635
 
@@ -664,22 +629,4 @@ Pour le réentraîner :
664
  1. Exporter les nouvelles données depuis la table `predictions`
665
  2. Réentraîner le pipeline scikit-learn sur les données enrichies
666
  3. Sauvegarder le nouveau `pipeline.pkl`
667
- 4. Ouvrir une PR sur `main` → le CI/CD redéploie automatiquement le modèle
668
-
669
- ---
670
-
671
- ## Besoins analytiques
672
-
673
- La table `predictions` constitue un **journal de bord structuré** de toutes les prédictions émises par l'API. Chaque ligne contient les inputs complets de l'employé, la prédiction, la probabilité associée et un horodatage automatique.
674
-
675
- ### Exemples de métriques exploitables
676
-
677
- | Analyse | Champ(s) concerné(s) |
678
- |---|---|
679
- | Taux de churn prédit par département | `GROUP BY departement` sur `prediction = 1` |
680
- | Évolution du risque dans le temps | `GROUP BY DATE_TRUNC('month', created_at)` |
681
- | Profils à risque élevé (probabilité > 0.7) | `WHERE probabilite > 0.7` |
682
- | Corrélation churn / heures supplémentaires | `GROUP BY heure_supplementaires` |
683
- | Distribution par niveau hiérarchique | `GROUP BY niveau_hierarchique_poste` |
684
-
685
- > Ces analyses peuvent alimenter un tableau de bord BI (Metabase, Superset, Power BI) connecté directement à la base PostgreSQL.
 
6
  sdk: docker
7
  pinned: false
8
  ---
9
+ ![Coverage](https://img.shields.io/badge/coverage-96%25-brightgreen)
10
 
11
  # API de prédiction d'attrition RH
12
 
 
18
  ```
19
  ml-deployment-api/
20
  │
21
+ ├── app/ # Code source de l'API FastAPI
22
+ │ ├── main.py # Point d'entrée FastAPI, config et middlewares
23
  │ ├── routes/
24
+ │ │ └── predict.py # Endpoints /predict et /predictions
25
+ │ ├── middleware/
26
+ │ │ └── logging.py # Middleware de logging HTTP (table logs)
27
  │ ├── schemas/
28
+ │ │ └── prediction.py # Modèles Pydantic input/output
29
  │ ├── db/
30
+ │ │ ├── models.py # Modèles SQLAlchemy (ORM)
31
+ │ │ ├── session.py # Connexion PostgreSQL
32
+ │ │ └── crud.py # Opérations CRUD
33
  │
34
+ ├── ml_model/ # Pipeline ML sérialisé et logique d'inférence
35
  │ ├── pipeline.pkl
36
  │ ├── preprocessing.py
37
+ │ └── loader.py # Chargement du pipeline et inférence
38
  │
39
+ ├── scripts/ # Scripts utilitaires (BDD, données)
40
+ │ ├── create_db.py # Création des tables
41
+ │ ├── insert_data.py # Insère les employés (fixtures par défaut, ou dataset complet en argument)
42
+ │ └── query_db.py # Requêtes analytiques
43
  │
44
+ ├── tests/ # Tests unitaires et d'intégration
45
+ │ ├── conftest.py # Fixtures partagées
46
  │ ├── test_api.py
47
  │ ├── test_model.py
48
  │ └── test_db.py
49
  │
50
+ ├── docs/ # Documentation et diagrammes
51
  │ └── uml.png
52
  │
53
+ ├── fixtures/ # Données d'exemple pour le développement
54
+ │ └── employees.csv # 20 employés représentatifs (structure table `employees`)
55
  │
56
+ ├── .github/workflows/ # Pipeline CI/CD GitHub Actions
 
 
 
57
  │ └── ci_cd.yml
58
  ├── Dockerfile
59
  ├── docker-compose.yaml
60
  ├── requirements.txt
61
+ ├── .env.example # Template de configuration (variables d'environnement)
62
  ├── .gitignore
63
  └── README.md
64
  ```
 
257
  | `created_at` | `TIMESTAMPTZ` — posé par PostgreSQL (`server_default=func.now()`) |
258
  | Scores satisfaction / évaluation | `SMALLINT` — valeurs entre `0` et `5` |
259
 
 
 
260
  ## Utilisation
261
 
262
  ### Lancer l'API
 
280
  - L'API refuse de démarrer si `API_KEY` est vide ou absent du `.env`
281
  - Les secrets ne transitent jamais dans l'URL (header uniquement)
282
 
 
 
283
  ### Endpoints
284
 
285
  #### `GET /` — Accueil de l'API
 
294
  ```json
295
  {
296
  "name": "Futurisys HR Churn API",
297
+ "version": "1.3.0",
298
  "documentation": "http://localhost:8000/docs",
299
  "health": "http://localhost:8000/health"
300
  }
301
  ```
302
 
 
 
303
  #### `GET /health` — Health check
304
 
305
  Vérifie que l'API est opérationnelle. Pas d'authentification requise.
 
313
  { "status": "ok", "message": "API opérationnelle" }
314
  ```
315
 
 
 
316
  #### `GET /model/info` — Informations sur le modèle
317
 
318
  Retourne les métadonnées du modèle déployé.
 
331
  }
332
  ```
333
 
 
 
334
  #### `POST /predict` — Prédiction du risque de départ
335
 
336
  Envoie les features RH d'un employé et reçoit une prédiction de départ.
 
386
  | `label` | string | `"Reste"` ou `"Quitte"` |
387
  | `probabilite` | float | Probabilité de départ (entre 0 et 1) |
388
 
 
389
 
390
  #### `GET /predictions` — Historique des prédictions
391
 
 
402
  -H "X-API-Key: ta_clé_secrète"
403
  ```
404
 
 
 
405
  ## Analyse des données & tableau de bord
406
 
407
  La table `predictions` constitue un journal structuré de toutes les interactions
 
449
  ORDER BY taux_risque_pct DESC;
450
  ```
451
 
452
+ > Ces analyses peuvent alimenter un tableau de bord BI (Metabase, Superset, Power BI) connecté directement à la base PostgreSQL.
453
+
454
  ## Tests
455
 
456
  ### Lancer les tests
 
473
 
474
  ### Résultat de couverture
475
 
476
+ Dernière mesure : **96%** (18 tests, 247 instructions)
477
 
478
  | Fichier | Couverture |
479
  |---|---|
480
  | `app/db/crud.py` | 100% |
481
  | `app/db/models.py` | 100% |
482
+ | `app/middleware/__init__.py` | 100% |
483
+ | `app/schemas/prediction.py` | 98% |
484
+ | `app/main.py` | 97% |
485
+ | `app/middleware/logging.py` | 94% |
486
+ | `app/routes/predict.py` | 93% |
487
+ | `app/db/session.py` | 50% *(infrastructure PostgreSQL, non testée en isolation)* |
488
 
489
 
490
  ### Structure des tests
 
503
 
504
  | Environnement | Branche | URL | Base de données |
505
  |---|---|---|---|
506
+ | Production | `main` | https://rriviere-attrition-api.hf.space/docs | PostgreSQL Neon (instance prod) |
507
+ | Développement | `dev` | https://rriviere-attrition-api-dev.hf.space/docs | PostgreSQL Neon (instance dev) |
508
 
509
 
510
  - **Documentation Swagger (prod)** : https://rriviere-attrition-api.hf.space/docs
 
515
 
516
  Le déploiement est automatisé via GitHub Actions (`.github/workflows/ci_cd.yml`).
517
 
518
+ ### Cartographie des environnements
519
+
520
+ | Environnement | Déclencheur | Infrastructure |
521
+ |---|---|---|
522
+ | **CI / Tests unitaires** (éphémère) | Push/PR sur `main` ou `dev` | CI runner GitHub Actions (VM Ubuntu, détruite après les tests) |
523
+ | **Développement** | Push sur `dev` | Hugging Face Space `dev` + PostgreSQL Neon (instance dev) |
524
+ | **Production** | Push sur `main` | Hugging Face Space prod + PostgreSQL Neon (instance prod) |
525
 
526
  **Étapes :**
527
  1. **Test** — installation des dépendances + exécution de `pytest`
 
540
  | `HF_SPACE_PROD` | Nom du Space HF de production (ex : `username/space-name`) |
541
  | `HF_SPACE_DEV` | Nom du Space HF de développement |
542
 
 
 
543
  ## Sécurité
544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  ### Gestion des secrets
546
 
547
  | Contexte | Mécanisme |
 
563
 
564
  > Cette implémentation est acceptable pour un POC / projet démonstratif.
565
 
566
+ - La clé API est **statique et hachée (SHA-256)** : la comparaison se fait via `hmac.compare_digest` pour éviter les timing attacks
567
  - Pas de mécanisme de révocation ou d'expiration de token
568
  - Pas de rate limiting (protection contre le bruteforce absente)
569
  - En production réelle, préférer OAuth2 / JWT avec expiration
570
 
 
571
 
572
  ## Processus de stockage des données
573
 
 
595
 
596
  > **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.
597
 
 
 
 
 
 
 
 
 
 
 
 
598
 
599
  ## Performances du modèle
600
 
 
629
  1. Exporter les nouvelles données depuis la table `predictions`
630
  2. Réentraîner le pipeline scikit-learn sur les données enrichies
631
  3. Sauvegarder le nouveau `pipeline.pkl`
632
+ 4. Ouvrir une PR sur `main` → le CI/CD redéploie automatiquement le modèle
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/main.py CHANGED
@@ -55,7 +55,7 @@ entraîné sur des données RH pour prédire la probabilité qu'un employé quit
55
  ### Authentification
56
  Toutes les routes protégées nécessitent un header `X-API-Key`.
57
  """,
58
- version="1.3.0",
59
  lifespan=lifespan,
60
  )
61
 
 
55
  ### Authentification
56
  Toutes les routes protégées nécessitent un header `X-API-Key`.
57
  """,
58
+ version="1.3.1",
59
  lifespan=lifespan,
60
  )
61
 
scripts/query_db.py CHANGED
@@ -82,7 +82,9 @@ if __name__ == "__main__":
82
  try:
83
  stats_employees(session)
84
  apercu_employees(session, n=5)
85
- predict_from_db(session, employee_id=1)
 
 
86
  stats_predictions(session)
87
  finally:
88
  session.close()
 
82
  try:
83
  stats_employees(session)
84
  apercu_employees(session, n=5)
85
+ first_employee = session.query(Employee).first()
86
+ if first_employee:
87
+ predict_from_db(session, employee_id=first_employee.id)
88
  stats_predictions(session)
89
  finally:
90
  session.close()