Aurélie GABU commited on
Commit
eb56cd7
·
2 Parent(s): 1c95fd6 196e924

Merge branch 'main' of https://github.com/Diaure/Futurisys_ML_API

Browse files
.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 dependencies
24
- run: |
25
- pip install .
26
- pip install -r requirements.txt
27
- pip install pytest
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
@@ -7,4 +7,4 @@ venv/
7
  App/model/
8
  *.joblib
9
  *.json
10
- App/model/modele_final_xgb.joblib
 
7
  App/model/
8
  *.joblib
9
  *.json
10
+ App/model/modele_final_xgb.joblib
App/database.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from sqlalchemy import create_engine
4
+ from sqlalchemy.orm import sessionmaker, declarative_base
5
+
6
+ load_dotenv()
7
+
8
+ # Détection si on est en CI (GitHub Actions) ou en test
9
+ IS_CI = os.getenv("CI") == "true"
10
+ IS_PYTEST = "pytest" in os.getenv("PYTHONPATH", "") or os.getenv("PYTEST_CURRENT_TEST") is not None
11
+
12
+ SKIP_DB = IS_CI or IS_PYTEST
13
+
14
+ DB_USER = os.getenv("DB_USER", "postgres")
15
+ DB_PASSWORD = os.getenv("DB_PASSWORD", "password")
16
+ DB_HOST = os.getenv("DB_HOST", "localhost")
17
+ DB_PORT = os.getenv("DB_PORT", "5432")
18
+ DB_NAME = os.getenv("DB_NAME", "test_db")
19
+
20
+ DATABASE_URL = (f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}"f"@{DB_HOST}:{DB_PORT}/{DB_NAME}")
21
+
22
+ Base = declarative_base()
23
+
24
+ if not SKIP_DB:
25
+ engine = create_engine(DATABASE_URL)
26
+ SessionLocal = sessionmaker(autocommit = False, autoflush = False, bind = engine)
27
+
28
+ else:
29
+ engine = None
30
+ SessionLocal = None
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.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey
2
+ from sqlalchemy.sql import func
3
+ from App.database import Base
4
+
5
+ class Input(Base):
6
+ __tablename__ = "inputs"
7
+
8
+ id = Column(Integer, primary_key=True, index=True)
9
+ genre = Column(String)
10
+ statut_marital = Column(String)
11
+ departement = Column(String)
12
+ poste = Column(String)
13
+ domaine_etude = Column(String)
14
+ frequence_deplacement = Column(String)
15
+ heure_supplementaires = Column(Boolean)
16
+ evolution_cat_evol = Column(String)
17
+ categorie_employe = Column(String)
18
+ satisfaction_employee_nature_travail = Column(Integer)
19
+ nombre_participation_pee = Column(Integer)
20
+ ecart_note_evaluation = Column(Integer)
21
+ revenu_mensuel = Column(Integer)
22
+ distance_domicile_travail = Column(Integer)
23
+ satisfaction_globale = Column(Float)
24
+ niveau_education = Column(Integer)
25
+ note_evaluation_actuelle = Column(Integer)
26
+ satisfaction_employee_equipe = Column(Integer)
27
+ age = Column(Integer)
28
+ revenu_par_annee_experience_interne = Column(Integer)
29
+ satisfaction_employee_equilibre_pro_perso = Column(Integer)
30
+ nombre_experiences_precedentes = Column(Integer)
31
+ annees_dans_l_entreprise = Column(Integer)
32
+ nb_formations_suivies = Column(Integer)
33
+ revenu_par_annee_experience_totale = Column(Integer)
34
+ ratio_sans_promotion = Column(Integer)
35
+ satisfaction_employee_environnement = Column(Integer)
36
+ exp_hors_entreprise = Column(Integer)
37
+ mobilite_promotion = Column(Integer)
38
+ annees_depuis_la_derniere_promotion = Column(Integer)
39
+
40
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
41
+
42
+ class Predictions(Base):
43
+ __tablename__ = "predictions"
44
+ id = Column(Integer, primary_key=True, index=True)
45
+ input_id = Column(Integer, ForeignKey("inputs.id"))
46
+
47
+ prediction_label = Column(String)
48
+ prediction_proba = Column(Float)
49
+ model_version = Column(String)
50
+
51
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
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
@@ -2,16 +2,43 @@ import joblib
2
  import pandas as pd
3
  from App.schemas import EmployeeFeatures
4
  import json
 
 
 
 
 
5
 
 
6
 
7
- model = joblib.load("App/model/modele_final_xgb.joblib")
 
 
 
8
 
9
- FEATURES = list(EmployeeFeatures.model_fields.keys())
10
- with open("App/model/mapping_classes.json") as f:
11
- CLASS_MAPPING = json.load(f)
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  def predict_employee(data: dict):
14
- df = pd.DataFrame([data])[FEATURES]
 
 
15
 
16
  print("Colonnes API :", df.columns.tolist())
17
  print("Nombre colonnes API :", len(df.columns))
@@ -19,7 +46,29 @@ def predict_employee(data: dict):
19
  pred = model.predict(df)[0]
20
  proba = model.predict_proba(df)[0][1]
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  return {
23
- "Prediction": CLASS_MAPPING[str(pred)],
24
- "Probabilite_depart": float(proba)
25
- }
 
2
  import pandas as pd
3
  from App.schemas import EmployeeFeatures
4
  import json
5
+ from pathlib import Path
6
+ from huggingface_hub import hf_hub_download
7
+ from sqlalchemy.orm import Session
8
+ from App.database import SessionLocal
9
+ from App.model import Input, Predictions
10
 
11
+ MODEL_REPO = "Diaure/xgb_model"
12
 
13
+ # Variables chargées
14
+ model = None
15
+ classes_mapping = None
16
+ Features = list(EmployeeFeatures.model_fields.keys())
17
 
 
 
 
18
 
19
+
20
+ # 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
21
+ def files_load():
22
+ global model, classes_mapping
23
+
24
+ if model is None:
25
+ chemin_model = Path(hf_hub_download(repo_id=MODEL_REPO, filename="modele_final_xgb.joblib"))
26
+ # if not chemin_model.exists():
27
+ # raise RuntimeError("Eléments du modèle introuvable.")
28
+ model =joblib.load(chemin_model)
29
+
30
+ if classes_mapping is None:
31
+ chemin_mapping = Path(hf_hub_download(repo_id=MODEL_REPO, filename="mapping_classes.json"))
32
+ # if not chemin_mapping.exists():
33
+ # raise RuntimeError("Mapping des classes introuvable.")
34
+ with open(chemin_mapping) as f:
35
+ classes_mapping = json.load(f)
36
+
37
+ # Fonction prédiction
38
  def predict_employee(data: dict):
39
+ files_load()
40
+
41
+ df = pd.DataFrame([data])[Features]
42
 
43
  print("Colonnes API :", df.columns.tolist())
44
  print("Nombre colonnes API :", len(df.columns))
 
46
  pred = model.predict(df)[0]
47
  proba = model.predict_proba(df)[0][1]
48
 
49
+ db: Session = SessionLocal() if SessionLocal is not None else None
50
+
51
+ if db is not None:
52
+ try:
53
+ # enregistrer les inputs: à chaque appel de POST/predict, on stocke d'abord les entrées de l'utilisateur
54
+ input_row = Input(**data)
55
+ db.add(input_row)
56
+ db.commit()
57
+ db.refresh(input_row)
58
+
59
+ # puis on récupère les ids générés automatiquement et enregistre les prédictions liés aux ids
60
+ pred_row = Predictions(input_id = input_row.id, prediction_label = classes_mapping[str(pred)], prediction_proba = float(proba), model_version = "v1")
61
+ db.add(pred_row)
62
+ db.commit()
63
+
64
+ except Exception as e:
65
+ print("🔥 ERREUR DB :", e)
66
+ raise e
67
+
68
+ finally:
69
+ db.close()
70
+
71
+ # puis on renvoie la réponse API
72
  return {
73
+ "Prediction": classes_mapping[str(pred)],
74
+ "Probabilite_depart": float(proba)}
 
App/schemas.py CHANGED
@@ -10,7 +10,6 @@ class EmployeeFeatures(BaseModel):
10
  heure_supplementaires: bool
11
  evolution_cat_evol: str
12
  categorie_employe: str
13
-
14
  satisfaction_employee_nature_travail: int
15
  nombre_participation_pee: int
16
  ecart_note_evaluation: int
 
10
  heure_supplementaires: bool
11
  evolution_cat_evol: str
12
  categorie_employe: str
 
13
  satisfaction_employee_nature_travail: int
14
  nombre_participation_pee: int
15
  ecart_note_evaluation: int
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
@@ -5,9 +15,7 @@
5
  opérationnels et accessibles via une API performante.
6
 
7
  Ce projet correspond à un **Proof of Concept (POC)** visant à déployer un modèle de machine
8
- learning en production en appliquant les bonnes pratiques d’ingénierie logicielle :
9
- versionnage, tests, base de données et automatisation.
10
-
11
 
12
 
13
  ## Objectifs du projet
@@ -19,7 +27,7 @@ versionnage, tests, base de données et automatisation.
19
 
20
 
21
  ## Périmètre fonctionnel
22
- Le projet inclut :
23
  - Une API développée avec **FastAPI**
24
  - L’exposition d’un modèle de machine learning via des endpoints REST
25
  - Une base de données **PostgreSQL** pour stocker les entrées/sorties du modèle
@@ -27,53 +35,75 @@ 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 qualité du code
 
 
 
 
 
 
31
 
32
- Ce projet utilise une pipeline d’intégration continue (CI) via GitHub Actions.
33
 
34
- À chaque push sur les branches de travail et à chaque pull request vers `develop`,
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 unitaires via pytest
39
 
40
- L’objectif est de garantir que :
41
- - le projet reste installable
42
- - les transformations et composants (chargement du modèle, prédiction) ne régressent pas
43
- - toute fusion vers la branche `develop` est validée automatiquement
 
44
 
45
- ## Architecture de l’API
46
 
47
- L’API est développée avec **FastAPI** et repose sur :
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
- Les artefacts du modèle sont stockés dans le dossier `App/model/` :
53
- - `preprocesseur_fitted.joblib`
54
- - `model_final_xgb.joblib`
55
- - `mapping_classes.json`
56
 
57
- ## Lancer l’API en local
58
 
59
- Depuis la racine du projet :
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- ```bash
62
- uvicorn App.main:app --reload --log-level debug
63
- ```
64
- L’API est alors accessible à l’adresse http://127.0.0.1:8000/
65
 
66
- La documentation interactive à http://127.0.0.1:8000/docs
67
 
68
- ### Endpoint principal
 
 
 
 
 
 
 
 
 
 
69
  `POST /predict`
70
 
71
- Cet endpoint reçoit les caractéristiques d’un employé et retourne :
72
 
73
  - une prédiction lisible ("Reste" ou "Part")
74
  - la probabilité associée au départ
75
 
76
- Exemple de réponse :
77
  ```json
78
  {
79
  "Prediction": "Part",
@@ -83,26 +113,121 @@ 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
 
90
  **POST /predict**
91
  - Description : retourne une prédiction de départ d’un employé
92
- - Validation des données : Pydantic
93
- - Réponses possibles :
94
- - 200 : prédiction valide
95
- - 422 : données invalides
96
 
97
- ## Stack technique
98
- - **Langage** : Python
99
- - **API** : FastAPI
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
 
107
 
108
  ## Structure du projet
@@ -111,7 +236,9 @@ futurisys_ml-api/
111
  ├── github/workflows
112
  │ ├── ci.yml # Description des évènement déclenchants des tests
113
  ├── app/ # Code applicatif principal
 
114
  │ ├── main.py # Point d’entrée de l’API
 
115
  │ ├── predict.py # Application du modèle
116
  │ ├── schemas.py # Validation des données (Pydantic)
117
  │ ── model/ # Elements du modèle
@@ -119,12 +246,18 @@ futurisys_ml-api/
119
  │ ├── modele_final_xgb.joblib # Modèle final avec hyperparamètres
120
  │ ├── preprocesseur_fitted.joblib # Pipeline entrainé
121
  |
122
- ├── scripts/ # Scripts bd (BD, données)
123
- ├── tests/ # Tests unitaires, fonctionnels
 
 
 
124
  │ ├── test_api.py # Test automatisé de l'API via Pytest
125
  |
 
126
  ├── .gitignore # Nettoyage du dépôt
127
- ├── pyproject.toml # Librairies des modules entrainement ML
 
 
128
  ├── README.md # Présentation du projet
129
- └── requirements.txt # Librairies des modules dispensables API
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
 
15
  opérationnels et accessibles via une API performante.
16
 
17
  Ce projet correspond à un **Proof of Concept (POC)** visant à déployer un modèle de machine
18
+ learning en production en appliquant les bonnes pratiques d’ingénierie logicielle: versionnage, tests, base de données et automatisation.
 
 
19
 
20
 
21
  ## Objectifs du projet
 
27
 
28
 
29
  ## Périmètre fonctionnel
30
+ Le projet inclut:
31
  - Une API développée avec **FastAPI**
32
  - L’exposition d’un modèle de machine learning via des endpoints REST
33
  - Une base de données **PostgreSQL** pour stocker les entrées/sorties du modèle
 
35
  - Un pipeline **CI/CD** pour automatiser les tests et le déploiement
36
  - Une documentation technique claire
37
 
38
+ ## CI/CD et Déploiement
39
+
40
+ Ce projet met en œuvre une approche CI/CD complète, séparant:
41
+ - l’intégration continue (**CI**): garantir la qualité du code
42
+ - le déploiement continu (**CD**): rendre l’API accessible publiquement
43
+
44
+ ### `Intégration Continue (CI) – GitHub Actions`
45
 
46
+ L’intégration continue est assurée via GitHub Actions.
47
 
48
+ À chaque **push** sur les branches de travail et à chaque **pull request** vers **`develop`**,
49
  le pipeline exécute automatiquement les étapes suivantes :
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 stochés dans le dépôt GiHub principal pour les raisons suivantes:
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 en local`
87
 
88
+ L’API est déployée publiquement sur Hugging Face Spaces.
89
+
90
+ - URL de l’API :
91
+ https://diaure-futurisys-ml-api.hf.space
92
+ - Documentation interactive (Swagger UI):
93
+ https://diaure-futurisys-ml-api.hf.space/docs. Ele permet de:
94
+ - visualiser les endpoints
95
+ - tester directement l’endpoint `/predict`
96
+ - voir les schémas d’entrée et de sortie.
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",
 
113
  Les données d’entrée sont validées automatiquement avant l’appel au modèle,
114
  garantissant la cohérence avec les variables utilisées lors de l’entraînement.
115
 
116
+ ### `Documentation des endpoints`
117
 
118
  L’API expose un endpoint principal de prédiction.
119
 
120
  **POST /predict**
121
  - Description : retourne une prédiction de départ d’un employé
122
+ - Validation des données: Pydantic
123
+ - Réponses possibles:
124
+ - 200: prédiction valide
125
+ - 422: données invalides
126
 
127
+ ## Base de données et traçabilité des prédictions
128
+ ### `Objectifs`
129
+
130
+ L’intégration d’une base de données PostgreSQL permet d’inscrire le projet dans une logique MLOps et de répondre à plusieurs objectifs clés:
131
+ - assurer la traçabilité complète des prédictions du modèle
132
+ - conserver l’historique des données d’entrée utilisateur
133
+ - stocker les résultats de prédiction (label, probabilité, version du modèle)
134
+ - préparer une architecture compatible avec un déploiement en production.
135
+
136
+ ### `Méthodologie utilisée`
137
+ - **PostgreSQL** a été retenu pour:
138
+ - sa robustesse et sa fiabilité
139
+ - sa compatibilité native avec SQLAlchemy
140
+ - son usage courant en environnement professionnel
141
+
142
+ - **SQLAlchemy** est utilisé comme couche d’abstraction:
143
+ - gestion centralisée de la connexion à la base
144
+ - cohérence entre le schéma Python et la base SQL
145
+
146
+ Les identifiants de connexion sont stockés dans des variables d’environnement (`.env`) afin d’éviter toute exposition de secrets dans le dépôt Git.
147
+
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 deatures** du modèle. Il sert de:
152
+ - référence de schéma
153
+ - source de validation
154
+ - base documentaire du modèle
155
 
156
+ C'est une table qui n'est jamais alimentée par l'utilisateur.
157
+
158
+ ```python
159
+ load_dotenv()
160
+
161
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
162
+ csv_path = os.path.join(BASE_DIR, "dataset_final.csv")
163
+
164
+ df = pd.read_csv(csv_path, encoding="latin-1")
165
+
166
+ DB_USER = os.getenv("DB_USER")
167
+ DB_PASSWORD = os.getenv("DB_PASSWORD")
168
+ DB_HOST = os.getenv("DB_HOST")
169
+ DB_PORT = os.getenv("DB_PORT")
170
+ DB_NAME = os.getenv("DB_NAME")
171
+
172
+ DATABASE_URL = (f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}"f"@{DB_HOST}:{DB_PORT}/{DB_NAME}")
173
+
174
+ engine = create_engine(DATABASE_URL)
175
+
176
+ df.to_sql("employees_dataset", engine, if_exists="replace", index=False)
177
+ ```
178
+
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 Pydandic(`EmployeeFeatures`)
183
+ - Permet:
184
+ - l'audit des predictions
185
+ - l'analyse à posteriori
186
+ - la reproductibilité des résultats.
187
+ ```python
188
+ class Input(Base):
189
+ __tablename__ = "inputs"
190
+
191
+ id = Column(Integer, primary_key=True, index=True)
192
+ genre = Column(String)
193
+ statut_marital = Column(String)
194
+ departement = Column(String)
195
+ poste = Column(String)
196
+ ```
197
+
198
+ 3. `predictions - Résultats du modèle`
199
+ - Continet:
200
+ - le label de prédiction
201
+ - la probabilité associée
202
+ - Reliée à `inputs` via une clé étrangère
203
+ - Garantit une trçabilité complète.
204
+ ```python
205
+ class Predictions(Base):
206
+ __tablename__ = "predictions"
207
+ id = Column(Integer, primary_key=True, index=True)
208
+ input_id = Column(Integer, ForeignKey("inputs.id"))
209
+
210
+ prediction_label = Column(String)
211
+ prediction_proba = Column(Float)
212
+ model_version = Column(String)
213
+ ```
214
+
215
+ ### `Interaction API <> Base de données`
216
+ Lors d’un appel à l’endpoint `POST /predict`:
217
+ - les données utilisateur sont validées via **Pydantic**
218
+ - les entrées sont enregistrées dans la table **inputs**
219
+ - le modèle est exécuté
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
 
236
  ├── github/workflows
237
  │ ├── ci.yml # Description des évènement déclenchants des tests
238
  ├── app/ # Code applicatif principal
239
+ │ ├── database.py # Point de connexion à la base PostgreSQL
240
  │ ├── main.py # Point d’entrée de l’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/ # Elements du modèle
 
246
  │ ├── modele_final_xgb.joblib # Modèle final avec hyperparamètres
247
  │ ├── preprocesseur_fitted.joblib # Pipeline entrainé
248
  |
249
+ ├── scripts/ # Scripts bd (BD, données)
250
+ ├── create_tables.py # Créaton des tables définies dans model.py
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
258
+ ├── Dockerfile # Reproduction du dépôt
259
+ ├── poetry.lock # Nettoyage du dépôt
260
+ ├── pyproject.toml # Librairies dépendances ML
261
  ├── README.md # Présentation du projet
262
+ └── requirements.txt # Librairies dépendances API
263
  ```
poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
pyproject.toml CHANGED
@@ -21,11 +21,19 @@ 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
+ "python-dotenv ==1.2.1",
29
+ "psycopg2-binary ==2.9.11"
30
  ]
31
 
 
32
  [build-system]
33
  requires = ["poetry-core>=2.0.0,<3.0.0"]
34
  build-backend = "poetry.core.masonry.api"
35
 
36
+ [tool.poetry.group.dev.dependencies]
37
+ pytest = "9.0.2"
38
+
39
+
requirements.txt CHANGED
@@ -6,4 +6,11 @@ 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
16
+ python-dotenv ==1.2.1
scripts/create_tables.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from App.database import engine
2
+ from App.database import Base
3
+
4
+ Base.metadata.create_all(bind=engine)
5
+
6
+ print("Tables créées avec succès")
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,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = (f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}"f"@{DB_HOST}:{DB_PORT}/{DB_NAME}")
20
+
21
+ engine = create_engine(DATABASE_URL)
22
+
23
+ df.to_sql("employees_dataset", engine, if_exists="replace", index=False)
24
+
25
+ print("Dataset inséré dans PostgreSQL")