Spaces:
Runtime error
Runtime error
Merge branch 'develop' of https://github.com/Diaure/Futurisys_ML_API into develop
Browse files- .github/workflows/ci.yml +9 -10
- .gitignore +2 -2
- App/database.py +20 -0
- App/main.py +4 -0
- App/model/preprocesseur_fitted.joblib +0 -0
- App/model/variables_entree.json +0 -1
- App/predict.py +10 -7
- Dockerfile +13 -0
- README.md +64 -30
- poetry.lock +0 -0
- pyproject.toml +8 -2
- requirements.txt +7 -1
- scripts/dataset_final.csv +0 -0
- scripts/insert_dataset.py +28 -0
.github/workflows/ci.yml
CHANGED
|
@@ -16,16 +16,15 @@ jobs:
|
|
| 16 |
steps:
|
| 17 |
- uses: actions/setup-python@v5
|
| 18 |
with:
|
| 19 |
-
python-version: "3.11"
|
| 20 |
|
| 21 |
- uses: actions/checkout@v4
|
| 22 |
|
| 23 |
-
- name: Install
|
| 24 |
-
run:
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
- name: Run tests
|
| 30 |
-
run: pytest
|
| 31 |
-
|
|
|
|
| 16 |
steps:
|
| 17 |
- uses: actions/setup-python@v5
|
| 18 |
with:
|
| 19 |
+
python-version: "3.11.9"
|
| 20 |
|
| 21 |
- uses: actions/checkout@v4
|
| 22 |
|
| 23 |
+
- name: Install Poetry
|
| 24 |
+
run: pip install poetry
|
| 25 |
+
|
| 26 |
+
- name: Install dependencies with Poetry
|
| 27 |
+
run: poetry install --no-interaction --no-root
|
| 28 |
+
|
| 29 |
+
- name: Run tests
|
| 30 |
+
run: poetry run pytest
|
|
|
.gitignore
CHANGED
|
@@ -5,6 +5,6 @@ venv/
|
|
| 5 |
.pytest_cache/
|
| 6 |
.coverage
|
| 7 |
App/model/
|
| 8 |
-
App/model/modele_final_xgb.joblib
|
| 9 |
*.joblib
|
| 10 |
-
*.json
|
|
|
|
|
|
| 5 |
.pytest_cache/
|
| 6 |
.coverage
|
| 7 |
App/model/
|
|
|
|
| 8 |
*.joblib
|
| 9 |
+
*.json
|
| 10 |
+
App/model/modele_final_xgb.joblib
|
App/database.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
from sqlalchemy import create_engine
|
| 4 |
+
from sqlalchemy.orm import sessionmaker
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
DB_USER = os.getenv("DB_USER")
|
| 9 |
+
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
| 10 |
+
DB_HOST = os.getenv("DB_HOST")
|
| 11 |
+
DB_PORT = os.getenv("DB_PORT")
|
| 12 |
+
DB_NAME = os.getenv("DB_NAME")
|
| 13 |
+
|
| 14 |
+
DATABASE_URL = (
|
| 15 |
+
f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}"
|
| 16 |
+
f"@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
engine = create_engine(DATABASE_URL)
|
| 20 |
+
SessionLocal = sessionmaker(bind=engine)
|
App/main.py
CHANGED
|
@@ -8,6 +8,10 @@ app = FastAPI(
|
|
| 8 |
version="0.1.0"
|
| 9 |
)
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
@app.post("/predict")
|
| 12 |
def predict(data: EmployeeFeatures):
|
| 13 |
"""
|
|
|
|
| 8 |
version="0.1.0"
|
| 9 |
)
|
| 10 |
|
| 11 |
+
@app.get("/")
|
| 12 |
+
def root():
|
| 13 |
+
return {"status": "API OK"}
|
| 14 |
+
|
| 15 |
@app.post("/predict")
|
| 16 |
def predict(data: EmployeeFeatures):
|
| 17 |
"""
|
App/model/preprocesseur_fitted.joblib
DELETED
|
Binary file (8.04 kB)
|
|
|
App/model/variables_entree.json
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
["genre", "statut_marital", "departement", "poste", "domaine_etude", "frequence_deplacement", "heure_supplementaires", "evolution_cat_evol", "categorie_employe", "satisfaction_employee_nature_travail", "nombre_participation_pee", "ecart_note_evaluation", "revenu_mensuel", "distance_domicile_travail", "satisfaction_globale", "niveau_education", "note_evaluation_actuelle", "satisfaction_employee_equipe", "age", "revenu_par_annee_experience_interne", "satisfaction_employee_equilibre_pro_perso", "nombre_experiences_precedentes", "annees_dans_l_entreprise", "nb_formations_suivies", "revenu_par_annee_experience_totale", "ratio_sans_promotion", "satisfaction_employee_environnement", "exp_hors_entreprise", "mobilite_promotion", "annees_depuis_la_derniere_promotion"]
|
|
|
|
|
|
App/predict.py
CHANGED
|
@@ -3,10 +3,9 @@ import pandas as pd
|
|
| 3 |
from App.schemas import EmployeeFeatures
|
| 4 |
import json
|
| 5 |
from pathlib import Path
|
|
|
|
| 6 |
|
| 7 |
-
|
| 8 |
-
chemin_model = Path("App/model/modele_final_xgb.joblib")
|
| 9 |
-
chemin_mapping = Path("App/model/mapping_classes.json")
|
| 10 |
|
| 11 |
# Variables chargées
|
| 12 |
model = None
|
|
@@ -14,17 +13,21 @@ classes_mapping = None
|
|
| 14 |
Features = list(EmployeeFeatures.model_fields.keys())
|
| 15 |
|
| 16 |
|
|
|
|
| 17 |
# Chargement des fichiers: fonction pour charger le modèle, le mapping afin de permettre à l'API de démarrer m^me si les éléments ne sont pas présents
|
| 18 |
def files_load():
|
| 19 |
global model, classes_mapping
|
|
|
|
| 20 |
if model is None:
|
| 21 |
-
|
| 22 |
-
|
|
|
|
| 23 |
model =joblib.load(chemin_model)
|
| 24 |
|
| 25 |
if classes_mapping is None:
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
with open(chemin_mapping) as f:
|
| 29 |
classes_mapping = json.load(f)
|
| 30 |
|
|
|
|
| 3 |
from App.schemas import EmployeeFeatures
|
| 4 |
import json
|
| 5 |
from pathlib import Path
|
| 6 |
+
from huggingface_hub import hf_hub_download
|
| 7 |
|
| 8 |
+
MODEL_REPO = "Diaure/xgb_model"
|
|
|
|
|
|
|
| 9 |
|
| 10 |
# Variables chargées
|
| 11 |
model = None
|
|
|
|
| 13 |
Features = list(EmployeeFeatures.model_fields.keys())
|
| 14 |
|
| 15 |
|
| 16 |
+
|
| 17 |
# Chargement des fichiers: fonction pour charger le modèle, le mapping afin de permettre à l'API de démarrer m^me si les éléments ne sont pas présents
|
| 18 |
def files_load():
|
| 19 |
global model, classes_mapping
|
| 20 |
+
|
| 21 |
if model is None:
|
| 22 |
+
chemin_model = Path(hf_hub_download(repo_id=MODEL_REPO, filename="modele_final_xgb.joblib"))
|
| 23 |
+
# if not chemin_model.exists():
|
| 24 |
+
# raise RuntimeError("Eléments du modèle introuvable.")
|
| 25 |
model =joblib.load(chemin_model)
|
| 26 |
|
| 27 |
if classes_mapping is None:
|
| 28 |
+
chemin_mapping = Path(hf_hub_download(repo_id=MODEL_REPO, filename="mapping_classes.json"))
|
| 29 |
+
# if not chemin_mapping.exists():
|
| 30 |
+
# raise RuntimeError("Mapping des classes introuvable.")
|
| 31 |
with open(chemin_mapping) as f:
|
| 32 |
classes_mapping = json.load(f)
|
| 33 |
|
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# force rebuild
|
| 2 |
+
FROM python:3.11
|
| 3 |
+
|
| 4 |
+
WORKDIR /code
|
| 5 |
+
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 8 |
+
|
| 9 |
+
COPY . .
|
| 10 |
+
|
| 11 |
+
EXPOSE 7860
|
| 12 |
+
|
| 13 |
+
CMD ["uvicorn", "App.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Futurisys – Déploiement d’un modèle de Machine Learning via API
|
| 2 |
|
| 3 |
## Contexte
|
|
@@ -27,45 +37,67 @@ Le projet inclut :
|
|
| 27 |
- Un pipeline **CI/CD** pour automatiser les tests et le déploiement
|
| 28 |
- Une documentation technique claire
|
| 29 |
|
| 30 |
-
## CI/CD et
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
le pipeline exécute automatiquement les étapes suivantes :
|
| 36 |
- installation d’un environnement Python 3.11 isolé
|
| 37 |
- installation des dépendances définies dans le projet
|
| 38 |
-
- exécution des tests
|
| 39 |
|
| 40 |
-
L’objectif est de
|
| 41 |
-
- le projet
|
| 42 |
-
-
|
| 43 |
-
-
|
|
|
|
| 44 |
|
| 45 |
-
##
|
| 46 |
|
| 47 |
-
|
| 48 |
-
- un schéma d’entrée validé avec **Pydantic**
|
| 49 |
-
- un préprocesseur entraîné et sauvegardé
|
| 50 |
-
- un modèle de machine learning sérialisé avec **joblib**
|
| 51 |
|
| 52 |
-
|
| 53 |
-
-
|
| 54 |
-
-
|
| 55 |
-
- `mapping_classes.json`
|
| 56 |
|
| 57 |
-
|
| 58 |
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
### Endpoint principal
|
| 69 |
`POST /predict`
|
| 70 |
|
| 71 |
Cet endpoint reçoit les caractéristiques d’un employé et retourne :
|
|
@@ -83,7 +115,7 @@ Exemple de réponse :
|
|
| 83 |
Les données d’entrée sont validées automatiquement avant l’appel au modèle,
|
| 84 |
garantissant la cohérence avec les variables utilisées lors de l’entraînement.
|
| 85 |
|
| 86 |
-
## Documentation des endpoints
|
| 87 |
|
| 88 |
L’API expose un endpoint principal de prédiction.
|
| 89 |
|
|
@@ -100,7 +132,7 @@ L’API expose un endpoint principal de prédiction.
|
|
| 100 |
- **Machine Learning** : scikit-learn
|
| 101 |
- **Base de données** : PostgreSQL
|
| 102 |
- **Tests** : Pytest, pytest-cov
|
| 103 |
-
- **CI/CD** : GitHub Actions
|
| 104 |
- **Versionnage** : Git / GitHub
|
| 105 |
|
| 106 |
|
|
@@ -121,10 +153,12 @@ futurisys_ml-api/
|
|
| 121 |
|
|
| 122 |
├── scripts/ # Scripts bd (BD, données)
|
| 123 |
├── tests/ # Tests unitaires, fonctionnels
|
| 124 |
-
│ ├── test_api.py
|
| 125 |
|
|
| 126 |
├── .gitignore # Nettoyage du dépôt
|
| 127 |
-
├──
|
|
|
|
|
|
|
| 128 |
├── README.md # Présentation du projet
|
| 129 |
-
└── requirements.txt # Librairies
|
| 130 |
```
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Futurisys ML API
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
|
| 11 |
# Futurisys – Déploiement d’un modèle de Machine Learning via API
|
| 12 |
|
| 13 |
## Contexte
|
|
|
|
| 37 |
- Un pipeline **CI/CD** pour automatiser les tests et le déploiement
|
| 38 |
- Une documentation technique claire
|
| 39 |
|
| 40 |
+
## CI/CD et Déploiement
|
| 41 |
+
|
| 42 |
+
Ce projet met en œuvre une approche CI/CD complète, séparant:
|
| 43 |
+
- l’intégration continue (**CI**): garantir la qualité du code
|
| 44 |
+
- le déploiement continu (**CD**): rendre l’API accessible publiquement
|
| 45 |
|
| 46 |
+
### `Intégration Continue (CI) – GitHub Actions`
|
| 47 |
|
| 48 |
+
L’intégration continue est assurée via GitHub Actions.
|
| 49 |
+
|
| 50 |
+
À chaque **push** sur les branches de travail et à chaque **pull request** vers **`develop`**,
|
| 51 |
le pipeline exécute automatiquement les étapes suivantes :
|
| 52 |
- installation d’un environnement Python 3.11 isolé
|
| 53 |
- installation des dépendances définies dans le projet
|
| 54 |
+
- exécution des tests automatisés avec Pytest
|
| 55 |
|
| 56 |
+
L’objectif est de:
|
| 57 |
+
- vérifier que le projet est installable
|
| 58 |
+
- garantir que l’API démarre correctement
|
| 59 |
+
- valider le chargement du modèle et le endpoint /*`predict`*
|
| 60 |
+
- éviter toute régression avant fusion vers **`develop`**.
|
| 61 |
|
| 62 |
+
### `Déploiement Continu (CD) – Hugging Face Spaces`
|
| 63 |
|
| 64 |
+
Le déploiement de l’API est réalisé sur Hugging Face Spaces qui permet:
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
- d’héberger gratuitement des applications ML
|
| 67 |
+
- de déployer une API Dockerisée
|
| 68 |
+
- d’exposer un service accessible publiquement sans gérer de serveur
|
|
|
|
| 69 |
|
| 70 |
+
Dans ce projet, Hugging Face est utilisé comme plateforme de démonstration et de mise à disposition de l’API.
|
| 71 |
|
| 72 |
+
Le déploiement repose sur un Dockerfile, qui définit :
|
| 73 |
+
- l’image Python utilisée (Python 3.11)
|
| 74 |
+
- l’installation des dépendances
|
| 75 |
+
- le lancement de l’API avec Uvicorn
|
| 76 |
|
| 77 |
+
Il garantit la reproductibilité de l'environnement lors de l'exécution de l'API.
|
| 78 |
+
|
| 79 |
+
A noter que les ***fichiers binaires*** ne sont pas stochés dans le dépôt GiHub principal pour les raisons suivantes:
|
| 80 |
+
- Hugging Face bloque les push Git contenant des fichiers binaires lourds
|
| 81 |
+
- Git n’est pas conçu pour versionner des artefacts ML volumineux.
|
| 82 |
+
|
| 83 |
+
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:
|
| 84 |
+
- le code télécharge dynamiquement les artefacts via huggingface_hub
|
| 85 |
+
- l’API peut démarrer même si les fichiers ne sont pas présents localement
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
### `Lancer l’API en local`
|
| 89 |
+
|
| 90 |
+
L’API est déployée publiquement sur Hugging Face Spaces.
|
| 91 |
|
| 92 |
+
- URL de l’API :
|
| 93 |
+
https://diaure-futurisys-ml-api.hf.space
|
| 94 |
+
- Documentation interactive (Swagger UI) :
|
| 95 |
+
https://diaure-futurisys-ml-api.hf.space/docs. Ele permet de:
|
| 96 |
+
- visualiser les endpoints
|
| 97 |
+
- tester directement l’endpoint `/predict`
|
| 98 |
+
- voir les schémas d’entrée et de sortie.
|
| 99 |
|
| 100 |
+
### `Endpoint principal`
|
| 101 |
`POST /predict`
|
| 102 |
|
| 103 |
Cet endpoint reçoit les caractéristiques d’un employé et retourne :
|
|
|
|
| 115 |
Les données d’entrée sont validées automatiquement avant l’appel au modèle,
|
| 116 |
garantissant la cohérence avec les variables utilisées lors de l’entraînement.
|
| 117 |
|
| 118 |
+
### `Documentation des endpoints`
|
| 119 |
|
| 120 |
L’API expose un endpoint principal de prédiction.
|
| 121 |
|
|
|
|
| 132 |
- **Machine Learning** : scikit-learn
|
| 133 |
- **Base de données** : PostgreSQL
|
| 134 |
- **Tests** : Pytest, pytest-cov
|
| 135 |
+
- **CI/CD** : GitHub Actions, Hugging Face
|
| 136 |
- **Versionnage** : Git / GitHub
|
| 137 |
|
| 138 |
|
|
|
|
| 153 |
|
|
| 154 |
├── scripts/ # Scripts bd (BD, données)
|
| 155 |
├── tests/ # Tests unitaires, fonctionnels
|
| 156 |
+
│ ├── test_api.py # Test automatisé API Pytest
|
| 157 |
|
|
| 158 |
├── .gitignore # Nettoyage du dépôt
|
| 159 |
+
├── Dockerfile # Reproduction du dépôt
|
| 160 |
+
├── poetry.lock # Nettoyage du dépôt
|
| 161 |
+
├── pyproject.toml # Librairies dépendances ML
|
| 162 |
├── README.md # Présentation du projet
|
| 163 |
+
└── requirements.txt # Librairies dépendances API
|
| 164 |
```
|
poetry.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
pyproject.toml
CHANGED
|
@@ -21,11 +21,17 @@ dependencies = [
|
|
| 21 |
"catboost ==1.2.7",
|
| 22 |
"numba ==0.59.1",
|
| 23 |
"llvmlite ==0.42.0",
|
| 24 |
-
"ipykernel>=6.25,<7.0"
|
|
|
|
|
|
|
|
|
|
| 25 |
]
|
| 26 |
|
| 27 |
-
|
| 28 |
[build-system]
|
| 29 |
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
| 30 |
build-backend = "poetry.core.masonry.api"
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
"catboost ==1.2.7",
|
| 22 |
"numba ==0.59.1",
|
| 23 |
"llvmlite ==0.42.0",
|
| 24 |
+
"ipykernel>=6.25,<7.0",
|
| 25 |
+
"huggingface-hub ==1.3.1",
|
| 26 |
+
"fastapi ==0.115.0",
|
| 27 |
+
"uvicorn ==0.30.1"
|
| 28 |
]
|
| 29 |
|
|
|
|
| 30 |
[build-system]
|
| 31 |
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
| 32 |
build-backend = "poetry.core.masonry.api"
|
| 33 |
|
| 34 |
+
[tool.poetry.group.dev.dependencies]
|
| 35 |
+
pytest = "9.0.2"
|
| 36 |
+
|
| 37 |
+
|
requirements.txt
CHANGED
|
@@ -6,4 +6,10 @@ Pygments==2.19.2
|
|
| 6 |
pytest==9.0.2
|
| 7 |
fastapi==0.115.0
|
| 8 |
uvicorn==0.30.1
|
| 9 |
-
httpx==0.27.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
pytest==9.0.2
|
| 7 |
fastapi==0.115.0
|
| 8 |
uvicorn==0.30.1
|
| 9 |
+
httpx==0.27.0
|
| 10 |
+
huggingface-hub==1.3.1
|
| 11 |
+
joblib==1.4.2
|
| 12 |
+
pandas==2.2.2
|
| 13 |
+
scikit-learn==1.4.2
|
| 14 |
+
xgboost ==2.0.3
|
| 15 |
+
huggingface-hub ==1.3.1
|
scripts/dataset_final.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/insert_dataset.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from sqlalchemy import create_engine
|
| 5 |
+
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
+
csv_path = os.path.join(BASE_DIR, "dataset_final.csv")
|
| 10 |
+
|
| 11 |
+
df = pd.read_csv(csv_path, encoding="latin-1")
|
| 12 |
+
|
| 13 |
+
DB_USER = os.getenv("DB_USER")
|
| 14 |
+
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
| 15 |
+
DB_HOST = os.getenv("DB_HOST")
|
| 16 |
+
DB_PORT = os.getenv("DB_PORT")
|
| 17 |
+
DB_NAME = os.getenv("DB_NAME")
|
| 18 |
+
|
| 19 |
+
DATABASE_URL = (
|
| 20 |
+
f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}"
|
| 21 |
+
f"@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
engine = create_engine(DATABASE_URL)
|
| 25 |
+
|
| 26 |
+
df.to_sql("employees_dataset", engine, if_exists="replace", index=False)
|
| 27 |
+
|
| 28 |
+
print("Dataset inséré dans PostgreSQL")
|