Spaces:
Sleeping
Sleeping
Merge pull request #36 from Diaure/develop
Browse files- .github/workflows/ci.yml +32 -3
- .gitignore +1 -0
- README.md +283 -59
- poetry.lock +130 -5
- pyproject.toml +4 -2
- tests/fonctionnel/test_db_disabled.py +12 -0
- tests/fonctionnel/test_model_end_to_end.py +9 -0
- tests/fonctionnel/test_predict_endpoint.py +16 -0
- tests/sample_payload.py +31 -0
- tests/units/test_model_loading.py +18 -0
.github/workflows/ci.yml
CHANGED
|
@@ -10,7 +10,8 @@ on: # quand le pipeline doit s'exécuter, dans le projet:
|
|
| 10 |
- develop # le pipeline se lance quand une pull request cible develop
|
| 11 |
|
| 12 |
jobs:
|
| 13 |
-
|
|
|
|
| 14 |
runs-on: ubuntu-latest
|
| 15 |
|
| 16 |
steps:
|
|
@@ -25,6 +26,34 @@ jobs:
|
|
| 25 |
|
| 26 |
- name: Install dependencies with Poetry
|
| 27 |
run: poetry install --no-interaction --no-root
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
- name:
|
| 30 |
-
run: poetry
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
- develop # le pipeline se lance quand une pull request cible develop
|
| 11 |
|
| 12 |
jobs:
|
| 13 |
+
unit-tests:
|
| 14 |
+
name: Tests unitaires
|
| 15 |
runs-on: ubuntu-latest
|
| 16 |
|
| 17 |
steps:
|
|
|
|
| 26 |
|
| 27 |
- name: Install dependencies with Poetry
|
| 28 |
run: poetry install --no-interaction --no-root
|
| 29 |
+
|
| 30 |
+
- name: Add project root to PYTHONPATH
|
| 31 |
+
run: echo "PYTHONPATH=$PYTHONPATH:$(pwd)" >> $GITHUB_ENV
|
| 32 |
+
|
| 33 |
+
- name: Run unit tests with coverage
|
| 34 |
+
run: poetry run pytest tests/units --cov=App --cov-report=xml --cov-report=term-missing
|
| 35 |
+
|
| 36 |
+
functional-tests:
|
| 37 |
+
name: Tests fonctionnels API
|
| 38 |
+
runs-on: ubuntu-latest
|
| 39 |
+
needs: unit-tests
|
| 40 |
+
|
| 41 |
+
steps:
|
| 42 |
+
- uses: actions/checkout@v4
|
| 43 |
+
- uses: actions/setup-python@v5
|
| 44 |
+
with:
|
| 45 |
+
python-version: "3.11"
|
| 46 |
+
|
| 47 |
+
- name: Install Poetry
|
| 48 |
+
run: pip install poetry
|
| 49 |
|
| 50 |
+
- name: Install dependencies
|
| 51 |
+
run: poetry install --no-interaction --no-root
|
| 52 |
+
|
| 53 |
+
- name: Add project root to PYTHONPATH
|
| 54 |
+
run: echo "PYTHONPATH=$PYTHONPATH:$(pwd)" >> $GITHUB_ENV
|
| 55 |
+
|
| 56 |
+
- name: Run functional tests (DB disabled)
|
| 57 |
+
env:
|
| 58 |
+
SPACE_ID: "ci" # désactive la DB dans ton code
|
| 59 |
+
run: poetry run pytest tests/fonctionnel --cov=App --cov-append --cov-report=xml --cov-report=term-missing
|
.gitignore
CHANGED
|
@@ -8,3 +8,4 @@ App/model/
|
|
| 8 |
*.joblib
|
| 9 |
*.json
|
| 10 |
App/model/modele_final_xgb.joblib
|
|
|
|
|
|
| 8 |
*.joblib
|
| 9 |
*.json
|
| 10 |
App/model/modele_final_xgb.joblib
|
| 11 |
+
Other
|
README.md
CHANGED
|
@@ -33,7 +33,78 @@ Le projet inclut:
|
|
| 33 |
- Une base de données **PostgreSQL** pour stocker les entrées/sorties du modèle
|
| 34 |
- Des tests unitaires et fonctionnels avec **Pytest**
|
| 35 |
- Un pipeline **CI/CD** pour automatiser les tests et le déploiement
|
| 36 |
-
- Une documentation technique
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
## CI/CD et Déploiement
|
| 39 |
|
|
@@ -43,86 +114,96 @@ Ce projet met en œuvre une approche CI/CD complète, séparant:
|
|
| 43 |
|
| 44 |
### `Intégration Continue (CI) – GitHub Actions`
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
- installation d’un environnement Python 3.11 isolé
|
| 51 |
-
- installation des dépendances définies dans le projet
|
| 52 |
-
- exécution des tests automatisés avec Pytest
|
| 53 |
|
| 54 |
-
L’objectif est de
|
| 55 |
-
- vérifier que le projet est installable
|
| 56 |
-
- garantir que l’API démarre correctement
|
| 57 |
-
- valider le chargement du modèle et le endpoint /*`predict`*
|
| 58 |
-
- éviter toute régression avant fusion vers **`develop`**.
|
| 59 |
|
| 60 |
### `Déploiement Continu (CD) – Hugging Face Spaces`
|
| 61 |
|
| 62 |
-
Le déploiement de l’API est réalisé sur Hugging Face Spaces qui permet:
|
| 63 |
-
|
| 64 |
-
- d’héberger gratuitement des applications ML
|
| 65 |
-
- de déployer une API Dockerisée
|
| 66 |
-
- d’exposer un service accessible publiquement sans gérer de serveur
|
| 67 |
-
|
| 68 |
Dans ce projet, Hugging Face est utilisé comme plateforme de démonstration et de mise à disposition de l’API.
|
| 69 |
|
| 70 |
-
Le déploiement repose sur un Dockerfile, qui définit
|
| 71 |
- l’image Python utilisée (Python 3.11)
|
| 72 |
- l’installation des dépendances
|
| 73 |
-
- le lancement de l’API avec Uvicorn
|
| 74 |
-
|
| 75 |
-
Il garantit la reproductibilité de l'environnement lors de l'exécution de l'API.
|
| 76 |
|
| 77 |
-
A noter que les ***fichiers binaires*** ne sont pas
|
| 78 |
- Hugging Face bloque les push Git contenant des fichiers binaires lourds
|
| 79 |
- Git n’est pas conçu pour versionner des artefacts ML volumineux.
|
| 80 |
|
| 81 |
Pour contourner la situation, dans le projet, les artefacts sont stockés dans un Space Hugging Face dédié, séparé du code. Lors du démarrage de lAPI:
|
| 82 |
- le code télécharge dynamiquement les artefacts via huggingface_hub
|
| 83 |
-
- l’API peut démarrer même si les fichiers ne sont pas présents localement
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
|
|
|
| 85 |
|
| 86 |
-
### `Lancer l’API
|
| 87 |
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
-
-
|
| 91 |
-
https://diaure-futurisys-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
|
| 98 |
### `Endpoint principal`
|
| 99 |
`POST /predict`
|
| 100 |
|
| 101 |
Cet endpoint reçoit les caractéristiques d’un employé et retourne:
|
| 102 |
|
| 103 |
-
- une prédiction lisible ("Reste" ou "Part")
|
| 104 |
- la probabilité associée au départ
|
| 105 |
|
| 106 |
-
Exemple de réponse:
|
| 107 |
```json
|
| 108 |
{
|
| 109 |
"Prediction": "Part",
|
| 110 |
"Probabilite_depart": 0.795678996
|
| 111 |
}
|
| 112 |
```
|
| 113 |
-
Les données d’entrée sont validées
|
| 114 |
-
garantissant la cohérence avec les variables utilisées lors de l’entraînement.
|
| 115 |
|
| 116 |
-
### `
|
| 117 |
|
| 118 |
-
|
|
|
|
|
|
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
|
| 127 |
## Base de données et traçabilité des prédictions
|
| 128 |
### `Objectifs`
|
|
@@ -148,7 +229,7 @@ Les identifiants de connexion sont stockés dans des variables d’environnement
|
|
| 148 |
### `Modélisation de la base de données`
|
| 149 |
La base de données repose sur trois tables distinctes, chacune ayant un rôle précis.
|
| 150 |
1. `employees_dataset - Dataset de référence`
|
| 151 |
-
Il contient le dataset final nettoyé et préparé lors de l'entraînement du modèle en incluant l'ensemble des **32
|
| 152 |
- référence de schéma
|
| 153 |
- source de validation
|
| 154 |
- base documentaire du modèle
|
|
@@ -179,7 +260,7 @@ df.to_sql("employees_dataset", engine, if_exists="replace", index=False)
|
|
| 179 |
2. `inputs - Entrées utilisateur`
|
| 180 |
- Enregistre chaque requête utilisateur envoyée à l'endpoint `/predict`
|
| 181 |
- Contient exactement les features attendues par le modèle
|
| 182 |
-
- Structure strictement alignée avec le schéma
|
| 183 |
- Permet:
|
| 184 |
- l'audit des predictions
|
| 185 |
- l'analyse à posteriori
|
|
@@ -220,15 +301,148 @@ Lors d’un appel à l’endpoint `POST /predict`:
|
|
| 220 |
- la prédiction est enregistrée dans la table **predictions**
|
| 221 |
- la réponse est retournée à l’utilisateur.
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
## Stack technique
|
| 224 |
- **Langage**: Python
|
| 225 |
- **API**: FastAPI
|
| 226 |
- **Machine Learning**: scikit-learn
|
| 227 |
- **Base de données**: PostgreSQL
|
| 228 |
- **Tests**: Pytest, pytest-cov
|
| 229 |
-
- **CI/CD**: GitHub Actions
|
| 230 |
- **Versionnage**: Git / GitHub
|
| 231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
## Structure du projet
|
| 234 |
```text
|
|
@@ -241,17 +455,27 @@ futurisys_ml-api/
|
|
| 241 |
│ ├── model.py # Définition des tables de la database
|
| 242 |
│ ├── predict.py # Application du modèle
|
| 243 |
│ ├── schemas.py # Validation des données (Pydantic)
|
| 244 |
-
│ ── model/
|
| 245 |
-
│ ├── mapping_classes.json
|
| 246 |
-
│ ├── modele_final_xgb.joblib
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
|
| 249 |
-
|
| 250 |
-
│ ├──
|
| 251 |
-
│ ├── dataset_final.csv # Data final
|
| 252 |
-
│ ├── insert_dataset.py # Code chargement de la table dataset_final
|
| 253 |
-
├── tests/ # Tests unitaires, fonctionnels
|
| 254 |
-
│ ├── test_api.py # Test automatisé de l'API via Pytest
|
| 255 |
|
|
| 256 |
├── .env # Stockage des variables sensibles et de configuration
|
| 257 |
├── .gitignore # Nettoyage du dépôt
|
|
|
|
| 33 |
- Une base de données **PostgreSQL** pour stocker les entrées/sorties du modèle
|
| 34 |
- Des tests unitaires et fonctionnels avec **Pytest**
|
| 35 |
- Un pipeline **CI/CD** pour automatiser les tests et le déploiement
|
| 36 |
+
- Une documentation technique centralisée dans ce README
|
| 37 |
+
|
| 38 |
+
## Modèle de Machine Learning (ML)
|
| 39 |
+
### `Problématique`
|
| 40 |
+
Le modèle vise à résoudre un problème de classification binaire :
|
| 41 |
+
|
| 42 |
+
- `0`: l’employé reste dans l’entreprise
|
| 43 |
+
- `1`: l’employé présente un risque de départ
|
| 44 |
+
|
| 45 |
+
L’objectif métier est d’anticiper le turnover afin de permettre aux équipes RH de prioriser des actions de rétention.
|
| 46 |
+
|
| 47 |
+
### `Données et features`
|
| 48 |
+
Le modèle s’appuie sur un dataset RH préparé et nettoyé en amont contenant des:
|
| 49 |
+
- données professionnelles et contextuelles (poste, département, ancienneté, etc.)
|
| 50 |
+
- variables comportementales et organisationnelles
|
| 51 |
+
|
| 52 |
+
Dataset final composé de **32 features**:
|
| 53 |
+
```text
|
| 54 |
+
<class 'pandas.core.frame.DataFrame'>
|
| 55 |
+
RangeIndex: 1470 entries, 0 to 1469
|
| 56 |
+
Data columns (total 32 columns)
|
| 57 |
+
```
|
| 58 |
+
Les prétraitements incluent :
|
| 59 |
+
- le nettoyage et la normalisation des données brutes (3 bases distinctes brutes reçues)
|
| 60 |
+
- le feature engineering: enrichissement des données
|
| 61 |
+
- le choix des variables non redondantes (corrélation inférieure à 70% entre les features numériques & les plus correlées avec la cible)
|
| 62 |
+
- normalisation des variables numériques & encodage des variables catégorielles:
|
| 63 |
+
```python
|
| 64 |
+
transfo_colonnes = ColumnTransformer(
|
| 65 |
+
transformers=[
|
| 66 |
+
('num', StandardScaler(), colonnes_quantitatives),
|
| 67 |
+
('cat', OneHotEncoder(handle_unknown='ignore', max_categories=15, sparse_output=False), colonnes_qualitatives),
|
| 68 |
+
('ord', OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1), colonnes_ordinales)])
|
| 69 |
+
```
|
| 70 |
+
La table `employees_dataset` sert de référence documentaire du schéma attendu par le modèle.
|
| 71 |
+
|
| 72 |
+
### `Choix du modèle`
|
| 73 |
+
|
| 74 |
+
Le modèle retenu est un algorithme de gradient boosting (**XGBoost**) entraîné avec des hyperparamètres optimisés:
|
| 75 |
+
```python
|
| 76 |
+
# Les hyperparamètrs trouvés
|
| 77 |
+
Meilleurs paramètres : {'classifier__colsample_bytree': 0.8, 'classifier__gamma': 1, 'classifier__learning_rate': 0.3, 'classifier__max_depth': 3, 'classifier__min_child_weight': 5, 'classifier__n_estimators': 400, 'classifier__reg_lambda': 1, 'classifier__subsample': 0.8}
|
| 78 |
+
```
|
| 79 |
+
Ce choix est justifié par:
|
| 80 |
+
- de bonnes performances sur des données tabulaires
|
| 81 |
+
- un compromis efficace entre précision et capacité de généralisation
|
| 82 |
+
- un temps d’inférence compatible avec une exposition via API
|
| 83 |
+
|
| 84 |
+

|
| 85 |
+
|
| 86 |
+
### `Sérialisation et versionnage`
|
| 87 |
+
|
| 88 |
+
- Le modèle est sérialisé au format `joblib`
|
| 89 |
+
- Une version du modèle est associée à chaque prédiction dans la table correspondante(`v1`)
|
| 90 |
+
- Les artefacts ML:
|
| 91 |
+
- en **environnement local**, ils peuvent être présents pour les tests
|
| 92 |
+
- en **production**, ils sont systématiquement téléchargés depuis un espace Hugging Face Hub dédié au stockage des artefacts.
|
| 93 |
+
|
| 94 |
+
### `Performance et évaluation du modèle`
|
| 95 |
+
- Séparation du dataset en ensembles train / test
|
| 96 |
+
- Validation basée sur des métriques adaptées au contexte métier:
|
| 97 |
+
- **Recall (0.70)**: limiter les faux négatifs (départs non détectés)
|
| 98 |
+
- **F1-score (0.52 - validation croisée)**: équilibre entre précision et rappel
|
| 99 |
+
- **ROC-AUC (0.80 - validation croisée)**: capacité de discrimination globale du modèle
|
| 100 |
+
|
| 101 |
+
Le recall est volontairement privilégié afin de maximiser la détection des employés à risque, même au prix de quelques faux positifs.
|
| 102 |
+
|
| 103 |
+
### `Limites`
|
| 104 |
+
- Le modèle fournit une probabilité, ***pas une décision finale***
|
| 105 |
+
- Les prédictions doivent être interprétées comme une aide à la décision
|
| 106 |
+
- Les performances dépendent fortement de la qualité et de l’actualité des données RH
|
| 107 |
+
- Des biais peuvent exister si les données historiques sont déséquilibrées.
|
| 108 |
|
| 109 |
## CI/CD et Déploiement
|
| 110 |
|
|
|
|
| 114 |
|
| 115 |
### `Intégration Continue (CI) – GitHub Actions`
|
| 116 |
|
| 117 |
+
À chaque **push** ou **pull request** sur les branches de travail et vers **`develop`**, le pipeline CI exécute automatiquement:
|
| 118 |
+
- l'installation d’un environnement Python 3.11
|
| 119 |
+
- l'installation des dépendances définies dans le projet
|
| 120 |
+
- l'exécution des tests unitaires et fonctionnels
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
+
L’objectif est de garantir la stabilité de l’API et d’éviter toute régression.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
### `Déploiement Continu (CD) – Hugging Face Spaces`
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
Dans ce projet, Hugging Face est utilisé comme plateforme de démonstration et de mise à disposition de l’API.
|
| 127 |
|
| 128 |
+
Le déploiement repose sur un `Dockerfile`, qui définit:
|
| 129 |
- l’image Python utilisée (Python 3.11)
|
| 130 |
- l’installation des dépendances
|
| 131 |
+
- le lancement de l’API avec Uvicorn.
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
A noter que les ***fichiers binaires*** ne sont pas stockés dans le dépôt GiHub principal pour les raisons suivantes:
|
| 134 |
- Hugging Face bloque les push Git contenant des fichiers binaires lourds
|
| 135 |
- Git n’est pas conçu pour versionner des artefacts ML volumineux.
|
| 136 |
|
| 137 |
Pour contourner la situation, dans le projet, les artefacts sont stockés dans un Space Hugging Face dédié, séparé du code. Lors du démarrage de lAPI:
|
| 138 |
- le code télécharge dynamiquement les artefacts via huggingface_hub
|
| 139 |
+
- l’API peut démarrer même si les fichiers ne sont pas présents localement.
|
| 140 |
+
|
| 141 |
+
### `Installation et configuration`
|
| 142 |
+
|
| 143 |
+
Les prérequis:
|
| 144 |
+
- Python 3.11
|
| 145 |
+
- Poetry
|
| 146 |
+
- PostgreSQL (optionnel en local)
|
| 147 |
+
```git bash
|
| 148 |
+
git clone <repository_url>
|
| 149 |
+
cd futurisys_ml-api
|
| 150 |
+
poetry install
|
| 151 |
+
```
|
| 152 |
+
Les variables suivantes doivent être définies pour la connexion à la base SQL:
|
| 153 |
+
- `DB_USER`
|
| 154 |
+
- `DB_PASSWORD`
|
| 155 |
+
- `DB_HOST`
|
| 156 |
+
- `DB_PORT`
|
| 157 |
+
- `DB_NAME`
|
| 158 |
|
| 159 |
+
Elles sont chargées via un fichier `.env` non versionné.
|
| 160 |
|
| 161 |
+
### `Lancer l’API`
|
| 162 |
|
| 163 |
+
- **En local**:
|
| 164 |
+
```python
|
| 165 |
+
uvicorn App.main:app --reload
|
| 166 |
+
```
|
| 167 |
+
Documentation interactive (Swagger UI) - http://127.0.0.1:8000/docs
|
| 168 |
+
|
| 169 |
+
- **En production (Hugging Face Spaces**):
|
| 170 |
|
| 171 |
+
- API: https://diaure-futurisys-api-ml.hf.space
|
| 172 |
+
- Swagger UI: https://diaure-futurisys-api-ml.hf.space/docs
|
| 173 |
+
|
| 174 |
+
Le déploiement permet de:
|
| 175 |
+
- visualiser les endpoints
|
| 176 |
+
- tester directement l’endpoint `/predict`
|
| 177 |
+
- voir les schémas d’entrée et de sortie.
|
| 178 |
|
| 179 |
### `Endpoint principal`
|
| 180 |
`POST /predict`
|
| 181 |
|
| 182 |
Cet endpoint reçoit les caractéristiques d’un employé et retourne:
|
| 183 |
|
| 184 |
+
- une prédiction lisible (`"Reste"` ou `"Part"`)
|
| 185 |
- la probabilité associée au départ
|
| 186 |
|
|
|
|
| 187 |
```json
|
| 188 |
{
|
| 189 |
"Prediction": "Part",
|
| 190 |
"Probabilite_depart": 0.795678996
|
| 191 |
}
|
| 192 |
```
|
| 193 |
+
Les données d’entrée sont validées via **Pydantic** avec l’appel du modèle.
|
|
|
|
| 194 |
|
| 195 |
+
### `Maintenance et mise à jour du modèle`
|
| 196 |
|
| 197 |
+
Une mise à jour du modèle est recommandée:
|
| 198 |
+
- périodiquement (ex. tous les 6 à 12 mois)
|
| 199 |
+
- ou en cas de dérive des données.
|
| 200 |
|
| 201 |
+
Le processus inclut:
|
| 202 |
+
- la collecte de nouvelles données
|
| 203 |
+
- le réentraînement du modèle
|
| 204 |
+
- l'évaluation des performances
|
| 205 |
+
- la validation métier
|
| 206 |
+
- le déploiement d’une nouvelle version.
|
| 207 |
|
| 208 |
## Base de données et traçabilité des prédictions
|
| 209 |
### `Objectifs`
|
|
|
|
| 229 |
### `Modélisation de la base de données`
|
| 230 |
La base de données repose sur trois tables distinctes, chacune ayant un rôle précis.
|
| 231 |
1. `employees_dataset - Dataset de référence`
|
| 232 |
+
Il contient le dataset final nettoyé et préparé lors de l'entraînement du modèle en incluant l'ensemble des **32 features** du modèle. Il sert de:
|
| 233 |
- référence de schéma
|
| 234 |
- source de validation
|
| 235 |
- base documentaire du modèle
|
|
|
|
| 260 |
2. `inputs - Entrées utilisateur`
|
| 261 |
- Enregistre chaque requête utilisateur envoyée à l'endpoint `/predict`
|
| 262 |
- Contient exactement les features attendues par le modèle
|
| 263 |
+
- Structure strictement alignée avec le schéma Pydantic(`EmployeeFeatures`)
|
| 264 |
- Permet:
|
| 265 |
- l'audit des predictions
|
| 266 |
- l'analyse à posteriori
|
|
|
|
| 301 |
- la prédiction est enregistrée dans la table **predictions**
|
| 302 |
- la réponse est retournée à l’utilisateur.
|
| 303 |
|
| 304 |
+
## Tests et Qualité
|
| 305 |
+
|
| 306 |
+
### `Objectifs des tests`
|
| 307 |
+
|
| 308 |
+
Les tests ont été conçus pour:
|
| 309 |
+
- valider le bon fonctionnement des composants critiques (chargement du modèle, validation des données, etc)
|
| 310 |
+
- garantir que l’API répond correctement dans des scénarios réels
|
| 311 |
+
- détecter rapidement les régressions lors du développement
|
| 312 |
+
- assurer la reproductibilité des résultats
|
| 313 |
+
- fournir des indicateurs de qualité (couverture de tests)
|
| 314 |
+
|
| 315 |
+
L’ensemble des tests est exécuté automatiquement dans le pipeline CI à chaque push ou pull request.
|
| 316 |
+
|
| 317 |
+
### `Types de tests exécutés`
|
| 318 |
+
- **Tests unitaires** qui sont des tests rapides qui permettent de détecter immédiatement les erreurs de logique. Ils se concentrent sur les composants isolés du projet comme:
|
| 319 |
+
- la vérification du chargement du modèle et du mapping des classes sans levée d'erreurs (`model_loading.py`)
|
| 320 |
+
- la validation des données via Pydantic et test de l'endpoint de bout en bout(`test_api.py`)
|
| 321 |
+
|
| 322 |
+
- **Tests fonctionnels** qui évaluent l’application dans son ensemble en simulant un usage réel de l’API et garantissant son bon comportement en production:
|
| 323 |
+
- contrôle du fonctionnement de l'API en mode CI lorsque la base est désactivée CI (`test_db_disabled.py`)
|
| 324 |
+
- test tout le pipeline de prédiction (`test_predict_endpoint.py`)
|
| 325 |
+
- test spécifique de l'endpoint `/predict` et vérification de la cohérence de la réponse.
|
| 326 |
+
|
| 327 |
+
**Exécution locale** des tests:
|
| 328 |
+
```python
|
| 329 |
+
poetry run pytest
|
| 330 |
+
poetry run pytest tests.units
|
| 331 |
+
poetry run pytest tests.fonctionnel
|
| 332 |
+
```
|
| 333 |
+
### `Rapport de couverture`
|
| 334 |
+
Généré automatiquement dans GitHub Actions, c'est un apport qui mesure la proportion de code éxécutée par les tests en indiquant:
|
| 335 |
+
- quelles lignes ont été exécutées
|
| 336 |
+
- quelles lignes ne l'ont pas été
|
| 337 |
+
- le pourcentage global de couverture.
|
| 338 |
+
|
| 339 |
+
Il a pour rôle:
|
| 340 |
+
- d'évaluer la qualité de la suite de tests
|
| 341 |
+
- d'identifier les zones non testées
|
| 342 |
+
- de réduire les risques de régression
|
| 343 |
+
- de garantir la fiabilité du code avant déploiement
|
| 344 |
+
- de donner un indicateur objectif de maturité logicielle.
|
| 345 |
+
|
| 346 |
+
Dans ce projet, le pipeline CI désactive la base de données, ainsi il n'y a pas de test sur:
|
| 347 |
+
- la connexion DB
|
| 348 |
+
- les insertions
|
| 349 |
+
- les interactions **SQLALCHEMY**.
|
| 350 |
+
Donc toutes les lignes liées à la DB ne sont pas exécutées, d'où il peut être observé une `couverture plus faible GitHub Actions vs local`.
|
| 351 |
+
```text
|
| 352 |
+
============================== local tests coverage =====================================
|
| 353 |
+
|
| 354 |
+
Name Stmts Miss Cover Missing
|
| 355 |
+
-----------------------------------------------
|
| 356 |
+
App\database.py 25 4 84% 9-10, 35-36
|
| 357 |
+
App\main.py 10 1 90% 13
|
| 358 |
+
App\model.py 48 2 96% 55-56
|
| 359 |
+
App\predict.py 52 9 83% 15-19, 62, 77-79
|
| 360 |
+
App\schemas.py 32 0 100%
|
| 361 |
+
-----------------------------------------------
|
| 362 |
+
TOTAL 167 16 90%
|
| 363 |
+
|
| 364 |
+
========================== GitHub Actions tests coverage ================================
|
| 365 |
+
Name Stmts Miss Cover Missing
|
| 366 |
+
-----------------------------------------------
|
| 367 |
+
App/database.py 25 10 60% 9-10, 23-32
|
| 368 |
+
App/main.py 10 10 0% 1-20
|
| 369 |
+
App/model.py 48 2 96% 55-56
|
| 370 |
+
App/predict.py 52 28 46% 15-19, 49-85
|
| 371 |
+
App/schemas.py 32 0 100%
|
| 372 |
+
-----------------------------------------------
|
| 373 |
+
TOTAL 167 50 70%
|
| 374 |
+
```
|
| 375 |
+
|
| 376 |
## Stack technique
|
| 377 |
- **Langage**: Python
|
| 378 |
- **API**: FastAPI
|
| 379 |
- **Machine Learning**: scikit-learn
|
| 380 |
- **Base de données**: PostgreSQL
|
| 381 |
- **Tests**: Pytest, pytest-cov
|
| 382 |
+
- **CI/CD**: GitHub Actions, Hugging Face
|
| 383 |
- **Versionnage**: Git / GitHub
|
| 384 |
|
| 385 |
+
## Architecture du projet
|
| 386 |
+
|
| 387 |
+
L’architecture du projet repose sur une séparation claire des responsabilités afin de garantir la lisibilité, la maintenabilité et l’évolutivité de l’application.
|
| 388 |
+
|
| 389 |
+
### `Vue d’ensemble`
|
| 390 |
+
|
| 391 |
+
┌──────────────────────────────┐
|
| 392 |
+
│ Utilisateur │
|
| 393 |
+
│ (Client) │
|
| 394 |
+
└───────────────┬──────────────┘
|
| 395 |
+
│ Requête POST /predict
|
| 396 |
+
▼
|
| 397 |
+
┌──────────────────────────────┐
|
| 398 |
+
│ API FastAPI │
|
| 399 |
+
│ (main.py) │
|
| 400 |
+
└───────────────┬──────────────┘
|
| 401 |
+
│
|
| 402 |
+
▼
|
| 403 |
+
┌──────────────────────────────────┐
|
| 404 |
+
│ Module de prédiction │
|
| 405 |
+
│ (predict.py) │
|
| 406 |
+
│ - Chargement du modèle HF Hub │
|
| 407 |
+
│ - Validation │
|
| 408 |
+
│ - Prédiction │
|
| 409 |
+
└───────────────┬──────────────────┘
|
| 410 |
+
│
|
| 411 |
+
▼
|
| 412 |
+
┌────────────────────────────────────────┐
|
| 413 |
+
│ Base de données PostgreSQL │
|
| 414 |
+
│ (inputs / predictions / dataset) │
|
| 415 |
+
└────────────────────────────────────────┘
|
| 416 |
+
|
| 417 |
+
┌────────────────────────────────────────┐
|
| 418 |
+
│ CI/CD – GitHub Actions │
|
| 419 |
+
│ - Tests unitaires │
|
| 420 |
+
│ - Tests fonctionnels │
|
| 421 |
+
│ - Rapport de couverture │
|
| 422 |
+
│ - Déploiement sur HF Space │
|
| 423 |
+
└────────────────────────────────────────┘
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
### `Description du flux`
|
| 427 |
+
|
| 428 |
+
1. Un utilisateur envoie une requête `POST /predict` à l’API
|
| 429 |
+
|
| 430 |
+
2. L’API FastAPI agit comme point d’entrée:
|
| 431 |
+
- validation des données via **Pydantic**
|
| 432 |
+
- orchestration du traitement
|
| 433 |
+
|
| 434 |
+
3. Le module de prédiction:
|
| 435 |
+
- charge dynamiquement le modèle ML depuis **Hugging Face Hub**
|
| 436 |
+
- génère la prédiction et la probabilité associée
|
| 437 |
+
|
| 438 |
+
4. Les données d’entrée et les résultats sont enregistrés dans une base **PostgreSQL** afin d’assurer la traçabilité
|
| 439 |
+
|
| 440 |
+
5. La réponse est retournée au client sous forme JSON
|
| 441 |
+
|
| 442 |
+
6. Le cycle de développement, de test et de déploiement est automatisé via un pipeline **CI/CD GitHub Actions** avec déploiement sur **Hugging Face Spaces**.
|
| 443 |
+
|
| 444 |
+
Cette architecture permet une exposition fiable du modèle de Machine Learning, tout en respectant les bonnes pratiques MLOps et d’ingénierie logicielle.
|
| 445 |
+
|
| 446 |
|
| 447 |
## Structure du projet
|
| 448 |
```text
|
|
|
|
| 455 |
│ ├── model.py # Définition des tables de la database
|
| 456 |
│ ├── predict.py # Application du modèle
|
| 457 |
│ ├── schemas.py # Validation des données (Pydantic)
|
| 458 |
+
│ ── model/ # Elements du modèle
|
| 459 |
+
│ ├── mapping_classes.json # Correspondances des classes
|
| 460 |
+
│ ├── modele_final_xgb.joblib # Modèle final avec hyperparamètres
|
| 461 |
+
|
|
| 462 |
+
├── scripts/ # Scripts bd (BD, données)
|
| 463 |
+
│ ├── create_tables.py # Créaton des tables définies dans model.py
|
| 464 |
+
│ ├── dataset_final.csv # Data final
|
| 465 |
+
│ ├── insert_dataset.py # Code chargement de la table dataset_final
|
| 466 |
+
|
|
| 467 |
+
├── tests/ # Tests unitaires, fonctionnels
|
| 468 |
+
│ ├── test_sanity.py # Test de vérification rapide
|
| 469 |
+
│ ├── test_api.py # Tests API supplémentaires
|
| 470 |
+
│ ├── test_sanity.py # Test de vérification rapide
|
| 471 |
+
|
|
| 472 |
+
│ ── fonctionnel/ # Tests fonctionnels
|
| 473 |
+
│ ├── sample_payload.py # Test automatisé de l'API via Pytest
|
| 474 |
+
│ ├── test_api.py # Tests API supplémentaires
|
| 475 |
+
│ ├── test_sanity.py # Test de vérification rapide
|
| 476 |
|
|
| 477 |
+
│ ── units/ # Tests unitaires
|
| 478 |
+
│ ├── test_model_loading.py # Test automatisé de l'API via Pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
|
|
| 480 |
├── .env # Stockage des variables sensibles et de configuration
|
| 481 |
├── .gitignore # Nettoyage du dépôt
|
poetry.lock
CHANGED
|
@@ -422,6 +422,111 @@ mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "
|
|
| 422 |
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
|
| 423 |
test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"]
|
| 424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
[[package]]
|
| 426 |
name = "cycler"
|
| 427 |
version = "0.12.1"
|
|
@@ -883,7 +988,7 @@ version = "2.3.0"
|
|
| 883 |
description = "brain-dead simple config-ini parsing"
|
| 884 |
optional = false
|
| 885 |
python-versions = ">=3.10"
|
| 886 |
-
groups = ["dev"]
|
| 887 |
files = [
|
| 888 |
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
|
| 889 |
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
|
|
@@ -1822,7 +1927,7 @@ version = "1.6.0"
|
|
| 1822 |
description = "plugin and hook calling mechanisms for python"
|
| 1823 |
optional = false
|
| 1824 |
python-versions = ">=3.9"
|
| 1825 |
-
groups = ["dev"]
|
| 1826 |
files = [
|
| 1827 |
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
|
| 1828 |
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
|
@@ -2192,7 +2297,7 @@ version = "9.0.2"
|
|
| 2192 |
description = "pytest: simple powerful testing with Python"
|
| 2193 |
optional = false
|
| 2194 |
python-versions = ">=3.10"
|
| 2195 |
-
groups = ["dev"]
|
| 2196 |
files = [
|
| 2197 |
{file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"},
|
| 2198 |
{file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"},
|
|
@@ -2208,6 +2313,26 @@ pygments = ">=2.7.2"
|
|
| 2208 |
[package.extras]
|
| 2209 |
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
|
| 2210 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2211 |
[[package]]
|
| 2212 |
name = "python-dateutil"
|
| 2213 |
version = "2.9.0.post0"
|
|
@@ -3016,5 +3141,5 @@ scikit-learn = ["scikit-learn"]
|
|
| 3016 |
|
| 3017 |
[metadata]
|
| 3018 |
lock-version = "2.1"
|
| 3019 |
-
python-versions = "3.11.
|
| 3020 |
-
content-hash = "
|
|
|
|
| 422 |
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
|
| 423 |
test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"]
|
| 424 |
|
| 425 |
+
[[package]]
|
| 426 |
+
name = "coverage"
|
| 427 |
+
version = "7.13.1"
|
| 428 |
+
description = "Code coverage measurement for Python"
|
| 429 |
+
optional = false
|
| 430 |
+
python-versions = ">=3.10"
|
| 431 |
+
groups = ["main", "dev"]
|
| 432 |
+
files = [
|
| 433 |
+
{file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"},
|
| 434 |
+
{file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"},
|
| 435 |
+
{file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"},
|
| 436 |
+
{file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"},
|
| 437 |
+
{file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"},
|
| 438 |
+
{file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"},
|
| 439 |
+
{file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"},
|
| 440 |
+
{file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"},
|
| 441 |
+
{file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"},
|
| 442 |
+
{file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"},
|
| 443 |
+
{file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"},
|
| 444 |
+
{file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"},
|
| 445 |
+
{file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"},
|
| 446 |
+
{file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"},
|
| 447 |
+
{file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"},
|
| 448 |
+
{file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"},
|
| 449 |
+
{file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"},
|
| 450 |
+
{file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"},
|
| 451 |
+
{file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"},
|
| 452 |
+
{file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"},
|
| 453 |
+
{file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"},
|
| 454 |
+
{file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"},
|
| 455 |
+
{file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"},
|
| 456 |
+
{file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"},
|
| 457 |
+
{file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"},
|
| 458 |
+
{file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"},
|
| 459 |
+
{file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"},
|
| 460 |
+
{file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"},
|
| 461 |
+
{file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"},
|
| 462 |
+
{file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"},
|
| 463 |
+
{file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"},
|
| 464 |
+
{file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"},
|
| 465 |
+
{file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"},
|
| 466 |
+
{file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"},
|
| 467 |
+
{file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"},
|
| 468 |
+
{file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"},
|
| 469 |
+
{file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"},
|
| 470 |
+
{file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"},
|
| 471 |
+
{file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"},
|
| 472 |
+
{file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"},
|
| 473 |
+
{file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"},
|
| 474 |
+
{file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"},
|
| 475 |
+
{file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"},
|
| 476 |
+
{file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"},
|
| 477 |
+
{file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"},
|
| 478 |
+
{file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"},
|
| 479 |
+
{file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"},
|
| 480 |
+
{file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"},
|
| 481 |
+
{file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"},
|
| 482 |
+
{file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"},
|
| 483 |
+
{file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"},
|
| 484 |
+
{file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"},
|
| 485 |
+
{file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"},
|
| 486 |
+
{file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"},
|
| 487 |
+
{file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"},
|
| 488 |
+
{file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"},
|
| 489 |
+
{file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"},
|
| 490 |
+
{file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"},
|
| 491 |
+
{file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"},
|
| 492 |
+
{file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"},
|
| 493 |
+
{file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"},
|
| 494 |
+
{file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"},
|
| 495 |
+
{file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"},
|
| 496 |
+
{file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"},
|
| 497 |
+
{file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"},
|
| 498 |
+
{file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"},
|
| 499 |
+
{file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"},
|
| 500 |
+
{file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"},
|
| 501 |
+
{file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"},
|
| 502 |
+
{file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"},
|
| 503 |
+
{file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"},
|
| 504 |
+
{file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"},
|
| 505 |
+
{file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"},
|
| 506 |
+
{file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"},
|
| 507 |
+
{file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"},
|
| 508 |
+
{file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"},
|
| 509 |
+
{file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"},
|
| 510 |
+
{file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"},
|
| 511 |
+
{file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"},
|
| 512 |
+
{file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"},
|
| 513 |
+
{file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"},
|
| 514 |
+
{file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"},
|
| 515 |
+
{file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"},
|
| 516 |
+
{file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"},
|
| 517 |
+
{file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"},
|
| 518 |
+
{file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"},
|
| 519 |
+
{file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"},
|
| 520 |
+
{file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"},
|
| 521 |
+
{file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"},
|
| 522 |
+
{file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"},
|
| 523 |
+
{file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"},
|
| 524 |
+
{file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"},
|
| 525 |
+
]
|
| 526 |
+
|
| 527 |
+
[package.extras]
|
| 528 |
+
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
|
| 529 |
+
|
| 530 |
[[package]]
|
| 531 |
name = "cycler"
|
| 532 |
version = "0.12.1"
|
|
|
|
| 988 |
description = "brain-dead simple config-ini parsing"
|
| 989 |
optional = false
|
| 990 |
python-versions = ">=3.10"
|
| 991 |
+
groups = ["main", "dev"]
|
| 992 |
files = [
|
| 993 |
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
|
| 994 |
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
|
|
|
|
| 1927 |
description = "plugin and hook calling mechanisms for python"
|
| 1928 |
optional = false
|
| 1929 |
python-versions = ">=3.9"
|
| 1930 |
+
groups = ["main", "dev"]
|
| 1931 |
files = [
|
| 1932 |
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
|
| 1933 |
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
|
|
|
| 2297 |
description = "pytest: simple powerful testing with Python"
|
| 2298 |
optional = false
|
| 2299 |
python-versions = ">=3.10"
|
| 2300 |
+
groups = ["main", "dev"]
|
| 2301 |
files = [
|
| 2302 |
{file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"},
|
| 2303 |
{file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"},
|
|
|
|
| 2313 |
[package.extras]
|
| 2314 |
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
|
| 2315 |
|
| 2316 |
+
[[package]]
|
| 2317 |
+
name = "pytest-cov"
|
| 2318 |
+
version = "7.0.0"
|
| 2319 |
+
description = "Pytest plugin for measuring coverage."
|
| 2320 |
+
optional = false
|
| 2321 |
+
python-versions = ">=3.9"
|
| 2322 |
+
groups = ["main", "dev"]
|
| 2323 |
+
files = [
|
| 2324 |
+
{file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"},
|
| 2325 |
+
{file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"},
|
| 2326 |
+
]
|
| 2327 |
+
|
| 2328 |
+
[package.dependencies]
|
| 2329 |
+
coverage = {version = ">=7.10.6", extras = ["toml"]}
|
| 2330 |
+
pluggy = ">=1.2"
|
| 2331 |
+
pytest = ">=7"
|
| 2332 |
+
|
| 2333 |
+
[package.extras]
|
| 2334 |
+
testing = ["process-tests", "pytest-xdist", "virtualenv"]
|
| 2335 |
+
|
| 2336 |
[[package]]
|
| 2337 |
name = "python-dateutil"
|
| 2338 |
version = "2.9.0.post0"
|
|
|
|
| 3141 |
|
| 3142 |
[metadata]
|
| 3143 |
lock-version = "2.1"
|
| 3144 |
+
python-versions = ">=3.11,<3.12"
|
| 3145 |
+
content-hash = "0e1a23e63a8991d00a7612508d1a9029e99cac2f61175dd45c56fd1f623bc8e7"
|
pyproject.toml
CHANGED
|
@@ -6,7 +6,7 @@ authors = [
|
|
| 6 |
{name = "Diane.Aurélie",email = "aurelie.gabu@gmail.com"}
|
| 7 |
]
|
| 8 |
readme = "README.md"
|
| 9 |
-
requires-python = "3.11.
|
| 10 |
dependencies = [
|
| 11 |
"pandas ==2.2.3",
|
| 12 |
"numpy ==1.26.4",
|
|
@@ -26,7 +26,8 @@ dependencies = [
|
|
| 26 |
"fastapi ==0.115.0",
|
| 27 |
"uvicorn ==0.30.1",
|
| 28 |
"python-dotenv ==1.2.1",
|
| 29 |
-
"psycopg2-binary ==2.9.11"
|
|
|
|
| 30 |
]
|
| 31 |
|
| 32 |
[build-system]
|
|
@@ -35,5 +36,6 @@ build-backend = "poetry.core.masonry.api"
|
|
| 35 |
|
| 36 |
[tool.poetry.group.dev.dependencies]
|
| 37 |
pytest = "9.0.2"
|
|
|
|
| 38 |
|
| 39 |
|
|
|
|
| 6 |
{name = "Diane.Aurélie",email = "aurelie.gabu@gmail.com"}
|
| 7 |
]
|
| 8 |
readme = "README.md"
|
| 9 |
+
requires-python = ">=3.11,<3.12"
|
| 10 |
dependencies = [
|
| 11 |
"pandas ==2.2.3",
|
| 12 |
"numpy ==1.26.4",
|
|
|
|
| 26 |
"fastapi ==0.115.0",
|
| 27 |
"uvicorn ==0.30.1",
|
| 28 |
"python-dotenv ==1.2.1",
|
| 29 |
+
"psycopg2-binary ==2.9.11",
|
| 30 |
+
"pytest-cov ==7.0.0"
|
| 31 |
]
|
| 32 |
|
| 33 |
[build-system]
|
|
|
|
| 36 |
|
| 37 |
[tool.poetry.group.dev.dependencies]
|
| 38 |
pytest = "9.0.2"
|
| 39 |
+
pytest-cov = "^7.0.0"
|
| 40 |
|
| 41 |
|
tests/fonctionnel/test_db_disabled.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from App.predict import predict_employee
|
| 3 |
+
from tests.sample_payload import valid_payload
|
| 4 |
+
|
| 5 |
+
def test_db_disabled(monkeypatch):
|
| 6 |
+
# Simule l'environnement Hugging Face
|
| 7 |
+
monkeypatch.setenv("SPACE_ID", "ci")
|
| 8 |
+
|
| 9 |
+
result = predict_employee(valid_payload)
|
| 10 |
+
|
| 11 |
+
assert "Prediction" in result
|
| 12 |
+
assert "Probabilite_depart" in result
|
tests/fonctionnel/test_model_end_to_end.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from App.predict import predict_employee
|
| 2 |
+
from tests.sample_payload import valid_payload
|
| 3 |
+
|
| 4 |
+
def test_model_end_to_end():
|
| 5 |
+
result = predict_employee(valid_payload)
|
| 6 |
+
|
| 7 |
+
assert "Prediction" in result
|
| 8 |
+
assert "Probabilite_depart" in result
|
| 9 |
+
assert 0 <= result["Probabilite_depart"] <= 1
|
tests/fonctionnel/test_predict_endpoint.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tests/functional/test_predict_endpoint.py
|
| 2 |
+
|
| 3 |
+
from fastapi.testclient import TestClient
|
| 4 |
+
from App.main import app
|
| 5 |
+
from tests.sample_payload import valid_payload
|
| 6 |
+
|
| 7 |
+
client = TestClient(app)
|
| 8 |
+
|
| 9 |
+
def test_predict_endpoint():
|
| 10 |
+
response = client.post("/predict", json=valid_payload)
|
| 11 |
+
assert response.status_code == 200
|
| 12 |
+
|
| 13 |
+
body = response.json()
|
| 14 |
+
assert "Prediction" in body
|
| 15 |
+
assert "Probabilite_depart" in body
|
| 16 |
+
assert isinstance(body["Probabilite_depart"], float)
|
tests/sample_payload.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
valid_payload = {
|
| 2 |
+
"genre": "F",
|
| 3 |
+
"statut_marital": "Célibataire",
|
| 4 |
+
"departement": "Ressources Humaines",
|
| 5 |
+
"poste": "Manager",
|
| 6 |
+
"domaine_etude": "Ressources Humaines",
|
| 7 |
+
"frequence_deplacement": "Ocasionnel",
|
| 8 |
+
"heure_supplementaires": False,
|
| 9 |
+
"evolution_cat_evol": "hausse",
|
| 10 |
+
"categorie_employe": "employe_nouveau_poste_interne",
|
| 11 |
+
"satisfaction_employee_nature_travail": 3,
|
| 12 |
+
"nombre_participation_pee": 1,
|
| 13 |
+
"ecart_note_evaluation": 0,
|
| 14 |
+
"revenu_mensuel": 3000,
|
| 15 |
+
"distance_domicile_travail": 10,
|
| 16 |
+
"satisfaction_globale": 3.5,
|
| 17 |
+
"niveau_education": 2,
|
| 18 |
+
"note_evaluation_actuelle": 4,
|
| 19 |
+
"satisfaction_employee_equipe": 3,
|
| 20 |
+
"age": 35,
|
| 21 |
+
"revenu_par_annee_experience_interne": 2571,
|
| 22 |
+
"satisfaction_employee_equilibre_pro_perso": 3,
|
| 23 |
+
"nombre_experiences_precedentes": 2,
|
| 24 |
+
"annees_dans_l_entreprise": 5,
|
| 25 |
+
"nb_formations_suivies": 1,
|
| 26 |
+
"revenu_par_annee_experience_totale": 2117,
|
| 27 |
+
"ratio_sans_promotion": 1,
|
| 28 |
+
"satisfaction_employee_environnement": 3,
|
| 29 |
+
"exp_hors_entreprise": 4,
|
| 30 |
+
"mobilite_promotion": 1,
|
| 31 |
+
"annees_depuis_la_derniere_promotion": 2}
|
tests/units/test_model_loading.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from App.predict import files_load, model, classes_mapping
|
| 2 |
+
|
| 3 |
+
def test_model_loading():
|
| 4 |
+
# Appel initial
|
| 5 |
+
try:
|
| 6 |
+
files_load()
|
| 7 |
+
except Exception as e:
|
| 8 |
+
assert False, f"files_load() ne doit pas lever d'exception : {e}"
|
| 9 |
+
|
| 10 |
+
# assert model is not None
|
| 11 |
+
# assert classes_mapping is not None
|
| 12 |
+
|
| 13 |
+
# Appel supplémentaire pour vérifier que rien ne casse
|
| 14 |
+
# files_load()
|
| 15 |
+
|
| 16 |
+
# assert model is not None
|
| 17 |
+
# assert classes_mapping is not None
|
| 18 |
+
assert True
|