Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +44 -97
- app/main.py +1 -1
- scripts/query_db.py +3 -1
README.md
CHANGED
|
@@ -6,7 +6,7 @@ colorTo: green
|
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
-

|
| 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.
|
| 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 : **
|
| 489 |
|
| 490 |
| Fichier | Couverture |
|
| 491 |
|---|---|
|
| 492 |
| `app/db/crud.py` | 100% |
|
| 493 |
| `app/db/models.py` | 100% |
|
| 494 |
-
| `app/
|
| 495 |
-
| `app/
|
| 496 |
-
| `app/main.py` |
|
| 497 |
-
| `app/
|
|
|
|
|
|
|
| 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 |
-
|
| 529 |
-
|
| 530 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
+

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