From 83059cd9955795ccbd063a04da91873c26c8d6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9o=20BARAS=20=28IUT1=20Grenoble=29?= Date: Tue, 20 Feb 2024 09:13:19 +0100 Subject: [PATCH] =?UTF-8?q?Relecture=20+=20am=C3=A9liorations=20diverses?= =?UTF-8?q?=20(dont=20tri=20syst=C3=A9matique=20par=20etudids=5Fsorted,=20?= =?UTF-8?q?acronymes=5Fsorted,=20competences=5Fsorted)=20des=20dataframes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/pe/pe_jury.py | 37 ++++- app/pe/pe_moytag.py | 18 +-- app/pe/pe_rcss_jury.py | 3 +- app/pe/pe_rcstag.py | 290 ++++++++++++++++++++++++----------- app/pe/pe_ressemtag.py | 161 +++++++++++++------ app/pe/pe_sxtag.py | 172 +++++++++++---------- tests/unit/yaml_setup_but.py | 2 +- 7 files changed, 442 insertions(+), 241 deletions(-) diff --git a/app/pe/pe_jury.py b/app/pe/pe_jury.py index 323080d3..168cdb9b 100644 --- a/app/pe/pe_jury.py +++ b/app/pe/pe_jury.py @@ -60,6 +60,7 @@ from app.pe.pe_rcstag import RCSTag from app.pe.pe_ressemtag import ResSemBUTTag from app.pe.pe_interclasstag import RCSInterclasseTag import app.pe.pe_rcss_jury as pe_rcss_jury +import app.pe.rcss.rcss_constantes as rcss_constantes class JuryPE(object): @@ -107,6 +108,7 @@ class JuryPE(object): self._gen_xls_diplomes(zipfile) self._gen_xls_ressembuttags(zipfile) self._gen_rcss() + self._gen_rcsf() self._gen_xls_sxtags(zipfile) self._gen_rcrcfs() self._gen_xls_rcrcss_tags(zipfile) @@ -166,11 +168,14 @@ class JuryPE(object): with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: + onglets = [] for res_sem_tag in self.ressembuttags.values(): - onglet = res_sem_tag.get_repr(verbose=False) + onglet = res_sem_tag.get_repr(verbose=True) + onglets += [] df = res_sem_tag.df_moyennes_et_classements() # écriture dans l'onglet df.to_excel(writer, onglet, index=True, header=True) + pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") output.seek(0) self.add_file_to_zip( @@ -190,16 +195,19 @@ class JuryPE(object): self.rcss_jury.cree_rcss(self.etudiants) - def _gen_xls_sxtags(self, zipfile: ZipFile): - """Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour - identifier les redoublements impactant les semestres taggués). - """ + def _gen_rcsf(self): + """Génère les RCF, regroupement de semestres de type Sx pour préparer + le calcul des moyennes par Sx""" # Génère les regroupements de semestres de type Sx pe_affichage.pe_print( "*** Génère les RCSValid (RCS de même Sx donnant lieu à validation du semestre)" ) self.rcss_jury.cree_rcfs(self.etudiants) + def _gen_xls_sxtags(self, zipfile: ZipFile): + """Génère les semestres taggués en s'appuyant sur les RCF de type Sx (pour + identifier les redoublements impactant les semestres taggués). + """ # Génère les moyennes des RCS de type Sx pe_affichage.pe_print("*** Calcule les moyennes des SxTag") @@ -216,11 +224,15 @@ class JuryPE(object): with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: + onglets = [] for sxtag in self.sxtags.values(): onglet = sxtag.get_repr(verbose=False) + onglets += [onglet] df = sxtag.df_moyennes_et_classements() # écriture dans l'onglet df.to_excel(writer, onglet, index=True, header=True) + pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") + output.seek(0) self.add_file_to_zip( @@ -269,11 +281,14 @@ class JuryPE(object): with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: + onglets = [] for rcs_tag in self.rcss_tags.values(): onglet = rcs_tag.get_repr(verbose=False) + onglets += [onglet] df = rcs_tag.df_moyennes_et_classements() # écriture dans l'onglet df.to_excel(writer, onglet, index=True, header=True) + pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") output.seek(0) self.add_file_to_zip( @@ -296,12 +311,16 @@ class JuryPE(object): with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: + onglets = [] for interclass_tag in self.interclassements_taggues.values(): if interclass_tag.significatif: # Avec des notes onglet = interclass_tag.get_repr() + onglets += [onglet] df = interclass_tag.df_moyennes_et_classements() # écriture dans l'onglet df.to_excel(writer, onglet, index=True, header=True) + pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") + output.seek(0) self.add_file_to_zip( @@ -322,9 +341,12 @@ class JuryPE(object): with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: + onglets = [] for onglet, df in self.synthese.items(): + onglets += [onglet] # écriture dans l'onglet: df.to_excel(writer, onglet, index=True, header=True) + pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") output.seek(0) self.add_file_to_zip( @@ -342,9 +364,12 @@ class JuryPE(object): with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated output, engine="openpyxl" ) as writer: + onglets = [] for onglet, df in synthese.items(): + onglets += [onglet] # écriture dans l'onglet: df.to_excel(writer, onglet, index=True, header=True) + pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}") output.seek(0) self.add_file_to_zip( @@ -433,7 +458,7 @@ class JuryPE(object): # Ajout des aggrégats for aggregat in pe_rcs.TOUS_LES_RCS: - descr = app.pe.rcss.constantes.TYPES_RCS[aggregat]["descr"] + descr = rcss_constantes.TYPES_RCS[aggregat]["descr"] # Les trajectoires (tagguées) suivies par les étudiants pour l'aggrégat et le tag # considéré diff --git a/app/pe/pe_moytag.py b/app/pe/pe_moytag.py index d5e64a79..1029c66c 100644 --- a/app/pe/pe_moytag.py +++ b/app/pe/pe_moytag.py @@ -3,9 +3,7 @@ import pandas as pd from app import comp from app.comp.moy_sem import comp_ranks_series -from app.models import UniteEns from app.pe import pe_affichage -from app.scodoc.codes_cursus import UE_SPORT class Moyenne: @@ -24,12 +22,12 @@ class Moyenne: """Classe centralisant la synthèse des moyennes/classements d'une série de notes : - * des "notes": la Serie pandas des notes (float), - * des "classements": la Serie pandas des classements (float), - * des "min": la note minimum, - * des "max": la note maximum, - * des "moy": la moyenne, - * des "nb_inscrits": le nombre d'étudiants ayant une note, + * des "notes" : la Serie pandas des notes (float), + * des "classements" : la Serie pandas des classements (float), + * des "min" : la note minimum, + * des "max" : la note maximum, + * des "moy" : la moyenne, + * des "nb_inscrits" : le nombre d'étudiants ayant une note, """ self.notes = notes """Les notes""" @@ -171,7 +169,7 @@ class MoyennesTag: tag: Un tag matrice_notes: Les moyennes (etudid x acronymes_ues ou etudid x compétences) aux différentes UEs ou compétences matrice_coeffs: Les coeff à appliquer pour le calcul de la moyenne générale - # notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues) + # notes_gen: Une série de notes (moyenne) sous forme d'un ``pd.Series`` (toutes UEs confondues) """ self.tag = tag """Le tag associé aux moyennes""" @@ -207,7 +205,7 @@ class MoyennesTag: ont des notes Returns: - True si a des notes, False sinon + True si la moytag a des notes, False sinon """ notes = self.matrice_notes nbre_nan = notes.isna().sum().sum() diff --git a/app/pe/pe_rcss_jury.py b/app/pe/pe_rcss_jury.py index e514e5bb..58697cb2 100644 --- a/app/pe/pe_rcss_jury.py +++ b/app/pe/pe_rcss_jury.py @@ -152,7 +152,8 @@ class RCSsJuryPE: # Ajout du RCRCF if rcf_id not in self.rcrcfs: - self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_id, rcf.formsemestre_final) + rcf_nom = rcf_id[0] + self.rcrcfs[rcf_id] = pe_rcrcf.RCRCF(rcf_nom, rcf.formsemestre_final) rcrcf = self.rcrcfs[rcf_id] # Ajout des RCFs au RCRCF diff --git a/app/pe/pe_rcstag.py b/app/pe/pe_rcstag.py index 882044a9..43425941 100644 --- a/app/pe/pe_rcstag.py +++ b/app/pe/pe_rcstag.py @@ -37,6 +37,7 @@ Created on Fri Sep 9 09:15:05 2016 """ from app.comp.res_sem import load_formsemestre_results +from app.models import FormSemestre from app.pe import pe_affichage import pandas as pd import numpy as np @@ -49,7 +50,7 @@ from app.pe.pe_moytag import MoyennesTag class RCSTag(TableTag): - def __init__(self, rcrcf: pe_rcs.RCS, sxstags: dict[(str, int): pe_sxtag.SxTag]): + def __init__(self, rcrcf: pe_rcs.RCS, sxstags: dict[(str, int) : pe_sxtag.SxTag]): """Calcule les moyennes par tag (orientées compétences) d'un regroupement de SxTag (RCRCF), pour extraire les classements par tag pour un @@ -71,17 +72,18 @@ class RCSTag(TableTag): self.nom = self.get_repr() """Représentation textuelle du RCS taggué""" - self.formsemestre_terminal = rcrcf.formsemestre_final - """Le formsemestre terminal""" + # Les données du semestre final + self.formsemestre_terminal: FormSemestre = rcrcf.formsemestre_final + """Le semestre final""" + self.fid_final: int = rcrcf.formsemestre_final.formsemestre_id + """Le fid du semestre final""" + # Affichage pour debug pe_affichage.pe_print(f"-> {self.get_repr(verbose=True)}") - # Les résultats du formsemestre terminal - nt = load_formsemestre_results(self.formsemestre_terminal) - + # Les données aggrégés (RCRCF + SxTags self.rcfs_aggreges = rcrcf.rcfs_aggreges """Les RCFs aggrégés""" - self.sxstags = {} """Les SxTag associés aux RCF aggrégés""" try: @@ -91,41 +93,57 @@ class RCSTag(TableTag): raise ValueError("Semestres SxTag manquants") # Les étudiants (etuds, états civils & etudis) - self.etuds = nt.etuds - self.add_etuds(nt.etuds) + sxtag_final = self.sxstags[self.rcs_id] + self.etuds = sxtag_final.etuds + """Les étudiants (extraits du semestre final)""" + self.add_etuds(self.etuds) self.etudids_sorted = sorted(self.etudids) - """Etudids triés""" + """Les étudids triés""" # Les compétences (extraites de tous les Sxtags) - self.association_ues_comp = self.mapping_ue_competences() - """Association indiquant pour chaque UE , quelle compétence lui correspond""" - pe_affichage.pe_print(f"* Association UEs -> compétences : {self.association_ues_comp}") - - self.competences_sorted = self.do_complist() - """Compétences (triées) extraites de tous les SxTag aggrégés""" + self.acronymes_ues_to_competences = self._do_acronymes_to_competences() + """L'association acronyme d'UEs -> compétence (extraites des SxTag aggrégés)""" + pe_affichage.pe_print( + f"* Association UEs -> compétences : {self.acronymes_ues_to_competences}" + ) + self.competences_sorted = sorted(self.acronymes_ues_to_competences.values()) + """Compétences (triées par nom, extraites des SxTag aggrégés)""" pe_affichage.pe_print(f"* Compétences : {', '.join(self.competences_sorted)}") # Les tags - self.tags_sorted = self.do_taglist() + self.tags_sorted = self._do_taglist() """Tags extraits de tous les SxTag aggrégés""" pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}") # Les moyennes self.moyennes_tags: dict[str, MoyennesTag] = {} """Synthétise les moyennes/classements par tag (qu'ils soient personnalisé ou de compétences)""" - for tag in self.tags_sorted: - # Cube de note - notes_cube, coeffs_cube = self.compute_notes_comps_cube(tag, self.etudids_sorted, self.competences_sorted) - - # Calcule des moyennes/coeffs sous forme d'un dataframe""" - moys_competences, coeffs_competences = compute_notes_competences( - notes_cube, coeffs_cube, self.etudids_sorted, self.competences_sorted + # Cube de notes (etudids_sorted x compétences_sorted x sxstags) + notes_df, notes_cube = self.compute_notes_comps_cube( + tag, self.etudids_sorted, self.competences_sorted, self.sxstags + ) + # Calcule des moyennes/coeffs sous forme d'un dataframe""" + moys_competences = compute_notes_competences( + notes_cube, self.etudids_sorted, self.competences_sorted + ) + # Cube de coeffs pour la moyenne générale, + # traduisant les inscriptions des étudiants aux UEs (etudids_sorted x compétences_sorted x sxstags) + coeffs_df, coeffs_cube = self.compute_coeffs_comps_cube( + tag, + self.etudids_sorted, + self.competences_sorted, + self.sxstags, + ) + # Calcule la synthèse des coefficients à prendre en compte pour la moyenne + # générale + matrice_coeffs_moy_gen = compute_coeffs_competences( + coeffs_cube, notes_cube, self.etudids_sorted, self.competences_sorted + ) + # Mémorise les moyennes et les coeff associés + self.moyennes_tags[tag] = MoyennesTag( + tag, moys_competences, matrice_coeffs_moy_gen ) - - # Les moyennes - self.moyennes_tags[tag] = MoyennesTag(tag, moys_competences, - coeffs_competences) def __eq__(self, other): """Egalité de 2 RCS taggués sur la base de leur identifiant""" @@ -139,107 +157,200 @@ class RCSTag(TableTag): else: return f"{self.__class__.__name__} ({self.rcs_id})" - def compute_notes_comps_cube(self, tag, etudids_sorted: list[int], competences_sorted: list[str]): - """Pour un tag donné, construit : - * le cube de notes (etudid x competences x SxTag) nécessaire au calcul des moyennes, - en remplaçant les données d'UE (obtenus du SxTag) par les compétences - * le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions) - appliqué au calcul des différents SxTag + def compute_notes_comps_cube( + self, + tag, + etudids_sorted: list[int], + competences_sorted: list[str], + sxstags: dict[(str, int) : pe_sxtag.SxTag], + ): + """Pour un tag donné, construit le cube de notes (etudid x competences x SxTag) + nécessaire au calcul des moyennes, + en remplaçant les données d'UE (obtenus du SxTag) par les compétences Args: tag: Le tag visé - etudids_sorted: Les etudis triés - competences_sorted: Les compétences triées + etudids_sorted: Les etudis triés (dim 0) + competences_sorted: Les compétences triées (dim 1) + sxstags: Les SxTag à réunir """ - # nb_tags = len(self.tags_sorted) - # nb_etudiants = len(self.etuds) - # nb_semestres = len(self.semestres_tags_aggreges) - - # Index du cube (etudids -> dim 0, tags -> dim 1) - # etudids = [etud.etudid for etud in self.etuds] - # competences_sorted = self.competences_sorted - sxstags_ids = list(self.sxstags.keys()) - notes_dfs = {} - coeffs_dfs = {} - for sxtag_id, sxtag in self.sxstags.items(): + for sxtag_id, sxtag in sxstags.items(): # Partant d'un dataframe vierge - notes_df = pd.DataFrame(np.nan, index=etudids_sorted, columns=competences_sorted) - coeffs_df = pd.DataFrame(np.nan, index=etudids_sorted, columns=competences_sorted) - + notes_df = pd.DataFrame( + np.nan, index=etudids_sorted, columns=competences_sorted + ) + # Charge les notes du semestre tag (copie car changement de nom de colonnes à venir) moys_tag = sxtag.moyennes_tags[tag] - - # Charge les notes et les coeffs du semestre tag notes = moys_tag.matrice_notes.copy() # avec une copie - coeffs = moys_tag.matrice_coeffs_moy_gen.copy() # les coeffs - # Traduction des UE en compétences - ues_columns_df = notes.columns - comp_associes_aux_ues = [self.association_ues_comp[ue] for ue in ues_columns_df] - notes.columns = comp_associes_aux_ues - coeffs.columns = comp_associes_aux_ues + # Traduction des acronymes d'UE en compétences + acronymes_ues_columns = notes.columns + acronymes_to_comps = [ + self.acronymes_ues_to_competences[acro] + for acro in acronymes_ues_columns + ] + notes.columns = acronymes_to_comps # Les étudiants et les compétences communes - etudids_communs, comp_communes = pe_comp.find_index_and_columns_communs(notes_df, notes) + etudids_communs, comp_communes = pe_comp.find_index_and_columns_communs( + notes_df, notes + ) # Recopie des notes et des coeffs notes_df.loc[etudids_communs, comp_communes] = notes.loc[ etudids_communs, comp_communes ] + + # Supprime tout ce qui n'est pas numérique + # for col in notes_df.columns: + # notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce") + + # Stocke les dfs + notes_dfs[sxtag_id] = notes_df + + """Réunit les notes sous forme d'un cube etudids x competences x semestres""" + sxtag_x_etudids_x_comps = [notes_dfs[sxtag_id] for sxtag_id in sxstags] + notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) + + return notes_dfs, notes_etudids_x_comps_x_sxtag + + def compute_coeffs_comps_cube( + self, + tag, + etudids_sorted: list[int], + competences_sorted: list[str], + sxstags: dict[(str, int) : pe_sxtag.SxTag], + ): + """Pour un tag donné, construit + le cube de coeffs (etudid x competences x SxTag) (traduisant les inscriptions + des étudiants aux UEs en fonction de leur parcours) + qui s'applique aux différents SxTag + en remplaçant les données d'UE (obtenus du SxTag) par les compétences + + Args: + tag: Le tag visé + etudids_sorted: Les etudis triés + competences_sorted: Les compétences triées + sxstags: Les SxTag à réunir + """ + coeffs_dfs = {} + + for sxtag_id, sxtag in sxstags.items(): + # Partant d'un dataframe vierge + coeffs_df = pd.DataFrame( + np.nan, index=etudids_sorted, columns=competences_sorted + ) + + moys_tag = sxtag.moyennes_tags[tag] + + # Charge les notes et les coeffs du semestre tag + coeffs = moys_tag.matrice_coeffs_moy_gen.copy() # les coeffs + + # Traduction des acronymes d'UE en compétences + acronymes_ues_columns = coeffs.columns + acronymes_to_comps = [ + self.acronymes_ues_to_competences[acro] + for acro in acronymes_ues_columns + ] + coeffs.columns = acronymes_to_comps + + # Les étudiants et les compétences communes + etudids_communs, comp_communes = pe_comp.find_index_and_columns_communs( + coeffs_df, coeffs + ) + + # Recopie des notes et des coeffs coeffs_df.loc[etudids_communs, comp_communes] = coeffs.loc[ etudids_communs, comp_communes ] - # Supprime tout ce qui n'est pas numérique - for col in notes_df.columns: - notes_df[col] = pd.to_numeric(notes_df[col], errors="coerce") - # Stocke les dfs - notes_dfs[sxtag_id] = notes_df coeffs_dfs[sxtag_id] = coeffs_df - """Réunit les notes sous forme d'un cube etudids x competences x semestres""" - sxtag_x_etudids_x_comps = [notes_dfs[fid].values for fid in notes_dfs] - notes_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) - """Réunit les coeffs sous forme d'un cube etudids x competences x semestres""" - sxtag_x_etudids_x_comps = [coeffs_dfs[fid].values for fid in notes_dfs] + sxtag_x_etudids_x_comps = [coeffs_dfs[sxtag_id] for sxtag_id in sxstags] coeffs_etudids_x_comps_x_sxtag = np.stack(sxtag_x_etudids_x_comps, axis=-1) - return notes_etudids_x_comps_x_sxtag, coeffs_etudids_x_comps_x_sxtag + return coeffs_dfs, coeffs_etudids_x_comps_x_sxtag - def do_taglist(self): - """Synthétise les tags à partir des Sxtags aggrégés + def _do_taglist(self) -> list[str]: + """Synthétise les tags à partir des Sxtags aggrégés. Returns: - Une liste de tags triés par ordre alphabétique + Liste de tags triés par ordre alphabétique """ tags = [] for frmsem_id in self.sxstags: tags.extend(self.sxstags[frmsem_id].tags_sorted) return sorted(set(tags)) - def mapping_ue_competences(self): - """Dictionnaire {ue: competences} extrait des SxTags""" + def _do_acronymes_to_competences(self) -> dict[str:str]: + """Synthétise l'association complète {acronyme_ue: competences} + extraite de toutes les données/associations des SxTags + aggrégés. + + Returns: + Un dictionnaire {'acronyme_ue' : 'compétences'} + """ dict_competences = {} for sxtag_id, sxtag in self.sxstags.items(): - comp = sxtag.competences - dict_competences |= comp + dict_competences |= sxtag.acronymes_ues_to_competences return dict_competences - def do_complist(self): - """Synthétise les compétences à partir des Sxtags aggrégés""" - dict_competences = self.mapping_ue_competences() - return sorted(set(dict_competences.values())) + +def compute_coeffs_competences( + coeff_cube: np.array, + set_cube: np.array, + etudids_sorted: list, + competences_sorted: list, +): + """Calcule les coeffs à utiliser pour la moyenne générale (toutes compétences + confondues), en fonction des notes (set_cube) aggrégées. + + Args: + coeffs_cube: coeffs impliqués dans la moyenne générale (semestres par semestres) + set_cube: notes moyennes aux modules ndarray + (etuds x UEs|compétences x sxtags), des floats avec des NaN + etudids_sorted: liste des étudiants (dim. 0 du cube) + competences_sorted: list + + Returns: + Un DataFrame de coefficients (etudids_sorted x compétences_sorted) + """ + nb_etuds, nb_comps, nb_semestres = set_cube.shape + assert nb_etuds == len(etudids_sorted) + assert nb_comps == len(competences_sorted) + + # Quelles entrées du cube contiennent des notes ? + mask = ~np.isnan(set_cube) + + # Enlève les NaN du cube de notes pour les entrées manquantes + coeffs_cube_no_nan = np.nan_to_num(coeff_cube, nan=0.0) + + # Retire les coefficients associées à des données sans notes + coeffs_cube_no_nan = coeffs_cube_no_nan * mask + + # Somme les coefficients (correspondant à des notes) + coeff_tag = np.sum(coeffs_cube_no_nan, axis=2) + + # Le dataFrame des coeffs + coeffs_df = pd.DataFrame( + coeff_tag, index=etudids_sorted, columns=competences_sorted + ) + # Remet à Nan les coeffs à 0 + coeffs_df.fillna(np.nan) + + return coeffs_df def compute_notes_competences( - set_cube: np.array, coeff_cube: np.array, etudids_sorted: list, competences_sorted: list + set_cube: np.array, + etudids_sorted: list, + competences_sorted: list, ): - """Calcule: - * la moyenne par compétences à un tag donné sur plusieurs semestres (partant du set_cube). - * la somme des coeffs à utiliser pour la moyenne générale. + """Calcule la moyenne par compétences (à un tag donné) sur plusieurs semestres (partant du set_cube). La moyenne est un nombre (note/20), ou NaN si pas de notes disponibles @@ -249,7 +360,6 @@ def compute_notes_competences( Args: set_cube: notes moyennes aux modules ndarray (etuds x UEs|compétences x sxtags), des floats avec des NaN - coeffs_cube: somme des coeffs impliqués dans la moyennes etudids_sorted: liste des étudiants (dim. 0 du cube) competences_sorted: list tags: liste des tags (dim. 1 du cube) @@ -266,13 +376,10 @@ def compute_notes_competences( # Enlève les NaN du cube de notes pour les entrées manquantes set_cube_no_nan = np.nan_to_num(set_cube, nan=0.0) - coeffs_cube_no_nan = np.nan_to_num(coeff_cube, nan=0.0) # Les moyennes par tag with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN) etud_moy_tag = np.sum(set_cube_no_nan, axis=2) / np.sum(mask, axis=2) - # La somme des coeffs - coeff_tag = np.sum(coeffs_cube_no_nan, axis=2) # Le dataFrame des notes moyennes etud_moy_tag_df = pd.DataFrame( @@ -282,7 +389,4 @@ def compute_notes_competences( ) etud_moy_tag_df.fillna(np.nan) - coeffs_df = pd.DataFrame(coeff_tag, index=etudids_sorted, columns=competences_sorted) - coeffs_df.fillna(np.nan) - - return etud_moy_tag_df, coeffs_df + return etud_moy_tag_df diff --git a/app/pe/pe_ressemtag.py b/app/pe/pe_ressemtag.py index 8b734855..09b88ab7 100644 --- a/app/pe/pe_ressemtag.py +++ b/app/pe/pe_ressemtag.py @@ -41,7 +41,7 @@ from app import db, ScoValueError from app import comp from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_sem import load_formsemestre_results -from app.models import FormSemestre +from app.models import FormSemestre, UniteEns from app.models.moduleimpls import ModuleImpl import app.pe.pe_affichage as pe_affichage import app.pe.pe_etudiant as pe_etudiant @@ -55,7 +55,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): """ Un ResSemBUTTag représente les résultats des étudiants à un semestre, en donnant accès aux moyennes par tag. - Il s'appuie principalement sur FormSemestre et sur ResultatsSemestreBUT. + Il s'appuie principalement sur un ResultatsSemestreBUT. """ def __init__(self, formsemestre: FormSemestre): @@ -69,51 +69,59 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): # Le nom du res_semestre taggué self.nom = self.get_repr(verbose=True) - pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}") + pe_affichage.pe_print(f"--> ResultatsSemestreBUT taggués {self.nom}") # Les étudiants (etuds, états civils & etudis) ajouté self.add_etuds(self.etuds) self.etudids_sorted = sorted(self.etudids) + """Les etudids des étudiants du ResultatsSemestreBUT triés""" # Les UEs (et les dispenses d'UE) - # self.ues - ues_standards = [ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD] + self.ues_standards: list[UniteEns] = [ + ue for ue in self.ues if ue.type == sco_codes.UE_STANDARD + ] + """Liste des UEs standards du ResultatsSemestreBUT""" # Les UEs en fonction des parcours self.ues_inscr_parcours_df = self.load_ues_inscr_parcours() - - # Les compétences associées aux UEs (définies par les acronymes) - self.competences = {} - """L'association acronyme d'UEs -> compétence""" - for ue in self.ues: - if ue.type == sco_codes.UE_STANDARD: - assert ue.niveau_competence, ScoValueError( - "Des UEs ne sont pas rattachées à des compétences" - ) - nom = ue.niveau_competence.competence.titre - self.competences[ue.acronyme] = nom - + """Les inscriptions des étudiants aux UEs du parcours""" # Les acronymes des UEs - self.ues_to_acronymes = {ue.id: ue.acronyme for ue in ues_standards} + self.ues_to_acronymes = {ue.id: ue.acronyme for ue in self.ues_standards} self.acronymes_sorted = sorted(self.ues_to_acronymes.values()) """Les acronymes de UE triés par ordre alphabétique""" + # Les compétences associées aux UEs (définies par les acronymes) + self.acronymes_ues_to_competences = {} + """L'association acronyme d'UEs -> compétence""" + for ue in self.ues_standards: + assert ue.niveau_competence, ScoValueError( + "Des UEs ne sont pas rattachées à des compétences" + ) + nom = ue.niveau_competence.competence.titre + self.acronymes_ues_to_competences[ue.acronyme] = nom + self.competences_sorted = sorted( + list(set(self.acronymes_ues_to_competences.values())) + ) + """Les compétences triées par nom""" + # Les tags personnalisés et auto: tags_dict = self._get_tags_dict() self._check_tags(tags_dict) - # Les coefficients pour le calcul de la moyenne générale - self.matrice_coeffs_moy_gen = self.ues_inscr_parcours_df * [ - ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé - ] + # Les coefficients pour le calcul de la moyenne générale, donnés par + # acronymes d'UE + self.matrice_coeffs_moy_gen = self._get_matrice_coeffs( + self.ues_inscr_parcours_df, self.ues_standards + ) + """DataFrame indiquant les coeffs des UEs par ordre alphabétique d'acronyme""" # Les capitalisations (mask etuids x acronyme_ue valant True si capitalisée, False sinon) - self.capitalisations = self._get_capitalisations(ues_standards) - + self.capitalisations = self._get_capitalisations(self.ues_standards) + """DataFrame indiquant les UEs capitalisables d'un étudiant (etudids x )""" # Calcul des moyennes & les classements de chaque étudiant à chaque tag self.moyennes_tags = {} - + """Les moyennes par tags (personnalisés ou 'but')""" for tag in tags_dict["personnalises"]: # pe_affichage.pe_print(f" -> Traitement du tag {tag}") infos_tag = tags_dict["personnalises"][tag] @@ -123,49 +131,76 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): ) # Ajoute les moyennes par UEs + la moyenne générale (but) - df_ues = pd.DataFrame( - {ue.id: self.etud_moy_ue[ue.id] for ue in ues_standards}, - index=self.etudids, - ) - # Transforme les UEs en acronyme - colonnes = df_ues.columns - acronymes = [self.ues_to_acronymes[col] for col in colonnes] - df_ues.columns = acronymes - + moy_gen = self.compute_moy_gen() self.moyennes_tags["but"] = MoyennesTag( - "but", df_ues, self.matrice_coeffs_moy_gen # , moy_gen_but + "but", moy_gen, self.matrice_coeffs_moy_gen # , moy_gen_but ) self.tags_sorted = self.get_all_tags() """Tags (personnalisés+compétences) par ordre alphabétique""" - def get_repr(self, verbose=False): - """Nom affiché pour le semestre taggué""" - if verbose: - return f"{self.formsemestre} (#{self.formsemestre.formsemestre_id})" + def get_repr(self, verbose=False) -> str: + """Nom affiché pour le semestre taggué, de la forme (par ex.): + + * S1#69 si verbose est False + * S1 FI 2023 si verbose est True + """ + if not verbose: + return f"{self.formsemestre}#{self.formsemestre.formsemestre_id}" else: return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True) - def _get_capitalisations(self, ues_hors_sport) -> pd.DataFrame: - """Renvoie un dataFrame résumant les UEs capitalisables par les - étudiants, d'après les décisions de jury + def _get_matrice_coeffs( + self, ues_inscr_parcours_df: pd.DataFrame, ues_standards: list[UniteEns] + ) -> pd.DataFrame: + """Renvoie un dataFrame donnant les coefficients à appliquer aux UEs + dans le calcul de la moyenne générale (toutes UEs confondues). + Prend en compte l'inscription des étudiants aux UEs en fonction de leur parcours + (cf. ues_inscr_parcours_df). Args: - ues_hors_sport: Liste des UEs autres que le sport + ues_inscr_parcours_df: Les inscriptions des étudiants aux UEs + ues_standards: Les UEs standards à prendre en compte + + Returns: + Un dataFrame etudids x acronymes_UEs avec les coeffs des UEs """ - capitalisations = pd.DataFrame(False, index=self.etudids_sorted, columns=self.acronymes_sorted) + matrice_coeffs_moy_gen = ues_inscr_parcours_df * [ + ue.ects for ue in ues_standards # if ue.type != UE_SPORT <= déjà supprimé + ] + matrice_coeffs_moy_gen.columns = [ + self.ues_to_acronymes[ue.id] for ue in ues_standards + ] + # Tri par etudids (dim 0) et par acronymes (dim 1) + matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index() + matrice_coeffs_moy_gen = matrice_coeffs_moy_gen.sort_index(axis=1) + return matrice_coeffs_moy_gen + + def _get_capitalisations(self, ues_standards) -> pd.DataFrame: + """Renvoie un dataFrame résumant les UEs capitalisables par les + étudiants, d'après les décisions de jury (sous réserve qu'elles existent). + + Args: + ues_standards: Liste des UEs standards (notamment autres que le sport) + Returns: + Un dataFrame etudids x acronymes_UEs dont les valeurs sont ``True`` si l'UE + est capitalisable, ``False`` sinon + """ + capitalisations = pd.DataFrame( + False, index=self.etudids_sorted, columns=self.acronymes_sorted + ) self.get_formsemestre_validations() # charge les validations res_jury = self.validations if res_jury: for etud in self.etuds: etudid = etud.etudid decisions = res_jury.decisions_jury_ues.get(etudid, {}) - for ue in ues_hors_sport: + for ue in ues_standards: if ue.id in decisions and decisions[ue.id]["code"] == sco_codes.ADM: capitalisations.loc[etudid, ue.acronyme] = True - # pe_affichage.pe_print( - # f" ⚠ Capitalisation de {ue.acronyme} pour {etud.etat_civil}" - # ) + # Tri par etudis et par accronyme d'UE + capitalisations = capitalisations.sort_index() + capitalisations = capitalisations.sort_index(axis=1) return capitalisations def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame: @@ -208,14 +243,38 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag): block=self.formsemestre.block_moyennes, ) + # Ne conserve que les UEs standards + colonnes = [ue.id for ue in self.ues_standards] + moyennes_ues_tag = moyennes_ues_tag[colonnes] + # Transforme les UEs en acronyme - colonnes = moyennes_ues_tag.columns - ue_to_acro = {ue.id: ue.acronyme for ue in self.ues} - acronymes = [ue_to_acro[col] for col in colonnes] + acronymes = [self.ues_to_acronymes[ue.id] for ue in self.ues_standards] moyennes_ues_tag.columns = acronymes + # Tri par etudids et par ordre alphabétique d'acronyme + moyennes_ues_tag = moyennes_ues_tag.sort_index() + moyennes_ues_tag = moyennes_ues_tag.sort_index(axis=1) + return moyennes_ues_tag + def compute_moy_gen(self): + """Récupère les moyennes des UEs pour le calcul de la moyenne générale, + en associant à chaque UE.id son acronyme (toutes UEs confondues) + """ + df_ues = pd.DataFrame( + {ue.id: self.etud_moy_ue[ue.id] for ue in self.ues_standards}, + index=self.etudids, + ) + # Transforme les UEs en acronyme + colonnes = df_ues.columns + acronymes = [self.ues_to_acronymes[col] for col in colonnes] + df_ues.columns = acronymes + + # Tri par ordre aphabétique de colonnes + df_ues.sort_index(axis=1) + + return df_ues + def _get_tags_dict(self): """Renvoie les tags personnalisés (déduits des modules du semestre) et les tags automatiques ('but'), et toutes leurs informations, diff --git a/app/pe/pe_sxtag.py b/app/pe/pe_sxtag.py index 208d9629..f96b7807 100644 --- a/app/pe/pe_sxtag.py +++ b/app/pe/pe_sxtag.py @@ -54,20 +54,24 @@ class SxTag(TableTag): ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag], ): """Calcule les moyennes/classements par tag d'un semestre de type 'Sx' - (par ex. 'S1', 'S2', ...) avec une orientation par UE : + (par ex. 'S1', 'S2', ...) représentés par acronyme d'UE. - * pour les étudiants non redoublants, ce sont les moyennes/classements + Il représente : + + * pour les étudiants *non redoublants* : moyennes/classements du semestre suivi - * pour les étudiants redoublants, c'est une fusion des moyennes/classements - dans les (2) 'Sx' qu'il a suivi + * pour les étudiants *redoublants* : une fusion des moyennes/classements + dans les (2) 'Sx' qu'il a suivi, en exploitant les informations de capitalisation : + meilleure moyenne entre l'UE capitalisée et l'UE refaite (la notion de meilleure + s'appliquant à la moyenne d'UE) - Un SxTag peut donc regrouper plusieurs semestres. + Un SxTag (regroupant potentiellement plusieurs semestres) est identifié + par un tuple ``(Sx, fid)`` où : - Un SxTag est identifié par un tuple (x, fid) où x est le numéro (semestre_id) - du semestre et fid le formsemestre_id du semestre final (le plus récent) du - regrouprement. + * ``x`` est le rang (semestre_id) du semestre + * ``fid`` le formsemestre_id du semestre final (le plus récent) du regroupement. - Les **tags**, les **UE** et les inscriptions aux UEs (pour les etudiants) + Les **tags**, les **UE** et les inscriptions aux UEs (pour les étudiants) considérés sont uniquement ceux du semestre final. Args: @@ -85,6 +89,7 @@ class SxTag(TableTag): self.rcf = rcf """Le RCF sur lequel il s'appuie""" + assert rcf.rcs_id == sxtag_id, "Problème de correspondance SxTag/RCF" # Les resultats des semestres taggués à prendre en compte dans le RCF self.ressembuttags = {fid: ressembuttags[fid] for fid in rcf.semestres_aggreges} @@ -95,10 +100,9 @@ class SxTag(TableTag): self.ressembuttag_final = ressembuttags[self.fid_final] """Le ResSemBUTTag final""" - self.etuds = ressembuttags[self.fid_final].etuds - """Les étudiants du ReSemBUTTag final""" - - # Ajout les etudids et les états civils + # Ajoute les etudids et les états civils + self.etuds = self.ressembuttag_final.etuds + """Les étudiants (extraits du ReSemBUTTag final)""" self.add_etuds(self.etuds) self.etudids_sorted = sorted(self.etudids) """Les etudids triés""" @@ -108,88 +112,93 @@ class SxTag(TableTag): # Les tags self.tags_sorted = self.ressembuttag_final.tags_sorted - """Tags (extraits uniquement du semestre final)""" + """Tags (extraits du ReSemBUTTag final)""" pe_affichage.pe_print(f"* Tags : {', '.join(self.tags_sorted)}") - # Les UE - moy_sem_final = self.ressembuttag_final.moyennes_tags["but"] - self.ues = list(moy_sem_final.matrice_notes.columns) + # Les UE données par leur acronyme + self.acronymes_sorted = self.ressembuttag_final.acronymes_sorted + """Les acronymes des UEs (extraits du ResSemBUTTag final)""" # L'association UE-compétences extraites du dernier semestre - self.competences = self.ressembuttag_final.competences + self.acronymes_ues_to_competences = ( + self.ressembuttag_final.acronymes_ues_to_competences + ) + """L'association acronyme d'UEs -> compétence""" + self.competences_sorted = sorted(self.acronymes_ues_to_competences.values()) + """Les compétences triées par nom""" - # Les acronymes des UE - self.acronymes_ues_sorted = sorted(self.ues) - - # Les inscriptions des étudiants aux UEs - # => ne conserve que les UEs du semestre final (pour les redoublants) - self.ues_inscr_parcours_df = self.ressembuttag_final.ues_inscr_parcours_df - self.ues_inscr_parcours_df.sort_index() - - # Les coeffs pour la moyenne générale - self.matrice_coeffs_moy_gen = self.ressembuttag_final.moyennes_tags[ - "but" - ].matrice_coeffs_moy_gen - self.matrice_coeffs_moy_gen.sort_index() # Trie les coeff par etudids - - # Les moyennes par tag - self.moyennes_tags: dict[str, pd.DataFrame] = {} - """Les notes aux UEs dans différents tags""" + # Les coeffs pour la moyenne générale (traduisant également l'inscription + # des étudiants aux UEs) (etudids_sorted x acronymes_ues_sorted) + self.matrice_coeffs_moy_gen = self.ressembuttag_final.matrice_coeffs_moy_gen # Masque des inscriptions et des capitalisations - self.masque_df, masque_cube = compute_masques_ues_cube( + self.masque_df = None + """Le DataFrame traduisant les capitalisations des différents semestres""" + self.masque_df, masque_cube = compute_masques_capitalisation_cube( self.etudids_sorted, - self.acronymes_ues_sorted, + self.acronymes_sorted, self.ressembuttags, self.fid_final, ) self._aff_capitalisations() + # Les moyennes par tag + self.moyennes_tags: dict[str, pd.DataFrame] = {} + """Moyennes aux UEs (identifiées par leur acronyme) des différents tags""" for tag in self.tags_sorted: # Y-a-t-il des notes ? if not self.has_notes(tag): pe_affichage.pe_print(f"> MoyTag 🏷{tag} actuellement sans ◯ notes") - matrice_moys_ues = pd.DataFrame(np.nan, index=self.etudids_sorted, columns=self.acronymes_ues_sorted) - + matrice_moys_ues = pd.DataFrame( + np.nan, index=self.etudids_sorted, columns=self.acronymes_sorted + ) else: # Cube de note etudids x UEs notes_df, notes_cube = compute_notes_ues_cube( tag, self.etudids_sorted, - self.acronymes_ues_sorted, + self.acronymes_sorted, self.ressembuttags, ) - # self.ues_inscr_parcours = ~np.isnan(self.matrice_coeffs.to_numpy()) - # inscr_mask = self.ues_inscr_parcours + # Masque des inscriptions aux UEs (extraits de la matrice de coefficients) + inscr_mask: np.array = ~np.isnan(self.matrice_coeffs_moy_gen.to_numpy()) - # Calcule des moyennes sous forme d'un dataframe - inscr_mask = ~np.isnan(self.ues_inscr_parcours_df.to_numpy()) + # Matrice des moyennes matrice_moys_ues: pd.DataFrame = compute_notes_ues( notes_cube, masque_cube, self.etudids_sorted, - self.acronymes_ues_sorted, + self.acronymes_sorted, inscr_mask, ) - # Les profils d'ects (pour debug) - profils_ects = [] - for i in self.matrice_coeffs_moy_gen.index: - val = tuple(self.matrice_coeffs_moy_gen.loc[i].fillna("x")) - if tuple(val) not in profils_ects: - profils_ects.append(tuple(val)) - pe_affichage.pe_print( - f"> MoyTag 🏷{tag} avec " - + f"ues={self.acronymes_ues_sorted} " - + f"ects={profils_ects}" - ) + # Affichage de debug + self.__aff_profil_coeff_ects(tag) - # Les moyennes au tag + # Mémorise les infos pour la moyennes au tag self.moyennes_tags[tag] = MoyennesTag( - tag, matrice_moys_ues, self.matrice_coeffs_moy_gen - ) + tag, matrice_moys_ues, self.matrice_coeffs_moy_gen + ) + def __aff_profil_coeff_ects(self, tag): + """Extrait de la matrice des coeffs, les différents types d'inscription + et de coefficients (appelés profil) des étudiants et les affiche + (pour debug) + """ + + # Les profils d'ects (pour debug) + profils_ects = [] + for i in self.matrice_coeffs_moy_gen.index: + val = tuple(self.matrice_coeffs_moy_gen.loc[i].fillna("x")) + if tuple(val) not in profils_ects: + profils_ects.append(tuple(val)) + + # L'affichage + ues = ", ".join(self.acronymes_sorted) + pe_affichage.pe_print( + f"> MoyTag 🏷{tag} avec " + f"ues={ues} " + f"inscr/ects={profils_ects}" + ) def has_notes(self, tag): """Détermine si le SxTag, pour un tag donné, est en cours d'évaluation. @@ -229,7 +238,7 @@ class SxTag(TableTag): cap = [] for frmsem_id in self.ressembuttags: if frmsem_id != self.fid_final: - for accr in self.acronymes_ues_sorted: + for accr in self.acronymes_sorted: if self.masque_df[frmsem_id].loc[etud.etudid, accr] > 0.0: cap += [accr] if cap: @@ -239,7 +248,7 @@ class SxTag(TableTag): def compute_notes_ues_cube( - tag, etudids_sorted, acronymes_ues_sorted, ressembuttags + tag, etudids_sorted, acronymes_sorted, ressembuttags ) -> (pd.DataFrame, np.array): """Construit le cube de notes des UEs (etudid x accronyme_ue x semestre_aggregé) nécessaire au calcul des moyennes du tag pour le RCS Sx. @@ -247,7 +256,7 @@ def compute_notes_ues_cube( Args: etudids_sorted: La liste des etudids triés par ordre croissant (dim 0) - acronymes_ues_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1) + acronymes_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1) ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus) """ # Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2) @@ -259,7 +268,7 @@ def compute_notes_ues_cube( for frmsem_id in semestres_id: # Partant d'un dataframe vierge - df = pd.DataFrame(np.nan, index=etudids_sorted, columns=acronymes_ues_sorted) + df = pd.DataFrame(np.nan, index=etudids_sorted, columns=acronymes_sorted) # Charge les notes du semestre tag sem_tag = ressembuttags[frmsem_id] @@ -289,23 +298,27 @@ def compute_notes_ues_cube( return dfs, etudids_x_ues_x_semestres -def compute_masques_ues_cube( +def compute_masques_capitalisation_cube( etudids_sorted: list[int], - acronymes_ues_sorted: list[str], + acronymes_sorted: list[str], ressembuttags: dict[int, pe_ressemtag.ResSemBUTTag], formsemestre_id_final: int, ) -> (pd.DataFrame, np.array): - """Construit le cube traduisant le masque des UEs à prendre en compte dans le calcul - des moyennes, en utilisant le df capitalisations de chaque ResSemBUTTag + """Construit le cube traduisant les masques des UEs à prendre en compte dans le calcul + des moyennes, en utilisant le dataFrame de capitalisations de chaque ResSemBUTTag - Ce masque contient : 1 si la note doit être prise en compte ; 0 sinon + Ces masques contiennent : 1 si la note doit être prise en compte, 0 sinon + + Le masque des UEs à prendre en compte correspondant au semestre final (identifié par + son formsemestre_id_final) est systématiquement à 1 (puisque les résultats + de ce semestre doivent systématiquement + être pris en compte notamment pour les étudiants non redoublant). Args: etudids_sorted: La liste des etudids triés par ordre croissant (dim 0) - acronymes_ues_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1) - # ues_inscr_parcours_df: Le dataFrame des inscriptions au UE en fonction du parcours + acronymes_sorted: La liste des acronymes de UEs triés par acronyme croissant (dim 1) ressembuttags: Le dictionnaire des résultats de semestres BUT (tous tags confondus) - formsemestre_id_final: L'identifiant du formsemestre_id_final (dont il faut forcément prendre en compte les coeffs) + formsemestre_id_final: L'identifiant du formsemestre_id_final """ # Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2) # etudids_sorted = etudids_sorted @@ -317,15 +330,16 @@ def compute_masques_ues_cube( for frmsem_id in semestres_id: # Partant d'un dataframe contenant des 1.0 if frmsem_id == formsemestre_id_final: - df = pd.DataFrame(1.0, index=etudids_sorted, columns=acronymes_ues_sorted) + df = pd.DataFrame(1.0, index=etudids_sorted, columns=acronymes_sorted) else: # semestres redoublés - df = pd.DataFrame(0.0, index=etudids_sorted, columns=acronymes_ues_sorted) + df = pd.DataFrame(0.0, index=etudids_sorted, columns=acronymes_sorted) - # Traitement des capitalisations + # Traitement des capitalisations : remplace les infos de capitalisations par les coeff 1 ou 0 capitalisations = ressembuttags[frmsem_id].capitalisations capitalisations = capitalisations.replace(True, 1.0).replace(False, 0.0) - # Met à 0 les coeffs des UEs non capitalisées : 1.0*False => 0.0 + # Met à 0 les coeffs des UEs non capitalisées pour les étudiants + # inscrits dans les 2 semestres: 1.0*False => 0.0 etudids_communs, acronymes_communs = pe_comp.find_index_and_columns_communs( df, capitalisations ) @@ -347,7 +361,7 @@ def compute_notes_ues( set_cube: np.array, masque_cube: np.array, etudids_sorted: list, - acronymes_ues_sorted: list, + acronymes_sorted: list, inscr_mask: np.array, ): """Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE @@ -359,7 +373,7 @@ def compute_notes_ues( masque_cube: masque indiquant si la note doit être prise en compte ndarray (semestre_ids x etudids x UEs), des 1.0 ou des 0.0 etudids_sorted: liste des étudiants (dim. 0 du cube) trié par etudid - acronymes_ues_sorted: liste des acronymes des ues (dim. 1 du cube) trié par acronyme + acronymes_sorted: liste des acronymes des ues (dim. 1 du cube) trié par acronyme inscr_mask: masque etudids x UE traduisant les inscriptions des étudiants aux UE (du semestre terminal) Returns: @@ -369,7 +383,7 @@ def compute_notes_ues( nb_etuds, nb_ues, nb_semestres = set_cube.shape nb_etuds_mask, nb_ues_mask = inscr_mask.shape assert nb_etuds == len(etudids_sorted) - assert nb_ues == len(acronymes_ues_sorted) + assert nb_ues == len(acronymes_sorted) assert nb_etuds == nb_etuds_mask assert nb_ues == nb_ues_mask @@ -397,7 +411,7 @@ def compute_notes_ues( etud_moy_tag_df = pd.DataFrame( etud_moy, index=etudids_sorted, # les etudids - columns=acronymes_ues_sorted, # les tags + columns=acronymes_sorted, # les acronymes d'UEs ) etud_moy_tag_df.fillna(np.nan) diff --git a/tests/unit/yaml_setup_but.py b/tests/unit/yaml_setup_but.py index c4ab421d..b7026c26 100644 --- a/tests/unit/yaml_setup_but.py +++ b/tests/unit/yaml_setup_but.py @@ -105,7 +105,7 @@ def associe_ues_et_parcours(formation: Formation, formation_infos: dict): # Niveaux compétences: if ue_infos.get("competence"): - competence = referentiel_competence.competences.filter_by( + competence = referentiel_competence.acronymes_ues_to_competences.filter_by( titre=ue_infos["competence"] ).first() assert competence is not None # La compétence de titre indiqué doit exister