425 lines
17 KiB
Python
425 lines
17 KiB
Python
"""
|
|
Ce module contient les Utilitaires de scoring
|
|
"""
|
|
# import des pré requis
|
|
import json
|
|
from datetime import date
|
|
from django.db.models import Q
|
|
import time
|
|
import pandas as pd
|
|
import numpy as np
|
|
from django.utils import timezone
|
|
from .models import Administre, Domaine, FMOB, Fonction, FormationEmploi, Garnison, Grade, Poste, Notation, Postes_Pams, \
|
|
PreferencesListe, Marque, MarquesGroupe, Filiere, Competence, StatutPamChoices, AvisPosteChoices as AvisPoste, \
|
|
Administres_Pams
|
|
from .utils.logging import get_logger
|
|
import itertools
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
# calcul de la date
|
|
today = date.today()
|
|
|
|
|
|
# Fonction de lancement du preprocessing et d'encoding
|
|
def encoding(a_fil, a_nf, a_comp, p_fil, p_nf, p_comp):
|
|
"""
|
|
Retourne l'encodage de l' "eip + département" par rapport à un poste donné
|
|
|
|
:type a_fil: numpy list
|
|
:param a_fil: filière de l'eip actuel militaire
|
|
|
|
:type a_nf: numpy list
|
|
:param a_nf: nf de l'eip actuel militaire
|
|
|
|
:type a_comp: ?
|
|
:param a_comp: ?
|
|
|
|
:type p_fil: numpy list
|
|
:param p_fil: filière du poste vacant
|
|
|
|
:type p_nf: numpy list
|
|
:param p_nf: nf du poste vacant
|
|
|
|
:type p_comp: ?
|
|
:param p_comp: ?
|
|
|
|
:return: - **encoding** (*numpy list*): encoding de l'eip actuel du militaire et du département où il souhaite travailler par rapport à un poste vacant
|
|
"""
|
|
b_fil = (a_fil == p_fil).astype(int)
|
|
b_nf = (a_nf == p_nf).astype(int)
|
|
|
|
b_comp = [len(list(filter(i.__contains__, j)))/len(j) if j!=[None] else 0 for i,j in zip(a_comp,p_comp)]
|
|
logger.debug('Calcul des compétences')
|
|
|
|
encoding = (b_fil, b_nf, b_comp)
|
|
return encoding
|
|
|
|
|
|
# fonction de calcul du scores
|
|
def notePonderee(a_fil, a_nf, a_comp, p_fil, p_nf, p_comp, p_poids_fil, p_poids_nf, p_poids_comp):
|
|
"""
|
|
Retourne la note d'une combinaison eip+département par rapport à un poste donné
|
|
|
|
:type a_dom: numpy list
|
|
:param a_dom: domaine de l'eip actuel du militaire
|
|
|
|
:type a_fil: numpy list
|
|
:param a_fil: filière de l'eip actuel militaire
|
|
|
|
:type a_dep: numpy list
|
|
:param a_dep: département où souhaite travailler le militaire
|
|
|
|
:type a_nf: numpy list
|
|
:param a_nf: nf de l'eip actuel militaire
|
|
|
|
:type p_dom: numpy list
|
|
:param p_dom: domaine du poste vacant
|
|
|
|
:type p_fil: numpy list
|
|
:param p_fil: filière du poste vacant
|
|
|
|
:type p_dep: numpy list
|
|
:param p_dep: département du poste vacant
|
|
|
|
:type p_nf: numpy list
|
|
:param p_nf: nf du poste vacant
|
|
|
|
:type p_poids_dom: numpy list
|
|
:param p_poids_dom: poids du domaine
|
|
|
|
:type p_poids_fil: numpy list
|
|
:param p_poids_fil: poids de la filière
|
|
|
|
:type p_poids_dep: numpy list
|
|
:param p_poids_dep: poids du département
|
|
|
|
:type p_poids_nf: numpy list
|
|
:param p_poids_nf: poids du nf
|
|
|
|
|
|
:return: - **note** (*float*): note de l'eip par rapport au poste à pourvoir
|
|
|
|
"""
|
|
|
|
e = encoding(a_fil, a_nf, a_comp, p_fil, p_nf, p_comp)
|
|
note = p_poids_fil * e[0] + p_poids_nf * e[1] + p_poids_comp * e[2]
|
|
return note
|
|
|
|
|
|
def notations(pam_id, sv_id):
|
|
"""
|
|
Remplit la colonne note de la table Notations
|
|
|
|
:type sv_id: char
|
|
:param sv_id: sous vivier étudié
|
|
|
|
|
|
:return: - **notations** (*dataframe*): table des notations avec les notes complétées
|
|
|
|
|
|
"""
|
|
# Sélection des postes sur lesquels aucune décision n'est prise
|
|
logger.debug('start scoring')
|
|
|
|
colonnes_pos = ('id','poste_id', 'poste__competences', 'poste__p_filiere_id', 'poste__p_nf', 'p_avis_pam')
|
|
colonnes_pos_name = ('poste_pam_id','poste_id', 'competences', 'p_filiere_id', 'p_nf', 'p_avis_pam')
|
|
statut_poste =[AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4]
|
|
|
|
postes = Postes_Pams.objects.values_list(*colonnes_pos).filter(Q(p_pam_id=pam_id)
|
|
& Q(poste__sous_viviers=sv_id)
|
|
& Q(decisions__de_decision__isnull=True)
|
|
& Q(p_avis_pam__in=statut_poste))
|
|
|
|
if not postes.exists:
|
|
logger.debug(f"Pas de poste avec le bon statut {statut_poste} dans le sous vivier {sv_id} et le pam {pam_id}.")
|
|
raise Exception("Aucun poste avec le bon statut.")
|
|
|
|
pos = pd.DataFrame.from_records(postes, columns=colonnes_pos_name)
|
|
pos=pos.groupby(['poste_pam_id','poste_id','p_filiere_id', 'p_nf', 'p_avis_pam'])['competences'].apply(list).reset_index(name='competences')
|
|
|
|
|
|
# Remplire les poids des postes
|
|
p_poids = ['p_poids_filiere', 'p_poids_nf', 'p_poids_competences']
|
|
try:
|
|
pos['p_poids'] = pos.apply(lambda x: Poste.objects.get(p_id=x['poste_id']).p_poids_filiere_nf_competences, axis=1)
|
|
pos[p_poids] = pd.DataFrame(pos['p_poids'].tolist(), index=pos.index)
|
|
|
|
except Exception as e:
|
|
raise Exception("Tous les postes sont déjà affectés à un adminstré : Veuillez en sélectionner de nouveau")
|
|
|
|
# Sélection des administrés dont le statut PAM est "à muter" et sur lesquels aucune décision n'est prise
|
|
colonnes_adm = ('id','administre__a_id_sap', 'administre__a_filiere_futur_id',
|
|
'administre__a_nf_futur', 'administre__a_liste_id_competences')
|
|
|
|
colonnes_adm_name = ('administre_pam_id', 'a_id_sap', 'a_filiere_futur_id',
|
|
'a_nf_futur', 'a_liste_id_competences')
|
|
|
|
statut = [StatutPamChoices.A_MUTER,StatutPamChoices.A_ETUDIER]
|
|
|
|
administres = Administres_Pams.objects.filter(pam_id=pam_id, administre__sous_vivier_id=sv_id, a_statut_pam_annee__in=statut,
|
|
decision__de_decision__isnull=True).values_list(*colonnes_adm)
|
|
|
|
if not administres.exists:
|
|
logger.debug(f"Pas de poste avec le bon statut {statut} dans le sous vivier {sv_id} et le pam {pam_id}.")
|
|
raise Exception(f"Aucun administrés avec le bon statut.")
|
|
|
|
adm = pd.DataFrame.from_records(administres, columns=colonnes_adm_name)
|
|
|
|
if len(adm) == 0:
|
|
raise Exception("Arret du scoring 0 administrés dont le statut PAM est à muter et sur lesquels aucune décision n'est prise")
|
|
|
|
logger.debug('Nb admin traités : %s', len(adm))
|
|
logger.debug('Nb groupe_poste traités : %s', len(pos))
|
|
|
|
adm=adm.groupby(['administre_pam_id','a_id_sap','a_filiere_futur_id','a_nf_futur'])['a_liste_id_competences'].apply(list).reset_index(name='a_liste_id_competences')
|
|
|
|
|
|
# Ajout de la colonne Key avec la valeur 1 aux deux bases de données, pour pouvoir effectuer la jointure croisée.
|
|
adm['key'] = 1
|
|
pos['key'] = 1
|
|
|
|
# Jointure croisée et suppression de la colonne clé
|
|
result = pd.merge(pos, adm, on='key').drop("key", 1)
|
|
|
|
# Ajout de la date d'exécution
|
|
now = timezone.now()
|
|
result['no_date_execution'] = now
|
|
# Calcul de la notePondéré
|
|
result['no_score_administre'] = notePonderee(result['a_filiere_futur_id'].to_numpy(),
|
|
result['a_nf_futur'].to_numpy(),
|
|
result['a_liste_id_competences'].to_numpy(),
|
|
result['p_filiere_id'].to_numpy(), result['p_nf'].to_numpy(),
|
|
result['competences'].to_numpy(),
|
|
result['p_poids_filiere'].to_numpy(), result['p_poids_nf'].to_numpy(),
|
|
result['p_poids_competences'].to_numpy())
|
|
|
|
|
|
# Suppression de toutes les colonnes inutiles
|
|
result.drop(result.columns.difference(['poste_pam_id','administre_pam_id','poste_id','a_id_sap','no_score_administre','no_date_execution']), 1, inplace=True)
|
|
# Création de la colonne no_flag_cple_ideal
|
|
result['no_flag_cple_ideal'] = False
|
|
# Changer le nom des colonnes
|
|
result = result.rename(columns={'a_id_sap': 'administre_id'})
|
|
result = result.astype({'administre_id': 'object', 'no_flag_cple_ideal': 'object'})
|
|
result['pam_id'] = pam_id
|
|
|
|
logger.debug('end scoring')
|
|
return result
|
|
|
|
def notations_liste(sv_id, pam_id, l_a_id, l_p_id):
|
|
"""
|
|
Remplit la colonne note de la table Notations
|
|
|
|
:type sv_id: char
|
|
:param sv_id: sous vivier étudié
|
|
|
|
|
|
:return: - **notations** (*dataframe*): table des notations avec les notes complétées
|
|
|
|
|
|
"""
|
|
# Sélection des postes sur lesquels aucune décision n'est prise
|
|
logger.debug('start scoring')
|
|
|
|
colonnes_pos = ('id','poste_id', 'poste__competences', 'poste__p_filiere_id', 'poste__p_nf', 'p_avis_pam')
|
|
colonnes_pos_name = ('poste_pam_id','poste_id', 'competences', 'p_filiere_id', 'p_nf', 'p_avis_pam')
|
|
statut_poste =[AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4]
|
|
|
|
postes = Postes_Pams.objects.values_list(*colonnes_pos).filter(Q(p_pam_id=pam_id)
|
|
& Q(poste__sous_viviers=sv_id)
|
|
& Q(decisions__de_decision__isnull=True)
|
|
& Q(p_avis_pam__in=statut_poste)
|
|
& Q(poste_id__in=l_p_id))
|
|
|
|
if not postes.exists:
|
|
logger.debug(f"Pas de poste avec le bon statut {statut_poste} dans le sous vivier {sv_id} et le pam {pam_id}.")
|
|
raise Exception("Aucun poste avec le bon statut.")
|
|
|
|
pos = pd.DataFrame.from_records(postes, columns=colonnes_pos_name)
|
|
pos=pos.groupby(['poste_pam_id','poste_id','p_filiere_id', 'p_nf', 'p_avis_pam'])['competences'].apply(list).reset_index(name='competences')
|
|
|
|
|
|
# Remplire les poids des postes
|
|
p_poids = ['p_poids_filiere', 'p_poids_nf', 'p_poids_competences']
|
|
try:
|
|
pos['p_poids'] = pos.apply(lambda x: Poste.objects.get(p_id=x['poste_id']).p_poids_filiere_nf_competences, axis=1)
|
|
pos[p_poids] = pd.DataFrame(pos['p_poids'].tolist(), index=pos.index)
|
|
|
|
except Exception as e:
|
|
raise Exception("Tous les postes sont déjà affectés à un adminstré : Veuillez en sélectionner de nouveau")
|
|
|
|
# Sélection des administrés dont le statut PAM est "à muter" et sur lesquels aucune décision n'est prise
|
|
colonnes_adm = ('id','administre__a_id_sap', 'administre__a_filiere_futur_id',
|
|
'administre__a_nf_futur', 'administre__a_liste_id_competences')
|
|
|
|
colonnes_adm_name = ('administre_pam_id', 'a_id_sap', 'a_filiere_futur_id',
|
|
'a_nf_futur', 'a_liste_id_competences')
|
|
|
|
statut = [StatutPamChoices.A_MUTER,StatutPamChoices.A_ETUDIER]
|
|
|
|
administres = Administres_Pams.objects.filter(pam_id=pam_id, administre__sous_vivier_id=sv_id, a_statut_pam_annee__in=statut,administre_id__in=l_a_id,
|
|
decision__de_decision__isnull=True).values_list(*colonnes_adm)
|
|
|
|
if not administres.exists:
|
|
logger.debug(f"Pas de poste avec le bon statut {statut} dans le sous vivier {sv_id} et le pam {pam_id}.")
|
|
raise Exception(f"Aucun administrés avec le bon statut.")
|
|
|
|
adm = pd.DataFrame.from_records(administres, columns=colonnes_adm_name)
|
|
|
|
if len(adm) == 0:
|
|
raise Exception("Arret du scoring 0 administrés dont le statut PAM est à muter et sur lesquels aucune décision n'est prise")
|
|
|
|
logger.debug('Nb admin traités : %s', len(adm))
|
|
logger.debug('Nb groupe_poste traités : %s', len(pos))
|
|
|
|
adm=adm.groupby(['administre_pam_id','a_id_sap','a_filiere_futur_id','a_nf_futur'])['a_liste_id_competences'].apply(list).reset_index(name='a_liste_id_competences')
|
|
|
|
|
|
# Ajout de la colonne Key avec la valeur 1 aux deux bases de données, pour pouvoir effectuer la jointure croisée.
|
|
adm['key'] = 1
|
|
pos['key'] = 1
|
|
|
|
# Jointure croisée et suppression de la colonne clé
|
|
result = pd.merge(pos, adm, on='key').drop("key", 1)
|
|
|
|
# Ajout de la date d'exécution
|
|
now = timezone.now()
|
|
result['no_date_execution'] = now
|
|
# Calcul de la notePondéré
|
|
result['no_score_administre'] = notePonderee(result['a_filiere_futur_id'].to_numpy(),
|
|
result['a_nf_futur'].to_numpy(),
|
|
result['a_liste_id_competences'].to_numpy(),
|
|
result['p_filiere_id'].to_numpy(), result['p_nf'].to_numpy(),
|
|
result['competences'].to_numpy(),
|
|
result['p_poids_filiere'].to_numpy(), result['p_poids_nf'].to_numpy(),
|
|
result['p_poids_competences'].to_numpy())
|
|
|
|
|
|
# Suppression de toutes les colonnes inutiles
|
|
result.drop(result.columns.difference(['poste_pam_id','administre_pam_id','poste_id','a_id_sap','no_score_administre','no_date_execution']), 1, inplace=True)
|
|
# Création de la colonne no_flag_cple_ideal
|
|
result['no_flag_cple_ideal'] = False
|
|
# Changer le nom des colonnes
|
|
result = result.rename(columns={'a_id_sap': 'administre_id'})
|
|
result = result.astype({'administre_id': 'object', 'no_flag_cple_ideal': 'object'})
|
|
result['pam_id'] = pam_id
|
|
|
|
logger.debug('end scoring')
|
|
return result
|
|
|
|
|
|
|
|
# TODO:Revoir les entrées de la fonction notation_test
|
|
# fonction notation pour le test
|
|
def notations_test():
|
|
"""
|
|
Remplit la colonne note de la table Notations
|
|
|
|
|
|
:return: - **notations** (*dataframe*): table des notations avec les notes complétées
|
|
|
|
|
|
"""
|
|
|
|
# test dataframes
|
|
p = pd.DataFrame(
|
|
columns=["p_id", "fonction_id", "sous_vivier_id", "formation_emploi_id", "p_liste_id_marques", "p_eip",
|
|
"p_date_fin", "p_flag_pam", "p_notes_gestionnaire", "p_notes_partagees", "p_statut_pam",
|
|
"p_poids_domaine",
|
|
"p_poids_filiere", "p_poids_nf", "p_poids_garnison", "p_gar"])
|
|
p['p_id'] = [1, 2, 3]
|
|
p['fonction_id'] = [2, 4, 5]
|
|
p['sous_vivier_id'] = [6, 7, 8]
|
|
p['formation_emploi_id'] = [9, 5, 10]
|
|
p['p_liste_id_marques'] = ["1_P1,1_P2,1_P4", "1_P1,2_C,1_P4", "2_T,1_P2,1_P4"]
|
|
p['p_eip'] = ['MAIGMA3a', 'ADMAES3b', 'SICEDR3a']
|
|
p['p_date_fin'] = ['', '', '']
|
|
p['p_flag_pam'] = [1, 1, 0]
|
|
p['p_gar'] = [13, 14, 18]
|
|
p['p_notes_gestionnaire'] = ['', '', '']
|
|
p['p_notes_partagees'] = ['', '', '']
|
|
p['p_statut_pam'] = ['P4', 'P4', 'P4']
|
|
p['p_poids_domaine'] = [0.25, 0.25, 0.25]
|
|
p['p_poids_filiere'] = [0.25, 0.25, 0.25]
|
|
p['p_poids_nf'] = [0.25, 0.25, 0.25]
|
|
p['p_poids_garnison'] = [0.25, 0.25, 0.25]
|
|
|
|
a_bis = {'a_id_sap': [9984, 56754, 78905],
|
|
'formation_emploi_id': [2, 4, 6],
|
|
'fonction_id': [3, 78, 90],
|
|
'sous_vivier_id': [5, 8, 98],
|
|
'grade_id': [4, 9, 5],
|
|
'a_liste_id_marques': ['[,,,]', '[,,,]', '[,,,]'],
|
|
'a_nom': ['Nom_1', 'Nom_2', 'Nom_3'],
|
|
'a_prenom': ['Prenom_1', 'Prenom_2', 'Prenom_3'],
|
|
'a_sexe': ['M', 'F', 'M'],
|
|
'a_id_def': [4, 5, 6],
|
|
'a_eip': ['MAIGMA3a', 'ADMAES3b', 'SICEDR3a'],
|
|
'a_domaine': ['MAI', 'ADM', 'SIC'],
|
|
'a_filiere': ['GMA', 'AES', 'EDR'],
|
|
'a_nf': ['3a', '3b', '3a'],
|
|
'a_domaine_gestion': ['', '', ''],
|
|
'a_date_entree_service': ['', '', ''],
|
|
'a_gar': [13, 14, 18],
|
|
'a_arme': ['', '', ''],
|
|
'a_rg_origine_recrutement': ['', '', ''],
|
|
'a_date_naissance': ['', '', ''],
|
|
'a_diplome_HL': ['', '', ''],
|
|
'a_dernier_diplome': ['', '', ''],
|
|
'a_credo_fe': ['', '', ''],
|
|
'a_date_arrivee_fe': ['', '', ''],
|
|
'a_date_pos_statuaire': ['', '', ''],
|
|
'a_pos_statuaire': ['', '', ''],
|
|
'a_interuption_service': ['', '', ''],
|
|
'a_situation_fam': ['', '', ''],
|
|
'a_nombre_enfants': [2, 6, 9],
|
|
'a_eis': ['', '', ''],
|
|
'a_sap_conjoint': [987, 456, 908],
|
|
'a_flag_particulier': [0, 1, 0],
|
|
'a_flag_pam': [1, 0, 1],
|
|
'a_statut_pam': ['non etudié', 'affecté', 'affecté'],
|
|
'a_notes_gestionnaire': ['', '', ''],
|
|
'a_notes_partagees': ['', '', ''],
|
|
'a_eip_futur': ['', '', ''],
|
|
'a_affectation1': ['', '', ''],
|
|
'a_affectation2': ['', '', ''],
|
|
'a_affectation3': ['', '', '']
|
|
}
|
|
a = pd.DataFrame(data=a_bis)
|
|
|
|
notation = pd.DataFrame(
|
|
columns=["poste_id", "administres_id", "pam_id", "no_date_execution", "no_score_administre",
|
|
"no_flag_cple_ideal"])
|
|
|
|
for i in range(len(p)):
|
|
p_id = p.loc[i, "p_id"]
|
|
p_dom = p.loc[i, "p_eip"][0:3]
|
|
p_fil = p.loc[i, "p_eip"][3:6]
|
|
p_nf = p.loc[i, "p_eip"][6:]
|
|
p_gar = p.loc[i, "p_gar"]
|
|
p_poids_dom = 0.25
|
|
p_poids_fil = 0.25
|
|
p_poids_nf = 0.25
|
|
p_poids_gar = 0.25
|
|
|
|
for j in range(len(a)):
|
|
a_id_sap = a.at[j, 'a_id_sap']
|
|
a_dom = a.at[j, 'a_eip'][0:3]
|
|
a_fil = a.at[j, 'a_eip'][4:7]
|
|
a_nf = a.at[j, 'a_eip'][8:]
|
|
a_gar = a.at[j, 'a_gar']
|
|
|
|
n = notePonderee(a_dom, a_fil, a_nf, a_gar, p_dom, p_fil, p_nf, p_gar, p_poids_dom, p_poids_fil, p_poids_nf,
|
|
p_poids_gar)
|
|
|
|
new_row = {'administres_id': a_id_sap,
|
|
'poste_id': p_id,
|
|
'no_date_execution': '14/06/2021',
|
|
'no_note_militaire': n,
|
|
"no_flag_cple_ideal": 0
|
|
}
|
|
notation = notation.append(new_row, ignore_index=True)
|
|
|
|
return notation
|