joannes59 commited on
Commit
5154df5
·
1 Parent(s): 637dba4

Add test shape of features

Browse files
app/db_log.py CHANGED
@@ -13,7 +13,7 @@ import os
13
  # createdb -O openclassrooms openclassrooms
14
  # EXPORT DATABASE_URL="postgresql+psycopg2://openclassrooms:openclassrooms@localhost:5432/openclassrooms"
15
 
16
- # Récupératin de la variable d'envirronnement de connection à la base de donnée
17
  DATABASE_URL = os.getenv("DATABASE_URL")
18
 
19
 
 
13
  # createdb -O openclassrooms openclassrooms
14
  # EXPORT DATABASE_URL="postgresql+psycopg2://openclassrooms:openclassrooms@localhost:5432/openclassrooms"
15
 
16
+ # Récupération de la variable d'envirronnement de connection à la base de donnée
17
  DATABASE_URL = os.getenv("DATABASE_URL")
18
 
19
 
app/main.py CHANGED
@@ -33,9 +33,10 @@ def home():
33
  @app.post("/predict")
34
  def predict(data: InputData):
35
  """
 
36
 
37
- return : {"prediction": integer,
38
  "probabilite": float
39
- }
40
  """
41
  return prediction.predict(data)
 
33
  @app.post("/predict")
34
  def predict(data: InputData):
35
  """
36
+ input: voir détail dans le fichier schemas.py
37
 
38
+ return: {"prediction": integer,
39
  "probabilite": float
40
+ }
41
  """
42
  return prediction.predict(data)
app/prediction.py CHANGED
@@ -31,7 +31,7 @@ def predict(data):
31
  y_proba = model.predict_proba(X)
32
 
33
  # format reponse
34
- response = {"prediction": int(y), "probabilite": float(y_proba[0][1])}
35
 
36
  # log the response in postgres db
37
  if db_input_id:
 
31
  y_proba = model.predict_proba(X)
32
 
33
  # format reponse
34
+ response = {"prediction": int(y[0]), "probabilite": float(y_proba[0][1])}
35
 
36
  # log the response in postgres db
37
  if db_input_id:
app/schemas.py CHANGED
@@ -1,48 +1,169 @@
1
- from pydantic import BaseModel, Field
2
  from typing import Literal
 
3
 
4
 
5
  class InputData(BaseModel):
6
- """ Définition des entrées du modèle."""
7
-
8
- id_employee: int = Field(..., description="Identifiant unique de l'employé")
 
 
 
 
9
 
10
- age: int = Field(..., ge=16, le=70)
 
 
 
 
 
11
 
12
- statut_marital: Literal['Célibataire', 'Divorcé(e)', 'Marié(e)']
 
 
 
13
 
14
  poste: Literal[
15
- 'Assistant de Direction', 'Cadre Commercial', 'Consultant', 'Directeur Technique',
16
- 'Manager', 'Représentant Commercial', 'Ressources Humaines', 'Senior Manager', 'Tech Lead'
17
- ]
18
-
19
- nombre_experiences_precedentes: int = Field(..., ge=0, le=20)
20
- annee_experience_totale: int = Field(..., ge=0, le=65)
21
- annees_dans_le_poste_actuel: int = Field(..., ge=0, le=65)
22
-
23
- satisfaction_employee_environnement: int = Field(..., ge=1, le=4)
24
- note_evaluation_precedente: int = Field(..., ge=1, le=4)
25
- niveau_hierarchique_poste: int = Field(..., ge=1, le=5)
26
- satisfaction_employee_nature_travail: int = Field(..., ge=1, le=4)
27
- satisfaction_employee_equipe: int = Field(..., ge=1, le=4)
28
- satisfaction_employee_equilibre_pro_perso: int = Field(..., ge=1, le=4)
29
-
30
- heure_supplementaires: Literal['Non', 'Oui']
31
-
32
- augmentation_salaire: int = Field(..., ge=0)
33
- nombre_participation_pee: int = Field(..., ge=0)
34
- nb_formations_suivies: int = Field(..., ge=0)
35
- distance_domicile_travail: int = Field(..., ge=0)
36
-
37
- niveau_education: int = Field(..., ge=1, le=5)
38
-
39
- domaine_etude: Literal['Autre', 'Entrepreunariat', 'Infra & Cloud', 'Marketing',
40
- 'Ressources Humaines', 'Transformation Digitale']
41
-
42
- frequence_deplacement: Literal['Aucun', 'Frequent', 'Occasionnel']
43
-
44
- annees_depuis_la_derniere_promotion: int = Field(..., ge=0, le=25)
45
- annes_sous_responsable_actuel: int = Field(..., ge=0, le=25)
46
-
47
-
48
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from typing import Literal
2
+ from pydantic import BaseModel, Field
3
 
4
 
5
  class InputData(BaseModel):
6
+ """Définition des données d'entrée décrivant
7
+ le profil professionnel et personnel d'un employé."""
8
+
9
+ id_employee: int = Field(
10
+ ...,
11
+ description="Identifiant unique de l'employé dans le système d'information RH"
12
+ )
13
 
14
+ age: int = Field(
15
+ ...,
16
+ ge=16,
17
+ le=70,
18
+ description="Âge de l'employé exprimé en années"
19
+ )
20
 
21
+ statut_marital: Literal['Célibataire', 'Divorcé(e)', 'Marié(e)'] = Field(
22
+ ...,
23
+ description="Situation matrimoniale actuelle de l'employé"
24
+ )
25
 
26
  poste: Literal[
27
+ 'Assistant de Direction',
28
+ 'Cadre Commercial',
29
+ 'Consultant',
30
+ 'Directeur Technique',
31
+ 'Manager',
32
+ 'Représentant Commercial',
33
+ 'Ressources Humaines',
34
+ 'Senior Manager',
35
+ 'Tech Lead'
36
+ ] = Field(
37
+ ...,
38
+ description="Intitulé du poste occupé actuellement par l'employé"
39
+ )
40
+
41
+ nombre_experiences_precedentes: int = Field(
42
+ ...,
43
+ ge=0,
44
+ le=20,
45
+ description="Nombre total d'expériences professionnelles précédentes avant le poste actuel"
46
+ )
47
+
48
+ annee_experience_totale: int = Field(
49
+ ...,
50
+ ge=0,
51
+ le=65,
52
+ description="Nombre total d'années d'expérience professionnelle cumulée"
53
+ )
54
+
55
+ annees_dans_le_poste_actuel: int = Field(
56
+ ...,
57
+ ge=0,
58
+ le=65,
59
+ description="Ancienneté de l'employé dans son poste actuel, exprimée en années"
60
+ )
61
+
62
+ satisfaction_employee_environnement: int = Field(
63
+ ...,
64
+ ge=1,
65
+ le=4,
66
+ description="Niveau de satisfaction de l'employé concernant son environnement de travail (1 = très insatisfait, 4 = très satisfait)"
67
+ )
68
+
69
+ note_evaluation_precedente: int = Field(
70
+ ...,
71
+ ge=1,
72
+ le=4,
73
+ description="Note obtenue lors de la dernière évaluation annuelle de performance"
74
+ )
75
+
76
+ niveau_hierarchique_poste: int = Field(
77
+ ...,
78
+ ge=1,
79
+ le=5,
80
+ description="Niveau hiérarchique associé au poste occupé (1 = niveau opérationnel, 5 = direction)"
81
+ )
82
+
83
+ satisfaction_employee_nature_travail: int = Field(
84
+ ...,
85
+ ge=1,
86
+ le=4,
87
+ description="Niveau de satisfaction de l'employé concernant la nature de ses missions"
88
+ )
89
+
90
+ satisfaction_employee_equipe: int = Field(
91
+ ...,
92
+ ge=1,
93
+ le=4,
94
+ description="Niveau de satisfaction de l'employé vis-à-vis de son équipe de travail"
95
+ )
96
+
97
+ satisfaction_employee_equilibre_pro_perso: int = Field(
98
+ ...,
99
+ ge=1,
100
+ le=4,
101
+ description="Niveau de satisfaction concernant l'équilibre entre vie professionnelle et vie personnelle"
102
+ )
103
+
104
+ heure_supplementaires: Literal['Non', 'Oui'] = Field(
105
+ ...,
106
+ description="Indique si l'employé effectue régulièrement des heures supplémentaires"
107
+ )
108
+
109
+ augmentation_salaire: int = Field(
110
+ ...,
111
+ ge=0,
112
+ description="Pourcentage d'augmentation du salaire de l'employé"
113
+ )
114
+
115
+ nombre_participation_pee: int = Field(
116
+ ...,
117
+ ge=0,
118
+ description="Nombre de participations de l'employé à un plan d'épargne entreprise (PEE)"
119
+ )
120
+
121
+ nb_formations_suivies: int = Field(
122
+ ...,
123
+ ge=0,
124
+ description="Nombre total de formations professionnelles suivies par l'employé"
125
+ )
126
+
127
+ distance_domicile_travail: int = Field(
128
+ ...,
129
+ ge=0,
130
+ description="Distance entre le domicile de l'employé et son lieu de travail (en kilomètres)"
131
+ )
132
+
133
+ niveau_education: int = Field(
134
+ ...,
135
+ ge=1,
136
+ le=5,
137
+ description="Niveau d'éducation de l'employé (1 = niveau le plus bas, 5 = niveau le plus élevé)"
138
+ )
139
+
140
+ domaine_etude: Literal[
141
+ 'Autre',
142
+ 'Entrepreunariat',
143
+ 'Infra & Cloud',
144
+ 'Marketing',
145
+ 'Ressources Humaines',
146
+ 'Transformation Digitale'
147
+ ] = Field(
148
+ ...,
149
+ description="Domaine principal d'activités de l'employé"
150
+ )
151
+
152
+ frequence_deplacement: Literal['Aucun', 'Frequent', 'Occasionnel'] = Field(
153
+ ...,
154
+ description="Fréquence des déplacements professionnels liés au poste"
155
+ )
156
+
157
+ annees_depuis_la_derniere_promotion: int = Field(
158
+ ...,
159
+ ge=0,
160
+ le=25,
161
+ description="Nombre d'années écoulées depuis la dernière promotion de l'employé"
162
+ )
163
+
164
+ annes_sous_responsable_actuel: int = Field(
165
+ ...,
166
+ ge=0,
167
+ le=25,
168
+ description="Nombre d'années pendant lesquelles l'employé travaille sous son responsable hiérarchique actuel"
169
+ )
requirements.txt CHANGED
@@ -1,10 +1,14 @@
1
  annotated-doc==0.0.4
2
  annotated-types==0.7.0
3
  anyio==4.12.0
 
4
  click==8.3.1
 
5
  fastapi==0.124.0
6
  greenlet==3.3.0
7
  h11==0.16.0
 
 
8
  idna==3.11
9
  iniconfig==2.3.0
10
  joblib==1.5.2
@@ -12,11 +16,13 @@ numpy==2.3.5
12
  packaging==25.0
13
  pandas==2.3.3
14
  pluggy==1.6.0
 
15
  psycopg2-binary==2.9.11
16
  pydantic==2.12.5
17
  pydantic_core==2.41.5
18
  Pygments==2.19.2
19
  pytest==9.0.2
 
20
  python-dateutil==2.9.0.post0
21
  pytz==2025.2
22
  scikit-learn==1.7.2
 
1
  annotated-doc==0.0.4
2
  annotated-types==0.7.0
3
  anyio==4.12.0
4
+ certifi==2025.11.12
5
  click==8.3.1
6
+ coverage==7.13.1
7
  fastapi==0.124.0
8
  greenlet==3.3.0
9
  h11==0.16.0
10
+ httpcore==1.0.9
11
+ httpx==0.28.1
12
  idna==3.11
13
  iniconfig==2.3.0
14
  joblib==1.5.2
 
16
  packaging==25.0
17
  pandas==2.3.3
18
  pluggy==1.6.0
19
+ psycopg2==2.9.11
20
  psycopg2-binary==2.9.11
21
  pydantic==2.12.5
22
  pydantic_core==2.41.5
23
  Pygments==2.19.2
24
  pytest==9.0.2
25
+ pytest-cov==7.0.0
26
  python-dateutil==2.9.0.post0
27
  pytz==2025.2
28
  scikit-learn==1.7.2
tests/ko_test_api.py DELETED
@@ -1,15 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from fastapi.testclient import TestClient
4
- from app import main
5
-
6
- client = TestClient(main.app)
7
-
8
- def test_predict_endpoint():
9
- response = client.post(
10
- "/"
11
- )
12
-
13
- assert response.status_code == 200
14
- #assert "prediction" in response.json()
15
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_main.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from fastapi.testclient import TestClient
3
+ from app.main import app
4
+
5
+
6
+ @pytest.fixture
7
+ def client():
8
+ return TestClient(app)
9
+
10
+
11
+ def test_home_endpoint(client, monkeypatch):
12
+ # Mock de prediction.home
13
+ def mock_home():
14
+ return {"message": "API opérationnelle"}
15
+
16
+ monkeypatch.setattr(
17
+ "app.main.prediction.home",
18
+ mock_home
19
+ )
20
+
21
+ response = client.get("/")
22
+
23
+ assert response.status_code == 200
24
+ assert response.json() == {"message": "API opérationnelle"}
tests/test_prediction.py CHANGED
@@ -13,7 +13,6 @@ import pytest
13
  import json
14
 
15
  model_files = ['model/model.joblib', 'model/onehotencoder.joblib', 'model/scaler.joblib']
16
- model = utils.load_model("model.joblib")
17
 
18
 
19
  def test_load_model():
 
13
  import json
14
 
15
  model_files = ['model/model.joblib', 'model/onehotencoder.joblib', 'model/scaler.joblib']
 
16
 
17
 
18
  def test_load_model():
tests/test_preprocess.py CHANGED
@@ -7,16 +7,17 @@ from app import preprocess
7
  import pytest
8
  import json
9
 
10
- encoder = utils.load_model("onehotencoder.joblib")
11
- scaler = utils.load_model("scaler.joblib")
12
 
13
  def test_load_encoder():
14
  """ check the configuration model load """
15
  encoder = utils.load_model("onehotencoder.joblib")
16
  scaler = utils.load_model("scaler.joblib")
 
17
 
18
  assert encoder is not None
19
  assert scaler is not None
 
 
20
 
21
  @pytest.fixture
22
  def data_example():
@@ -30,9 +31,15 @@ def data_example():
30
 
31
  def test_encode(data_example):
32
  """ check the response of encode data """
 
33
  encoder = utils.load_model("onehotencoder.joblib")
34
  scaler = utils.load_model("scaler.joblib")
35
- if encoder and scaler:
 
 
36
  x_exemple_scaled = preprocess.encode(data_example)
37
-
 
 
 
38
 
 
7
  import pytest
8
  import json
9
 
 
 
10
 
11
  def test_load_encoder():
12
  """ check the configuration model load """
13
  encoder = utils.load_model("onehotencoder.joblib")
14
  scaler = utils.load_model("scaler.joblib")
15
+ model = utils.load_model("model.joblib")
16
 
17
  assert encoder is not None
18
  assert scaler is not None
19
+ assert model is not None
20
+
21
 
22
  @pytest.fixture
23
  def data_example():
 
31
 
32
  def test_encode(data_example):
33
  """ check the response of encode data """
34
+
35
  encoder = utils.load_model("onehotencoder.joblib")
36
  scaler = utils.load_model("scaler.joblib")
37
+ model = utils.load_model("model.joblib")
38
+
39
+ if encoder and scaler and model:
40
  x_exemple_scaled = preprocess.encode(data_example)
41
+
42
+ if x_exemple_scaled.shape[1] != model.n_features_in_:
43
+ raise ValueError("Incorrect number of features in preprocess")
44
+
45