lcamara/deployMLModel
0
1"""Preprocessing pipeline — reproduces the P4 notebook's prepare_features + encode_features."""2 3import pandas as pd4 5 6def load_and_merge(sirh_path: str, eval_path: str, sondage_path: str) -> pd.DataFrame:7 """Load the 3 CSV files and merge them on id_employee."""8 sirh = pd.read_csv(sirh_path)9 evaluation = pd.read_csv(eval_path)10 sondage = pd.read_csv(sondage_path)11 12 # Clean evaluation13 evaluation["augementation_salaire_precedente"] = (14 evaluation["augementation_salaire_precedente"]15 .str.replace(" %", "", regex=False)16 .astype(float)17 )18 evaluation["id_employee"] = evaluation["eval_number"].apply(lambda x: int(x.replace("E_", "")))19 evaluation = evaluation.drop(columns=["eval_number"])20 21 # Clean sondage22 sondage = sondage.rename(columns={"code_sondage": "id_employee"})23 24 # Drop constant columns25 sirh = sirh.drop(columns=["nombre_heures_travailless"], errors="ignore")26 sondage = sondage.drop(27 columns=["ayant_enfants", "nombre_employee_sous_responsabilite"], errors="ignore"28 )29 30 # Merge31 df = sirh.merge(evaluation, on="id_employee").merge(sondage, on="id_employee")32 return df33 34 35def prepare_features(df: pd.DataFrame) -> tuple[pd.DataFrame, pd.Series]:36 """Feature engineering + drop columns. Returns (X, y)."""37 df = df.copy()38 39 # Target40 y = df["a_quitte_l_entreprise"].map({"Oui": 1, "Non": 0})41 42 # Engineered features43 df["ratio_anciennete_experience"] = df.apply(44 lambda r: (45 r["annees_dans_l_entreprise"] / r["annee_experience_totale"]46 if r["annee_experience_totale"] != 047 else 048 ),49 axis=1,50 )51 df["satisfaction_moyenne"] = df[52 [53 "satisfaction_employee_environnement",54 "satisfaction_employee_nature_travail",55 "satisfaction_employee_equipe",56 "satisfaction_employee_equilibre_pro_perso",57 ]58 ].mean(axis=1)59 df["ecart_evaluation"] = df["note_evaluation_actuelle"] - df["note_evaluation_precedente"]60 df["anciennete_sans_promotion"] = (61 df["annees_dans_l_entreprise"] - df["annees_depuis_la_derniere_promotion"]62 )63 64 # Drop columns65 cols_to_drop = [66 "a_quitte_l_entreprise",67 "id_employee",68 "niveau_hierarchique_poste",69 "note_evaluation_precedente",70 "annes_sous_responsable_actuel",71 "annees_dans_le_poste_actuel",72 ]73 X = df.drop(columns=cols_to_drop, errors="ignore")74 return X, y75 76 77def encode_features(X: pd.DataFrame) -> pd.DataFrame:78 """Encode categorical features — same logic as notebook."""79 X = X.copy()80 81 # Binary mappings82 X["genre"] = X["genre"].map({"F": 0, "M": 1})83 X["heure_supplementaires"] = X["heure_supplementaires"].map({"Non": 0, "Oui": 1})84 85 # Ordinal mapping86 X["frequence_deplacement"] = X["frequence_deplacement"].map(87 {"Aucun": 0, "Occasionnel": 1, "Frequent": 2}88 )89 90 # One-hot encoding91 X = pd.get_dummies(X, drop_first=True, dtype=int)92 return X93 94 95# Exact feature order the trained model expects (40 features)96EXPECTED_FEATURES = [97 "age",98 "genre",99 "revenu_mensuel",100 "nombre_experiences_precedentes",101 "annee_experience_totale",102 "annees_dans_l_entreprise",103 "satisfaction_employee_environnement",104 "satisfaction_employee_nature_travail",105 "satisfaction_employee_equipe",106 "satisfaction_employee_equilibre_pro_perso",107 "note_evaluation_actuelle",108 "heure_supplementaires",109 "augementation_salaire_precedente",110 "nombre_participation_pee",111 "nb_formations_suivies",112 "distance_domicile_travail",113 "niveau_education",114 "frequence_deplacement",115 "annees_depuis_la_derniere_promotion",116 "ratio_anciennete_experience",117 "satisfaction_moyenne",118 "ecart_evaluation",119 "anciennete_sans_promotion",120 "statut_marital_Divorcé(e)",121 "statut_marital_Marié(e)",122 "departement_Consulting",123 "departement_Ressources Humaines",124 "poste_Cadre Commercial",125 "poste_Consultant",126 "poste_Directeur Technique",127 "poste_Manager",128 "poste_Représentant Commercial",129 "poste_Ressources Humaines",130 "poste_Senior Manager",131 "poste_Tech Lead",132 "domaine_etude_Entrepreunariat",133 "domaine_etude_Infra & Cloud",134 "domaine_etude_Marketing",135 "domaine_etude_Ressources Humaines",136 "domaine_etude_Transformation Digitale",137]138 139 140def preprocess_single(data: dict) -> pd.DataFrame:141 """Preprocess a single prediction input (dict of raw features) into model-ready DataFrame.142 143 Accepts raw human-readable values and returns a 1-row DataFrame with all 40 encoded features.144 """145 row = pd.DataFrame([data])146 147 # Binary + ordinal encoding148 row["genre"] = row["genre"].map({"F": 0, "M": 1})149 row["heure_supplementaires"] = row["heure_supplementaires"].map({"Non": 0, "Oui": 1})150 row["frequence_deplacement"] = row["frequence_deplacement"].map(151 {"Aucun": 0, "Occasionnel": 1, "Frequent": 2}152 )153 154 # Engineered features155 row["ratio_anciennete_experience"] = row.apply(156 lambda r: (157 r["annees_dans_l_entreprise"] / r["annee_experience_totale"]158 if r["annee_experience_totale"] != 0159 else 0160 ),161 axis=1,162 )163 row["satisfaction_moyenne"] = row[164 [165 "satisfaction_employee_environnement",166 "satisfaction_employee_nature_travail",167 "satisfaction_employee_equipe",168 "satisfaction_employee_equilibre_pro_perso",169 ]170 ].mean(axis=1)171 row["ecart_evaluation"] = row["note_evaluation_actuelle"] - row["note_evaluation_precedente"]172 row["anciennete_sans_promotion"] = (173 row["annees_dans_l_entreprise"] - row["annees_depuis_la_derniere_promotion"]174 )175 176 # One-hot columns — create all expected columns with 0, then set relevant ones to 1177 for col in EXPECTED_FEATURES:178 if col not in row.columns:179 row[col] = 0180 181 # Set one-hot flags from categorical inputs182 categorical_mappings = {183 "statut_marital": "statut_marital_",184 "departement": "departement_",185 "poste": "poste_",186 "domaine_etude": "domaine_etude_",187 }188 for cat_col, prefix in categorical_mappings.items():189 if cat_col in row.columns:190 value = row[cat_col].iloc[0]191 dummy_col = f"{prefix}{value}"192 if dummy_col in EXPECTED_FEATURES:193 row[dummy_col] = 1194 row = row.drop(columns=[cat_col])195 196 # Drop columns not needed for prediction197 cols_to_drop = [198 "id_employee",199 "niveau_hierarchique_poste",200 "note_evaluation_precedente",201 "annes_sous_responsable_actuel",202 "annees_dans_le_poste_actuel",203 ]204 row = row.drop(columns=[c for c in cols_to_drop if c in row.columns])205 206 # Reorder to match training order, fill any missing with 0207 row = row.reindex(columns=EXPECTED_FEATURES, fill_value=0)208 return row209 